Skip to content

Commit 01be898

Browse files
committed
fix logic
1 parent 1510960 commit 01be898

1 file changed

Lines changed: 98 additions & 47 deletions

File tree

.github/scripts/update_readme.py

Lines changed: 98 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,23 @@
11
import os
22
import re
3+
import time
34
import requests
45
from bs4 import BeautifulSoup
56

67
ROOT_DIR = "."
78
VALID_EXT = (".py", ".cpp", ".java", ".kt", ".js")
89

10+
# solved.ac level mapping (0..30 -> readable)
11+
LEVEL_MAP = {
12+
0: "Unrated",
13+
1: "Bronze V", 2: "Bronze IV", 3: "Bronze III", 4: "Bronze II", 5: "Bronze I",
14+
6: "Silver V", 7: "Silver IV", 8: "Silver III", 9: "Silver II", 10: "Silver I",
15+
11: "Gold V", 12: "Gold IV", 13: "Gold III", 14: "Gold II", 15: "Gold I",
16+
16: "Platinum V", 17: "Platinum IV", 18: "Platinum III", 19: "Platinum II", 20: "Platinum I",
17+
21: "Diamond V", 22: "Diamond IV", 23: "Diamond III", 24: "Diamond II", 25: "Diamond I",
18+
26: "Ruby V", 27: "Ruby IV", 28: "Ruby III", 29: "Ruby II", 30: "Ruby I",
19+
}
20+
921
# -----------------------------
1022
# 문제 파일 수집
1123
# -----------------------------
@@ -38,73 +50,108 @@ def parse_readme(lines):
3850
max_index = -1
3951

4052
for line in lines:
53+
# 표에서 "순번 | 아이콘 | 플랫폼 | [번호](링크)" 형태를 잡음
4154
m = re.search(
42-
r"\|\s*(\d+)\s*\|\s*(⬜|✅|❌)\s*\|\s*(백준|프로그래머스)\s*\|\s*\[?(\d+)\]?",
55+
r"\|\s*(\d+)\s*\|\s*(?:⬜|✅|❌)\s*\|\s*(백준|프로그래머스)\s*\|\s*\[?(\d+)\]?",
4356
line,
4457
)
4558
if m:
46-
idx, _, _, number = m.groups()
59+
idx, _, number = m.groups()
4760
existing_numbers.add(number)
4861
max_index = max(max_index, int(idx))
4962

5063
return existing_numbers, max_index
5164

5265

5366
# -----------------------------
54-
# 백준 정보 수집 (정상 동작)
67+
# solved.ac API 시도 (가장 안정적)
5568
# -----------------------------
56-
def fetch_baekjoon_info(number):
57-
url = f"https://www.acmicpc.net/problem/{number}"
69+
def fetch_from_solvedac(number):
70+
url = f"https://solved.ac/api/v3/problem/show?problemId={number}"
5871
headers = {
59-
"User-Agent": "Mozilla/5.0"
72+
"Accept": "application/json",
73+
"User-Agent": "github-actions/auto-readme (https://github.com)"
6074
}
75+
try:
76+
resp = requests.get(url, headers=headers, timeout=8)
77+
if resp.status_code != 200:
78+
return None
79+
data = resp.json()
80+
# solved.ac returns localized titles; try titleKo first then title
81+
title = data.get("titleKo") or data.get("title") or data.get("title_en") or data.get("title")
82+
level_val = data.get("level")
83+
level = LEVEL_MAP.get(level_val, "UNKNOWN") if isinstance(level_val, int) else "UNKNOWN"
84+
return {"title": title or "UNKNOWN", "level": level, "link": f"https://www.acmicpc.net/problem/{number}"}
85+
except Exception:
86+
return None
6187

62-
res = requests.get(url, headers=headers, timeout=10)
63-
if res.status_code != 200:
64-
return {
65-
"title": "UNKNOWN",
66-
"level": "UNKNOWN",
67-
"link": url,
68-
}
69-
70-
soup = BeautifulSoup(res.text, "html.parser")
7188

