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。