feat(v2.2.1): 修复 Brotli 乱码 + 缓存治理 + 多页聚合 + 研究模式增强
核心修复(v2.2.1): - 修复 Brotli 乱码 bug: build_browser_headers 智能声明 Accept-Encoding, 仅在 brotli 可用时才声明 br; fetch.py 双路径 br 解压(requests + stdlib) 此前 Chrome/Edge UA 抓取 example.com 等返回 br 的站点输出乱码 v2.2.0 新功能: - main() 拆分为 _handle_verify/_handle_research/_handle_batch/_handle_single - --cache-max-size MB: 缓存大小上限 + LRU 淘汰(默认 100MB) - --pages N: 多页聚合 + 跨页去重 - --research 跨角度合并: 新增 merged_results 字段 - --stream / --progress: JSON Lines 流式输出 + request_id 贯穿 - --dry-run / --save-config / --log-format json - --similarity-dedup / --throttle-* 参数化 - 15-UA 池 + PDF/docx 解析 + error_code 字段 文档与测试: - SKILL.md: 版本号唯一(元数据),删除版本标记干扰 - README.md: 测试数量 539 -> 544 - 544 passed (新增 5 个 Content-Encoding 解压测试)
This commit is contained in:
@@ -69,6 +69,7 @@ AI 调用后,stdout 输出网页正文(text/html/markdown 三种格式),
|
||||
| `E_EMPTY` | 空结果(exit 2) | 调整查询词、扩大 `--time-range`/`--categories` |
|
||||
| `E_INPUT` | 输入错误(参数/文件) | 检查语法、标志组合、文件路径 |
|
||||
| `E_INTERNAL` | 内部错误 | 用 `--verbose` 重跑并报告 |
|
||||
| `E_UNSUPPORTED_MEDIA` (v2.2.0) | 不支持的二进制媒体类型(PDF/docx/xlsx 解析失败) | 换 URL,或安装 `pdftotext` 用于 PDF 解析 |
|
||||
|
||||
### 流式输出与进度事件(AI 高级用法)
|
||||
|
||||
@@ -85,15 +86,19 @@ AI 调用后,stdout 输出网页正文(text/html/markdown 三种格式),
|
||||
|
||||
**`--progress`**:进度事件流(JSON Lines 到 stderr),AI 可实时跟踪执行:
|
||||
```
|
||||
{"event": "start", "query": "...", "instances": 2}
|
||||
{"event": "instance_try", "url": "https://inst1.example.com", "attempt": 1}
|
||||
{"event": "instance_ok", "url": "https://inst1.example.com", "latency": 0.342, "results": 10}
|
||||
{"event": "instance_fail", "url": "https://inst2.example.com", "error": "HTTP 503", "error_code": "E_NETWORK"}
|
||||
{"event": "cache_hit", "query": "...", "ttl": 30}
|
||||
{"event": "fetch_ok", "url": "...", "chars": 12345}
|
||||
{"event": "done", "results": 10, "query": "..."}
|
||||
{"event": "start", "query": "...", "instances": 2, "request_id": "a1b2c3d4"}
|
||||
{"event": "instance_try", "url": "https://inst1.example.com", "attempt": 1, "request_id": "a1b2c3d4"}
|
||||
{"event": "instance_ok", "url": "https://inst1.example.com", "latency": 0.342, "results": 10, "request_id": "a1b2c3d4"}
|
||||
{"event": "instance_fail", "url": "https://inst2.example.com", "error": "HTTP 503", "error_code": "E_NETWORK", "request_id": "a1b2c3d4"}
|
||||
{"event": "cache_hit", "query": "...", "ttl": 30, "request_id": "a1b2c3d4"}
|
||||
{"event": "page_ok", "pageno": 1, "results": 10, "request_id": "a1b2c3d4"}
|
||||
{"event": "page_fail", "pageno": 2, "error": "HTTP 503", "request_id": "a1b2c3d4"}
|
||||
{"event": "fetch_ok", "url": "...", "chars": 12345, "request_id": "a1b2c3d4"}
|
||||
{"event": "done", "results": 10, "query": "...", "request_id": "a1b2c3d4"}
|
||||
```
|
||||
|
||||
v2.2.0:每个事件含 `request_id`(8 位 hex,每次运行自动生成);`--pages N` 时新增 `page_ok`/`page_fail` 事件。
|
||||
|
||||
**`--dump-schema`**:输出当前版本的 JSON Schema 到 stdout 并退出,AI 可程序化发现字段名与类型,无需解析文档。
|
||||
|
||||
```bash
|
||||
@@ -158,19 +163,23 @@ python scripts/search.py -q "查询词" -i https://your-instance \
|
||||
[--language zh-CN] \
|
||||
[--sort-by score|date|engine|none] \
|
||||
[--no-dedup] \
|
||||
[--similarity-dedup] [--similarity-threshold 0.85] \
|
||||
[--max-results 10] \
|
||||
[--fetch 3] \
|
||||
[--fetch-report] \
|
||||
[--pages N] \
|
||||
[--fetch 3] [--fetch-report [json]] \
|
||||
[--no-fallback] \
|
||||
[--referer URL] \
|
||||
[--request-delay 0.3] \
|
||||
[--cache-ttl 30] \
|
||||
[--cache-ttl 30] [--cache-max-size 100] \
|
||||
[--queries-file queries.txt] \
|
||||
[--research "研究主题"] \
|
||||
[--research "研究主题"] [--research-angles "a,b,c"] \
|
||||
[--include-domain example.com] \
|
||||
[--exclude-domain spam.com] \
|
||||
[--proxy http://corp:8080] \
|
||||
[--auth-bearer-file ~/.token] \
|
||||
[--throttle-failure-threshold 3] [--throttle-pause-seconds 30] [--throttle-max-delay 10] \
|
||||
[--log-format text|json] \
|
||||
[--dry-run] [--save-config FILE] \
|
||||
[--verify] \
|
||||
[--stream] \
|
||||
[--progress] \
|
||||
@@ -209,6 +218,18 @@ python scripts/search.py -q "查询词" -i https://your-instance \
|
||||
| `--progress` | 进度事件(JSON Lines 到 stderr) | 关闭 |
|
||||
| `-v / --verbose` | 调试日志 | — |
|
||||
| `--quiet` | 仅输出警告和错误 | — |
|
||||
| `--pages N` | v2.2.0 分页聚合:一次获取 N 页并跨页去重合并 | 1 |
|
||||
| `--similarity-dedup` | v2.2.0 相似度去重(SimHash + Jaccard) | 关闭 |
|
||||
| `--similarity-threshold` | v2.2.0 相似度阈值 | 0.85 |
|
||||
| `--log-format` | v2.2.0 日志格式:text/json | text |
|
||||
| `--dry-run` | v2.2.0 预览模式(不发 HTTP 请求) | 关闭 |
|
||||
| `--throttle-failure-threshold` | v2.2.0 限流失败阈值 | 3 |
|
||||
| `--throttle-pause-seconds` | v2.2.0 429 全局暂停秒数 | 30 |
|
||||
| `--throttle-max-delay` | v2.2.0 限流最大延迟秒数 | 10 |
|
||||
| `--research-angles` | v2.2.0 自定义研究角度(逗号分隔) | 默认 5 角度 |
|
||||
| `--save-config` | v2.2.0 保存当前参数为 searxng.toml 并退出 | — |
|
||||
| `--cache-max-size` | v2.2.0 缓存大小上限(MB) | 100 |
|
||||
| `--fetch-report json` | v2.2.0 JSON 格式抓取报告 | text |
|
||||
|
||||
### fetch.py — 网页抓取
|
||||
|
||||
@@ -242,6 +263,27 @@ python scripts/fetch.py -u https://example.com \
|
||||
|
||||
## 能力清单
|
||||
|
||||
**v2.2.1 修复**
|
||||
- Brotli 乱码修复:`build_browser_headers()` 智能声明 `Accept-Encoding`——仅当本机安装了 brotli/brotlicffi 包时才声明 `br`,避免服务器返回 Brotli 压缩字节而 requests 无法自动解压导致全页乱码
|
||||
- `fetch.py` 双路径 br 解压:requests 路径和 stdlib urllib 路径都添加了 Brotli 手动解压逻辑(作为双保险,应对代理/CDN 强制返回 br 的边缘情况)
|
||||
- 此前 bug 表现:Chrome/Edge UA 抓取 example.com 等返回 `Content-Encoding: br` 的站点时,输出 302 字符乱码(gzip 二进制被当作文本解码);修复后输出 127 字符正常文本
|
||||
|
||||
**v2.2.0 新功能**
|
||||
- `--pages N` 分页聚合:一次获取 N 页结果并跨页去重合并,每页独立缓存(cache key 含 pageno),进度事件新增 `page_ok`/`page_fail`
|
||||
- 缓存治理:缓存大小上限 + LRU 淘汰,新增 `--cache-max-size MB`(默认 100MB);`stats()` 新增 `total_bytes`/`max_size_bytes`/`evicted_count`/`utilization_pct` 字段;新增 `evict_expired()` 主动清理方法
|
||||
- `--similarity-dedup` 相似度去重:基于标题 SimHash + Jaccard 相似度,默认关闭;`--similarity-threshold`(默认 0.85)控制严格程度;O(n²) 复杂度,结果数 > 500 时自动跳过
|
||||
- PDF/文档解析:fetch 支持解析 PDF(pdftotext subprocess)、`.docx`/`.xlsx`(stdlib zipfile);不支持的二进制类型返回 `E_UNSUPPORTED_MEDIA`
|
||||
- `--log-format json` 结构化日志:每行输出 JSON 对象 `{ts, level, logger, msg, request_id}`,便于 AI Agent 程序化解析
|
||||
- request ID 贯穿:每次运行自动生成 8 位 hex request_id,贯穿所有日志和进度事件
|
||||
- `--dry-run` 预览模式:不发 HTTP 请求,打印 `{action, url, params, headers_count}` JSON 到 stdout;支持 search/research/batch/verify 四种模式
|
||||
- AdaptiveThrottle 参数可配置:新增 `--throttle-failure-threshold`(默认 3)、`--throttle-pause-seconds`(默认 30)、`--throttle-max-delay`(默认 10)
|
||||
- `--research-angles` 自定义研究角度:覆盖默认 5 角度,每个角度直接作为查询后缀
|
||||
- `--save-config FILE`:将当前 CLI 参数保存为 searxng.toml 配置文件并退出
|
||||
- `--fetch-report json`:输出完整 JSON 报告(含 items 数组 + summary 摘要);原 `--fetch-report`(无参数)保持 text 格式
|
||||
- UA 池更新到 2026 年版本:Chrome 138-140 / Edge 138 / Firefox 140 / Safari 18,池移至 `_config.py`(SSOT)
|
||||
- readability-lite 按语言调整:CJK 内容 100 字符阈值,其他 200 字符阈值
|
||||
- classify_error 改用异常链(内部改进)、`_domain_ua_cache` 加锁(内部改进)
|
||||
|
||||
**搜索**
|
||||
- 多实例故障转移 + 并行探测
|
||||
- 指数退避重试(429/5xx/连接错误)
|
||||
@@ -264,11 +306,12 @@ python scripts/fetch.py -u https://example.com \
|
||||
- text:提取纯文本
|
||||
- html:原始 HTML
|
||||
- markdown:增强 Markdown 转换(GFM 表格、代码块、引用块、嵌套列表、定义列表)
|
||||
- v2.0.0 readability-lite:`<article>`/`<main>` 缺失时,用文本密度算法选最可能正文的 `<div>`
|
||||
- v2.0.0 readability-lite:`<article>`/`<main>` 缺失时,用文本密度算法选最可能正文的 `<div>`(v2.2.0:CJK 内容 100 字符阈值,其他 200 字符阈值)
|
||||
- v2.2.0 PDF/文档解析:支持解析 PDF(pdftotext subprocess)、`.docx`/`.xlsx`(stdlib zipfile);不支持的二进制类型返回 `E_UNSUPPORTED_MEDIA`
|
||||
|
||||
**反爬与抓取稳定性(v2.0.0)**
|
||||
- 浏览器指纹头:`build_browser_headers()` 发送完整 Sec-Ch-Ua / Sec-Fetch-* / Accept-Language,不仅靠 User-Agent
|
||||
- 12 个 UA 池:Chrome/Edge/Firefox × Windows/macOS/Linux × 129-131 版本
|
||||
- 15 个 UA 池(v2.2.0 更新):Chrome 138-140 / Edge 138 / Firefox 140 / Safari 18 × Windows/macOS/Linux;UA 池移至 `_config.py`(SSOT),common.py 通过导入引用
|
||||
- 确定性 UA 轮换:`get_ua_for_domain()` 用 SHA-256 为每个域名固定一个 UA(会话内稳定,跨进程可复现)
|
||||
- requests.Session 复用:连接池 + cookie 持久化 + TLS 会话恢复
|
||||
- 超时分离:`(connect, read)` 元组,避免大页面下载中途超时浪费已建连接
|
||||
@@ -281,13 +324,15 @@ python scripts/fetch.py -u https://example.com \
|
||||
- v2.1.1 精确化:`baidu.com` 从子域匹配改为精确子域列表(www/baike/zhidao/tieba/wenku),`pan.baidu.com`(网盘)/`cloud.baidu.com`(智能云)不再被误伤
|
||||
- 自适应限流:`AdaptiveThrottle` 状态机,连续 3 次失败自动翻倍延迟 + 减半并发,429 触发全局暂停 30s
|
||||
- v2.1.1:`report_failure` 新增 `error_code` 参数,优先用结构化 `E_RATE_LIMIT` 检测 429(原字符串匹配"429"会漏判"Too Many Requests");`fetch_page` 返回结果新增 `error_code` 字段
|
||||
- `--fetch-report`:结构化抓取报告到 stderr(每 URL 状态/WAF 类型/兜底方式/字符数 + JSON 摘要)
|
||||
- v2.2.0:原硬编码值现可通过 CLI 配置——`--throttle-failure-threshold`(默认 3)、`--throttle-pause-seconds`(默认 30)、`--throttle-max-delay`(默认 10)
|
||||
- `--fetch-report`:结构化抓取报告到 stderr(每 URL 状态/WAF 类型/兜底方式/字符数 + JSON 摘要);v2.2.0:`--fetch-report json` 输出完整 JSON 报告(items 数组 + summary 摘要)
|
||||
- `--referer` / `--request-delay`:精细控制 Referer 头和请求间隔
|
||||
- fetch 结果新增字段:`anti_bot_detected`(bool)、`waf_type`(str|null)、`fallback_used`(str|null)、`error_code`(str|null,v2.1.1)
|
||||
|
||||
**缓存**
|
||||
- SQLite 缓存(`--cache-ttl`),相同查询在 TTL 内跳过网络
|
||||
- `--clear-cache` / `--cache-stats` 管理缓存
|
||||
- v2.2.0 缓存治理:`--cache-max-size MB`(默认 100MB)大小上限 + LRU 淘汰;`stats()` 新增 `total_bytes`/`max_size_bytes`/`evicted_count`/`utilization_pct` 字段;新增 `evict_expired()` 主动清理方法
|
||||
|
||||
**网络**
|
||||
- 代理支持(`--proxy`)
|
||||
@@ -306,7 +351,7 @@ python scripts/fetch.py -u https://example.com \
|
||||
- JSON Lines 流式输出(`--stream`,含 `error` 事件类型)
|
||||
- 进度事件(`--progress`,含 `instance_try`/`instance_ok`/`instance_fail`,JSON Lines 到 stderr)
|
||||
- batch 模式统一 schema(`status` 字段区分成功/失败)
|
||||
- 539 个单元+集成测试
|
||||
- 544 个单元+集成测试
|
||||
|
||||
## 跨 Agent 兼容性
|
||||
|
||||
|
||||
Reference in New Issue
Block a user