Scrapy预定义URL爬虫提速:两步认证后爬虫2速度慢10倍
Scrapy爬虫速度差异问题分析与优化方案
问题背景
我有两个Scrapy爬虫:
- Spider 1:爬取产品分类页(多分页)生成约10000条产品链接并保存到CSV,运行频率低,速度无关紧要;每分钟约爬500页。
- Spider 2:读取预定义链接列表,访问每个产品详情页提取信息并保存到CSV,需每日运行,速度至关重要;每分钟仅约爬50页,速度比Spider 1慢10倍。
两者爬取同一网站,均需完成多步认证(无法使用静态token,需模拟用户登录)。核心差异在于URL发现方式:
- Spider 1从少量初始URL开始,逐步发现后续页面;
- Spider 2从文件读取大量预定义URL,循环发起请求。
猜测可能是循环请求导致Scrapy无法异步爬取,或产品详情页本身加载较慢,但缺乏页面加载耗时分析方法。现提出三个问题:
- 为何同环境下Spider 2速度慢10倍?
- 有没有更快的预定义URL爬取方式?
- 如何分析页面加载耗时?
Spider 1 代码
# Spider 1 import scrapy from scrapy.http import TextResponse from product_pages import PRODUKTSEITEN # 约27个分类URL的预定义列表 class ProductLinkSpider(scrapy.Spider): name = 'my_name' start_urls = ['URL to login page'] custom_settings = { 'FEEDS': { 'path\\product_links.csv': { 'format': 'csv', 'encoding': 'utf8', 'overwrite': True, }, }, } def parse(self, response): # 处理登录表单 return scrapy.FormRequest.from_response( response, formid='loginForm', formdata={ # 填写用户名和密码 }, callback=self.after_login ) def after_login(self, response): # 通过响应内容验证登录是否成功 if "Your User ID or Password is incorrect. Please try again" in response.text: self.logger.error("Login failed") # 第二步登录 return scrapy.FormRequest.from_response( response, formxpath='//form[@action="/lorem ipsum"]', formdata={ # 填写表单数据 }, callback=self.after_2nd_step ) def after_2nd_step(self, response): for page in PRODUKTSEITEN: yield scrapy.Request( page+"?sort=asc&q=%3Arelevance#&page=0", callback=self.parse_products_list_page, cb_kwargs=dict(url_without_page=page+"?sort=asc&q=%3Arelevance", page=0) ) def parse_products_list_page(self, response: TextResponse, url_without_page: str, page: int): product_links = response.css('a css selector to get the links').getall() if There is another page: # 检查是否有下一页,发起请求 yield scrapy.Request( url_without_page + f"&page={page+1}", callback=self.parse_products_list_page, cb_kwargs=dict(url_without_page=url_without_page, page=page+1) ) for link in product_links: yield {"url": link}
Spider 2 代码
# Spider 2 import scrapy from scrapy.http import TextResponse class ProductInfoSpider(scrapy.Spider): name = 'lorem ipsum' start_urls = ['login page'] custom_settings = { 'FEEDS': { 'path\\product_info_added_2.csv': { 'format': 'csv', 'encoding': 'utf8', 'overwrite': True, }, }, } def parse(self, response): # 处理登录表单 return scrapy.FormRequest.from_response( response, formid='loginForm', formdata={ # 填写用户名和密码 }, callback=self.after_login ) def after_login(self, response): # 通过响应内容验证登录是否成功 if "Your User ID or Password is incorrect. Please try again" in response.text: self.logger.error("Login failed") return scrapy.FormRequest.from_response( response, formxpath='//form[@action="/lorem ipsum"]', formdata={ # 填写与Spider1相同的表单数据 }, callback=self.after_2nd_step ) def after_2nd_step(self, response): with open(r"path\product_links.csv", "r") as file: # 核心差异在此 lines = file.readlines() product_links = lines[1:] for link in product_links: yield scrapy.Request("domain"+link, callback=self.parse_product_page) def parse_product_page(self, response): # 使用4种不同CSS选择器提取产品信息 yield {提取的信息,共6列}
问题解答
1. 为何同环境下Spider 2速度慢10倍?
主要有三个核心原因:
- 页面本身差异:产品详情页通常比分类页包含更多内容(如商品参数、图片、评论模块等),服务器处理详情页请求的计算量更大,响应耗时自然更长,这是最本质的原因。
- 请求调度逻辑差异:Spider 1是增量式生成请求,爬完一页分类页才生成下一页及产品链接请求,Scrapy调度器能持续维持合理的请求队列长度,充分发挥异步并发能力;而Spider 2是一次性生成所有请求,大量请求瞬间涌入调度器,易导致队列拥堵、优先级混乱,甚至触发网站的频率限制。
- 反爬机制触发差异:批量请求详情页的模式更易被识别为爬虫,网站可能暗中限制响应速度(如延长等待时间),而分类页的浏览模式更接近普通用户,受到的限制更少。
2. 有没有更快的预定义URL爬取方式?
可以从以下几个维度优化:
(1)分批生成请求,避免调度器过载
不要一次性读取所有URL,而是分批次加入请求队列,让调度器更高效地处理:
def after_2nd_step(self, response): def read_links_batch(file_path, batch_size=100): with open(file_path, "r") as file: next(file) # 跳过CSV表头 batch = [] for line in file: link = line.strip() if link: batch.append(link) if len(batch) == batch_size: yield batch batch = [] if batch: yield batch for batch in read_links_batch(r"path\product_links.csv"): for link in batch: yield scrapy.Request("domain"+link, callback=self.parse_product_page)
(2)调整Scrapy并发配置,最大化异步能力
在custom_settings中修改并发相关参数,根据网站承受能力调整:
custom_settings = { # 原有FEEDS配置... 'CONCURRENT_REQUESTS': 32, # 默认16,可适当提高 'CONCURRENT_REQUESTS_PER_DOMAIN': 16, # 默认8,单域名并发数 'DOWNLOAD_DELAY': 0.1, # 降低请求间隔,根据反爬情况调整 'AUTOTHROTTLE_ENABLED': True, # 自动根据响应速度调整请求频率 'AUTOTHROTTLE_START_DELAY': 0.1, 'AUTOTHROTTLE_MAX_DELAY': 1.0, }
(3)用CSV作为初始请求源(推荐)
通过start_requests方法直接从CSV读取URL,更贴合Scrapy的调度逻辑:
def start_requests(self): # 先发起登录请求 yield scrapy.Request('login page', callback=self.parse) def after_2nd_step(self, response): import csv with open(r"path\product_links.csv", "r") as file: reader = csv.DictReader(file) for row in reader: link = row['url'].strip() if link: yield scrapy.Request("domain"+link, callback=self.parse_product_page)
(4)关闭不必要的下载组件
如果不需要加载图片、JS等静态资源,关闭对应组件减少无效下载耗时:
custom_settings = { # 原有配置... 'DOWNLOADER_MIDDLEWARES': { 'scrapy.downloadermiddlewares.images.ImagesMiddleware': None, 'scrapy.downloadermiddlewares.jsmiddleware.JSMiddleware': None, }, }
3. 如何分析页面加载耗时?
可以通过以下几种方式精准分析:
- Scrapy日志统计:运行爬虫时添加
--logfile=scrapy.log参数,日志中会记录每个请求的downloader/request_time字段,可通过工具(如grep、Python脚本)统计平均耗时、请求分布。 - 自定义计时中间件:编写下载中间件记录每个请求的起止时间,输出耗时信息:
from scrapy import signals import time class TimingMiddleware: @classmethod def from_crawler(cls, crawler): s = cls() crawler.signals.connect(s.spider_opened, signal=signals.spider_opened) return s def process_request(self, request, spider): request.meta['start_time'] = time.time() def process_response(self, request, response, spider): elapsed = time.time() - request.meta['start_time'] spider.logger.info(f"请求 {request.url} 耗时: {elapsed:.2f}秒") return response
然后在custom_settings中启用中间件:
custom_settings = { # 原有配置... 'DOWNLOADER_MIDDLEWARES': { '你的项目名.middlewares.TimingMiddleware': 543, }, }
- 浏览器开发者工具分析:手动打开产品详情页,用Chrome/Firefox的Network标签查看页面总加载时间、各资源加载耗时,判断是页面内容过多还是服务器响应慢导致的延迟。
- Scrapy Bench工具:运行
scrapy bench命令测试爬虫性能,可得到并发数、平均请求耗时等核心统计数据。
内容的提问来源于stack exchange,提问作者LoahL
相关产品推荐
相关产品推荐

