Python爬虫开发中,Scrapy框架凭借高封装、高并发、可拓展的特性成为首选,其核心架构围绕Spider、Item、Pipeline、Downloader四大核心组件构建,再配合引擎(Engine)协调工作,实现从请求发起、网页下载、数据提取到数据持久化的全流程自动化。
一、前置知识:核心架构整体认知与环境准备
1. 四大核心组件定位(一张表看懂)
| 核心组件 | 核心作用 | 工作阶段 | 核心输出/输入 | 关键价值 |
|---|---|---|---|---|
| Spider(爬虫) | 发起请求、解析响应、提取Item/新请求 | 请求发起+数据提取 | 输入:Start URLs;输出:Item对象/新Request | 定义爬取规则和数据提取逻辑,是爬虫的“大脑” |
| Downloader(下载器) | 接收请求、下载网页响应、返回给Spider | 网页下载 | 输入:Request对象;输出:Response对象 | 高效处理批量请求,支持并发、延迟、代理配置 |
| Item(数据项) | 结构化存储爬取数据、实现数据验证 | 数据提取+预处理 | 输入:原始解析数据;输出:结构化Item对象 | 规范数据格式,避免脏数据,方便后续Pipeline处理 |
| Pipeline(管道) | 处理Item对象(去重、清洗、持久化) | 数据后处理+持久化 | 输入:结构化Item对象;输出:持久化数据(文件/数据库) | 解耦数据提取与存储,支持多管道协同工作 |
2. 整体工作流程(引擎协调下的组件协同)
- 引擎(Engine)从Spider中获取初始Start URLs,封装为Request对象传递给Downloader;
- Downloader下载网页内容,封装为Response对象,通过引擎返回给Spider;
- Spider解析Response,提取有效数据封装为Item对象,或提取新的URL封装为Request对象,通过引擎传递;
- 引擎将Item对象传递给Pipeline,将新Request对象再次传递给Downloader,循环执行;
- Pipeline对Item进行去重、清洗、持久化存储,完成整个爬取流程。
3. 环境安装(Scrapy核心框架)
# 安装Scrapy框架(Python 3.8~3.10版本最佳,避免兼容性问题)
pip install scrapy==2.11.0 # 指定稳定版本,减少踩坑
4. 环境验证
# 终端输入以下命令,输出Scrapy版本即验证成功
scrapy version
补充:Windows环境若安装失败,先安装依赖库
pip install pywin32,再重新安装Scrapy。
二、Spider组件:爬虫的“大脑”,定义爬取与解析逻辑
1. 核心作用
Spider是整个爬虫的业务核心,负责发起初始请求、解析网页响应、提取目标数据、跟进新请求,所有爬取规则和数据提取逻辑都在Spider中定义。
2. 实战:创建第一个Spider,爬取静态网页数据
以爬取“博客园首页文章列表”为例,实现Spider的核心功能:
步骤1:创建Scrapy项目
# 终端执行:创建名为cnblog_spider的项目
scrapy startproject cnblog_spider
项目目录结构(核心文件):
cnblog_spider/
├── cnblog_spider/
│ ├── items.py # Item组件定义文件
│ ├── pipelines.py # Pipeline组件定义文件
│ ├── settings.py # 全局配置(下载延迟、管道启用等)
│ └── spiders/ # Spider组件存放目录
└── scrapy.cfg # 项目配置文件
步骤2:创建Spider文件
进入项目目录,创建名为cnblog_article_spider.py的Spider文件(存放于spiders/目录下):
import scrapy
from cnblog_spider.items import CnblogArticleItem # 后续会定义Item
class CnblogArticleSpider(scrapy.Spider):
# 1. 爬虫名称(唯一,运行爬虫时需指定)
name = "cnblog_article"
# 2. 允许爬取的域名(防止爬取到无关域名,提升安全性)
allowed_domains = ["cnblogs.com"]
# 3. 初始请求URL列表
start_urls = ["https://www.cnblogs.com/"]
# 4. 核心解析方法:处理下载器返回的Response,提取数据
def parse(self, response):
# 4.1 提取首页文章列表(使用XPath定位元素,Scrapy内置支持)
article_list = response.xpath('//div[@class="post-item"]')
for article in article_list:
# 4.2 提取单篇文章的标题、作者、链接
article_title = article.xpath('.//a[@class="post-item-title"]/text()').extract_first()
article_author = article.xpath('.//a[@class="post-item-author"]/text()').extract_first()
article_link = article.xpath('.//a[@class="post-item-title"]/@href').extract_first()
# 4.3 封装为Item对象(后续讲解Item组件)
item = CnblogArticleItem()
item["title"] = article_title
item["author"] = article_author
item["link"] = article_link
# 4.4 将Item传递给Pipeline处理(通过yield返回给引擎)
yield item
# 4.5 (可选)跟进文章详情页URL,发起新请求(递归爬取)
if article_link:
yield scrapy.Request(
url=article_link,
callback=self.parse_article_detail, # 详情页解析方法
meta={"item": item} # 传递已有Item数据到详情页
)
# 5. 详情页解析方法(处理新请求的响应)
def parse_article_detail(self, response):
# 提取详情页内容(如发布时间)
item = response.meta["item"]
publish_time = response.xpath('//span[@class="post-date"]/text()').extract_first()
item["publish_time"] = publish_time
# 将完善后的Item传递给Pipeline
yield item
3. Spider关键技巧
- 数据提取方法:支持XPath(推荐,万能高效)和CSS选择器,
extract_first()提取单个结果(避免返回列表),extract()提取所有结果; - 跟进新请求:使用
yield scrapy.Request()发起新请求,callback指定新请求的解析方法,meta传递跨请求数据; - 请求去重:Scrapy自动对URL进行去重(基于
dupefilter),无需手动处理,避免重复爬取; - 异常处理:对可能为空的字段添加默认值,避免
None值导致后续Pipeline报错,如article_title or "未知标题"。
三、Item组件:数据的“容器”,实现结构化与验证
1. 核心作用
Item是爬取数据的结构化容器,类似于Python的字典,但提供了字段定义、数据验证、格式规范的功能,避免爬取的数据格式混乱、字段缺失,为后续Pipeline处理提供统一的数据格式。
2. 实战:定义Item,规范爬取数据
编辑项目中的items.py文件,定义博客文章的Item字段:
import scrapy
from scrapy.item import Field, Item
from scrapy.loader.processors import MapCompose, TakeFirst
# 自定义数据清洗函数(用于Item字段验证)
def clean_title(title):
"""清洗文章标题:去除前后空格、特殊字符"""
if title:
return title.strip().replace("\n", "").replace("\t", "")
return "未知标题"
def clean_author(author):
"""清洗作者名称:去除前后空格"""
return author.strip() if author else "未知作者"
# 定义Item类(继承scrapy.Item)
class CnblogArticleItem(scrapy.Item):
# 定义字段:Field()中可配置数据处理管道
title = Field(
input_processor=MapCompose(clean_title), # 输入处理:调用自定义清洗函数
output_processor=TakeFirst() # 输出处理:提取第一个结果
)
author = Field(
input_processor=MapCompose(clean_author),
output_processor=TakeFirst()
)
link = Field(output_processor=TakeFirst())
publish_time = Field(output_processor=TakeFirst(), default="未知时间") # 默认值
3. Item关键技巧
- 字段定义:所有需要爬取的字段都需在
Item类中用Field()定义,未定义的字段无法传递到Pipeline; - 数据清洗:使用
MapCompose调用自定义清洗函数,实现数据的前置处理(去空格、特殊字符等); - 默认值设置:为可选字段设置
default值,避免后续Pipeline处理时出现KeyError; - Item Loader:复杂场景下可使用
Item Loader进一步简化数据提取和清洗,提升代码复用性。
四、Downloader组件:网页的“下载器”,高效获取响应
1. 核心作用
Downloader负责接收引擎传递的Request对象,下载网页内容、处理请求头/代理、控制下载并发/延迟,最终将下载结果封装为Response对象返回给Spider,是爬虫的“传输枢纽”。
2. 实战:配置Downloader,优化下载性能
Downloader的配置主要在项目的settings.py文件中进行,无需手动编写代码,核心配置如下:
# 1. 配置下载延迟(单位:秒):避免高频请求被反爬,默认0
DOWNLOAD_DELAY = 2
# 2. 配置并发请求数:全局最大并发(默认16),单域名最大并发(默认8)
CONCURRENT_REQUESTS = 16
CONCURRENT_REQUESTS_PER_DOMAIN = 8
# 3. 配置请求头:模拟浏览器请求,避免被反爬
USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36"
# 4. 配置超时时间(单位:秒):请求超时后放弃,默认180
DOWNLOAD_TIMEOUT = 30
# 5. 配置代理(可选,应对反爬)
# DOWNLOAD_PROXIES = {
# "http": "http://127.0.0.1:7890",
# "https": "https://127.0.0.1:7890"
# }
# 6. 禁用Cookies(可选,减少请求头体积,避免被追踪)
COOKIES_ENABLED = False
# 7. 配置下载中间件(可选,自定义下载逻辑,如添加动态请求头)
DOWNLOADER_MIDDLEWARES = {
"cnblog_spider.middlewares.CnblogSpiderDownloaderMiddleware": 543,
}
3. Downloader关键技巧
- 下载延迟优先配置:针对反爬严格的网站,
DOWNLOAD_DELAY设置为2~5秒,避免IP被封禁; - 请求头优化:除了
USER_AGENT,还可添加Referer、Accept等字段,更贴近真人浏览器请求; - 代理池集成:复杂场景下可集成代理池,通过下载中间件动态切换代理,应对IP封禁;
- 避免冗余下载:通过
allowed_domains限制爬取域名,避免Downloader下载无关网页,浪费资源。
五、Pipeline组件:数据的“处理工厂”,实现持久化与去重
1. 核心作用
Pipeline负责接收Spider传递的Item对象,实现数据的后续处理,包括数据去重、进一步清洗、持久化存储(文件、数据库等),支持多Pipeline协同工作,按优先级顺序执行。
2. 实战:定义Pipeline,实现数据去重与持久化
编辑项目中的pipelines.py文件,实现两个Pipeline:去重Pipeline和Excel存储Pipeline。
步骤1:定义去重Pipeline(基于文章标题去重)
class CnblogDuplicatePipeline:
"""数据去重Pipeline:去除重复的文章"""
def __init__(self):
# 初始化集合,用于存储已爬取的文章标题
self.article_titles = set()
def process_item(self, item, spider):
"""核心处理方法:必须实现,返回处理后的Item或丢弃Item"""
title = item.get("title")
# 去重逻辑:标题已存在则丢弃,否则保留并加入集合
if title in self.article_titles:
# 丢弃重复Item,不传递给后续Pipeline
raise scrapy.exceptions.DropItem(f"重复文章:{title}")
else:
self.article_titles.add(title)
# 返回Item,传递给下一个Pipeline
return item
步骤2:定义Excel存储Pipeline(将Item保存到Excel)
import pandas as pd
from openpyxl import load_workbook
class CnblogExcelPipeline:
"""Excel存储Pipeline:将Item保存到cnblog_articles.xlsx"""
def __init__(self):
# 初始化Excel文件路径和数据列表
self.excel_path = "cnblog_articles.xlsx"
self.item_list = []
def process_item(self, item, spider):
# 将Item转为字典,添加到数据列表
self.item_list.append(dict(item))
return item
def close_spider(self, spider):
"""爬虫关闭时执行:将数据写入Excel"""
# 转为DataFrame
df = pd.DataFrame(self.item_list)
# 写入Excel
try:
# 若文件已存在,追加写入(不覆盖)
book = load_workbook(self.excel_path)
with pd.ExcelWriter(self.excel_path, engine="openpyxl", mode="a", if_sheet_exists="overlay") as writer:
writer.book = book
# 获取已有数据行数,从下一行开始写入
startrow = writer.sheets["Sheet1"].max_row
df.to_excel(writer, sheet_name="Sheet1", startrow=startrow, index=False, header=False)
except FileNotFoundError:
# 若文件不存在,创建新文件并写入
df.to_excel(self.excel_path, sheet_name="Sheet1", index=False)
spider.logger.info(f"数据已成功写入Excel:{self.excel_path},共{len(self.item_list)}条数据")
步骤3:启用Pipeline,配置优先级
在settings.py文件中启用定义的Pipeline,并配置优先级(数字越小,优先级越高):
# 启用Pipeline,配置优先级
ITEM_PIPELINES = {
"cnblog_spider.pipelines.CnblogDuplicatePipeline": 300, # 去重Pipeline,优先级300
"cnblog_spider.pipelines.CnblogExcelPipeline": 400, # Excel存储Pipeline,优先级400
}
3. Pipeline关键技巧
- 核心方法:必须实现
process_item(self, item, spider)方法,返回item则传递给下一个Pipeline,抛出DropItem则丢弃该Item; - 优先级配置:
ITEM_PIPELINES中的数字越小,优先级越高,建议按“去重→清洗→存储”的顺序配置; - 生命周期方法:
open_spider(self, spider)(爬虫启动时执行,如初始化数据库连接)、close_spider(self, spider)(爬虫关闭时执行,如关闭数据库连接、写入最终数据); - 多存储支持:可扩展实现MySQL、MongoDB等数据库存储Pipeline,只需在
process_item中添加对应的数据库写入逻辑。
六、全流程实战:运行爬虫,验证四大组件协同工作
1. 运行爬虫
在终端进入项目根目录,执行以下命令运行Spider:
scrapy crawl cnblog_article
2. 验证结果
- 爬虫运行完成后,查看项目根目录下是否生成
cnblog_articles.xlsx文件; - 打开Excel文件,验证数据是否完整(标题、作者、链接、发布时间),是否存在重复数据;
- 查看终端日志,确认无报错,且显示“数据已成功写入Excel”的提示信息。
3. 常见问题排查
- Pipeline不执行:检查
settings.py中是否启用了Pipeline,且ITEM_PIPELINES配置正确; - 数据为空:检查Spider中的XPath/CSS选择器是否正确,可通过
response.xpath("xxx").extract()在终端调试; - Excel写入报错:安装必要依赖
pip install pandas openpyxl,确保Excel文件未被占用。
七、核心架构进阶技巧与避坑指南
1. 进阶技巧
- 中间件拓展:通过Spider中间件、Downloader中间件扩展功能(如动态设置代理、处理反爬验证码);
- 分布式爬取:结合
scrapy-redis实现分布式爬虫,共享请求队列和Item数据,提升爬取效率; - 数据增量爬取:在Pipeline中记录已爬取的URL或发布时间,下次爬取时只获取新增数据;
- 日志配置:在
settings.py中配置日志级别和存储路径,方便排查爬取过程中的问题。
2. 避坑指南
- Spider解析不到数据:
- 原因:XPath/CSS选择器错误、网页是动态渲染(AJAX)、请求被反爬;
- 解决:通过
scrapy shell https://www.cnblogs.com/在终端调试选择器、使用scrapy-splash处理动态网页、优化请求头和下载延迟。
- Item字段缺失:
- 原因:Spider中未给Item字段赋值、数据清洗函数返回
None; - 解决:检查Spider中的Item赋值逻辑、为Field设置默认值、优化清洗函数。
- 原因:Spider中未给Item字段赋值、数据清洗函数返回
- Pipeline去重失效:
- 原因:去重标识(如标题)不唯一、
__init__中的集合未正确初始化; - 解决:选择唯一标识(如文章链接)进行去重、检查去重逻辑是否正确。
- 原因:去重标识(如标题)不唯一、
- Downloader被反爬封禁:
- 原因:请求频率过高、请求头不规范、无代理;
- 解决:增大
DOWNLOAD_DELAY、配置多用户代理轮换、集成代理池。