You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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无法异步爬取,或产品详情页本身加载较慢,但缺乏页面加载耗时分析方法。现提出三个问题:

  1. 为何同环境下Spider 2速度慢10倍?
  2. 有没有更快的预定义URL爬取方式?
  3. 如何分析页面加载耗时?

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 05:44:58