我有一个自定义的 dupefilter,当我启动/停止它时,我用它来加载/保存我的抓取状态到 s3 中。
我想从重复过滤器中删除具有特定 http status_code 的网址,例如 429。
我最初的想法是收集Spider_Closed方法中的每个失败的URL,并在将其保存到S3之前从看到的所有非200个URL中删除,但我无法让它工作,我不确定这是最好的方法。
这是我的 dupefilter 类:
from scrapy.dupefilters import BaseDupeFilter
import hashlib
import pickle
class S3DupeFilter(BaseDupeFilter):
@classmethod
def from_crawler(cls, crawler):
bucket_name = ...
key_name = ...
logger = crawler.spider.logger
return cls(bucket_name, key_name, logger)
def __init__(self, bucket, key, logger):
super(S3DupeFilter, self).__init__()
self.bucket_name = bucket
self.key_name = key
self.logger = logger
self.seen = set()
self.load()
def request_seen(self, request):
fp = self.request_fingerprint(request)
if fp in self.seen:
return True
self.seen.add(fp)
return False
def close(self, reason):
self.save()
def load(self):
try:
self.seen = aws_s3_read_data(...)
if not self.seen:
self.seen = set()
self.logger.info(f"Loaded {len(self.seen)} fingerprints from S3")
except Exception as e:
self.logger.error(f"Error loading dupefilter from S3: {e}")
def save(self):
try:
serialized_seen = pickle.dumps(self.seen)
aws_s3_insert_data(...)
self.logger.info(f"Saved {len(self.seen)} fingerprints to S3")
except Exception as e:
self.logger.error(f"Error saving dupefilter to S3: {e}")
def request_fingerprint(self, request):
return hashlib.sha256(request.url.encode('utf-8')).hexdigest()
def open(self):
pass
回答我自己的问题,这是我找到的解决方案:
我在自定义 dupefilter 类中添加了一个
unsee_request()
方法。它从 seen
集合中删除一个 url
def unsee_request(self, request):
fp = self.request_fingerprint(request)
if fp in self.seen:
self.seen.remove(fp)
我重写了
_retry
的 RetryMiddleware
方法。它检查请求是否失败以及是否达到 max_retries。如果是这样,它会调用 unsee_request()
方法。
def _retry(self, request, reason, spider):
retry_req = super()._retry(request, reason, spider)
if not retry_req:
spider.crawler.engine.slot.scheduler.df.unsee_request(request)
return retry_req