Repository navigation
实现通知列表解析、配置和历史翻页 - #83
Conversation
JunieXD
left a comment
There was a problem hiding this comment.
感谢!往回翻页的停止条件想得很细,重复的置顶、日期未知、闰日、翻页循环都有测试;非法配置转成 StepError、只能用 HTTP 的来源也都照 Issue 做了。我们本地跑了全部检查(148 项通过),逐个文件读了代码,还用这个 PR 的代码把 sources.csv 里 45 个来源的列表首页各读了一次。
实测结果:要用浏览器的 15 个来源归 #41,不算在内;剩下 30 个,通用抓取标题和日期都取对的有 9 个。
| 情况 | 来源 |
|---|---|
| 都取对了(9) | 北科大人工智能学院、电子科大研究生院、东南电子科学与工程学院、东南网安、东南计算机、厦大信息学院、西电电子工程学院、计算所、软件所 |
| 取到标题,没有日期(10) | 北科大计算机与通信工程学院、电子科大信通学院、厦大航空航天学院、西电人工智能学院、西电研究生院、西电计算机、信工所、国科大杭州高等研究院智能科学与技术学院、沈自、自动化所;其中北科大计通、西电人工智能学院的日期混进了标题 |
| 取成了导航栏(1) | 厦大人工智能研究院 |
| 一条也没取到(10) | 北科大研究生院、北科大自动化学院、东南研究生院、东南蒙纳士大学国际学院、厦大电子科学与技术学院、厦大电影学院、厦大研究生院、西电网信、国科大前沿交叉科学学院、国科大研究生院 |
原因是 5 套模板认的是个别网站的特征:网址里带 info/ 或 /t20,class 叫 infoBox、views-element-container;日期也只认 class 里带 date、time、news_meta、pull-right 的元素(listing.py:54-102)。失败的页面其实都很规整,比如:
- 西电研究生院:
<li><a>标题</a><span>2026-10-21</span></li>,日期的span没有 class; - 沈自:日期在
<span class="right">里; - 北科大研究生院:通知链接是一串字母和数字的
.htm,不带info/; - 东南研究生院:表格排版,一条通知一个
<tr>; - 西电人工智能学院:日期写在链接里面,
<a><span>2021-04-25</span>标题</a>。
必须改
- 通用抓取改成按页面结构认列表:Issue 要的是「一种写法管一批网站」。建议换成一条规则:在同一个父元素下,找出「恰好一个通知链接、整行文字里有一个完整日期」的子元素,最多的那一组就是列表;日期从整行的文字里找,标题优先用链接的
title,没有就用链接的文字,并去掉其中的日期。我们用临时脚本试了这条规则,上面 5 个页面都取对了(各 14–20 条)。你已经处理好的写法(日和年月分开写、标题和摘要在同一个链接里)照样保留。改完用sources.csv里不用浏览器的 30 个来源实测,在 PR 里列出每个来源取到几条、有没有日期;通用规则取不对的再写配置,并说明原因。出问题的页面结构存成样例(标题换成编的),加进测试。 - 往回翻时某一页没有条目,已经取到的都被丢掉了:
listing.py:270-271在这种情况下return [],连首页取到的也不交,这一轮记成no_items;下一轮这个来源还算「第一次抓」,又从头翻,可能一直交不上。改成当作翻到头了,交已经取到的,并补上测试(首页有条目、下一页没有)。 - 配置的
link、next选中元素时取成了文字:_value(listing.py:119-136)对元素取的是它的文字。配置写成./div/a、没写./div/a/@href时,_url会把通知标题、「下一页」这样的文字当成相对网址,拼出一个错的地址,也不报错。link和next选中元素时,取它的href。管理员和 #46 的 LLM 都要写这种配置,很容易踩到。 - 读页面和翻页放进
crawl:开发约定 §1 的模块表里,抓列表页归crawl,parse只从网页里取文字、链接和日期,parse/dates.py、parse/indexing.py都是这样。parse/listing.py只解析一页,不用Fetcher、不抛StepError;读页面、往回翻页、HTTP 状态,以及把解析的错误转成StepError,都放到crawl里(比如crawl/listing.py)。当前时间照crawl_source的写法传clock,不另加today参数(listing.py:241、:255)。
建议一起改
- 开发约定 §2.1 写得太细:停止条件的细节、闰日、「模板和样例放在
parse/」这些删掉,只留管理员和 #46 照着写配置要知道的:格式、每个选择器相对于谁、link和next取的是网址、force_http什么时候用。 - 判断 XPath 返回的类型,用
isinstance就够了:_elements、_value(listing.py:51、:111-136)用TypeAdapter逐个校验返回值的类型,写得绕。
说明
- 要用浏览器的 15 个来源(北邮 8 个、电子科大 7 个)归 #41,这个 PR 不用管。
- 改完转回待审,或者在这里说一声。
JunieXD
left a comment
There was a problem hiding this comment.
感谢!这一版改得很大,效果也很明显。我们用这版代码把 sources.csv 里不用浏览器的 30 个来源的列表首页各读了一次:30 个都取到了条目,390 条全部有日期,上次只取对 9 个。国科大研究生院只有 2 条、厦大航空航天学院只有 4 条,我们打开页面核对过,页面上本来就只有这么多。上次的 4 条必须改和 2 条建议也都改了,./check.sh 的 159 项都通过。
必须改
-
标题被改坏了:
- 括号被去掉:
parse/listing.py:213的.strip(" []【】()()")会去掉标题开头和结尾的括号。实测 390 条里有 39 条被改坏,比如「【预推免】北京科技大学……」变成「预推免】北京科技大学……」,「……报名通知(第二批)」变成「……报名通知(第二批」。 - 日期删多了:同一行的
_DATE.sub("", text)会删掉标题里所有的完整日期,「关于2026年9月10日开展夏令营的通知」会变成「关于开展夏令营的通知」。 - 日期删少了:日和年月分开写、又写在链接里的(
<a><span>09.10 2026</span>通知甲</a>),日期反而删不掉。
上次我们说「去掉其中的日期」,指的只是发布日期,没说清楚,抱歉。请改成:标题只去掉被当作发布日期的那一段文字,连同紧挨着它的一对括号(比如
[2026-10-21]),其他文字原样保留。_row_date找日期时已经知道它在哪个元素或哪段文字里,把这个位置一起交给_title,照着去掉那一段,不再用正则删日期,也不再 strip 括号。补上这几种测试:
- 「【博士】……(第二批)」这样带括号的标题;
- 标题里写了活动日期、旁边另有发布日期的;
- 日和年月分开写的日期在链接里的。
- 括号被去掉:
建议一起改
- 翻页链接指回读过的页时,当作已经翻到头:
crawl/listing.py:37-38现在会报「分页链接循环」,这一轮什么都不交。道理和上次第 2 条一样:往回翻只在第一次抓时做,这一轮交不上,下一轮还算第一次抓,又从头翻、又失败,连首页上的新通知也一直交不上。请改成和遇到空页一样停下来,交已经取到的;test_cyclic_pagination_is_an_error_instead_of_a_partial_success跟着改。 - 页面解析不了时,不要写成「配置不对」:
crawl/listing.py:48-49给parse_listing的所有错误都加了「配置不对」的前缀。一个没写配置的来源,页面编码认不出来时,看板上也会显示「配置不对: 列表页无法解析」。parse_listing的错误文字已经说清楚了,这里直接用它。
说明
JunieXD
left a comment
There was a problem hiding this comment.
感谢!这一轮把标题改对了:
- 只去掉发布日期那一段和包着它的括号,标题里本来的日期和括号都留着;
- 翻页链接指回读过的页时,停下来交已经取到的;
- 解析错误不再写成「配置不对」。
我们本地跑了全部检查(170 项测试通过),又用这一版把 27 个普通来源的首页各读了一次:
- 一共 390 条,和上一轮一样多,每个来源的条数也没变;
- 上一轮被去掉括号的 39 条都恢复了,括号全部配对;
- 标题里没有混进发布日期,也没有晚于今天的日期。
这就合并。
关联
Closes #6。配置格式供来源管理及 #46 的试抓生成复用。
改了什么
通用列表解析按页面结构识别:同一父元素下,每行只有一个有效通知目标且有完整日期,选择符合条件最多的一组。忽略空链接,日期从行文字和日期容器取,支持完整日期、日与年月分栏、月日与年份分栏;标题优先用链接 title,再取标题元素或链接文字。保留发布日期在行文字中的位置,只删除标题内对应的发布日期及紧邻的一对括号,保留活动日期和其他括号;支持链接内的分栏日期,保留标题与摘要分离能力。不按域名、通知网址片段或建站系统 class 选择模板。
parse/listing.py只解析单页,返回条目及下一页;crawl/listing.py的list_items(fetcher, source, clock=...)负责请求、回补、HTTP 状态和错误转换,可传给Steps.list_items。时间采用可注入 clock,与一轮流程一致。回补近两年,保留边界当天和未知日期,处理重复置顶、去重、闰日、循环及连续页码。历史页没有条目或下一页指回已读页面时结束翻页并返回已累计结果;HTTP 或网络失败仍报错,不把失败当成功结束。配置 link 和 next 选中元素时取 href;支持选中属性或字符串的写法。非法配置记入现有抓取记录,页面解析错误保留原始错误描述。配置文档只保留格式、相对位置、网址和 force_http 的用法。
怎么验证的
格式、lint、严格类型、生成类型检查通过,170 项离线测试通过。增加 8 份从失败页面截取、替换全部通知内容的结构样例,覆盖字母数字链接、无 class 日期、right 日期、链接内日期、嵌套表格、分栏日期、空链接及标题摘要分离;另覆盖 HTML 注释、空历史页、元素 href、标题括号与活动日期保留、链接内分栏日期、分页循环保留已取结果和页面解析错误提示。固定页面测试不访问外网。
git diff --check通过,按审查意见、Issue 及 AGENTS.md 自查。2026-11-07 通过原版 Fetcher 对 sources.csv 的 30 个非浏览器来源各读一次首页,保留 UA、同站串行及间隔;本次标题修复后只用这批本地页面复算,仍为 390 条、390 条都有完整日期,修正 40 个标题,未新增源站请求。以下是每个来源取得的条数与完整日期数,均使用通用规则,不需要专用配置。标题未混入导航、发布日期或摘要;航空航天来源首页实际只有 4 条,西电网信为 8 条。
需浏览器的北邮 8 个及电子科大 7 个来源留给 #41。不在真实网站批量回补。另保留 Drupal 及显式 HTTP 配置样例,未新增来源或修改 sources.csv。
影响
新增抓取节点读列表步骤及配置格式,不改网站接口、MCP 工具、数据库或生成类型。#45 从 crawl.listing 导入步骤,#46 可复用 ListConfig;本 PR 不实现浏览器、正文附件、初筛或抽取。