Scrapy核心对象:Response超详细指南
# Scrapy 核心对象:Response 超详细指南
在 Scrapy 的工作流中,当下载器完成页面下载后,会将获取到的内容封装成一个 Response
对象,并发送给 Spider 进行解析。这个 Response
对象是我们进行数据提取和发起后续请求的全部依据,因此,深入理解它的属性和方法至关重要。
学习建议:本文档是
Response
对象的详细参考手册。如果你是 Scrapy 的初学者,建议先完成我们的 Scrapy 入门实战教程,对 Scrapy 的项目流程有了基本概念后,再来深入阅读本文,效果会更佳。
# 一、Response 对象的核心属性
这些属性提供了关于响应本身最基本、最重要的信息。
属性 | 类型 | 描述 |
---|---|---|
response.url | str | 当前响应的最终 URL 地址。如果途中发生过重定向,这里会是重定向后的地址。 |
response.status | int | HTTP 响应状态码。例如 200 (成功), 404 (未找到), 302 (重定向)。 |
response.headers | dict | 服务器返回的响应头。这是一个类字典对象,包含了 Content-Type , Content-Length , Set-Cookie 等信息。 |
response.body | bytes | 原始的响应体,是未经任何解码的字节 (bytes) 类型。当你需要处理图片、视频等二进制文件时,必须使用此属性。 |
response.text | str | 解码后的响应体,是字符串 (str) 类型。Scrapy 会根据响应头的 Content-Type 自动选择编码格式进行解码。当你处理 HTML、JSON、XML 等文本内容时,通常使用此属性。 |
response.encoding | str | Scrapy 推断出的响应编码格式,response.text 就是基于这个编码来解码 response.body 的。 |
# 处理 JSON 数据
如果响应内容是 JSON 格式,可以直接使用 .json()
方法将其解析为 Python 的字典或列表。
response.json()
: 将 JSON 响应体反序列化为 Python 对象。如果响应体不是有效的 JSON,会引发错误。
# 假设 response 的内容是 '{"name": "Scrapy", "version": "2.5"}'
data = response.json()
print(data['name']) # 输出: Scrapy
2
3
# 二、与请求 (Request) 相关的属性
Response
对象还保留了当初引发它的那个 Request
对象的信息,方便我们追溯和传递数据。
属性 | 类型 | 描述 |
---|---|---|
response.request | Request 对象 | 产生此响应的那个 Request 对象实例。 |
response.request.url | str | 原始请求的 URL 地址,不会包含重定向后的地址。 |
response.request.headers | dict | 发送请求时所使用的请求头。 |
response.request.meta | dict | 一个非常重要的属性!它是 Request 的元数据字典,用于在不同的请求处理函数之间传递数据。你在发起请求时附加的 meta 信息,可以在接收响应时通过此属性取回。 |
# 三、内置选择器 (Selectors)
Scrapy 的 Response
对象最强大的地方在于它内置了功能强大的选择器,无需再引入 BeautifulSoup
等第三方库。
response.xpath(...)
: 使用 XPath 语法进行选择。response.css(...)
: 使用 CSS 语法进行选择。
# 3.1 Selector 和 SelectorList
当你调用 response.xpath()
或 response.css()
时,你得到的并不是直接的数据,而是:
Selector
对象:如果你的选择器只匹配到一个结果。SelectorList
对象:如果匹配到多个结果,它表现得像一个 Python 列表,里面包含了多个Selector
对象。
选择器可以链式调用:你可以对一个 Selector
或 SelectorList
对象再次调用 .xpath()
或 .css()
方法,这表示从当前已选中的节点下进行相对查找,非常有用。
# 3.2 高级选择器技巧
除了基本的标签和类选择,你还可以使用更高级的技巧来精确定位。
XPath 示例:
//a/@href
: 选取所有<a>
标签的href
属性值。//p/text()
: 选取所有<p>
标签下的直接文本节点。//div[contains(@class, "info")]
: 选取class
属性包含 "info" 字符串的<div>
标签。//button[contains(text(), "下一页")]
: 选取文本内容包含 "下一页" 的<button>
标签。
CSS 示例:
a::attr(href)
: (Scrapy 特有)选取所有<a>
标签的href
属性值。p::text
: 选取所有<p>
标签下的文本内容。div[class*="info"]
: 选取class
属性包含 "info" 字符串的<div>
标签。a:nth-child(2)
: 选取作为其父元素的第二个子元素的<a>
标签。
# 3.3 提取数据:get() vs extract()
要从 Selector
或 SelectorList
中提取出最终的文本或属性数据,你需要调用提取方法。Scrapy 提供了两套API,新版推荐使用 get()
/getall()
。
新版 API (推荐) | 旧版 API (兼容) | 描述 |
---|---|---|
.get() | .extract_first() | 从选择器结果中提取第一个匹配项。如果结果为空,返回 None 。这是最安全、最常用的方法。 |
.getall() | .extract() | 提取所有匹配项,返回一个列表。如果结果为空,返回一个空列表 [] 。 |
# 四、便捷的请求创建 (response.follow
)
response.follow()
是一个用于创建后续请求的快捷方法,它比手动创建 scrapy.Request
对象更智能。
response.follow(url, callback, meta, ...)
:- 它能自动处理相对 URL。例如
url="page2.html"
会被自动转换成http://example.com/page2.html
。 - 它能处理
Selector
对象。你可以直接传入一个指向<a>
标签的Selector
,它会自动提取href
属性来创建请求。
- 它能自动处理相对 URL。例如
# 手动方式
next_page_url = response.css('a.next::attr(href)').get()
if next_page_url:
# 需要手动拼接完整 URL
absolute_url = response.urljoin(next_page_url)
yield scrapy.Request(absolute_url, callback=self.parse)
# 使用 response.follow() 的便捷方式
next_page_selector = response.css('a.next')
if next_page_selector:
# 直接传入 selector,自动处理
yield response.follow(next_page_selector, callback=self.parse)
2
3
4
5
6
7
8
9
10
11
12
# 五、博客列表页抓取文章
下面的代码演示了如何从一个博客列表页抓取文章,然后通过 meta
传递文章标题到详情页,并使用 response.follow
进行翻页。
import scrapy
class ResponseDemoSpider(scrapy.Spider):
name = 'response_demo'
start_urls = ['http://duanzixing.com/']
def parse(self, response, **kwargs):
print(f"\n>>>>>> 正在解析列表页: {response.url}")
# 使用链式调用和高级选择器
for article in response.css('article.excerpt'):
title = article.css('h2 a::text').get()
detail_url = article.css('h2 a::attr(href)').get()
if title and detail_url:
# 使用 meta 将标题传递给下一个解析函数
yield response.follow(
detail_url,
callback=self.parse_detail,
meta={'article_title': title.strip()}
)
# --- 处理分页 ---
# 寻找文本为 "下一页" 的链接
next_page_link = response.xpath('//a[contains(text(), "下一页")]')
if next_page_link:
yield response.follow(next_page_link, callback=self.parse)
def parse_detail(self, response):
# 通过 response.request.meta 获取传递过来的数据
title = response.request.meta.get('article_title', '未知标题')
print(f"\n------ 正在解析详情页: {response.url}")
print(f"------ 从 meta 中获取到的标题: {title}")
# 提取详情页的内容
content_paragraphs = response.css('article.article-content p::text').getall()
content = "\n".join(p.strip() for p in content_paragraphs if p.strip())
yield {
'title': title,
'url': response.url,
'content': content,
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
# 六、网站的编码判断错误处理
偶尔,Scrapy 可能会错误地判断网站的编码,导致 response.text
出现乱码。此时,你可以:
- 查看
response.encoding
,确认 Scrapy 推断的编码是否正确。 - 手动解码
response.body
:correct_text = response.body.decode('gbk')
- 覆盖编码并重新生成响应:
new_response = response.replace(encoding='gbk')
,然后就可以正常使用new_response.text
。
# 七、总结
- 数据类型要分清:
response.body
是bytes
(二进制),response.text
是str
(文本)。处理 JSON 用response.json()
。 meta
是数据传递的桥梁: 使用request.meta
可以在请求链中携带信息。follow
是翻页利器: 优先使用response.follow()
创建后续请求,代码更简洁。- 选择器返回的是
Selector
对象: 必须调用.get()
或.getall()
才能提取最终数据。 - 优先使用
get()
: 提取单个数据时,.get()
比.getall()[0]
更安全。 allowed_domains
的作用域: 它限制的是 Spider 生成的后续请求,不限制start_urls
。- 启动位置: 永远在项目的根目录下(包含
scrapy.cfg
的目录)执行scrapy crawl
。