72-
# 문제 제목
73-
title_tag = soup.select_one("#problem_title")
74-
title = title_tag.text.strip() if title_tag else "UNKNOWN"
75-
76-
# solved.ac tier 이미지에서 난이도 추출
77-
tier_img = soup.select_one("img.solvedac-tier")
78-
level = "UNKNOWN"
79-
80-
if tier_img and "src" in tier_img.attrs:
81-
src = tier_img["src"]
82-
# 예: silver5.svg
83-
tier = src.split("/")[-1].replace(".svg", "")
89+
# -----------------------------
90+
# acmicpc.net 페이지 폴백 스크래핑
91+
# -----------------------------
92+
def fetch_from_acmicpc(number):
93+
url = f"https://www.acmicpc.net/problem/{number}"
94+
headers = {
95+
"User-Agent": "Mozilla/5.0 (compatible; github-actions; +https://github.com)"
96+
}
97+
try:
98+
resp = requests.get(url, headers=headers, timeout=10)
99+
if resp.status_code != 200:
100+
return {"title": "UNKNOWN", "level": "UNKNOWN", "link": url}
101+
102+
soup = BeautifulSoup(resp.text, "html.parser")
103+
104+
# 제목 시도 (여러 가능성에 대비)
105+
title_tag = soup.select_one("#problem_title") or soup.select_one("h1") or soup.select_one("title")
106+
title = title_tag.text.strip() if title_tag else "UNKNOWN"
107+
108+
# solved.ac tier 이미지를 찾아서 난이도 추출
109+
level = "UNKNOWN"
110+
# 이미지(예: /tier/silver5.svg) 또는 alt 속성에 tier가 있는 경우
111+
tier_img = soup.find("img", {"class": re.compile(".*tier.*", re.I)}) or soup.find("img", {"src": re.compile(".*/tier/.*", re.I)})
112+
if tier_img and tier_img.get("src"):
113+
src = tier_img["src"]
114+
# 파일명 예: silver5.svg -> silver5
115+
base = os.path.basename(src).replace(".svg", "").replace(".png", "")
116+
# 'silver5' -> 'Silver V'
117+
m = re.match(r"([a-zA-Z]+)(\d+)", base)
118+
if m:
119+
kind, num = m.groups()
120+
kind = kind.lower()
121+
kind_map = {
122+
"bronze": "Bronze",
123+
"silver": "Silver",
124+
"gold": "Gold",
125+
"platinum": "Platinum",
126+
"diamond": "Diamond",
127+
"ruby": "Ruby",
128+
}
129+
if kind in kind_map:
130+
lvl = num.upper()
131+
level = f"{kind_map[kind]} {lvl}"
132+
133+
return {"title": title or "UNKNOWN", "level": level or "UNKNOWN", "link": url}
134+
except Exception:
135+
return {"title": "UNKNOWN", "level": "UNKNOWN", "link": url}
84136

85-
tier_map = {
86-
"bronze": "Bronze",
87-
"silver": "Silver",
88-
"gold": "Gold",
89-
"platinum": "Platinum",
90-
"diamond": "Diamond",
91-
"ruby": "Ruby",
92-
}
93137

94-
for k, v in tier_map.items():
95-
if tier.startswith(k):
96-
level = f"{v} {tier[len(k):].upper()}"
97-
break
138+
# -----------------------------
139+
# 통합 fetch 함수 (solved.ac 우선, acmicpc 폴백)
140+
# -----------------------------
141+
def fetch_baekjoon_info(number):
142+
# 1) try solved.ac API
143+
info = fetch_from_solvedac(number)
144+
if info:
145+
return info
98146

99-
return {
100-
"title": title,
101-
"level": level,
102-
"link": url,
103-
}
147+
# 2) fallback to scrapping acmicpc
148+
# small sleep to be polite if many requests
149+
time.sleep(0.5)
150+
return fetch_from_acmicpc(number)
104151

105152

106153
# -----------------------------
107-
# 프로그래머스 정보
154+
# 프로그래머스 정보 (심플)
108155
# -----------------------------
109156
def fetch_programmers_info(number):
110157
return {
@@ -119,10 +166,14 @@ def fetch_programmers_info(number):
119166
# -----------------------------
120167
def make_row(index, status, platform, number, info):
121168
icon = "✅" if status == "SUCCESS" else "❌"
169+
# 안전하게 이스케이프 할 필요가 있으면 여기서 처리 가능
170+
title = info.get("title", "UNKNOWN")
171+
level = info.get("level", "UNKNOWN")
172+
link = info.get("link", f"https://www.acmicpc.net/problem/{number}")
122173
return (
123174
f"| {index:03d} | {icon} | {platform} | "
124-
f"[{number}]({info['link']}) | "
125-
f"{info['title']} | {info['level']} | |\n"
175+
f"[{number}]({link}) | "
176+
f"{title} | {level} | |\n"
126177
)
127178

128179

0 commit comments

Comments
 (0)