首页
/ Scrapling Scrapy 集成详解:用 scrapling_response 装饰器在现有爬虫中无缝切换解析 API

Scrapling Scrapy 集成详解:用 scrapling_response 装饰器在现有爬虫中无缝切换解析 API

2026-09-05 13:48:34作者:宗隆裙

Scrapling 提供的 Scrapy 集成模块,可以让已有的 Scrapy 项目在不改动爬虫逻辑的前提下,直接在 spider 回调中使用 Scrapling 的 Response 解析 API:Scrapy 继续负责调度与抓取,Scrapling 负责页面解析。读完本篇,你将掌握 scrapling_response 装饰器与 convert_response 转换函数的完整用法、回调类型兼容性原理,以及 meta、Cookie、响应头在两套对象之间的映射细节。

为什么需要 Scrapy 集成

如果你在维护一个 Scrapy 项目,通常不想为了换一套解析 API 而重写整个爬虫。Scrapling 的集成方案(源码位于 scrapling/integrations/scrapy.py)思路很直接:在 spider 回调的入口处把 Scrapy 的 response 转换成 Scrapling 的 Response 对象,回调函数内部就可以自由使用 find_by_textfind_similarget_all_text 等 Scrapling 解析方法。

该集成模块的文件头注释说明了设计目标:

Decorate Scrapy spider callbacks with scrapling_response to receive a Scrapling Response object instead of the Scrapy response, so you get Scrapling's full parsing API inside existing Scrapy projects without changing how the spider crawls.

安装说明:该集成随 Scrapling 默认安装(pip install scrapling)即可使用,无需额外 extras;唯一前提是你的环境里已经装了 Scrapy——Scrapy 项目本身就满足这一点。源码中也有对应的保护性导入:

try:
    from scrapy.http import Response as ScrapyResponse
except (ImportError, ModuleNotFoundError) as e:
    raise ModuleNotFoundError(
        "This integration requires Scrapy installed, please install it first with `pip install scrapy`"
    ) from e

scrapling/integrations/scrapy.py。此外,各集成模块都是显式导入的,因此 Scrapy 永远不会成为 Scrapling 的必需依赖(见 scrapling/integrations/init.py 的说明)。

基本用法:scrapling_response 装饰器

scrapling_response 装饰器放在任意 spider 回调上,回调接收到的 response 参数就变成 Scrapling 的 Response

import scrapy
from scrapling.integrations.scrapy import scrapling_response


class QuotesSpider(scrapy.Spider):
    name = "quotes"
    start_urls = ["https://quotes.toscrape.com"]

    @scrapling_response
    def parse(self, response):  # `response` is now a Scrapling Response
        first_quote = response.find_by_text("The world as we have created it", partial=True)
        for quote in [first_quote, *first_quote.find_similar()]:
            card = quote.parent
            yield {
                "text": quote.get_all_text(strip=True),
                "author": card.find("small", class_="author").text,
                "tags": [tag.text for tag in card.find_all("a", class_="tag")],
            }
        next_page = response.css("li.next a::attr(href)").get()
        if next_page:
            yield scrapy.Request(response.urljoin(next_page), callback=self.parse)

这个示例覆盖了三个要点:

  • Scrapling 风格的文本定位response.find_by_text(..., partial=True) 按文本内容查找元素,再用 find_similar() 找出结构相似的兄弟节点,这是纯 CSS/XPath 难以一步完成的解析逻辑;
  • Scrapy 风格的翻页:翻页仍然 yield scrapy.Request(response.urljoin(href))。原因见后文"注意事项";
  • 两种 CSS 用法混用:Scrapling 的 find / find_all 与 Scrapy 熟悉的 response.css(...) 选择器语法在同一响应对象上都能工作。

装饰器兼容所有回调类型

装饰器对 Scrapy 支持的全部四种回调形式都有效:普通函数、生成器函数、协程(coroutine)、异步生成器。实现上(scrapling/integrations/scrapy.py)对每种类型分别构造同类型的包装函数:

  • 异步生成器回调 → 用 async def 包装并 async for ... yield
  • 协程回调 → 用 async def 包装并 await
  • 生成器回调 → 普通 def 包装并 yield from
  • 普通函数 → 普通 def 包装直接透传返回值。

