Scrapy Requests and Responses:完整新手指南(包含文档未透露的秘密)

发布: (2025年12月23日 GMT+8 15:47)
8 min read
原文: Dev.to

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
methodHTTP 方法(GETPOSTPUTDELETE …)。默认:GET
body原始请求体(在 POSTPUT 时有用)。
headers自定义请求头。
cookies随请求发送的 Cookie。
meta任意字典,会传递给 Responseresponse.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 是完全可配置的对象(urlmethodheaderscookiesmetapriority,……)。
  • Responses 为你提供提取数据所需的全部信息(urlbodytextstatusheaders,以及指向原始请求的回溯引用)。
  • 使用 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() 为你完成的工作

  1. 查找第一个 <form> 元素(或匹配 formname/formid 的表单)。
  2. 提取 所有 表单字段,保留隐藏输入(例如 CSRF token)。
  3. formdata 中提供的值覆盖相应字段。
  4. 发送请求。

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
Back to Blog

相关文章

阅读更多 »

第26天提升我的数据科学技能

第26天提升我的 Data Science 技能的封面图片 https://media2.dev.to/dynamic/image/width=1000,height=420,fit=cover,gravity=auto,format=auto/https%3A...