为什么 Hacker News 这种顶级社区会出现首页重复链接?聊聊抓取端的去重实战
URL -> StoryID 的映射校验,只要链接一致,第二次提交理应直接重定向到原帖,而不是生成一个新的 ID。从技术实现角度分析,这种“漏洞”出现的原因大概率出在分布式系统的可见性延迟(Consistency issue)上。在高性能的后端架构中,写入操作和查询校验之间往往存在毫秒级的同步差。如果两个相同的 URL 在极短的时间差内被并发提交,且此时分布式数据库的索引尚未完成全局同步,那么第二次提交的校验请求可能会在“旧数据尚未可见”的情况下通过审核,从而导致重复项抢先入库。另一种可能性是 URL 携带了细微的查询参数差异,导致哈希校验失效,被系统判定为两个不同的资源。
对于大多数用户来说,这只是个小 Bug,但对于我们这些写 AI Agent 或构建自动化资讯聚合工作流的开发者来说,这其实是一个巨大的坑。很多人的习惯是“信任源端”,认为像 HN 这样级别的 API 返回的数据天然就是唯一的。但实战证明,过度信任源端唯一性会导致下游数据库出现主键冲突,或者在 AI 处理环节浪费 Token 去分析重复的内容。
如果你正在写一个基于 HN 抓取的聚合器,千万不要在代码里假设 stories 列表是纯净的。最稳健的做法是在自己的数据库层强制加上唯一索引(Unique Constraint),或者在数据入库前增加一层前置过滤逻辑。
这里分享一个最简单且高效的 Python 处理方案。在处理 API 返回的 JSON 列表时,利用 set 的 O(1) 查询复杂度进行快速去重,比在数据库里用 DISTINCT 慢速查询要快得多:
def filter_duplicates(stories):
# 使用 set 记录已经处理过的 URL,确保时间复杂度在 O(n)
seen_urls = set()
unique_stories = []
for story in stories:
url = story.get('url')
# 只有当 URL 不在集合中,且 URL 确实存在时才记录
if url and url not in seen_urls:
unique_stories.append(story)
seen_urls.add(url)
return unique_stories在实际部署这个逻辑时,建议将 seen_urls 的生命周期与抓取批次绑定。如果你处理的是实时流数据,可以将 set 替换为 Redis 的 SADD 命令,设置一个 24 小时的过期时间。这样既能拦截掉像 HN 这种偶尔失效的源端去重,也能防止在分布式抓取环境下出现重复入库。
总结来说,无论源端平台多么强大,在构建数据 pipeline 时,始终坚持“零信任”原则,在自己的清洗层(Cleaning Layer)构建一套稳健的去重机制,才是避免生产环境报错的唯一方案。