源码注释解释了为什么必须这么做:"Each callback kind gets a wrapper of the same kind because Scrapy inspects the callback function itself, not just what it returns."——Scrapy 会检查回调函数本身(而非仅看返回值)来决定如何迭代它,包装函数如果丢了"生成器/协程"身份,Scrapy 的回调内省就会失效。

同时,所有包装函数都用 functools.wraps 保留了原回调的 kind、name 和 docstring,因此 Scrapy 的 callback introspection 和 contracts 机制照常工作。这一点在测试用例中有明确断言(tests/integrations/test_scrapy.py):

spider = TestSpider()
assert inspect.isgeneratorfunction(spider.parse)
assert spider.parse.__name__ == "parse"
assert spider.parse.__doc__ == "Parse the page title"

另外,测试中还验证了几个边界行为:

参数化形式:传递 Selector 配置

装饰器还支持带参形式,把 Selector 的配置项原样转发给生成的 Response

    @scrapling_response(adaptive=True, keep_comments=True)
    def parse_product(self, response):
        ...

从源码签名 def scrapling_response(func=None, **selector_config) 看,**selector_config 会被完整透传给 convert_response,最终进入 Response 构造函数。文档中列出的可传配置项包括:huge_treekeep_commentskeep_cdataadaptivestoragestorage_argsadaptive_domain(见 scrapling/integrations/scrapy.py 的 docstring)。参数化形式在测试中的验证方式是:keep_comments=Trueresponse.xpath("//comment()") 能查回注释节点(test_parameterized_form)。

回调之外:直接使用 convert_response 转换函数

如果在回调以外的地方拿到了 Scrapy 响应——中间件、管道(pipeline)等处——可以直接调用转换函数,而不必走装饰器:

from scrapling.integrations.scrapy import convert_response

scrapling_response = convert_response(scrapy_response, keep_comments=False, keep_cdata=False)

convert_response 的完整映射逻辑在 scrapling/integrations/scrapy.py 中,值得逐字段看清楚:

Scrapling Response 字段 来源 说明
url / body / status response.url / response.body / response.status 直接映射
reason StatusText.get(response.status) 由状态码查短语表,未知状态码返回 "Unknown Status Code"test_unknown_status_code_reason 用 599 验证)
headers response.headers.to_unicode_dict() 响应头转字符串字典
request_headers request.headers.to_unicode_dict() 无关联 request 时为 {}
encoding getattr(response, "encoding", "utf-8") 二进制响应没有 encoding 属性,回退为 utf-8
method request.method 无 request 时回退为 "GET"
meta dict(response.meta) 浅拷贝,无 request 时为 {}
cookies 从原始 Set-Cookie 头行解析 见下文

测试用例 test_field_mapping 对以上映射做了完整断言,包括 response.meta is not scrapy_response.meta(确认确实是拷贝而非同一对象)。

Cookie 为什么从原始 Set-Cookie 头行解析

这是该转换函数里一个有意思的实现细节。convert_response 没有使用 Scrapy 自带的 Cookie 处理,而是逐行读取原始 Set-Cookie 头:

cookies: Dict[str, str] = {}
# `to_unicode_dict` below joins duplicate headers with commas, which corrupts multiple
# `Set-Cookie` headers, so cookies are parsed from the raw header lines instead.
for line in response.headers.getlist(b"Set-Cookie"):
    pair = line.split(b";", 1)[0]
    if b"=" in pair:
        name, _, value = pair.decode("latin-1").partition("=")
        cookies[name.strip()] = value.strip()

源码注释说明了动机:to_unicode_dict() 会把重名头用逗号拼接,而 Set-CookieExpires 等属性值本身就含逗号,拼接后的结果无法正确还原出每一条 Cookie。因此这里改用 getlist(b"Set-Cookie") 逐行取原始字节,只截取 ; 前的 name=value 部分。对应测试 test_cookies_parsed_from_raw_set_cookie_headers 构造了一条含 Expires=Wed, 09 Jun 2027 10:18:14 GMT(值中带逗号)的 Cookie,验证了 response.cookies == {"sid": "abc123", "lang": "en"}

生成后的 Response 能做什么

