Skip to content

接通抓取节点试抓与配置生成 - #101

Merged
JunieXD merged 2 commits into
mousebaoyan:mainfrom
zzsky233:crawler-probe
Nov 13, 2026
Merged

JunieXD merged 2 commits into
mousebaoyan:mainfrom
zzsky233:crawler-probe

Conversation

@zzsky233

@zzsky233 zzsky233 commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

关联

Closes #46

改了什么

管理员发起试抓后,抓取节点处理 Work.probes,将条目、可采用的配置、失败原因和模型用量交回网站。

  • 带配置直接解析;无配置先通用解析,空列表才让 DeepSeek 生成 Selectors,校验后包装成 ListConfig,在同一份页面上实际解析。模型只生成选择器,浏览器和 HTTP 设置由管理员决定。
  • 请求复用 Fetcher 和浏览器会话。失败原因沿用正式抓取的写法,失败时也上报已知用量;任务异常隔离,退出前等待正在执行的任务,异常日志带 probe_id 和堆栈。
  • 试抓与正文抽取共用模型客户端和并发上限。模型日志统一在 Responses.generate 记录,耗时从取得许可后开始计算。
  • 修正 JSON Schema 转换误删业务字段 title 的问题;补充合成列表页和固定响应测试。文档只保留规则。

怎么验证的

  • 当前修订的抓取节点 ./check.sh 全部通过:生成类型一致性、格式、lint、Pyright 和 266 个测试。
  • 当前修订的网站 pnpm check 通过:类型、lint、格式和 100 个测试。Node 26.12.1、pnpm 12.11.2。
  • 自动测试不访问外网,覆盖配置生成和复用、错误原因和用量、共享并发、任务隔离及退出等待。假时钟回归验证排队 100 秒、请求 1 秒时,模型日志耗时为 1 秒;异常日志在任务内格式化,验证 probe_id 与堆栈。
  • 按审查要求重新真实调用 DeepSeek(2026-11-12):使用 probe.html,直接以 response_schema(Selectors) 请求 deepseek-flash。通用解析为空,模型生成选择器后交回 2 条通知;标题、绝对链接、日期及下一页地址全部符合预期。采用配置再次试抓得到相同条目,未再调用模型。真实调用一次,输入 566、输出 55 token。页面获取和网站上报使用固定响应,模型调用真实。
  • 此前真实网页联调:云南大学信息学院通知公告。HTTP 200,通用解析 0 条;生成配置后返回 29 条,标题和链接逐条一致;采用配置再次试抓仍为相同的 29 条,无新增模型调用。该记录来自初版输出结构,本次没有重复请求源站。
  • 网站构建、数据库类型和 OpenAPI 一致性检查在初次提交前通过;本次未改网站及接口,未重复构建。git diff --check 通过。

已知限制与验证边界

  • 云南大学样本的 29 条发布日期均为空。 年份在分组标题,行内只有日和中文月份;生成的日期选择器未包含年份,现有列表日期解析器也不支持该布局。维护者已在审查中确认这是读列表的通用能力,留到路线图另做,不在本 PR 修复。
  • 通用解析非空但选错栏目或漏项时,由管理员预览判断,不自动调用模型。
  • 试抓只读第一页。真实网站的完整翻页、浏览器脚本挑战未做端到端验证;浏览器路径使用固定响应测试。

影响

  • 抓取节点开始处理已有试抓任务,继续使用 LLM_API_KEY,无新增依赖和环境变量。
  • 网站接口、MCP 工具、表结构及已有数据格式无变化。正文抽取的模型设置和抽取规则不变,模型日志由共享客户端记录。
  • 只提交代码、文档和合成测试页面,不包含真实通知正文、快照、密钥或本地部署文件。

@zzsky233
zzsky233 marked this pull request as ready for review November 11, 2026 14:04

@JunieXD JunieXD left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢!试抓从头到尾接通了:带配置的直接读,不带的先通用解析,取不到条目才让模型生成配置,生成的配置在同一份页面上再解析一遍,请求照样走 Fetcher 和浏览器会话。在云南大学那页上实测了生成和采用,采用后再试抓得到同样的 29 条、不再调用模型,这一步做得很扎实。schema.py 里 title 被当成 JSON Schema 关键字删掉的问题也确实存在,修得对。我们拉到本地跑了 ./check.sh,267 个测试全部通过;把共享并发上限、title 的修复、任务隔离、停止时等待分别撤掉,都有测试失败。

剩下的问题主要是多了一些到不了的检查,失败原因的写法和正式抓取不一样,另外文档写得太长。

