上周我跑 trae-novel 项目的番茄小说榜单爬虫时,突然收到一堆 404 告警:男频科幻末世、玄幻奇幻等核心榜单的数据全抓失败了。第一反应以为是反爬升级,查到一半才弄明白,根子不在反爬,而是我们硬编码的 API 参数和页面的路由规则悄悄「脱节」了。
先怀疑自己:翻 URL 配置
故障刚冒头时,我先去翻代码里的 URL 配置。原来爬虫请求的榜单页面路径是 rank/1_2_8,中间那段 2 对应硬编码的 rankMold=2 参数,用来标识榜单类型。可我打开浏览器访问最新的番茄小说男频科幻末世榜单,发现页面路径已经变成 rank/1_3_8,中间数字从 2 变成了 3。当时我估摸着是页面路由规则改了,只要把爬虫的请求路径同步更新就行------结果改完一测还是报错。
这时候才醒过来:页面路径可能只是表层变化,真正决定数据能否返回的,是页面发起 API 请求时带的参数,不是我们直接请求的页面地址。
用 live check 确认页面路由变了
为了确认,我用 Playwright 做了一次 live check:访问新的 rank/1_3_8 页面,把页面运行时的所有网络请求都抓下来。关键信号很快出来了------页面自己发起的榜单数据 API 请求里,携带的 rankMold 参数已经从 2 变成 3,而我们的爬虫代码里还硬写着 rankMold=2,这才是请求报错的核心原因。
这里我差点栽个跟头:一看到路径变了就默认是路径的问题,差点直接去改爬虫的请求 URL,还好先抓了网络请求验证,不然至少多花半小时。后来翻代码提交记录才看清,当初写爬虫的人直接把当时的 rankMold 值写死了,压根没考虑平台会改这个参数。
不写死参数,做适配性改造
找到根因后,我没图省事直接写死 rankMold=3,而是做了一次适配性改造:既然页面路径中间那段就是 rankMold 的值,不如直接从页面路由里动态提取,再赋给 API 请求。改造后的核心逻辑是这样:
改造后的取参逻辑
python
def get_rank_params(page_url: str) -> dict:
# 页面路径格式固定为 /rank/{rankMold}_{categoryId}_{pageType}
path_segments = page_url.strip("/").split("/")[-1].split("_")
return {
"rankMold": int(path_segments[0]),
"categoryId": int(path_segments[1]),
"page": 1
}
加一道连通性校验兜底
除了参数动态化,我还加了个轻量的连通性校验:每天自动跑一遍核心榜单请求,连续 3 次返回错误就自动往项目群发告警,不用等业务方找上门才知道挂了。
复盘:别把页面变化和接口参数变化混为一谈
这次排查的根子,是把「页面表层变化」和「接口核心参数变化」混为一谈了。后来番茄小说又改了一次页面路径,爬虫一点没受影响------只要规则还是中间段对应 rankMold,动态提取的逻辑就能自动适配。回过头看,那次差点白改 URL 的教训挺值:爬虫挂着平台规则跑,最忌讳把参数写死,宁可多抓一次网络请求验证,也别凭页面路径的表象下结论。
做自动化这几年,最耗时间的从来不是写代码,是摸清每个平台的脾气。
这篇里提到的坑,都是真金白银踩出来的。
如果你手上也有重复度很高的活儿------批量发布、数据搬运、有固定规则的机械操作
------可以在评论区说说你的场景,我看看能不能自动化掉。