Python爬虫去重方案
为什么去重?
- 防止发出重复请求
- 防止存储重复数据 (字符串、文件、图片)
去重实现基本原理
根据给定的判断依据和给定的去重容器,将原始数据逐一进行判断,判断去重容器中是否有该数据,若没有该数据则添加进去重容器中,同时标记该数据不是重复数据。 若去重容器中有该数据则不添加,同时标记该数据为重复数据
去重需求步骤
-
先明确数据类型(字符串、数值、对象…) 不同数据类型去重方案不一样
-
明确重复的界定,什么情况下是重复的,怎么判断的?
# 剔除重复项 == data = [123,123,121,'23','123','123',23212] set(data) # {'23', '123', 23212, 121, 123} # 剔除重复项 相同 # 先将列表中所有元素转为字符串再去重 set([str(i) for i in mylist]) #{'23', '123', '121', '23212'}
mylist = [123,123,121,'23','123','123',23212] result = [] #去重结果 sign = [] #判断依据 str(原始数据) 统一以字符串形式存放mylist for new_data in mylist: if str(new_data) not in sign: sign.append(str(new_data)) result.append(new_data) #[123, 121, '23', 23212]
信息摘要算法
- 信息摘要 hash算法指可以将任意长度文本、字节数据,通过一个算法得到一个固定长度文本。如MD5(128位)、SHA1(160位)等。
- 特征:只要源文本不同,计算得到的结果必然不同(摘要)
- 摘要:摘要算法主要用于比对信息源是否一致,因为只要源发生变化,得到的摘要必然不同;而且摘要通常要比源短很多,所以称为摘要
- 因此,利用摘要算法能大大降低去重容器的存储空间使用率,并提高判断速度,且由于其唯一性的特征,几乎不存误判
信息摘要hash算法去重方案实现
- 普通内存版本
- Redis持久化版本
- Mysql持久化版本
import hashlib
import six
#基类
class BaseFilter(object):
def __init__(self, hash_func_name="md5",mysql_url="None",mysql_table_name="filter",):
self.mysql_url = mysql_url
self.hash_func = getattr(hashlib, hash_func_name)
self.storage = self._get_storage()
self.mysql_table_name = mysql_table_name
def _safe_data(self, data):
'''
加密编码时
python2 中 str === python3 中的 bytes
python2 中 unicocde === python3 中的 str
:param data:给定原始数据
:return:二进制类型字符串
'''
if six.PY3:
if isinstance(data, str):
return data.encode()
elif isinstance(data, bytes):
return data
else:
raise Exception("请提供二进制或字符串")
else:
if isinstance(data, unicode):
return data.encode()
elif isinstance(data, str):
return data
else:
raise Exception("请提供str或unicode")
def _get_hash_value(self, data):
'''
通过二进制格式原始数据生成对应指纹
:param data: 二进制原始数据
:return:hash值
'''
hashobj = self.hash_func()
hashobj.update(self._safe_data(data))
hash_value = hashobj.hexdigest()
return hash_value
def save(self, data):
'''
根据data计算出对应指纹进行存储
:param data: 给定原始数据
:return: 存储结果 True False
'''
hash_value = self._get_hash_value(data)
return self._save(hash_value)
def _save(self, hash_value):
'''
存储对应的hash值 (交给对应子类继承)
:param hash_value:通过信息摘要算法求出hash值
:return: 存储结果 True False
'''
pass
def is_exits(self, data):
'''
通过判断给定数据对应的指纹是否存在
:param data: 原始数据
:return: 查询结果 True False
'''
hash_value = self._get_hash_value(data)
return self._is_exits(hash_value)
def _is_exits(self, hash_value):
'''
判断对应的hash是否存在(交给对应子类继承)
:param hash_value:
:return: True False
'''
pass
def _get_storage(self):
'''
返回对应存储对象
:return:
'''
pass
from . import BaseFilter
#基于内存去重方案
class MemoryFilter(BaseFilter):
def _save(self, hash_value):
'''
利用set进行存储
:param hash_value:
:return:
'''
return self.storage.add(hash_value)
def _is_exits(self, hash_value):
if hash_value in self.storage:
return True
return False
def _get_storage(self):
return set()
from . import BaseFilter
from sqlalchemy import create_engine, Column, Integer, String
from sqlalchemy.orm import sessionmaker
from sqlalchemy.ext.declarative import declarative_base
Base = declarative_base()
#基于mysql去重方案
class Filter(Base):
__tablename__ = "filter"
id = Column(Integer, primary_key=True)
hash_value = Column(String(40), index=True, unique=True)
# mysql_url = "pymysql+mysql://root:password@localhost:3306/db_name?charset=utf8"
class MysqlFilter(BaseFilter):
def _get_storage(self):
engine = create_engine(self.mysql_url)
Base.metadata.create_all(engine) # 创建表
session = sessionmaker(engine)
return session
def _save(self, hash_value):
session = self.storage()
filter = Filter(hash_value=hash_value)
session.add(filter)
session.commit()
session.close()
def _is_exits(self, hash_value):
session = self.storage()
ret = session.query(Filter).filter_by(hash_value = hash_value).first()
session.close()
if ret is None:
return False
return True
# 基于Reids的持久化存储去重判断依据
import redis
from . import BaseFilter
class RedisFilter(BaseFilter):
def __init__(self, redis_host="localhost", redis_port=6379, redis_db=0, redis_key="filter"):
self.redis_host = redis_host
self.redis_port = redis_port
self.redis_db = redis_db
self.redis_key = redis_key
def _get_storage(self):
'''
返回 redis 连接池连接对象
:return:
'''
pool = redis.ConnectionPool(host=self.redis_host, port=self.redis_port, db=self.redis_db)
client = redis.StrictRedis(connection_pool=pool)
return client
def _save(self, hash_value):
'''
将数据存储到redis的无序集合中
:param hash_value:
:return:
'''
return self.storage.sadd(self.redis_key, hash_value)
def _is_exits(self, hash_value):
'''
判断redis无序集合中是否有对应判断数据
:param hash_value:
:return:
'''
return self.storage.sismember(self.redis_key, hash_value)
Simhash 算法
Simhash算法是一种局部敏感哈希算法,能实现相似文本内容的去重。
两篇文章得出的Simhash值
Simhash对长文本500字+比较适用,短文本可能偏差较大
在Google的论文给出数据中,64位的Simhash值,在海明距离(两个Simhash值二进制不对应的个数)为3的情况下,可以认为两篇文档是相似或重复的(仅参考)