Scrapling Scrapy 集成详解:用 scrapling_response 装饰器在现有爬虫中无缝切换解析 API
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_text、find_similar、get_all_text 等 Scrapling 解析方法。
该集成模块的文件头注释说明了设计目标:
Decorate Scrapy spider callbacks with
scrapling_responseto receive a ScraplingResponseobject 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"
另外,测试中还验证了几个边界行为:
cb_kwargs透传:parse(response, category=None)形式的回调可以照常接收 Scrapy 传入的额外关键字参数(test_cb_kwargs_passthrough);- response 以关键字参数传入也能被正确转换(test_response_passed_as_keyword);
- 若回调参数里找不到任何 Scrapy response,装饰器会抛出
TypeError: No Scrapy response found in ...(test_no_response_in_arguments_raises)。
参数化形式:传递 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_tree、keep_comments、keep_cdata、adaptive、storage、storage_args 和 adaptive_domain(见 scrapling/integrations/scrapy.py 的 docstring)。参数化形式在测试中的验证方式是:keep_comments=True 后 response.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-Cookie 的 Expires 等属性值本身就含逗号,拼接后的结果无法正确还原出每一条 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(
css、xpath、find、find_all、find_by_text、find_similar等)全部可用; response.urljoin("/next")等 URL 处理行为与独立使用 Scrapling 时一致(测试中验证response.urljoin("/next") == "http://example.com/next");- 无 request 的响应也能正常解析(test_response_without_request)。
注意事项
官方文档特别列出三条需要注意的点,它们都直接对应 Scrapy 与 Scrapling 两套体系的边界:
- 翻页请继续用
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 原生写法。 meta是浅拷贝,中间件写入的对象仍然可达。dict(response.meta)只是复制外层字典,值本身不复制。例如配合scrapy-playwright使用时,页面对象仍然能通过response.meta["playwright_page"]访问到。- 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 集成测试。
atomcodeClaude Code 的开源替代方案。连接任意大模型,编辑代码,运行命令,自动验证 — 全自动执行。用 Rust 构建,极致性能。 | An open-source alternative to Claude Code. Connect any LLM, edit code, run commands, and verify changes — autonomously. Built in Rust for speed. Get StartedRust0623
Hy4-previewHy4 preview 是由腾讯混元团队研发的新一代混合专家(MoE)旗舰模型。模型总参数量 770B,每个 token 激活 49B,主干共包含78层,第一层采用标准 FFN,其余 77 层均为 MoE 结构,每层包含 256 个路由专家与 1 个共享专家,每个 token 激活 top-8 路由专家及共享专家。主干之外原生内置 1 层 MTP(总参数量 10B,激活 0.7B)以支持投机解码。Python00
GLM-5.3GLM-5.3 与 GLM-5.2 使用相同的基座模型——所有提升均来自后训练。与 GLM-5.2 相比,它在复杂编程和长程任务上的表现显著提升。Jinja00
GLM-5.3-FlashGLM-5.3-Flash (320B-A18B),是GLM-5系列的首个原生多模态模型。320B总参数,能力超过GLM-5.2Jinja00
Spark-X2.5-4BSpark-X2.5-4B 旨在让强大的 AI 更实用、更高效、更易获得。在广泛日常任务中表现强劲,涵盖对话、写作、翻译、推理、编码、工具调用以及智能体工作流,并在同等规模的开源模型中取得领先成绩。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00
Spark-X2.5-1.7BSpark-X2.5-1.7B 旨在让强大的 AI 更加实用、高效且易于获取。这些模型在广泛的日常任务中表现出色,涵盖对话、写作、翻译、推理、编程、工具调用和智能体工作流,并在同等规模的开源模型中取得领先结果。Spark-X2.5 将面向效率的架构与最高 1M tokens 的原生上下文窗口相结合,并支持 200 多种语言。Python00