Scrapy Requests and Responses:完整新手指南(包含文档未透露的秘密)
Source: Dev.to
1. 基础
| 概念 | 在 Scrapy 中的含义 |
|---|---|
| Request | 一个表示“我想访问这个 URL”的对象。 |
| Response | 一个包含网站返回内容(HTML、JSON 等)的对象。 |
把网页抓取想象成一次对话:
Request: "Hey website, can you show me this page?"
Response: "Sure, here's the HTML!"
2. Scrapy 在幕后做了什么
一个最小的爬虫:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
start_urls = ['https://example.com']
def parse(self, response):
# Do something with response
pass
实际发生的事情
def start_requests(self):
for url in self.start_urls:
yield scrapy.Request(url=url, callback=self.parse)
Scrapy 会为 start_urls 中的每个 URL 自动创建一个 Request,并将其发送到 parse。
Source: …
3. 手动创建请求
您可以自行构建请求,以控制每个细节:
import scrapy
class MySpider(scrapy.Spider):
name = 'myspider'
def start_requests(self):
yield scrapy.Request(
url='https://example.com',
callback=self.parse,
method='GET',
headers={'User-Agent': 'My Custom Agent'},
cookies={'session': 'abc123'},
meta={'page_num': 1},
dont_filter=False,
priority=0,
)
def parse(self, response):
# 处理响应
pass
3.1 请求参数(快速参考)
| 参数 | 描述 |
|---|---|
url | 目标 URL(必填)。 |
callback | 接收 Response 的函数。默认是 parse。 |
method | HTTP 方法(GET、POST、PUT、DELETE …)。默认:GET。 |
body | 原始请求体(在 POST、PUT 时有用)。 |
headers | 自定义请求头。 |
cookies | 随请求发送的 Cookie。 |
meta | 任意字典,会传递给 Response(response.meta),用于在回调之间共享数据。 |
dont_filter | 若为 True,Scrapy 将 不 将此 URL 视为重复而过滤。 |
priority | 整数优先级;数值越大越先处理(默认 = 0)。 |
示例
# 1️⃣ 简单 URL
yield scrapy.Request(url='https://example.com/products')
# 2️⃣ 自定义回调
yield scrapy.Request(
url='https://example.com/products',
callback=self.parse_products,
)
def parse_products(self, response):
# 在此处理响应
pass
# 3️⃣ 带 JSON 体的 POST 请求
yield scrapy.Request(
url='https://example.com/api',
method='POST',
body='{"key": "value"}',
headers={'Content-Type': 'application/json'},
)
# 4️⃣ 自定义请求头
yield scrapy.Request(
url='https://example.com',
headers={
'User-Agent': 'Mozilla/5.0',
'Accept': 'text/html',
'Referer': 'https://google.com',
},
)
# 5️⃣ Cookies
yield scrapy.Request(
url='https://example.com',
cookies={'session_id': '12345', 'user': 'john'},
)
# 6️⃣ 通过 meta 传递数据
yield scrapy.Request(
url='https://example.com/details',
meta={'product_name': 'Widget', 'price': 29.99},
callback=self.parse_details,
)
def parse_details(self, response):
name = response.meta['product_name']
price = response.meta['price']
# 使用 name 和 price 做点什么
# 7️⃣ 绕过重复过滤
yield scrapy.Request(
url='https://example.com',
dont_filter=True,
)
# 8️⃣ 为请求设定优先级
yield scrapy.Request(
url='https://example.com/important',
priority=10, # 在优先级为 0 的请求之前处理
)
Source: …
4. Response 对象
当请求完成后,Scrapy 会把一个 Response 传递给回调函数。通常会得到以下属性:
def parse(self, response):
# 基本属性
url = response.url # 最终 URL(重定向后的)
body = response.body # 原始字节
text = response.text # 解码后的字符串(默认 UTF‑8)
status = response.status # HTTP 状态码(200、404,…)
headers = response.headers # 响应头(不区分大小写的 dict)
# 与请求关联的属性
request = response.request # 原始 Request 对象
meta = response.meta # 从请求传递来的 meta 字典
4.1 选择数据
# CSS 选择器(最易读)
titles = response.css('h1.title::text').getall()
first_title = response.css('h1.title::text').get()
# XPath 选择器(更强大)
titles = response.xpath('//h1[@class="title"]/text()').getall()
4.2 跟随链接
# 手动方式(冗长)
next_page = response.css('a.next::attr(href)').get()
if next_page:
full_url = response.urljoin(next_page)
yield scrapy.Request(full_url, callback=self.parse)
# 推荐方式 – `response.follow`
next_page = response.css('a.next::attr(href)').get()
if next_page:
yield response.follow(next_page, callback=self.parse)
# 甚至可以直接传入选择器:
yield response.follow(
response.css('a.next::attr(href)').get(),
callback=self.parse,
)
# 或遍历所有 <a> 标签:
for link in response.css('a'):
yield response.follow(link, callback=self.parse_page)
response.follow() 会自动:
- 处理相对 URL(内部调用
urljoin)。 - 当传入选择器时提取
href属性。 - 为你创建
Request对象(包括默认的callback)。
5. 调试与自省
有时你需要查看生成响应的原始请求(尤其是在重定向之后)。
def parse(self, response):
# Original request data
original_url = response.request.url
original_headers = response.request.headers
original_meta = response.request.meta
# Log useful info
self.logger.info(f'Requested: {original_url}')
self.logger.info(f'Got back: {response.url}') # May differ after redirects
TL;DR
- Requests 是完全可配置的对象(
url、method、headers、cookies、meta、priority,……)。 - Responses 为你提供提取数据所需的全部信息(
url、body、text、status、headers,以及指向原始请求的回溯引用)。 - 使用
response.follow()实现简洁、清晰的链接跟随逻辑。 - 利用
meta在回调之间传递数据,使用priority/dont_filter控制爬取顺序和去重处理。
掌握这些细节后,你就可以超越基础,编写健壮高效的 Scrapy 爬虫,精准实现需求——没有隐藏的惊喜。祝爬取愉快!
Source: …
Scrapy 快速参考备忘单
下面是一份整理好的、结构清晰的 Scrapy 常用模式集合。所有代码片段保持原样,只是对排版做了优化。
1. 处理 Response Header
def parse(self, response):
# Get all headers
all_headers = response.headers
# Get a specific header
content_type = response.headers.get('Content-Type')
# Check cookies the server sent back
cookies = response.headers.getlist('Set-Cookie')
# Useful for debugging blocks
server = response.headers.get('Server')
self.logger.info(f'Server type: {server}')
2. 在重定向中保留 meta
def start_requests(self):
yield scrapy.Request(
'https://example.com/redirect',
meta={'important': 'data'}, # custom meta data
callback=self.parse
)
def parse(self, response):
# Even after a redirect, the meta dict is still there!
data = response.meta['important']
# The final URL may be different
self.logger.info(f'Ended up at: {response.url}')
3. 使用 priority 控制爬取顺序
def parse_listing(self, response):
# High priority for product pages (process first)
for product in response.css('.product'):
url = product.css('a::attr(href)').get()
yield response.follow(
url,
callback=self.parse_product,
priority=10 # higher number → earlier processing
)
# Low priority for pagination (process later)
next_page = response.css('.next::attr(href)').get()
if next_page:
yield response.follow(
next_page,
callback=self.parse_listing,
priority=0 # default priority
)
提示: 对“必须获取”的页面使用更高的优先级,对分页或辅助内容使用较低的优先级。
4. 提交表单 – FormRequest
a) 简单的 POST 请求
import scrapy
class LoginSpider(scrapy.Spider):
name = 'login'
def start_requests(self):
yield scrapy.FormRequest(
url='https://example.com/login',
formdata={
'username': 'myuser',
'password': 'mypass'
},
callback=self.after_login
)
def after_login(self, response):
if 'Welcome' in response.text:
self.logger.info('Login successful!')
else:
self.logger.error('Login failed!')
b) 从页面自动填充表单 (from_response)
class LoginSpider(scrapy.Spider):
name = 'login'
start_urls = ['https://example.com/login']
def parse(self, response):
# Automatically locate the form, keep hidden fields (e.g., CSRF)
# and submit the supplied data.
yield scrapy.FormRequest.from_response(
response,
formdata={
'username': 'myuser',
'password': 'mypass'
},
callback=self.after_login
)
def after_login(self, response):
# Now you're logged in – continue crawling.
yield response.follow('/dashboard', callback=self.parse_dashboard)
FormRequest.from_response() 为你完成的工作
- 查找第一个
<form>元素(或匹配formname/formid的表单)。 - 提取 所有 表单字段,保留隐藏输入(例如 CSRF token)。
- 用
formdata中提供的值覆盖相应字段。 - 发送请求。
5. 使用 meta 实现分页(记录页码)
import scrapy
class ProductSpider(scrapy.Spider):
name = 'products'
def start_requests(self):
yield scrapy.Request(
'https://example.com/products?page=1',
meta={'page': 1},
callback=self.parse
)
def parse(self, response):
page = response.meta['page']
self.logger.info(f'Scrapin
Source: …
g page {page}')
# Scrape products on the current page
for product in response.css('.product'):
yield {
'name': product.css('h2::text').get(),
'price': product.css('.price::text').get(),
'page': page
}
# Follow the next page, incrementing the page counter
next_page = response.css('.next::attr(href)').get()
if next_page:
yield response.follow(
next_page,
meta={'page': page + 1},
callback=self.parse
)
6. 链式请求 – 从列表到详情页
import scrapy
class DetailSpider(scrapy.Spider):
name = 'details'
start_urls = ['https://example.com/products']
def parse(self, response):
"""抓取产品列表并排队详情页。"""
for product in response.css('.product'):
item = {
'name': product.css('h2::text').get(),
'price': product.css('.price::text').get()
}
detail_url = product.css('a::attr(href)').get()
yield response.follow(
detail_url,
callback=self.parse_detail,
meta={'item': item} # 将部分填充的项向前传递
)
def parse_detail(self, response):
"""使用详情页的数据丰富该项。"""
item = response.meta['item']
item['description'] = response.css('.description::text').get()
item['rating'] = response.css('.rating::text').get()
item['reviews'] = response.css('.reviews::text').get()
yield item