Scrapy框架入门

网络爬虫专题 · 掌握Scrapy爬虫框架

专题:Python网络爬虫系统学习

关键词:Python, 网络爬虫, Scrapy, 爬虫框架, Spider, Pipeline, 中间件, 异步爬虫, XPath

一、Scrapy概述

Scrapy是Python生态中最流行的开源爬虫框架,由Scrapinghub公司维护,广泛应用于数据采集、信息监控和自动化测试等领域。它基于Twisted事件驱动框架构建,采用异步非阻塞I/O模型,能够在单线程内高效处理大量并发请求,极大提升爬取效率。

与Requests+BeautifulSoup的手动组合方案相比,Scrapy提供了完整的爬虫生命周期管理,包括请求调度、并发控制、数据解析、结果持久化和去重策略等。开发者只需关注爬虫逻辑本身,无需操心底层网络通信细节。

核心特点:异步架构、插件化中间件、强大的选择器、自动限速与重试、内置去重机制、支持分布式扩展。

安装方式

Scrapy的安装非常简便,推荐使用pip包管理器。在终端中直接执行以下命令即可完成安装:

pip install scrapy

安装完成后,可以在终端中运行 scrapy version 验证安装是否成功。如果看到类似 Scrapy 2.11.x 的版本号输出,说明已经安装成功。需要注意的是,Scrapy依赖的Twisted框架在某些Python版本或操作系统上可能需要预编译支持,在Windows环境下建议使用官方Python发行版以避免兼容性问题。

二、Scrapy架构

理解Scrapy的架构是高效开发的前提。Scrapy遵循组件化设计理念,各模块职责清晰,通过中间件机制实现高度的可扩展性。整体架构由五个核心组件和两个中间件层构成。

核心组件

  1. Engine(引擎):是整个框架的大脑,负责控制数据流在各个组件之间流转。它接收Spider发出的请求,协调Scheduler和Downloader的工作,并将Spider解析到的Item传递给Pipeline处理。引擎本身不可扩展,但其工作流程可被中间件拦截和修改。
  2. Scheduler(调度器):负责管理请求队列。引擎发来的请求会先进入调度器,调度器根据优先级排序后依次出队。调度器内置了去重过滤器(DupeFilter),默认使用RFPDupeFilter基于请求指纹进行去重,避免重复爬取同一个URL。
  3. Downloader(下载器):负责发送HTTP/HTTPS请求并获取响应内容。它基于Twisted的异步HTTP客户端实现,可以同时维护数百个并发连接。下载器支持自动处理Cookie、重定向、压缩、认证等HTTP标准特性。
  4. Spider(爬虫):是开发者编写业务逻辑的地方。Spider从起始URL开始爬取,解析响应内容(HTML、JSON、XML等),提取结构化数据并封装为Item对象,同时还可以生成新的Request来跟进链接,实现深度爬取。
  5. Item Pipeline(管道):负责处理爬虫产出的Item。典型的处理操作包括数据清洗(去除空白、格式化)、验证(字段完整性检查)、去重(基于数据库或集合)和数据持久化(存入CSV、JSON、数据库)。Pipeline依次执行,按优先级排序。

中间件

Scrapy提供两种中间件类型:Downloader Middleware 在引擎和下载器之间,用于处理请求/响应(如代理切换、User-Agent轮换、自定义请求头);Spider Middleware 在引擎和爬虫之间,用于处理Spider的输入(Response)和输出(Item/Request)。

数据流图

Engine → Scheduler → Engine → Downloader Middlewares → Downloader → Downloader Middlewares → Engine → Spider Middlewares → Spider → Spider Middlewares → Engine → Item Pipeline

简化的数据流向:Spider产生Request → Engine调度 → Downloader下载 → Spider解析Response → Pipeline存储数据。

三、创建Scrapy项目

Scrapy通过命令行工具提供了一套完整的项目脚手架,可以帮助开发者快速创建标准化的项目结构。项目一旦创建,目录组织、配置文件、组件注册等都由框架统一管理。

创建项目

使用 startproject 命令创建新项目:

