🐸 青蛙编程网
首页 / Python / 爬虫 / 5. Scrapy

5. Scrapy

这是什么:Scrapy 是 Python 爬虫界事实上的标准框架。前四章手写的每一样东西——请求队列、URL 去重、并发控制、失败重试、限速、数据导出——Scrapy 全部内置,你只需要写两块业务代码:从哪开始爬(Spider)和抓到后解析什么(parse)。

为什么用框架:手写爬虫到全站规模,调度和健壮性代码会膨胀到比业务逻辑还多;框架把这些抽成配置,一套代码从单页爬到百万页。反过来,小脚本级的需求用 requests 更轻——框架不是万能替代,是规模化工具。

如何用:建项目 → 写 Spider → 配置限速 → 一条命令爬完全站导出 JSON。

装包与建项目

pip install scrapy
scrapy startproject scrapy_demo     # 创建项目
cd scrapy_demo

生成的目录结构(每样东西的职责先有个印象):

文件/目录 职责 类比手写爬虫
scrapy_demo/spiders/ 放爬虫,每站一个文件 你的 parse_quotes.py
items.py 定义一条数据的字段结构 你存数据的字典长什么样
pipelines.py 数据清洗、去重、入库 第 4 章的存储代码
settings.py 限速、并发、UA、robots 开关 第 4 章的礼貌爬取
middlewares.py 请求发出前/响应回来后的加工车间 换代理、换 UA 的钩子(进阶)

写第一个 Spider

scrapy genspider quotes quotes.toscrape.com

打开 spiders/ 下生成的模板文件,改成下面这样(去掉多余注释):

import scrapy


class QuotesSpider(scrapy.Spider):
    name = "quotes"                                  # 爬虫名字,运行时用
    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("a.tag::text").getall(),
            }

        # 翻页:发现「下一页」就把它加入队列,Scrapy 自动去重、自动调度
        next_page = response.css("li.next a::attr(href)").get()
        if next_page:
            yield response.follow(next_page, callback=self.parse)

注意三个和手写版的差别:

手写版 Scrapy 版 框架帮你干了什么
requests.get() + 自己管理 start_urls + response 请求、重试、限速、并发全自动
a["href"] 取属性 a::attr(href) 直接取属性值 选择器语法统一,返回就是值
get_text() ::text 同上
自己写队列 + set 去重 yield response.follow(...) 入队、去重、调度、URL 拼接全内置

response.css 返回的是选择器列表:.get() 取第一个值,.getall() 取全部值——和第 3 章的 CSS 选择器是同一套语法。

运行:一条命令爬完导出

scrapy crawl quotes -O quotes.json

你会看到:终端刷一阵请求日志(绿色 200),项目目录出现 quotes.json——全部 10 页 100 条名言,你一行调度代码都没写。-O 是覆盖导出(.csv/.jl 后缀同样支持);只想看数据不想要日志,加 -s LOG_ENABLED=False。

settings.py:把礼貌爬取变成配置

打开 scrapy_demo/settings.py,这几行和第 4 章的规矩一一对应:

ROBOTSTXT_OBEY = True              # 遵守 robots.txt(新项目模板默认开启)
DOWNLOAD_DELAY = 1.0               # 每次请求间隔 1 秒,相当于内置 time.sleep
CONCURRENT_REQUESTS = 8            # 总并发请求数,别开大
CONCURRENT_REQUESTS_PER_DOMAIN = 2 # 单个网站的并发,礼貌红线
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/126.0"
RETRY_TIMES = 3                    # 失败自动重试 3 次

配置即代码——限速、并发、重试这些「手写要几百行」的东西,Scrapy 里就是几个常量。

Item 与 Pipeline:数据加工车间

yield 字典 已经能用,但正式项目建议定义 Item(字段有名字、有约束),并在 Pipeline 里做清洗和入库:

# items.py
import scrapy

class QuoteItem(scrapy.Item):
    text = scrapy.Field()
    author = scrapy.Field()
    tags = scrapy.Field()
# pipelines.py —— 数据流经的加工车间
class CleanPipeline:
    def process_item(self, item, spider):
        item["text"] = item["text"].strip()
        if not item["author"]:                     # 也能在这里丢弃脏数据
            from scrapy.exceptions import DropItem
            raise DropItem("作者为空,丢弃")
        return item
# settings.py 里启用管道(数字越小越先执行)
ITEM_PIPELINES = {"scrapy_demo.pipelines.CleanPipeline": 300}

之后 Spider 里 yield QuoteItem(...),数据就自动流过管道再落盘。记住分工:Spider 管提取,Pipeline 管清洗入库,settings 管行为。

常见翻车现场

症状 原因 修复
scrapy 命令找不到 装在了别的环境/没激活 venv 激活装包的那个环境
运行报 Spider not found crawl 后面没写对 name scrapy list 查看可用爬虫名
抓到的都是空值 选择器和页面结构对不上 F12 重新核对,注意数据是否 JS 渲染
全是 403 settings 里没配 USER_AGENT 加上 UA(模板默认可能没开)
输出 JSON 中文乱码 用了 -o 且没设置编码 导出用 -O,或导出 .jsonl 后自己转

动手改

  1. 给 Spider 加一个 start_urls 起点:https://books.toscrape.com/,抓书名 + 价格
  2. 把 DOWNLOAD_DELAY 改成 0 再跑,对比抓完 10 页的耗时(也体会一下网站的压力差)
  3. 写一个 Pipeline:把 author 去空格、tags 合并成逗号字符串后再导出

上一章:数据存储与去重 · 下一章:feapder 🐸

📥 下载本章代码

ZIP 内含本章全部代码文件与说明,解压即用,与教程内容同步生成。