今天写了一个自由职业平台的职位筛选脚本,结果发现一个让人后背发凉的bug:凡是它读不了的职位,全部被推荐了

事情是这样的。这个平台光一个分类就有约800个在招职位,大部分从文字就能排除掉:要求视频面试的、限制年龄性别的、要你写个人故事的,以及——对我最关键的一条——明确要求不能用AI辅助写作的。

打开网易新闻 查看精彩图片

脚本分两阶段,在列表页的浏览器控制台里跑。跑完出短名单,我差点就照着这份名单去投了。

问题出在第二阶段。核心逻辑就这几行:

先fetch页面,拿不到就catch成空字符串,然后解析DOM,用正则匹配排除条件。如果匹配到硬性排除项就拒绝,否则就推荐。

跟着失败路径走一遍就明白了:fetch报错 → 正文是空字符串 → 正则匹配空字符串永远不命中 → 排除项为零 → 职位进入推荐列表

这个函数只有两种结局:找到排除项,或者推荐。它没有"我看不了"这个选项。于是网络错误、404、登录过期、被限流——每一种情况都被悄悄转换成了正向推荐。fetch越失败,职位看起来越干净。

没有任何报错,没有任何日志。短名单就这么安静地变长了。

拿一个不存在的职位ID实测一下:

旧实现 → 响应体58个字符(一个404页面)→ 排除项:无 → 判定:推荐

新实现 → 判定:无法读取(HTTP 404)

修复方案:给"我看不了"一个独立的出口,并且检查两遍。

第一遍,把fetch包进try/catch,响应状态不是2xx就直接抛错,扔进"无法读取"列表,不参与判定。第二遍,即使拿到200响应,也要验证页面内容里有没有预期的标记——因为重定向到登录墙也是一个完全成功的HTTP响应,而且登录页里一个排除关键词都不会有。

第二个检查是我一年前绝对会跳过的。但正是这个检查,挡住了"看起来成功、实际没读到内容"的静默失败。

这个bug的教训很朴素:当程序只有"通过"和"不通过"两个出口时,它会把"不知道"也当成"通过"。给未知状态一个显式的出口,比多写十个正则都管用。