From 69db9bcb94cca19a5ec1cf6543a2743795a22022 Mon Sep 17 00:00:00 2001 From: hyungi Date: Wed, 10 Jun 2026 17:04:11 +0900 Subject: [PATCH] =?UTF-8?q?fix(news):=20=EC=95=88=ED=8B=B0=EB=B4=87=20?= =?UTF-8?q?=EC=B1=8C=EB=A6=B0=EC=A7=80=20=ED=8E=98=EC=9D=B4=EC=A7=80=20?= =?UTF-8?q?=EC=8B=9D=EB=B3=84=20=EA=B2=8C=EC=9D=B4=ED=8A=B8=20=E2=80=94=20?= =?UTF-8?q?DataDome=20corruption=20=EC=B0=A8=EB=8B=A8=20(B-3=20=EC=8B=A4?= =?UTF-8?q?=EC=B8=A1)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 르몽드 기사 = DataDome Client Challenge(316자)가 200자 본문 floor 통과 → 챌린지 HTML 이 기사 본문으로 승격되는 silent corruption 위험. fetch_page_via_browser 에 챌린지 마커 게이트 추가 → CrawlBlocked(degrade=RSS 요약 유지). 헤드리스 탐지라 재시도 무의미. Co-Authored-By: Claude Fable 5 --- app/core/crawl_politeness.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/app/core/crawl_politeness.py b/app/core/crawl_politeness.py index abcbaf7..bba760a 100644 --- a/app/core/crawl_politeness.py +++ b/app/core/crawl_politeness.py @@ -40,6 +40,16 @@ _AUTH_DELAY_MAX = 60.0 _FETCHER_URL = "http://playwright-fetcher:3400" _FETCHER_TIMEOUT = 120.0 # 브라우저 기동 + 네비게이션 + settle 포함 +# 안티봇 챌린지 페이지 식별 마커 (DataDome/Cloudflare 등) — 좁게 유지(오탐 회피). +# 실측: 르몽드 기사 = DataDome "Client Challenge" + "Entrez les caractères" CAPTCHA. +_CHALLENGE_MARKERS = ( + "Client Challenge", + "Entrez les caractères affichés", + "Checking your browser before", + "captcha-delivery.com", + "geo.captcha-delivery", +) + _ROBOTS_CACHE_TTL = 24 * 3600 # 24h _MAX_PAGE_BYTES = 5 * 1024 * 1024 # 피드 fetch 와 동일 5MB cap _PAGE_TIMEOUT = 20.0 @@ -230,6 +240,11 @@ async def fetch_page_via_browser(url: str, profile: str) -> tuple[str, str]: html_text = data.get("html", "") if len(html_text.encode("utf-8", errors="replace")) > _MAX_PAGE_BYTES: raise CrawlSkip(f"크기 초과 (browser): {url}") + # 안티봇 챌린지 페이지(DataDome 등) 식별 — 본문 길이 게이트(200자)를 통과하는 + # 짧은 챌린지 HTML 이 기사 본문으로 승격되는 silent corruption 차단. 헤드리스 탐지라 + # 재시도 무의미 → CrawlBlocked(=degrade, RSS 요약 유지). 마커는 보수적으로 좁게. + if any(m in html_text for m in _CHALLENGE_MARKERS): + raise CrawlBlocked(f"안티봇 챌린지 페이지(headless 차단): {url}") return html_text, data.get("final_url", url)