Scrapy 网络爬虫怎么入门?分页请求、数据去重与导出三个练习

编程狮(w3cschool.cn) 2026-09-10 14:42:33 浏览数 (9)
反馈

Scrapy 入门可以按三个练习推进:解析一页、跟随下一页、统一清洗后导出。请求去重与数据去重要分别处理,前者避免重复下载,后者避免同一条业务记录被保存多次。

Scrapy 分页采集请求与记录分开处理

当你的 Python 小脚本开始混入分页、重试、输出文件和重复数据判断,主循环会越来越长。Scrapy 的价值是让调度、下载与数据处理分工明确,但它不会自动替你定义“哪两条记录算重复”。本文用本地两页课程列表完成一个可复现项目,演示三个练习如何接起来。编程狮使用 Python 3.10 以上、Scrapy 2.13 以上的 start 接口,页面由本机服务提供,便于观察行为,也避免把学习压力施加给外部站点。

一、先搭本地两页样本,理解请求和记录的区别

创建项目后,你会看到 settings.py、pipelines.py 和 spiders 目录。Spider 负责发现请求并提取数据,Pipeline 负责统一处理记录,导出器负责写出结果。项目根目录的 scrapy.cfg 帮助命令定位设置。

在空目录运行以下命令。python 指已安装 Scrapy 的解释器;Windows 可使用虚拟环境中的 python.exe,macOS 或 Linux 使用相应环境的 python。

python -m pip install "scrapy>=2.13,<3"
python -m scrapy startproject course_demo

在 course_demo 目录中新建 pages 文件夹,分别保存 page1.html 和 page2.html:

<!-- page1.html -->
<ul><li class="course" data-id="a"> Python 入门 </li></ul>
<a class="next" href="page2.html">下一页</a>

<!-- page2.html -->
<ul>
 <li class="course" data-id="a">Python 入门</li>
 <li class="course" data-id="b">JavaScript 实践</li>
</ul>

两个文件故意共享编号 a。即使两个页面各只下载一次,记录 a 仍会被提取两次,这正是后续数据去重需要解决的问题。先确认你能解释这个差异,再接入更多页面。

二、Scrapy 分页请求由回调产生,不要手写同步循环

在项目的 course_demo/spiders 下保存 courses.py。启动本地页面服务后,Spider 会从第一页开始解析,再从下一页链接产生第二个请求。

import scrapy

class CoursesSpider(scrapy.Spider):
    name = "courses"
    allowed_domains = ["127.0.0.1"]

    async def start(self):
        yield scrapy.Request("http://127.0.0.1:8765/page1.html")

    def parse(self, response):
        for node in response.css("li.course"):
            yield {
                "id": node.attrib.get("data-id", ""),
                "title": node.xpath("string(.)").get(),
                "source": response.url,
            }
        next_page = response.css("a.next::attr(href)").get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

response.follow 会以当前响应地址解析相对链接。parse 既可以产生记录,也可以产生新的请求,它们会进入不同处理路径;不是 Python for 循环直接等待下一页下载结束。

在一个终端从项目根运行本地服务,另一个终端执行爬虫。第一个终端需要保持运行,完成练习后用 Ctrl+C 停止:

python -m http.server 8765 --bind 127.0.0.1 --directory pages

分页链接缺失时不再提交请求,任务自然结束。真实网站还应确认下一页没有指向站外或回到当前页,不要为了“强制继续”随意打开 dont_filter。请求调度可以自动去重,但分页规则本身写错仍需修复。相关 Python 类与生成器概念可在 Python3 教程 中补齐。

三、用 Pipeline 清洗和去重,确定保留哪一条

将项目生成的 pipelines.py 替换为以下内容,保留模块所在位置不变。Pipeline 按编号去重,并拒绝缺少编号或标题的记录。

from scrapy.exceptions import DropItem

class CleanCoursePipeline:
    def __init__(self):
        self.seen = set()

    def process_item(self, item, spider):
        item["id"] = str(item.get("id", "")).strip()
        item["title"] = " ".join((item.get("title") or "").split())
        if not item["id"] or not item["title"]:
            raise DropItem("课程缺少编号或标题")
        if item["id"] in self.seen:
            raise DropItem(f"重复课程编号:{item['id']}")
        self.seen.add(item["id"])
        return item

