pin_drop当前位置:知识文库 ❯ 图文

Scrapy分页爬取详解 - 从简单分页到多级分页完整指南

一、分页爬取概述

分页爬取是网络爬虫中最常见也是最重要的需求之一。在现实场景中,网站通常将大量数据分成多页展示,比如电商商品列表、搜索结果、论坛帖子等。Scrapy通过强大的请求调度机制,可以轻松实现分页数据的自动跟进采集。

Scrapy分页爬取的核心原理是:在 parse() 方法中提取当前页数据后,识别下一页的URL,然后通过 yield 创建新的请求对象。Scrapy的调度器会自动管理请求队列,实现高效的分页采集。Scrapy内置的URL去重机制(RFPDupeFilter)会自动过滤已请求过的URL,避免重复爬取。

二、分页爬取核心语法

最基本的分页爬取模式如下:提取当前页数据,找到下一页链接,发起新请求并指定回调函数为自身,形成循环:

代码示例

def parse(self, response):
    # 提取当前页数据
    for item in response.css('div.item'):
        yield {
            'name': item.css('h3::text').get(),
            'price': item.css('.price::text').get(),
        }

    # 查找下一页链接
    next_page = response.css('a.next::attr(href)').get()
    if next_page:
        # 使用urljoin将相对URL转为绝对URL
        yield scrapy.Request(
            url=response.urljoin(next_page),
            callback=self.parse,  # 回调自身实现循环
        )

三、分页相关方法与Request参数

分页常用方法

方法/属性 说明
response.urljoin(url) 将相对URL转换为绝对URL
response.follow(url, callback) 创建跟进请求,自动处理相对URL
response.follow_all(urls, callback) 批量创建跟进请求
scrapy.Request(url, callback) 创建请求对象,需手动处理URL

Request常用参数

参数 说明
url 请求的目标URL地址
callback 响应返回后的回调处理函数
meta 传递元数据给回调函数,通过response.meta获取
dont_filter 是否跳过URL去重,默认False
priority 请求优先级,数值越大优先级越高

四、完整代码示例

示例1:简单分页爬取(response.follow方式)

使用 response.follow() 是最推荐的分页方式,它自动将相对URL转换为绝对URL,代码更简洁:

代码示例

import scrapy

class SimplePaginationSpider(scrapy.Spider):
    name = 'simple_pagination'
    start_urls = ['https://quotes.toscrape.com/']

    def parse(self, response):
        # 提取当前页的名言数据
        for quote in response.css('div.quote'):
            yield {
                'text': quote.css('span.text::text').get(),
                'author': quote.css('small.author::text').get(),
                'tags': quote.css('div.tags a.tag::text').getall(),
            }

        # 查找并跟进下一页
        next_page = response.css('li.next a::attr(href)').get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

示例2:URL模式分页(有规律的分页URL)

当分页URL有固定规律时(如 ?page=1?page=2),可以直接生成URL列表批量请求,无需解析页面中的下一页链接:

代码示例

import scrapy

class UrlPatternSpider(scrapy.Spider):
    name = 'url_pattern'
    allowed_domains = ['example.com']

    def start_requests(self):
        base_url = 'https://example.com/products'
        # 生成第1-10页的URL并批量请求
        for page in range(1, 11):
            url = f'{base_url}?page={page}'
            yield scrapy.Request(
                url,
                callback=self.parse,
                meta={'page': page},  # 传递页码信息
            )

    def parse(self, response):
        page = response.meta['page']
        self.logger.info(f'正在爬取第{page}页')

        for product in response.css('div.product'):
            yield {
                'name': product.css('h3::text').get(),
                'price': product.css('.price::text').get(),
                'page': page,
            }

示例3:多级分页(列表页 + 详情页)

这是最实用的场景:先从列表页提取基本信息,然后跟进每个商品的详情页获取完整信息。通过 meta 字典在请求之间传递数据:

代码示例

import scrapy

class MultiLevelPaginationSpider(scrapy.Spider):
    name = 'multi_pagination'
    start_urls = ['https://quotes.toscrape.com/']

    def parse(self, response):
        """列表页:提取数据并跟进详情页和下一页"""
        for quote in response.css('div.quote'):
            # 提取基本信息到Item
            item = {
                'text': quote.css('span.text::text').get(),
                'author_name': quote.css('small.author::text').get(),
            }

            # 跟进作者详情页获取更多信息
            author_url = quote.css('small.author + a::attr(href)').get()
            if author_url:
                yield response.follow(
                    author_url,
                    callback=self.parse_author,
                    meta={'item': item},  # 传递基础数据
                )
            else:
                yield item

        # 跟进列表的下一页
        next_page = response.css('li.next a::attr(href)').get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

    def parse_author(self, response):
        """作者详情页:补充作者详细信息"""
        item = response.meta['item']  # 获取传递过来的基础数据
        item['author_born_date'] = response.css(
            'span.author-born-date::text'
        ).get()
        item['author_born_location'] = response.css(
            'span.author-born-location::text'
        ).get()
        item['author_description'] = response.css(
            'span.author-description::text'
        ).get()
        yield item

示例4:带分页终止条件的爬取

