@@ -186,6 +186,9 @@ async def get_user_solved_problems_from_solved_ac(baekjoon_id: str, target_probl
186186 # 전체 목록이 필요하거나 문제가 많으면 페이지 크롤링 사용
187187 return await _get_all_solved_problems_via_pages (baekjoon_id , target_problems , headers )
188188
189+ except (aiohttp .ClientConnectorError , aiohttp .ServerTimeoutError , asyncio .TimeoutError ) as e :
190+ logger .error (f"[solved.ac 크롤링] solved.ac 서버 연결 실패: { e } (서버 다운 가능성)" )
191+ return []
189192 except Exception as e :
190193 logger .error (f"[solved.ac 크롤링] 오류: { e } " , exc_info = True )
191194 return []
@@ -206,7 +209,8 @@ async def check_problems_individual_queries(baekjoon_id: str, target_problems: L
206209
207210 logger .info (f"[개별 문제 확인] { baekjoon_id } - { len (target_problems )} 개 문제 개별 확인 시작" )
208211
209- async with aiohttp .ClientSession (headers = headers ) as session :
212+ timeout = aiohttp .ClientTimeout (total = 10 )
213+ async with aiohttp .ClientSession (headers = headers , timeout = timeout ) as session :
210214 for problem_id in target_problems :
211215 # 각 문제마다 query: s@{handle}+{problem_id}
212216 query = f"s@{ baekjoon_id } +{ problem_id } "
@@ -216,7 +220,7 @@ async def check_problems_individual_queries(baekjoon_id: str, target_problems: L
216220 try :
217221 async with session .get (url ) as response :
218222 if response .status != 200 :
219- logger .debug (f"[개별 문제 확인] { baekjoon_id } - 문제 { problem_id } : HTTP { response .status } " )
223+ logger .debug (f"[개별 문제 확인] { baekjoon_id } - 문제 { problem_id } : HTTP { response .status } (서버 문제 가능성) " )
220224 await asyncio .sleep (0.2 )
221225 continue
222226
@@ -249,6 +253,10 @@ async def check_problems_individual_queries(baekjoon_id: str, target_problems: L
249253
250254 await asyncio .sleep (0.2 ) # Rate limiting 방지
251255
256+ except (aiohttp .ClientConnectorError , aiohttp .ServerTimeoutError , asyncio .TimeoutError ) as e :
257+ logger .error (f"[개별 문제 확인] { baekjoon_id } - 문제 { problem_id } : solved.ac 서버 연결 실패: { e } (서버 다운 가능성)" )
258+ await asyncio .sleep (0.2 )
259+ continue
252260 except Exception as e :
253261 logger .error (f"[개별 문제 확인] { baekjoon_id } - 문제 { problem_id } 확인 중 오류: { e } " )
254262 await asyncio .sleep (0.2 )
@@ -258,6 +266,9 @@ async def check_problems_individual_queries(baekjoon_id: str, target_problems: L
258266 logger .info (f"[개별 문제 확인] { baekjoon_id } - { len (solved_problems )} /{ len (target_problems )} 개 해결" )
259267 return solved_problems
260268
269+ except (aiohttp .ClientConnectorError , aiohttp .ServerTimeoutError , asyncio .TimeoutError ) as e :
270+ logger .error (f"[개별 문제 확인] solved.ac 서버 연결 실패: { e } (서버 다운 가능성)" )
271+ return []
261272 except Exception as e :
262273 logger .error (f"[개별 문제 확인] 오류: { e } " , exc_info = True )
263274 return []
@@ -286,18 +297,21 @@ async def _check_problems_via_search_api(baekjoon_id: str, target_problems: List
286297
287298 logger .debug (f"[solved.ac 검색 API] { baekjoon_id } - 쿼리: { query } -> 인코딩: { encoded_query } " )
288299
289- async with aiohttp .ClientSession (headers = headers ) as session :
300+ # 타임아웃 설정 (10초)
301+ timeout = aiohttp .ClientTimeout (total = 10 )
302+ async with aiohttp .ClientSession (headers = headers , timeout = timeout ) as session :
290303 while page <= max_pages :
291304 url = f"https://solved.ac/problems?query={ encoded_query } &page={ page } "
292305 logger .debug (f"[solved.ac 검색 API] { baekjoon_id } - 페이지 { page } 크롤링: { url } " )
293306
294- async with session .get (url ) as response :
295- if response .status != 200 :
296- if page == 1 :
297- logger .warning (f"[solved.ac 검색 API] HTTP { response .status } 에러: { url } " )
298- return []
299- # 첫 페이지가 아니면 더 이상 페이지가 없는 것으로 간주
300- break
307+ try :
308+ async with session .get (url ) as response :
309+ if response .status != 200 :
310+ if page == 1 :
311+ logger .warning (f"[solved.ac 검색 API] HTTP { response .status } 에러: { url } (solved.ac 서버 문제 가능성)" )
312+ return []
313+ # 첫 페이지가 아니면 더 이상 페이지가 없는 것으로 간주
314+ break
301315
302316 html = await response .text ()
303317 soup = BeautifulSoup (html , 'html.parser' )
@@ -370,13 +384,27 @@ async def _check_problems_via_search_api(baekjoon_id: str, target_problems: List
370384
371385 page += 1
372386 await asyncio .sleep (0.3 ) # Rate limiting 방지
387+ except (aiohttp .ClientConnectorError , aiohttp .ServerTimeoutError , asyncio .TimeoutError ) as e :
388+ if page == 1 :
389+ logger .error (f"[solved.ac 검색 API] solved.ac 서버 연결 실패: { e } (서버 다운 가능성)" )
390+ return []
391+ # 첫 페이지가 아니면 더 이상 페이지가 없는 것으로 간주
392+ break
393+ except Exception as e :
394+ if page == 1 :
395+ logger .error (f"[solved.ac 검색 API] 예상치 못한 오류: { e } " )
396+ return []
397+ break
373398
374399 # 중복 제거 및 정렬
375400 solved_problems = sorted (list (set (solved_problems )))
376401
377402 logger .info (f"[solved.ac 검색 API] { baekjoon_id } - 목표 문제 중 { len (solved_problems )} /{ len (target_problems )} 개 해결 (페이지 { page - 1 } 개 크롤링)" )
378403 return solved_problems
379404
405+ except (aiohttp .ClientConnectorError , aiohttp .ServerTimeoutError , asyncio .TimeoutError ) as e :
406+ logger .error (f"[solved.ac 검색 API] solved.ac 서버 연결 실패: { e } (서버 다운 가능성)" )
407+ return []
380408 except Exception as e :
381409 logger .error (f"[solved.ac 검색 API] 오류: { e } " , exc_info = True )
382410 return []
@@ -399,19 +427,21 @@ async def _get_all_solved_problems_via_pages(baekjoon_id: str, target_problems:
399427 target_set = set (target_problems ) if target_problems else None
400428 last_page = None # 마지막 페이지 번호
401429
402- async with aiohttp .ClientSession (headers = headers ) as session :
430+ timeout = aiohttp .ClientTimeout (total = 10 )
431+ async with aiohttp .ClientSession (headers = headers , timeout = timeout ) as session :
403432 while page <= max_pages :
404433 url = f"https://solved.ac/profile/{ baekjoon_id } /solved"
405434 if page > 1 :
406435 url += f"?page={ page } "
407436
408- async with session .get (url ) as response :
409- if response .status != 200 :
410- if page == 1 :
411- logger .warning (f"[solved.ac 크롤링] HTTP { response .status } 에러: { url } " )
412- return []
413- # 첫 페이지가 아니면 더 이상 페이지가 없는 것으로 간주
414- break
437+ try :
438+ async with session .get (url ) as response :
439+ if response .status != 200 :
440+ if page == 1 :
441+ logger .warning (f"[solved.ac 크롤링] HTTP { response .status } 에러: { url } (서버 문제 가능성)" )
442+ return []
443+ # 첫 페이지가 아니면 더 이상 페이지가 없는 것으로 간주
444+ break
415445
416446 html = await response .text ()
417447 soup = BeautifulSoup (html , 'html.parser' )
@@ -466,6 +496,17 @@ async def _get_all_solved_problems_via_pages(baekjoon_id: str, target_problems:
466496
467497 page += 1
468498 await asyncio .sleep (0.3 ) # Rate limiting 방지
499+ except (aiohttp .ClientConnectorError , aiohttp .ServerTimeoutError , asyncio .TimeoutError ) as e :
500+ if page == 1 :
501+ logger .error (f"[solved.ac 크롤링] solved.ac 서버 연결 실패: { e } (서버 다운 가능성)" )
502+ return []
503+ # 첫 페이지가 아니면 더 이상 페이지가 없는 것으로 간주
504+ break
505+ except Exception as e :
506+ if page == 1 :
507+ logger .error (f"[solved.ac 크롤링] 예상치 못한 오류: { e } " )
508+ return []
509+ break
469510
470511 # 중복 제거 및 정렬
471512 solved_problems = sorted (list (set (solved_problems )))
@@ -479,6 +520,37 @@ async def _get_all_solved_problems_via_pages(baekjoon_id: str, target_problems:
479520
480521 return solved_problems
481522
523+ async def check_solved_ac_server_available () -> bool :
524+ """
525+ solved.ac 서버가 응답 가능한지 확인
526+
527+ Returns:
528+ True: 서버가 정상 응답
529+ False: 서버가 다운되었거나 응답 없음
530+ """
531+ try :
532+ # 간단한 API 호출로 서버 상태 확인 (존재하는 사용자로 테스트)
533+ url = "https://solved.ac/api/v3/user/show?handle=wookje"
534+ headers = {
535+ 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
536+ }
537+
538+ timeout = aiohttp .ClientTimeout (total = 5 ) # 빠른 확인을 위해 5초
539+ async with aiohttp .ClientSession (headers = headers , timeout = timeout ) as session :
540+ async with session .get (url ) as response :
541+ # 200 또는 404 모두 서버가 응답하는 것이므로 정상
542+ if response .status in [200 , 404 ]:
543+ return True
544+ # 기타 상태코드는 서버 문제 가능성
545+ logger .warning (f"[solved.ac 서버 확인] HTTP { response .status } 응답 (서버 문제 가능성)" )
546+ return False
547+ except (aiohttp .ClientConnectorError , aiohttp .ServerTimeoutError , asyncio .TimeoutError ) as e :
548+ logger .error (f"[solved.ac 서버 확인] 서버 연결 실패: { e } (서버 다운 가능성)" )
549+ return False
550+ except Exception as e :
551+ logger .error (f"[solved.ac 서버 확인] 오류: { e } " , exc_info = True )
552+ return False
553+
482554async def verify_user_exists (baekjoon_id : str ) -> bool :
483555 """
484556 백준(BOJ) 사용자 존재 여부 확인
@@ -497,16 +569,21 @@ async def verify_user_exists(baekjoon_id: str) -> bool:
497569 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
498570 }
499571
500- async with aiohttp .ClientSession (headers = headers ) as session :
572+ timeout = aiohttp .ClientTimeout (total = 10 )
573+ async with aiohttp .ClientSession (headers = headers , timeout = timeout ) as session :
501574 async with session .get (url ) as response :
502575 if response .status == 200 :
503576 return True
504577 if response .status == 404 :
505578 return False
506579 # 기타 상태코드는 보수적으로 False 처리
580+ logger .warning (f"[solved.ac API] HTTP { response .status } 에러: { url } (서버 문제 가능성)" )
507581 return False
582+ except (aiohttp .ClientConnectorError , aiohttp .ServerTimeoutError , asyncio .TimeoutError ) as e :
583+ logger .error (f"[solved.ac API] solved.ac 서버 연결 실패: { e } (서버 다운 가능성)" )
584+ return False
508585 except Exception as e :
509- print (f"solved.ac 사용자 확인 오류: { e } " )
586+ logger . error (f"[ solved.ac API] 사용자 확인 오류: { e } " , exc_info = True )
510587 return False
511588
512589async def check_problem_solved (baekjoon_id : str , problem_id : int ) -> bool :
@@ -536,9 +613,11 @@ async def get_weekly_solved_count(baekjoon_id: str, start_date: datetime, end_da
536613 'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36'
537614 }
538615
539- async with aiohttp .ClientSession (headers = headers ) as session :
616+ timeout = aiohttp .ClientTimeout (total = 10 )
617+ async with aiohttp .ClientSession (headers = headers , timeout = timeout ) as session :
540618 async with session .get (url ) as response :
541619 if response .status != 200 :
620+ logger .warning (f"[solved.ac API] HTTP { response .status } 에러: { url } (서버 문제 가능성)" )
542621 return {'count' : 0 , 'problems' : []}
543622
544623 data = await response .json ()
@@ -607,8 +686,11 @@ def cumulative_before(target_dt: datetime, inclusive: bool) -> int:
607686 'count' : count ,
608687 'problems' : []
609688 }
689+ except (aiohttp .ClientConnectorError , aiohttp .ServerTimeoutError , asyncio .TimeoutError ) as e :
690+ logger .error (f"[solved.ac API] solved.ac 서버 연결 실패: { e } (서버 다운 가능성)" )
691+ return {'count' : 0 , 'problems' : []}
610692 except Exception as e :
611- print (f"주간 해결한 문제 수 조회 오류(solved.ac) : { e } " )
693+ logger . error (f"[solved.ac API] 주간 해결한 문제 수 조회 오류: { e } " , exc_info = True )
612694 return {'count' : 0 , 'problems' : []}
613695
614696async def get_weekly_solved_from_boj_status (baekjoon_id : str , start_date : datetime , end_date : datetime , status_callback = None ) -> Dict :
0 commit comments