转换得到的是 Scrapling 统一的 Response 类型(scrapling/engines/toolbelt/custom.py),它是 Selector 的子类,即所有引擎返回的响应类型。这意味着:

  • 完整的 Scrapling 选择器 API(cssxpathfindfind_allfind_by_textfind_similar 等)全部可用;
  • response.urljoin("/next") 等 URL 处理行为与独立使用 Scrapling 时一致(测试中验证 response.urljoin("/next") == "http://example.com/next");
  • 无 request 的响应也能正常解析(test_response_without_request)。

注意事项

官方文档特别列出三条需要注意的点,它们都直接对应 Scrapy 与 Scrapling 两套体系的边界:

  1. 翻页请继续用 scrapy.Request,不要用 response.follow()。上面的示例中翻页写的是 yield scrapy.Request(response.urljoin(next_page), callback=self.parse)。原因是 Scrapling 的 Response.follow()scrapling/engines/toolbelt/custom.py)构造的是 Scrapling 自己 spider 体系(见 getting-started)使用的 Request 对象,而且 follow() 会检查 self.request 是否为 Scrapling 的 Request 实例、不是则抛 TypeError("This response has no request set yet.")——由 Scrapy 集成转换来的响应不满足这一前提,Scrapy 的引擎也不认识 Scrapling 的 Request。所以在 Scrapy 项目里,翻页、重定向请求一律保持 Scrapy 原生写法。
  2. meta 是浅拷贝,中间件写入的对象仍然可达dict(response.meta) 只是复制外层字典,值本身不复制。例如配合 scrapy-playwright 使用时,页面对象仍然能通过 response.meta["playwright_page"] 访问到。
  3. Cookie 已解析进 response.cookies 字典。如上文所述,Cookie 从原始 Set-Cookie 头行解析而来,格式为 {name: value}

集成模块的公共接口

scrapling.integrations.scrapy 模块通过 __all__ = ["scrapling_response", "convert_response"] 暴露两个公共接口:

  • scrapling_response:spider 回调装饰器,裸用(@scrapling_response)或带参(@scrapling_response(adaptive=True))均可,兼容全部四种回调类型;
  • convert_response:通用转换函数,适用于中间件、管道等任意持有 Scrapy 响应的位置。

相关文档可进一步参阅:Scrapy 集成说明(docs/integrations/scrapy.md)、Response 对象Selector 类 的解析 API、Scrapy 集成测试

登录后查看全文
热门项目推荐
相关项目推荐

项目优选

收起
kernelkernel
deepin linux kernel
C
33
18
ops-transformerops-transformer
本项目是CANN提供的transformer类大模型算子库,实现网络在NPU上加速计算。
C++
1.12 K
2.72 K
kernelkernel
openEuler内核是openEuler操作系统的核心,既是系统性能与稳定性的基石,也是连接处理器、设备与服务的桥梁。
C
528
588
ops-nnops-nn
本项目是CANN提供的神经网络类计算算子库,实现网络在NPU上加速计算。
C++
906
1.83 K
pytorchpytorch
作为 Ascend for PyTorch 社区的核心组件,TorchNPU 是昇腾专为 PyTorch 打造的深度学习适配插件,使 PyTorch 框架能够直接调用昇腾 NPU,为开发者提供昇腾 AI 处理器的超强算力。
Python
854
1.34 K
docsdocs
暂无描述
Markdown
891
5.79 K
jiuwenswarmjiuwenswarm
JiuwenSwarm 是一款基于openJiuwen开发的智能AI Agent,它能够将大语言模型的强大能力,通过你日常使用的各类通讯应用,直接延伸至你的指尖。
Python
3.53 K
1.01 K
ops-mathops-math
本项目是CANN提供的数学类基础计算算子库,实现网络在NPU上加速计算。
C++
1.34 K
1.45 K
cann-learning-hubcann-learning-hub
CANN 学习中心仓,支持在线互动运行、边学边练,提供教程、示例与优化方案,一站式助力昇腾开发者快速上手。
Jupyter Notebook
988
506
AscendNPU-IRAscendNPU-IR
AscendNPU-IR是基于MLIR(Multi-Level Intermediate Representation)构建的,面向昇腾亲和算子编译时使用的中间表示,提供昇腾完备表达能力,通过编译优化提升昇腾AI处理器计算效率,支持通过生态框架使能昇腾AI处理器与深度调优
C++
540
384