scrapy startproject tutorial cd tutorial

项目目录结构

创建完成后会生成以下目录和文件:

tutorial/ scrapy.cfg # 项目配置文件 tutorial/ # Python模块包 __init__.py items.py # Item定义文件 middlewares.py # 中间件定义 pipelines.py # Pipeline定义 settings.py # 项目设置 spiders/ # 爬虫目录 __init__.py

各个文件的作用:items.py 定义爬取的数据结构;pipelines.py 实现数据后处理逻辑;middlewares.py 编写自定义中间件;settings.py 集中管理所有配置项;spiders/ 目录存放所有爬虫类。

创建爬虫

进入项目目录后,使用 genspider 命令创建爬虫:

scrapy genspider quotes quotes.toscrape.com

此命令会在 spiders/ 目录下生成 quotes.py 文件,其中自动填充了爬虫名称和域名限制。生成的爬虫类继承自 scrapy.Spider,开发者只需填充 parse() 方法即可。

运行爬虫

使用 crawl 命令启动爬虫:

scrapy crawl quotes

如果需要将输出保存到文件,可以使用 -o 参数:

scrapy crawl quotes -o quotes.json scrapy crawl quotes -o quotes.csv

四、Spider开发

Spider是Scrapy中最核心的组件,包含了爬虫的所有业务逻辑。Scrapy提供了多种内置Spider基类以满足不同场景的需求,最常用的是 scrapy.Spider

Spider基本结构

一个最基本的Spider包含三个必须属性:name(爬虫唯一标识)、allowed_domains(允许爬取的域名白名单)、start_urls(起始URL列表),以及一个核心方法 parse()

import scrapy class QuotesSpider(scrapy.Spider): name = "quotes" allowed_domains = ["quotes.toscrape.com"] 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(), } next_page = response.css("li.next a::attr(href)").get() if next_page: yield response.follow(next_page, callback=self.parse)

parse()方法

parse() 是Spider的默认回调方法,接收 Response 对象作为参数。Response对象封装了服务器返回的所有信息,包括HTTP状态码、响应头和响应体。开发者在该方法中使用选择器提取所需数据,并通过 yield 关键字返回Item或Request。

注意:parse()是一个生成器函数,yield Item会自动进入Pipeline处理,yield Request会重置到引擎调度队列。这种设计使得Scrapy可以高效地处理大量数据,而不会因为单个页面解析耗时过长而阻塞其他请求。

跟进链接

网络爬虫的核心能力之一是自动发现和跟进链接。Scrapy通过 response.follow() 方法简化了这一过程。相比手动拼接URL,response.follow() 会自动处理相对路径和绝对路径的转换。如果需要传递额外参数到回调函数,可以使用 cb_kwargs 参数:

def parse_author(self, response, author_name): yield { "name": author_name, "birthday": response.css(".author-born-date::text").get(), }

在发起请求时传入参数:

yield response.follow( author_url, callback=self.parse_author, cb_kwargs={"author_name": author_name} )

对于需要更多控制的场景,也可以手动构造 scrapy.Request 对象,通过 meta 字典传递参数:

yield scrapy.Request( url=author_url, callback=self.parse_author, meta={"author_name": author_name} )

五、Item与Item Pipeline

当爬虫需要爬取结构化数据时,使用Item可以带来更好的类型安全和代码可读性。Pipeline则提供了集中化的数据后处理能力,与爬虫逻辑完全解耦。

Item类定义

items.py 中定义数据模型,每个字段使用 scrapy.Field() 声明:

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

Item的行为类似于Python字典,支持键值存取、序列化和反序列化。在Spider中使用时,直接实例化后按字段赋值即可。

Pipeline处理流程

Pipeline是一个实现了 process_item() 方法的Python类。方法接收Item和Spider两个参数,必须返回Item对象(或抛出 DropItem 异常以丢弃无效数据)。Pipeline典型的应用包括:

Pipeline示例