必须改

  1. 生成配置直接用 Selectors 做输出结构(extraction/probe.py:19):GeneratedConfig.force_http 只能是 false,为了它多了一条提示词和一个测试用例。改成:

    • response_schema(Selectors)、Selectors.model_validate_json(...),返回 ListConfig(selectors=...);
    • 删掉 GeneratedConfig、提示词里 force_http must be false 那一行,以及测「模型擅自降级 HTTP」的用例;提示词里的 selectors.items 等写成 items。

    我们看了 response_schema(Selectors) 的结果:是一个普通对象,date、next 是 string 或 null 的 anyOf,没有 $ref,你们遇到的那个 400 来自可空的 $ref,这里不会出现。我们没有调真实的模型,改完请拿 probe.html 实际调一次,结果写在 PR 描述里。

  2. 读列表的失败原因照 list_items 的写法(crawl/probe.py:25-62,对照 crawl/listing.py:19-22、:48-49):

    • 配置不合法写 配置不对: <字段位置>,和正式抓取一样取 error.errors(include_input=False)[0]["loc"];
    • parse_listing 抛的 ValueError 写 str(error)。Pydantic 的 ValidationError 也是 ValueError,单独那个 except ValidationError(「列表条目不符合接口要求」)删掉;
    • 没有条目写 StepError("no_items"),不带说明,和一轮的写法一样;
    • 包着 ProbeReport(...) 的 try(「试抓结果不符合接口要求」)和 detail[:1000] 删掉:一页列表到不了 5000 条,一次试抓最多一条用量,正式一轮也不截断原因。
  3. 模型这边去掉到不了的检查,原因写成英文错误码(extraction/probe.py):

    • prepare 里包着 parse_webpage 的 try 删掉:probe_source 已经用同一份 body 和 content_type 调过 parse_listing,它第一步就是 parse_webpage,解析不了的页面走不到这里;
    • 构造 Usage 的 try(invalid_usage)删掉,和 Extract 一样直接构造:模型名 100 个字符、token 数 21 亿的上限,模型的响应不会超过;
    • 原因照正式抽取的写法「试抓失败: 英文错误码」:试抓失败: page_too_large、试抓失败: missing_usage,后面不加中文说明。
  4. 模型调用的日志挪进 Responses.generate:并发许可挪进 Responses.generate 以后,extract.py:142 和 probe.py:69 量的耗时把排队等许可的时间也算进去了。用假时钟试过:排队 100 秒、调用 1 秒,日志记的是 101 秒。改成在 generate 里拿到许可后计时,响应通过校验后记一条日志(模型、状态、输入、输出和缓存命中的 token、耗时),Extract 和 ProbeConfig 里那两段一样的日志删掉。哪次调用属于哪一轮、哪次试抓,日志里的 source_id、probe_id 已经能分开。

  5. 入口的 _probe 照 _crawl 写(main.py:98-108):

    • 记成 logger.exception("Probe failed"),带上堆栈,和「Round failed」一样。预期的失败在 probe_source 里都转成了报告,走到这里的只有 bug 和上报失败,没有堆栈查不出来;logger.exception(..., exc_info=False) 其实等于 logger.error,读起来也容易误会。
    • current_probe.set(probe.id) 挪到 probe_source 开头,像 crawl_source 设 crawl_round 那样,去掉 token 和 reset:每个试抓在自己的 task 里跑,上下文不会串,_probe 记的日志也带得上 probe_id。
    • 入口测试里断言日志不含异常文字的部分改成断言「Probe failed」那条带 probe_id 和堆栈,参照 test_a_failing_round_leaves_the_others_and_stopping_waits_for_them。开发约定 §4 新加的那条改成「试抓的日志带 probe_id」。
  6. 文档只写现状和决定:

    • 总体设计 §8.5 新加的「试抓配置生成实测」整节删掉:那是这个 PR 的验证记录,里面还有排错的过程(「修正……后」「首次定位问题的两次请求」)。§8.5 记的是选模型和门槛的实验,验证结果放在 PR 描述里就行,描述里已经有了。

    • 开发约定 §2.2 只留规则,测试怎么写、Schema 转换的细节、「页面只是待分析的材料」这类 AGENTS.md 已经写了的,都不用重复。代码按第 1 条改完以后,可以写成:

      ### 2.2 试抓
      
      - 只读列表第一页,不往回翻、不读正文和附件;结果交 `ProbeReport`,不写抓取记录。
      - 带了配置就按配置读,空对象也算配置;没带配置先通用解析,取不到条目才让 LLM 按 `Selectors` 生成选择器,在同一份页面上再解析一遍,取到条目才交回生成的配置。
      - `browser` 和 `force_http` 由管理员定,模型只生成选择器。
      - 失败原因和一轮的写法相同,模型这一步写成「试抓失败: 错误码」;调用过模型的,失败时也交用量。
      - 发给模型的 HTML 只清空脚本和样式的内容,元素的层级和位置不变,位置 XPath 照样能用;超过 20 万字符的页面直接失败,不截断。
  7. 删掉手工演示用的页面:page2.html、notice/101.html 至 104.html、department/a/index.html、department/b/index.html 没有测试读取,是为了手工翻页、点开正文搭的演示站,超出了 #46 的范围(试抓只读第一页,验收用保存的页面和固定的模型响应)。这七个文件删掉;README.md 新加的两段只留第一句,也就是说明 probe.html 的那句。probe.html 里指向 page2.html 的下一页链接留着没关系,测 next 选择器用不着文件真的存在。

建议一起改

  • 按上面改完,为删掉的检查写的测试用例跟着删掉。

说明

  • 云南大学那页的日期:年份写在分组标题里、行内只有日和中文月份,这是读列表的一种通用能力,不在这个 PR 里做,我们记到路线图上另做。
  • 通用解析有结果但选错了栏目:照现在的做法交给管理员预览判断,和 #46 商定的一样。
  • 改完转为待审,或者在这里说一声,我们就复审。推了新提交后 CI 要我们再批准一次才会跑,这是 fork 来的 PR 的正常情况。

@JunieXD JunieXD left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

感谢!上次的 7 条都改好了:生成配置直接用 Selectors,失败原因和正式抓取一个写法,走不到的检查删干净了;模型日志挪进 Responses.generate 以后,耗时不再算排队的时间;_probe 和 _crawl 也是一个写法了;文档只留规则。描述里还补上了用 Selectors 实际调一次模型的结果。我们拉到本地跑了 ./check.sh,266 个测试全部通过,CI 也通过了,这就合并。

@JunieXD
JunieXD merged commit 900f234 into mousebaoyan:main Nov 13, 2026
2 checks passed
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.

[任务] 试抓:抓取节点这边

2 participants