Python 爬虫实战:基于 SimHash 的语义级去重与近重复检测
摘要
在大规模网络爬虫项目中,网页内容的重复或高度相似是常见痛点。简单的 URL 去重无法解决内容微调后的重复问题。本文介绍如何利用 SimHash 算法构建语义级去重系统,实现对亿级长文本的近重复检测,提升数据采集质量。
背景与需求
随着数据量的增长,传统哈希(如 MD5)对微小改动过于敏感,而简单的字符串匹配效率低下。我们需要一种能够容忍一定编辑距离的指纹算法。SimHash 通过局部敏感哈希特性,将文本映射为固定长度的指纹,通过计算汉明距离即可判断相似度,非常适合处理海量数据的近重复检测。
合规与注意事项
在实施爬虫前,务必遵守目标网站的 Robots 协议,控制请求频率,避免对服务器造成压力。同时注意数据隐私和版权合规,确保采集的数据仅用于合法用途。
技术选型与整体流程
我们采用 Python 作为主要开发语言,利用其丰富的生态库。核心流程包括:文本清洗、分词、SimHash 指纹生成、指纹存储(推荐 Redis 或 MongoDB)、以及基于阈值的比对去重。
环境准备与依赖安装
项目依赖主要包括 jieba 用于中文分词,simhash 或自定义实现。
pip install jieba simhash
核心实现:SimHash 引擎
SimHash 的核心在于将文本转化为特征向量。首先对文本进行分词,统计词频,然后对每个词进行 Hash 运算得到二进制串,最后累加权重生成最终指纹。
import hashlib
from collections import Counter
class SimHashEngine:
def __init__(self, k=64):
self.k = k # 指纹长度
def _hash(self, word):
h = hashlib.md5(word.encode('utf-8')).hexdigest()
return bin(int(h, 16))[2:].zfill(self.k)
def get_fingerprint(self, text):
# 简单分词示例,实际生产建议使用 jieba
words = text.split()
weights = Counter(words)
v = [0] * self.k
for word, count in weights.items():
bits = self._hash(word)
for i, bit in enumerate(bits):
if bit == '1':
v[i] += count
else:
v[i] -= count
# 生成二进制指纹
fingerprint = ''.join(['1' if x > 0 else '0' for x in v])
return int(fingerprint, 2)
核心实现:去重逻辑插件
有了指纹后,关键在于如何高效比对。对于新来的文档,计算其指纹并与数据库中的指纹比较汉明距离。如果距离小于阈值(通常为 3),则视为重复。
def hamming_distance(hash1, hash2, k=64):
xor = hash1 ^ hash2
return bin(xor).count('1')
def is_duplicate(new_hash, db_hashes, threshold=3):
for db_hash in db_hashes:
if hamming_distance(new_hash, db_hash) <= threshold:
return True
return False
核心实现:业务集成演练
在实际爬虫中,我们将上述模块封装为中间件。在抓取页面后,提取正文,计算指纹,查询缓存,若未命中则入库并更新索引。
关键代码解析
这里要注意分词的质量直接影响指纹准确性。对于中文,必须使用专业分词器。此外,阈值的选择需要权衡召回率和准确率,过小会导致漏判,过大会误杀。
常见问题与排错
- 内存溢出:亿级数据不能全部加载到内存,需借助外部存储或布隆过滤器预处理。
- 性能瓶颈:全量比对 O(N^2) 不可行,建议引入 LSH(局部敏感哈希)或倒排索引优化。
进阶优化
针对跨语言场景,可以结合翻译 API 统一语言后再计算。主题聚类方面,可利用 K-Means 对指纹向量进行分组,发现潜在的内容集群。
总结
SimHash 是解决大规模文本去重的经典方案。通过合理设计指纹生成与比对策略,可以在保证精度的前提下大幅提升爬虫系统的效率。后续可根据业务需求引入更复杂的深度学习模型进行语义理解。


