跳转到内容

Scrapy 框架基础知识

建议学习 45 分钟更新于 2025/4/23

Scrapy 是一个适合处理结构化爬取任务的 Python 爬虫框架。相比手写 requests 循环请求,Scrapy 内置了请求调度、并发控制、失败重试、数据管道和中间件机制,更适合需要持续抓取、批量抓取或长期维护的项目。

这篇文章用一个公开练习站点做示例:抓取名言、作者和标签,并处理分页。目标是先掌握 Scrapy 的基础项目结构,而不是一上来处理登录、验证码、风控这些复杂问题。

Scrapy 适合下面这些场景:

  • 需要抓取大量页面,并希望自动管理请求队列。
  • 需要处理并发、重试、下载延迟等稳定性问题。
  • 需要把解析逻辑、数据清洗、数据保存拆开维护。
  • 需要把抓取结果写入 CSV、JSON、MySQL、MongoDB 等存储。
  • 需要后续扩展代理、Cookies、请求头、中间件或分布式队列。

如果只是抓取一两个页面,requestshttpx 更轻量;如果项目会持续增长,Scrapy 的工程结构会更稳。

Scrapy 常见模块可以先这样理解:

  • Spider:定义从哪里开始抓、怎么解析页面、怎么继续发请求。
  • Request / Response:请求和响应对象,Scrapy 负责调度与下载。
  • Item:定义要提取的数据字段。
  • Pipeline:处理清洗、校验、保存数据。
  • Settings:配置并发、延迟、请求头、Pipeline 开关等。
  • Middleware:扩展请求和响应处理流程,例如代理、Cookies、重试策略。

最开始学习时,优先掌握 Spider、Item、Pipeline 和 Settings 就够了。

本教程抓取练习站点:

https://quotes.toscrape.com/

需要提取三类字段:

  • text:名言内容。
  • author:作者。
  • tags:标签列表。

同时处理分页,把所有页面的数据都抓下来。

Terminal window
pip install scrapy
Terminal window
scrapy startproject quotes_crawler

进入项目目录:

Terminal window
cd quotes_crawler

创建爬虫:

Terminal window
scrapy genspider quotes quotes.toscrape.com

生成后的目录大致如下:

  • 文件夹quotes_crawler/
    • scrapy.cfg
    • 文件夹quotes_crawler/
      • __init__.py
      • items.py
      • middlewares.py
      • pipelines.py
      • settings.py
      • 文件夹spiders/
        • __init__.py
        • quotes.py

先在 quotes_crawler/items.py 中定义要保存的数据结构。

import scrapy
class QuoteItem(scrapy.Item):
text = scrapy.Field()
author = scrapy.Field()
tags = scrapy.Field()

Item 的作用是让数据字段更明确。虽然直接 yield dict 也可以,但使用 Item 更适合后续维护和 Pipeline 处理。

打开 quotes_crawler/spiders/quotes.py,写入下面的爬虫逻辑。

import scrapy
from quotes_crawler.items import QuoteItem
class QuotesSpider(scrapy.Spider):
name = "quotes"
allowed_domains = ["quotes.toscrape.com"]
start_urls = ["https://quotes.toscrape.com/"]
def parse(self, response):
quote_blocks = response.css(".quote")
for quote in quote_blocks:
item = QuoteItem()
item["text"] = quote.css(".text::text").get()
item["author"] = quote.css(".author::text").get()
item["tags"] = quote.css(".tags .tag::text").getall()
yield item
next_page = response.css("li.next a::attr(href)").get()
if next_page:
yield response.follow(next_page, callback=self.parse)

这段代码做了几件事:

  1. 通过 .quote 找到每条名言所在的元素。
  2. 从每条名言中提取正文、作者和标签。
  3. yield item 把数据交给 Scrapy 后续处理。
  4. 找到下一页链接后,使用 response.follow() 继续请求。

Scrapy 支持 CSS 选择器和 XPath。上面的代码主要用了 CSS 选择器:

response.css(".quote")

表示选择页面里所有 class="quote" 的元素。

quote.css(".text::text").get()

表示获取当前 quote 内部 .text 元素的文本。

quote.css(".tags .tag::text").getall()

表示获取当前 quote 内所有标签文本,并返回列表。

常用方法:

  • .get():取第一个结果。
  • .getall():取全部结果。
  • ::text:取文本内容。
  • ::attr(href):取属性值。

