多源采集最天真的假设是“分页到底会 404”。实际上两个源各有各的骗法,而且都不报错。
01编码要先解决,否则后面全错
老站在编码上完全不统一,中文站点常见 GBK / GB2312 / GB18030 混用,而响应头里写的charset还可能和实际不一致。做法是不信任响应头:取原始字节,优先按 GB18030 解码(它是 GBK 的超集,能吃下更多历史字符),失败再退 UTF-8。写个 decode 的兜底链比写选择器更靠前。
02终止判据不能只有一条
| 源 | 超页行为 | 可靠判据 |
|---|---|---|
| ZOL | 返回 200 的兜底页(内容不是列表) | 判定“兜底页”后停止 |
| PConline | 不 404,回绕到同一批首页推荐 | 优先读 page-next 链接,并检测 offset 回绕 |
如果只看“响应里有没有条目”,两者都会被绕过:兜底页里可能仍有商品模块(是排行榜之类),回绕页条目数还很“正常”。所以我用三重判据,任一命中就停:
- 分页上限(硬阈值,防失控);
- 有效条目低于阈值(结构在但没货);
- 响应字节低于阈值(兜底页通常明显更小)。
兜底页判定独立成一个函数 is_fallback_page,好让下载中间件在解析之前就能拦住异常响应——解析之后再判会污染统计。
PConline 的回绕尤其阴:越界请求返回的还是合法列表,内容是首页推荐位。条目数完全正常,只有“这批商品链接和上一轮重复”能暴露它。所以除字节数外还要比指纹。
03选择器要同时兼容两代模板
同一个站的两代列表模板,DOM 结构不同:老版是 h3 > a 加兄弟节点 .price-type,新版是 p > a 加 .price / em.price。如果按 a.pic 的父节点切块,会漏掉主列表、只抓到侧栏排行榜——它确实抓到东西了,所以不会报错。
这是采集里最坏的一类 bug:产出非空、格式合法,但抓的是完全不同的模块。现在会把“每页入库条数”和“类目是否出现在结果里”当作健康指标看,而不是只看有没有异常抛出。
04两个必须显式转成“无”的占位
- 懒加载的图片地址在
src之外,真地址挂在.src这类自定义属性上,直接读src只会拿到占位图链接; - 评分条的
width:0%不是“零分”,而是“暂无评分”。当数值解析就会把该类目均分拖到极低。占位值必须在解析时就转成 None,否则下游聚合被静默污染,而且看上去很“合理”。
参数页链接也不在列表 HTML 里,是靠把 review.shtml 路径做字符串替换推导出来的。这类“站点的约定”一旦改版就会整批失效,所以推导失败要计入指标,不能默默跳过。
05一次 503 死循环:整晚只跑出一个类目
现象是任务跑了很久但产出一条都没有。逐层看下去发现链条是这样:
- 请求触发 503,退避策略重试到上限;
- 重试耗尽时中间件
return response而不是抛异常; - 于是那段 503 的 HTML 被当成一个正常页面送进解析器;
- 解析出的条目为空,但“下一页”链接仍在;
- 于是每一页都重新耗满整套退避时间,一晚上只走一个类目,日志还是空的。
修法:重试耗尽改为 raise CloseSpider 并把最后一次的响应存成 artifact 供复盘;退避加令牌桶限速且禁止突发;类目之间加长冷却。改完再全量跑,503 仍会出现,但退避能恢复,不再是空转。
教训是那句老话:**“没有异常”不等于“成功”。**尤其当你的失败路径是“把异常响应当普通响应返回”时,整条链路只会表现成一次很慢的无产出。
06解析覆盖率是治理项,不是采集量问题
两个源的规格形态天然不同:PConline 的 spec_raw 是 k:v;k:v 的结构化串,ZOL 则把规格写进营销长句里。所以同一套抽取规则在两边效果必然差很多。
覆盖率低主要来自三件事:大量面议 / 无报价;品牌词典没覆盖;自然语言里结构化规格本身占比就低。这些属于词典与解析治理,和“今天抓了多少条”是两回事,必须分开度量。品牌我改为优先从 URL 路径段取(站点分类比标题词典可靠),标题识别退化成兜底——一个类目的品牌覆盖因此明显改善。
07相关文章
- 波动怎么归因:采集量骤降的归因:先怀疑自己,再怀疑源站
- 契约设计:爬虫也要有数据契约