实际项目中,通常需要设置分页终止条件,如最大页数限制或采集数量限制,避免无限循环:

代码示例

import scrapy

class LimitedPaginationSpider(scrapy.Spider):
    name = 'limited_pagination'
    start_urls = ['https://quotes.toscrape.com/']
    max_pages = 5       # 最大爬取页数
    items_count = 0     # 已采集数量计数器

    def parse(self, response):
        for quote in response.css('div.quote'):
            self.items_count += 1
            yield {
                'text': quote.css('span.text::text').get(),
                'author': quote.css('small.author::text').get(),
            }

        # 分页终止条件:达到最大页数或采集数量上限
        current_page = response.meta.get('page', 1)
        if current_page < self.max_pages:
            next_page = response.css('li.next a::attr(href)').get()
            if next_page:
                yield response.follow(
                    next_page,
                    callback=self.parse,
                    meta={'page': current_page + 1},
                )

五、分页方式对比

分页方式 代码示例 适用场景
跟进下一页链接 response.follow(next_url) 页面有明确的"下一页"链接按钮
URL模式分页 f'{base}?page={n}' URL有固定规律,如?page=N或/page/N/
API分页 解析JSON中的next_page REST API返回的数据中包含下一页URL

六、实际应用场景

  • 场景1 - 电商商品列表采集:爬取淘宝、京东等电商网站的商品列表页,逐页采集商品名称、价格、销量等信息,然后跟进每个商品的详情页获取评价和详细描述。

  • 场景2 - 论坛帖子分页浏览:采集知乎、贴吧等论坛的帖子列表,逐页跟进。每个帖子可能有多个回复页面,需要实现多级分页采集所有回复内容。

  • 场景3 - 搜索结果分页采集:爬取搜索引擎或站内搜索的结果页,处理翻页URL中的 pnoffset 等参数,实现批量搜索结果采集。

小贴士

response.follow() 除了支持URL字符串外,还可以直接接收 Selector 对象,例如 response.follow(response.css('a.next'), callback=self.parse),它会自动提取 href 属性。另外,建议在 settings.py 中设置 DOWNLOAD_DELAY = 1 控制爬取间隔,避免对目标服务器造成过大压力。

七、注意事项与最佳实践

注意1:务必添加分页终止条件(最大页数、采集数量等),否则可能陷入无限循环,浪费资源甚至被封禁IP。

注意2response.follow() 自动处理相对URL,比手动使用 response.urljoin() 更方便,推荐优先使用。

注意3:Scrapy默认启用URL去重(RFPDupeFilter),不同分页URL不会被重复请求。如果需要请求相同URL多次,设置 dont_filter=True

注意4:设置 DOWNLOAD_DELAY 控制采集速度,建议设置为1-3秒。同时可以设置 CONCURRENT_REQUESTS_PER_DOMAIN 限制每个域名的并发请求数。

八、练习题

练习1

编写一个Spider,爬取 https://quotes.toscrape.com/ 的所有页面,使用 close_spider() 方法在爬虫结束时统计并输出总共采集了多少条名言。

练习2

编写一个Spider,使用URL模式分页,爬取某网站第1-20页的数据(URL格式为 https://example.com/data?page=N),并在每页数据中标记页码。要求设置最大并发请求数为3,下载延迟为2秒。


九、常见问题FAQ

常见问题

response.follow()和scrapy.Request()有什么区别?

response.follow() 自动将相对URL转换为绝对URL,还支持直接传入Selector对象;scrapy.Request() 需要完整的绝对URL。对于分页场景,推荐优先使用 response.follow()

如何在分页之间传递数据?

使用 meta 字典。在创建Request时通过 meta={'key': value} 传递数据,在回调函数中通过 response.meta['key'] 获取。这在多级分页中非常常用,可以逐级累积数据。

分页爬取时如何控制爬取速度?

settings.py 中设置 DOWNLOAD_DELAY = 2(秒),设置 CONCURRENT_REQUESTS_PER_DOMAIN = 2 限制并发数。也可以启用 AUTOTHROTTLE_ENABLED = True 让Scrapy自动调整爬取速度。

页面没有"下一页"按钮,如何判断分页结束?

有多种方法:(1) 检查当前页数据量,如果为空或少于预期则停止;(2) 设置最大页数限制;(3) 通过URL模式,检查是否超过最后一页;(4) 检查页面中是否有"最后一页"或"无更多数据"的标识元素。建议组合使用多种判断方式。

如何处理JavaScript动态加载的分页?

Scrapy默认不能执行JavaScript。解决方法:(1) 分析页面的API请求,直接请求JSON API获取分页数据;(2) 使用 scrapy-playwrightscrapy-splash 中间件渲染页面;(3) 使用Selenium获取渲染后的HTML再交给Scrapy处理。

标签: Scrapy 分页爬取 response.follow 多级分页 Python爬虫 爬虫教程

本文涉及AI创作

内容由AI创作,请仔细甄别

list快速访问

上一篇: Scrapy中间件详解 - 下载器与爬虫中间件完整指南 下一篇: Scrapy数据导出教程:JSON/CSV/XML多格式导出详解

poll相关推荐