在项目根目录运行:

Terminal window
scrapy crawl quotes

如果想直接导出 JSON 文件:

Terminal window
scrapy crawl quotes -O quotes.json

导出 CSV:

Terminal window
scrapy crawl quotes -O quotes.csv

-O 会覆盖已有文件。如果想追加写入,可以使用 -o

如果想更接近真实项目,可以使用 Pipeline 自己控制保存逻辑。

编辑 quotes_crawler/pipelines.py

import json
class JsonLinesPipeline:
def open_spider(self, spider):
self.file = open("quotes.jl", "w", encoding="utf-8")
def close_spider(self, spider):
self.file.close()
def process_item(self, item, spider):
line = json.dumps(dict(item), ensure_ascii=False)
self.file.write(line + "\n")
return item

然后在 quotes_crawler/settings.py 中开启 Pipeline:

ITEM_PIPELINES = {
"quotes_crawler.pipelines.JsonLinesPipeline": 300,
}

再次运行:

Terminal window
scrapy crawl quotes

运行结束后会生成:

quotes.jl

JSON Lines 的特点是每一行都是一条 JSON 数据,适合大规模数据逐行写入。

Scrapy 默认支持并发请求。如果需要控制请求速度,可以在 settings.py 中配置:

# 遵守 robots.txt,练习阶段建议开启
ROBOTSTXT_OBEY = True
# 下载延迟,避免请求过于密集
DOWNLOAD_DELAY = 0.5
# 最大并发请求数
CONCURRENT_REQUESTS = 16
# 单个域名的并发请求数
CONCURRENT_REQUESTS_PER_DOMAIN = 8
# 请求头,可以按需要补充
DEFAULT_REQUEST_HEADERS = {
"Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8",
"Accept-Language": "zh-CN,zh;q=0.9,en;q=0.8",
}

并发不是越高越好。真实项目中需要结合目标站点响应速度、机器资源和站点规则逐步调整。

可以先保存响应内容,确认请求到的页面是否和浏览器看到的一样。

def parse(self, response):
self.logger.info("Current URL: %s", response.url)
self.logger.info("Status: %s", response.status)

如果页面依赖 JavaScript 渲染,Scrapy 直接请求 HTML 可能拿不到最终数据。这时需要寻找接口数据,或使用 Playwright、Selenium 等方案。

检查下一页选择器是否正确:

next_page = response.css("li.next a::attr(href)").get()

如果 next_page 是相对路径,使用 response.follow() 会自动补全 URL。

JSON 导出可以指定编码:

Terminal window
scrapy crawl quotes -O quotes.json -s FEED_EXPORT_ENCODING=utf-8

Pipeline 中写文件时也要指定:

open("quotes.jl", "w", encoding="utf-8")

可以在 Pipeline 中统一清理文本:

def process_item(self, item, spider):
item["text"] = item["text"].strip("“”")
item["author"] = item["author"].strip()
return item

如果担心重复数据,可以用 text + author 作为去重键:

class DuplicatesPipeline:
def open_spider(self, spider):
self.seen = set()
def process_item(self, item, spider):
key = (item["text"], item["author"])
if key in self.seen:
raise DropItem(f"Duplicate item: {item['text']}")
self.seen.add(key)
return item

使用 DropItem 时需要导入:

from scrapy.exceptions import DropItem

当数据量增大后,可以把 Pipeline 改成写入数据库:

  • MySQL:适合结构化数据和后续查询。
  • MongoDB:适合字段变化较多的数据。
  • Elasticsearch:适合全文检索。

Spider 仍然只负责解析,Pipeline 负责保存,这样结构会比较清晰。

  • 优先选择公开、允许抓取或专门用于练习的网站。
  • 控制请求频率,不要对目标站点造成压力。
  • 遵守目标站点的 robots、服务条款和相关法律要求。
  • 不要把账号、密码、Token 等敏感信息写进代码。
  • 对需要登录、验证码、风控的网站,要单独评估合法性和技术复杂度。

Scrapy 的核心价值不是单次请求,而是提供了一套可维护的爬虫工程结构。

一个基础 Scrapy 项目通常包含:

  1. Spider 负责请求和解析。
  2. Item 定义数据字段。
  3. Pipeline 负责清洗和保存。
  4. Settings 控制并发、延迟和扩展功能。

掌握这条链路后,再去处理登录、代理、分布式队列或动态页面,会更容易扩展。