本文目录导读:

我来详细说明Python爬虫去重机制的设计方案:
基础去重方案
内存集合去重(适合小规模)
class SimpleDeduplicator:
def __init__(self):
self.seen = set()
def is_duplicate(self, item):
if item in self.seen:
return True
self.seen.add(item)
return False
def clear(self):
self.seen.clear()
哈希去重(节省内存)
import hashlib
class HashDeduplicator:
def __init__(self):
self.seen = set()
def _hash(self, url):
return hashlib.md5(url.encode()).hexdigest()
def is_duplicate(self, url):
url_hash = self._hash(url)
if url_hash in self.seen:
return True
self.seen.add(url_hash)
return False
使用Redis实现分布式去重
import redis
import hashlib
class RedisDeduplicator:
def __init__(self, redis_host='localhost', redis_port=6379, key='crawler:visited'):
self.client = redis.Redis(host=redis_host, port=redis_port)
self.key = key
def is_duplicate(self, url):
url_hash = hashlib.md5(url.encode()).hexdigest()
return self.client.sismember(self.key, url_hash)
def add(self, url):
url_hash = hashlib.md5(url.encode()).hexdigest()
self.client.sadd(self.key, url_hash)
def batch_add(self, urls):
hashes = [hashlib.md5(url.encode()).hexdigest() for url in urls]
self.client.sadd(self.key, *hashes)
Bloom Filter去重(高效内存使用)
from pybloom_live import BloomFilter
class BloomDeduplicator:
def __init__(self, capacity=100000, error_rate=0.001):
self.bloom = BloomFilter(capacity, error_rate)
def is_duplicate(self, url):
return url in self.bloom
def add(self, url):
self.bloom.add(url)
自定义Bloom Filter实现
import math
import hashlib
from bitarray import bitarray
class CustomBloomFilter:
def __init__(self, n, fpr):
"""
n: 预计元素数量
fpr: 期望误判率
"""
self.n = n
self.fpr = fpr
self.m = self._calc_m(n, fpr) # 位数组大小
self.k = self._calc_k(n, self.m) # 哈希函数数量
self.bit_array = bitarray(self.m)
self.bit_array.setall(0)
def _calc_m(self, n, p):
return int(-n * math.log(p) / (math.log(2) ** 2))
def _calc_k(self, n, m):
return int((m / n) * math.log(2))
def _get_hashes(self, item):
hashes = []
for i in range(self.k):
hash_val = hashlib.md5(f"{item}{i}".encode()).hexdigest()
hashes.append(int(hash_val, 16) % self.m)
return hashes
def add(self, item):
for hash_val in self._get_hashes(item):
self.bit_array[hash_val] = 1
def is_duplicate(self, item):
for hash_val in self._get_hashes(item):
if not self.bit_array[hash_val]:
return False
return True
综合去重方案
import re
from urllib.parse import urlparse, urlunparse
from collections import deque
class AdvancedDeduplicator:
def __init__(self,
method='bloom',
max_size=1000000,
redis_config=None):
self.method = method
self.method = method
self.fingerprints = set() if method == 'set' else None
if method == 'bloom':
self.deduplicator = BloomDeduplicator(max_size)
elif method == 'redis':
self.deduplicator = RedisDeduplicator(**redis_config)
elif method == 'hash':
self.deduplicator = HashDeduplicator()
else: # set
self.deduplicator = SimpleDeduplicator()
def normalize_url(self, url):
"""URL标准化处理"""
parsed = urlparse(url)
# 去除片段标识
clean_url = parsed._replace(fragment='')
# 标准化路径
path = re.sub(r'/$', '', clean_url.path)
clean_url = clean_url._replace(path=path)
# 标准化查询参数(排序)
if clean_url.query:
params = sorted(clean_url.query.split('&'))
clean_url = clean_url._replace(query='&'.join(params))
return urlunparse(clean_url)
def extract_fingerprint(self, url):
"""提取URL指纹"""
normalized_url = self.normalize_url(url)
return normalized_url
def is_duplicate(self, url):
fingerprint = self.extract_fingerprint(url)
return self.deduplicator.is_duplicate(fingerprint)
def add(self, url):
fingerprint = self.extract_fingerprint(url)
self.deduplicator.add(fingerprint)
内容去重(检测重复内容)
import hashlib
from simhash import Simhash
class ContentDeduplicator:
def __init__(self, threshold=3):
self.threshold = threshold # SimHash汉明距离阈值
self.simhashes = []
def simhash_distance(self, hash1, hash2):
"""计算SimHash汉明距离"""
x = hash1 ^ hash2
return bin(x).count('1')
def is_duplicate_content(self, content):
"""判断内容是否重复"""
current_hash = Simhash(content).value
for existing_hash in self.simhashes:
distance = self.simhash_distance(current_hash, existing_hash)
if distance <= self.threshold:
return True
self.simhashes.append(current_hash)
return False
完整爬虫去重示例
import requests
from urllib.parse import urljoin
from bs4 import BeautifulSoup
class Crawler:
def __init__(self):
self.deduplicator = AdvancedDeduplicator(method='bloom', max_size=1000000)
self.queue = deque()
self.content_dedup = ContentDeduplicator()
def extract_links(self, url, html):
"""提取页面链接"""
soup = BeautifulSoup(html, 'html.parser')
links = []
for a_tag in soup.find_all('a', href=True):
href = a_tag['href']
full_url = urljoin(url, href)
# 过滤非HTTP链接
if not full_url.startswith(('http://', 'https://')):
continue
# 去重检查
if not self.deduplicator.is_duplicate(full_url):
links.append(full_url)
return links
def crawl(self, start_url, max_pages=100):
"""开始爬取"""
self.queue.append(start_url)
crawled_count = 0
while self.queue and crawled_count < max_pages:
url = self.queue.popleft()
if self.deduplicator.is_duplicate(url):
continue
try:
response = requests.get(url, timeout=10)
response.raise_for_status()
# 内容去重
if self.content_dedup.is_duplicate_content(response.text):
print(f"重复内容跳过: {url}")
continue
# 标记为已访问
self.deduplicator.add(url)
crawled_count += 1
print(f"爬取: {url} ({crawled_count}/{max_pages})")
# 提取新链接
new_links = self.extract_links(url, response.text)
for link in new_links:
self.queue.append(link)
except Exception as e:
print(f"爬取失败 {url}: {str(e)}")
使用建议
场景选择
- 小规模爬虫(<1万URL):使用内存Set
- 中等规模(1-100万):使用Bloom Filter
- 大规模分布式(>100万):使用Redis去重需求**:添加SimHash
优化策略
# 1. 多级去重
class MultiLayerDeduplicator:
def __init__(self):
self.bloom = BloomDeduplicator()
self.redis_set = RedisDeduplicator()
def is_duplicate(self, url):
# 先在Bloom Filter检查(快速)
if not self.bloom.is_duplicate(url):
self.bloom.add(url)
return False
# Bloom Filter认为重复时,再到Redis确认
return self.redis_set.is_duplicate(url)
# 2. 定时清理
class TimedCleanDeduplicator:
def __init__(self, clean_interval=3600):
self.deduplicator = AdvancedDeduplicator()
self.clean_interval = clean_interval
self.last_clean = time.time()
def check_and_clean(self):
if time.time() - self.last_clean > self.clean_interval:
self.deduplicator.deduplicator.clear()
self.last_clean = time.time()
选择合适的去重方案取决于你的爬虫规模、运行环境和性能要求,建议从小规模开始,根据实际需求逐步升级。
版权声明:足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播
足球直播
足球直播
足球直播
足球直播
足球直播
中超直播
中超直播
中超直播
中超直播
中超直播
英超直播
英超直播
英超直播
英超直播
英超直播
德甲直播
德甲直播
德甲直播
德甲直播
德甲直播
西甲直播
西甲直播
西甲直播
西甲直播
西甲直播
意甲直播
意甲直播
意甲直播
意甲直播
意甲直播
NBA直播
NBA直播
NBA直播
NBA直播
NBA直播
篮球直播
篮球直播
篮球直播
篮球直播
篮球直播