这套规则保留最先进入 Pipeline 的同编号记录。示例串行抓取,顺序容易理解;如果以后提高并发,“先到”不一定等于“网站顺序最前”。需要按更新时间保留最新记录时,应比较字段或在数据库中执行相应更新,不能只靠集合。

内存集合只覆盖本次进程。重新启动程序会重新建立集合,历史结果不会自动成为去重依据。如果要求跨天去重,要把业务编号存入可持续查询的存储,并设计更新策略。请求指纹去重、业务主键去重和数据库唯一约束,各解决一个层面的问题。

你可以把第二页的编号 b 改成空字符串,再运行一次,观察日志中记录被丢弃的原因。这样验证的是异常路径,不只是看到一个输出文件就宣布处理成功。

四、配置限速与导出,再检查记录数是否符合预期

在 settings.py 中加入或修改以下配置。演示选择串行请求、固定三秒间隔,关闭随机延迟,方便观察两次请求不会紧挨着发出。AutoThrottle 可以在响应变慢时增加等待。

ITEM_PIPELINES = {"course_demo.pipelines.CleanCoursePipeline": 300}
ROBOTSTXT_OBEY = True
CONCURRENT_REQUESTS = 1
CONCURRENT_REQUESTS_PER_DOMAIN = 1
DOWNLOAD_DELAY = 3
RANDOMIZE_DOWNLOAD_DELAY = False
AUTOTHROTTLE_ENABLED = True
AUTOTHROTTLE_START_DELAY = 3
AUTOTHROTTLE_MAX_DELAY = 60
FEED_EXPORT_ENCODING = "utf-8"
TELNETCONSOLE_ENABLED = False

AutoThrottle 不是绕过站点限制的工具,仍应遵守站点约定。本文本地服务没有 robots.txt,可能出现对应的缺失日志;接入真实网站时,要读取并遵守有效规则。抓取失败不要用增加并发来“试着恢复”,应检查响应状态和重试原因。

从项目根导出一个 JSON 文件:

python -m scrapy crawl courses -O courses.json

大写 -O 会覆盖同名输出文件,因此首次练习应选择新的文件名或先备份;小写 -o 是追加语义,反复追加 JSON 可能不符合你对单个完整文档的预期。需要持续追加时,可以明确选用逐行记录的格式。

预期结果为两条课程,标题分别是 Python 入门和 JavaScript 实践,编号分别为 a、b。检查日志中的抓取、丢弃和导出记录,确认重复 a 被识别,而不是因为第二页根本没请求到才得到一个“没有重复”的结果。遇到文件阅读问题,可以配合 JSON 教程 理解输出结构。

一次解析,分流到两条不同路径

总结

Scrapy 网络爬虫的三个练习要分别验收:Spider 能提取当前页,分页回调能发现下一页,Pipeline 能清洗并拒绝重复记录。最后的文件只是这个过程的结果,不应该成为唯一成功标准。

本例两页有三条原始记录,导出后有两条有效记录。你可以继续加入一页循环链接或一条缺标题数据,观察请求去重与记录丢弃的差别。掌握这个小项目之后,再扩展持久化去重、错误重试和详情页,代码职责会更清楚,问题也更容易定位。

延伸学习

  1. 通过 Python Scrapy 网络爬虫入门课程 继续实践。
  2. HTML 教程 练习定位页面元素与属性。
  3. 阅读 常用 Python 爬虫库汇总,了解不同采集工具的分工。

常见问题

Q:为什么重复页面没抓,重复数据还是出现了?

A:请求去重比较的是请求身份,数据去重比较的是业务记录。同一课程可能出现在不同页面,应在 Pipeline 或存储层根据业务编号处理。

Q:断点继续能保证数据不重复吗?

A:不能自动保证所有业务条件。请求状态恢复与记录持久化是不同问题,应同时考虑已经保存的数据、恢复后的请求和存储层唯一约束。

Q:可以在 parse 里调用 time.sleep 吗?

A:不建议用它限速。它会阻塞处理线程,影响其他任务。应使用下载延迟、并发配置和框架的节流机制,让调度器管理等待。

0 人点赞