class CleanPipeline: def process_item(self, item, spider): item["text"] = item["text"].strip() item["author"] = item["author"].strip() return item class JsonPipeline: def open_spider(self, spider): self.file = open("output.json", "w", encoding="utf-8") self.file.write("[\n") def close_spider(self, spider): self.file.write("\n]") self.file.close() def process_item(self, item, spider): line = json.dumps(dict(item), ensure_ascii=False) self.file.write(f" {line},\n") return item

注册Pipeline并设置优先级

settings.py 中通过 ITEM_PIPELINES 字典注册Pipeline类,数字越小优先级越高:

ITEM_PIPELINES = { "tutorial.pipelines.CleanPipeline": 300, "tutorial.pipelines.JsonPipeline": 800, }

多个Pipeline按照优先级从小到大的顺序依次执行,前一个Pipeline处理完的Item会传递给下一个。如果某个Pipeline抛出 DropItem 异常,后续Pipeline将不再处理该Item,从而实现数据过滤。

六、Scrapy选择器

Scrapy内置了强大的Selector模块,同时支持CSS选择器和XPath表达式,底层封装了lxml解析库,具有高性能和容错能力。选择器对象可以直接在Response上调用,非常便捷。

CSS选择器

CSS选择器语法简洁,适合提取具有class或id属性的元素:

# 选取class为quote的所有div response.css("div.quote") # 选取第一个匹配元素内的文本 response.css("span.text::text").get() # 选取属性值 response.css("a::attr(href)").getall() # 链式选取 response.css("div.quote").css("span.text::text").get()

XPath选择器

XPath功能更为强大,支持文本匹配、位置筛选、轴操作等高级功能:

# 选取所有span元素的文本 response.xpath("//span/text()").getall() # 按文本内容筛选 response.xpath("//small[contains(text(), 'Author')]/text()").get() # 选取第二个元素的文本 response.xpath("(//div[@class='quote'])[2]/span/text()").get() # 使用@选取属性 response.xpath("//a/@href").getall()

提取方法

Scrapy提供了多种提取方法以适应不同的使用场景:

选择器嵌套

选择器支持在结果集上继续选择,这在处理复杂嵌套结构时非常有用:

quotes = response.css("div.quote") for quote in quotes: text = quote.css("span.text::text").get() author = quote.xpath(".//small[@class='author']/text()").get() tags = quote.css("a.tag::text").getall() print(f"{author}: {text} ({tags})")

注意XPath嵌套时,前面的 . 表示相对路径,从当前节点继续搜索,如果不加 . 则从文档根节点重新开始搜索,可能得到预期之外的结果。

七、Settings配置

Scrapy的绝大多数行为都可以通过 settings.py 文件配置。合理的配置能够显著提升爬虫的稳定性、效率和可维护性。以下是几个最常用的配置项:

配置项说明默认值建议值
ROBOTSTXT_OBEY是否遵守robots.txt协议True开发时设为False
CONCURRENT_REQUESTS最大并发请求数168-32
DOWNLOAD_DELAY请求间隔时间(秒)00.5-3.0
COOKIES_ENABLED是否启用CookieTrue一般保持默认
DEFAULT_REQUEST_HEADERS默认请求头{}模拟浏览器
USER_AGENT用户代理字符串Scrapy/版本号真实浏览器UA
ITEM_PIPELINESPipeline注册表{}按需注册
DOWNLOAD_TIMEOUT下载超时时间(秒)18010-30
RETRY_ENABLED是否自动重试True保持默认
DEPTH_LIMIT爬取最大深度0(不限)按需设置

一个典型的settings.py配置示例如下:

ROBOTSTXT_OBEY = False CONCURRENT_REQUESTS = 16 DOWNLOAD_DELAY = 1.0 COOKIES_ENABLED = False USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36" 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", } ITEM_PIPELINES = { "tutorial.pipelines.CleanPipeline": 300, }

最佳实践:在开发调试阶段建议将ROBOTSTXT_OBEY设为False,DOWNLOAD_DELAY设为0,便于快速测试。上线部署时务必根据目标网站的承受能力设置合理的延时和并发数,避免对目标服务器造成压力。同时建议开启DOWNLOAD_TIMEOUT和限制重试次数,防止异常请求长时间占用资源。