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

使用Scrapy记录请求URL访问轨迹的问题排查与最佳实践

问题排查结论

首先明确:CrawlSpider的Rule完全支持同时配置callback和process_request参数,二者作用阶段不冲突,问题出在代码逻辑的几处用法错误:

  • process_request方法签名错误:该方法是Rule提取到链接、生成请求后、发送请求前的预处理钩子,只接收self和request两个参数,不会传入response——因为这个阶段请求还没发送,根本不存在对应的响应对象,你写的def main(self, request, response)会直接触发参数不匹配的运行错误。
  • URL轨迹生成逻辑完全错误:[link for link in response.url]是把URL字符串按单个字符迭代,得到的是字符列表,根本不是URL路径;且dict(trail)要求传入的可迭代对象元素为键值对格式,传入普通URL列表会直接触发类型错误。
  • meta传参与取值逻辑错误:Scrapy回调函数只会将response作为第一个位置参数传入,不会把meta字段拆成独立形参,直接在parse_products里写trail形参永远拿不到值;同时你在process_request里手动新建Request对象返回,会绕开CrawlSpider的内置规则标记,导致follow=True的链接跟进逻辑完全失效。
  • 可变对象未做拷贝:meta里传递列表这类可变对象时,如果直接修改原列表,会因为Python的引用机制导致多个请求共享同一份轨迹数据,出现路径错乱。
最佳实现方案

核心逻辑是从起始请求开始,通过meta逐层传递已访问的URL列表,每生成一个新的页面请求,就把新请求的URL追加到轨迹副本中,全程不破坏CrawlSpider的内置运行逻辑。
完整可运行代码示例:

from scrapy import Request
from scrapy.linkextractors import LinkExtractor
from scrapy.spiders import CrawlSpider, Rule
# 自定义Parser按实际项目路径导入即可
# from your_project.parsers import Parser


class MinistryProductsSpider(CrawlSpider):
    name = "ministryproducts"
    allowed_domains = ["www.ministryofsupply.com"]
    start_urls = ["https://www.ministryofsupply.com/"]
    base_url = "https://www.ministryofsupply.com/"
    rules = [
        Rule(
            LinkExtractor(allow="products/"),
            callback="parse_products",
            follow=True,
            process_request="process_route_request",
        )
    ]

    def start_requests(self):
        # 初始化起始请求,写入初始轨迹
        for url in self.start_urls:
            yield Request(
                url,
                meta={"trail": [url]}
                # 不要自定义callback,CrawlSpider依赖默认parse方法执行规则,覆盖会导致规则失效
            )

    def process_route_request(self, request):
        # 取出上一级页面传递的轨迹,拷贝避免可变对象引用污染
        current_trail = request.meta.get("trail", []).copy()
        # 追加当前要访问的URL到轨迹
        current_trail.append(request.url)
        # 更新请求meta
        request.meta["trail"] = current_trail
        # 必须返回原request对象,不要新建Request,否则会打断CrawlSpider的规则跟进逻辑
        return request

    def parse_products(self, response):
        # 从response.meta中读取轨迹数据
        trail = response.meta.get("trail", [])
        self.logger.info("当前商品页: %s,访问轨迹: %s", response.url, " -> ".join(trail))
        parser = Parser()
        item = parser.parse_product(response, trail)
        yield item
关键注意事项
  • 绝对不要重写CrawlSpider的parse方法,该方法是CrawlSpider执行规则匹配、链接提取的核心入口,重写后所有Rule配置都会失效。
  • process_request钩子只能返回三种值:处理后的Request对象、None(过滤丢弃该请求)、或者抛出IgnoreRequest异常,不要返回其他类型数据。
  • meta中传递列表、字典这类可变对象时,必须做拷贝(浅拷贝即可满足URL轨迹场景),否则多个请求会共享同一份内存数据,导致轨迹记录错乱。
  • 如果需要过滤特定轨迹长度的请求,也可以直接在process_route_request里判断轨迹长度,超过阈值返回None丢弃即可。

内容的提问来源于stack exchange,提问作者Abdullah Nafees

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.31 00:33:19