如何实现Scrapy爬虫的可复现性且不牺牲性能?
问题
需求:让Scrapy爬虫具备可复现性,即对同一静态网站两次爬取时,页面访问顺序完全一致,parse函数打印的URL每次均相同。
已尝试实现:
- 定义SiteSpider类:
class SiteSpider(CrawlSpider): name = 'ReproducibleSpider' rules = [Rule(LinkExtractor(), callback='parse', follow=True)] def __init__(self, urls: list[str], domains: list[str]): super().__init__() self.start_urls = urls self.allowed_domain = domains def parse(self, response, **kwargs): url = response.url if hasattr(response, 'url') else None print(url) return None
- 自定义SortedLinkExtractor类对提取的链接排序:
class SortedLinkExtractor(LinkExtractor): def extract_links(self, response): links = super().extract_links(response) return sorted(links, key=lambda x: len(x.url))
核心问题:Scrapy的并发请求机制导致爬取顺序不可复现。通过设置CONCURRENT_REQUESTS = 1和CONCURRENT_REQUESTS_PER_DOMAIN = 1虽能实现可复现,但完全牺牲了爬虫性能,需要兼顾性能的解决方案。
解决方案
要同时保证并发性能和爬取顺序的可复现性,核心是让Scrapy的请求队列按固定规则排序,同时保留并发执行能力,具体实现步骤如下:
1. 给请求分配固定优先级
Scrapy调度器默认随机处理请求,需为每个请求分配唯一且固定的优先级,确保相同URL的请求优先级一致,队列顺序固定。可在parse方法中手动生成Request并设置优先级:
def parse(self, response, **kwargs): url = response.url if hasattr(response, 'url') else None print(url) # 使用自定义SortedLinkExtractor提取链接 link_extractor = SortedLinkExtractor() links = link_extractor.extract_links(response) # 基于URL哈希值设置固定优先级,相同URL优先级完全一致 for link in links: yield scrapy.Request( url=link.url, callback=self.parse, priority=abs(hash(link.url)) )
注意:需替换原Rule方式,改用手动生成Request以完全控制请求优先级。
2. 配置调度器使用有序队列
修改Scrapy配置,确保调度器严格按优先级执行请求,同时关闭随机化设置:
# settings.py # 使用默认调度器,确保优先级队列生效 SCHEDULER = 'scrapy.core.scheduler.Scheduler' # 内存队列使用优先级队列 SCHEDULER_MEMORY_QUEUE = 'scrapy.squeues.PriorityMemoryQueue' # 关闭下载延迟随机化,避免顺序波动 RANDOMIZE_DOWNLOAD_DELAY = False
如需持久化队列(如断点续爬),可设置磁盘队列为有序类型:
SCHEDULER_DISK_QUEUE = 'scrapy.squeues.PickleFifoDiskQueue'
3. 优化SortedLinkExtractor的排序规则
将链接排序规则调整为更稳定的URL字典序,避免因URL长度相同导致的顺序不确定:
class SortedLinkExtractor(LinkExtractor): def extract_links(self, response): links = super().extract_links(response) # 按URL字典序排序,保证提取顺序完全固定 return sorted(links, key=lambda x: x.url)
4. 固定爬虫环境配置
两次爬取需保持完全一致的配置:
- 固定
USER_AGENT为同一值 - 设置固定
DOWNLOAD_DELAY(不启用随机化) - 禁用所有引入随机性的中间件(如随机User-Agent中间件)
通过以上配置,既能保留高并发设置(如CONCURRENT_REQUESTS = 8)保证性能,又能确保两次爬取的页面访问顺序和parse打印的URL顺序完全一致。
内容的提问来源于stack exchange,提问作者Amir H
相关产品推荐
相关产品推荐

