Skip to content

fix(utils): check_robots 가 robots.txt 404 를 "가져오지 못함" 으로 오보 — 404 분기가 죽은 코드였다 - #2

Open
Gforce10-design wants to merge 1 commit into
byungjunjang:masterfrom
Gforce10-design:fix/robots-404-dead-branch
Open

Gforce10-design wants to merge 1 commit into
byungjunjang:masterfrom
Gforce10-design:fix/robots-404-dead-branch

Conversation

@Gforce10-design

Copy link
Copy Markdown

요약

check_robots() 가 robots.txt 가 없는(404) 사이트를 "허용됨" 이 아니라 "가져오지 못함(error 채워짐)" 으로 돌려준다. _fetch_robotsurlopen 을 그대로 쓰는데 urllib 은 4xx 에서 반환하지 않고 HTTPError 를 던지므로, check_robotsif status == 404 ... # 제한 없음 분기는 도달 불가능한 죽은 코드였다.

재현 (upstream c64cfbf, scrapling 0.4.15)

from utils import check_robots
check_robots("https://books.toscrape.com/catalogue/page-1.html")
# {"allowed": true, "crawl_delay": null, "robots_url": ".../robots.txt",
#  "error": "robots.txt 를 가져오지 못했습니다: HTTP Error 404: Not Found"}

SKILL.md Step 2-0 규칙대로면 에이전트는 이걸 "확인 못 함" 으로 사용자에게 보고한다 — 실제로는 robots.txt 가 없어 제한이 없는데.

원인

  • scripts/utils.py:350-355 _fetch_robotsurlopen 호출(354). 4xx/5xx 는 HTTPError raise.
  • scripts/utils.py:378 if status == 404 or not body.strip(): return result — 위 때문에 404 로는 절대 도달 못 함.

왜 기존 테스트가 못 잡았나

test_robots_missing_file_allows_fetch_robots("", 404)반환하도록 모킹한다. 실제 함수는 그 경우 반환하지 않고 던지므로 존재하지 않는 경로를 검증하고 있었다. 게다가 error is None 을 단언하지 않아 계약이 못 박혀 있지 않았다.

수정

  • _fetch_robots: HTTPError404 만 ("", 404) 로 반환. 5xx·URLError·timeout·OSError 는 그대로 raise — "허용된 것" 과 "가져오지 못한 것" 의 구분은 check_robots 가 계속 맡는다.
    • 모든 HTTPError(body, code) 로 돌리면 5xx 의 HTML 에러 페이지가 Protego.parse 로 들어가 error=None 이 되어 그 구분이 깨진다 → 404 로만 좁혔다.
  • 테스트: urllib.request.urlopen 을 모킹해 진짜 _fetch_robots 를 태우는 테스트 3건 추가(404 → allowed·error None / 503 → error 유지 / URLError → error 유지). 기존 테스트에 error is None 단언 추가. 네트워크 호출 없음.

검증

  • 수정 전: 새 404 테스트 1건만 정확히 실측 문자열로 실패(1 failed, 8 passed).
  • 수정 후: pytest -q -k "not e2e" → 418 passed. sync_codex_mirror.py --check exit 0.
  • 12케이스 매트릭스(404 본문 유무·403·410·500·503·URLError·timeout·200 disallow·200 allow·Crawl-delay) 소켓 차단 상태에서 직접 실행 — "error vs allowed" 구분 전 경로 유지.

범위 밖 (별도 논의 제안)

RFC 9309 §2.3.1 은 4xx 전체를 "제한 없음", 5xx 를 "전면 disallow" 로 다루라 한다. 현재 코드는 404 이외 4xx 와 5xx 를 error 로 보고하고 allowed=True 로 둔다. 계약 변경이라 이 PR 에선 손대지 않았다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01KoBwBJZ5FyNxkbcX2RpCZT

… 죽은 코드였다

_fetch_robots 가 urllib.request.urlopen 을 그대로 쓰는데, urllib 은 4xx/5xx 에서
반환하지 않고 HTTPError 를 던진다. 그래서 check_robots 의
`if status == 404 or not body.strip(): return result  # 제한 없음` 은 영원히
도달하지 못했고, robots.txt 가 없는 사이트(404)가 allowed=True 이면서 error 가
채워진 채로 돌아왔다 — "확인 못 함" 으로 보고된다.

실측 2026-09-01 books.toscrape.com:
  {"allowed": true, "error": "robots.txt 를 가져오지 못했습니다: HTTP Error 404: Not Found"}

수정: _fetch_robots 에서 HTTPError 중 404 만 ("", 404) 로 돌려준다. 그 외
HTTPError(5xx)·URLError·timeout·OSError 는 그대로 던진다 — "허용된 것(allowed)"
과 "가져오지 못한 것(error)" 의 구분(SKILL.md Step 2-0)은 그대로 check_robots 몫이다.
모든 HTTPError 를 (body, code) 로 돌리면 5xx 의 HTML 에러 페이지가 Protego 로
들어가 error=None 이 되어 그 구분이 깨진다 — 그래서 404 로만 좁혔다.

왜 기존 테스트가 못 잡았나: test_robots_missing_file_allows 가 _fetch_robots 를
("", 404) 를 *반환* 하도록 모킹했다. 실제 함수는 그 경우 반환하지 않고 던지므로
존재하지 않는 경로를 검증하고 있었다. 새 테스트 3건은 urllib.request.urlopen 을
모킹해 진짜 _fetch_robots 를 태운다(네트워크 없음). 기존 테스트에는
`error is None` 단언을 추가해 계약을 고정했다.

범위 밖(별도 논의): RFC 9309 §2.3.1 은 4xx 전체를 "제한 없음", 5xx 를 "전면
disallow" 로 다루라 한다. 현재 코드는 404 이외 4xx 와 5xx 를 error 로 보고한다.

Co-Authored-By: Claude Code <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KoBwBJZ5FyNxkbcX2RpCZT
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant