feat(v2.4.0): 新增 E_BLOCKED 错误码——细分 fetch 403 反爬拦截

真实环境问题: 抓取被墙/反爬站点 (如 baike.baidu.com) 返回 403 时,
classify_error 统一归为 E_AUTH, AI Agent 会误判为凭证问题而做无效的
认证重试。被封锁不是认证失败。

改动:
- common.py: 新增 E_BLOCKED 错误码 + recovery_hint (提示换 URL/镜像/
  用 Wayback 兜底/--exclude-domain); 新增 classify_fetch_error() 与
  _extract_status_code(): fetch 场景 403→E_BLOCKED, 401→E_AUTH,
  其余委托 classify_error (搜索场景 403 仍为 E_AUTH, 不变)
- fetch.py main(): 错误路径改用 classify_fetch_error (替代 classify_error)
- search.py: fetch_page 与 fetch_top_results 线程错误路径同样切换
- 新增 11 个测试 (test_v240_blocked_code.py), 全量 572 测试通过
- 真实环境验证: baike 403 → E_BLOCKED, 正常站点不受影响
- 文档同步 (SKILL.md/README.md 错误码表, 版本号 2.4.0)
This commit is contained in:
2026-08-05 20:24:25 +08:00
parent 4df521dc9d
commit 471818074d
7 changed files with 177 additions and 9 deletions
+6 -3
View File
@@ -38,6 +38,7 @@ from common import (
build_auth_headers,
build_wayback_url,
classify_error,
classify_fetch_error,
compute_backoff_delay,
detect_charset,
emit_progress,
@@ -906,8 +907,10 @@ def fetch_page(url: str, timeout: int = 10, auth_headers: dict = None,
except Exception as e:
error_msg = str(e) if str(e) else e.__class__.__name__
# v2.1.0:结构化错误码,让 AdaptiveThrottle 能用 error_code 检测 429
# 而非字符串匹配("Too Many Requests" 不含 "429" 会漏判)
error_code = classify_error(e)
# 而非字符串匹配("Too Many Requests" 不含 "429" 会漏判)
# v2.4.0:抓取场景用 classify_fetch_error——403 反爬拦截细分为
# E_BLOCKED,避免误判为 E_AUTH(凭证问题)。
error_code = classify_fetch_error(e)
# fetch_url 对 PDF/DOCX/XLSX 解析失败不抛异常,而是返回带 error_code
# 的 FetchResult——此处必须显式检查,否则失败会被当作成功处理
@@ -1430,7 +1433,7 @@ def fetch_top_results(results: dict, count: int, timeout: int = 10,
except Exception as e:
u = future_map[future]
fetched.append({"url": u, "status": "error", "error": str(e),
"error_code": classify_error(e),
"error_code": classify_fetch_error(e),
"text": "", "text_length": 0, "truncated": False,
"anti_bot_detected": False, "waf_type": None,
"fallback_used": None,