Python Scrapy框架爬虫项目结构与核心代码实现解析
深入解析Python Scrapy爬虫框架的标准项目结构与核心代码实现,剖析spiders、items、pipelines与settings各层职责与协作逻辑。
一、Scrapy 标准项目目录结构与各模块职责
在终端执行 scrapy startproject <project_name> 后,Scrapy 会自动生成一套规范化的项目目录。理解各文件的分工是编写可维护爬虫的第一步。典型目录结构如下:
myproject/
├── scrapy.cfg # 项目部署配置文件(标记项目根目录)
└── myproject/ # 项目 Python 模块源码目录
├── __init__.py
├── items.py # 数据模型定义文件(定义提取目标字段)
├── middlewares.py # 中间件文件(下载中间件与爬虫中间件钩子)
├── pipelines.py # 管道文件(数据清洗、去重与持久化存储)
├── settings.py # 项目全局设置文件(并发、延迟、请求头与管道注册)
└── spiders/ # 爬虫逻辑主目录
├── __init__.py
└── quotes_spider.py # 具体的爬虫实现类
各模块的具体职责划分如下:
scrapy.cfg:项目的配置文件,通常位于最外层。它指定了项目的settings模块路径以及部署目标(如 Scrapyd 服务端)。当在任意子目录运行scrapy命令时,Scrapy 正是通过向上查找该文件来定位项目根目录的。items.py:声明结构化数据的数据契约。通过继承scrapy.Item并使用scrapy.Field()声明字段,为爬虫产出的散乱数据提供统一约束,防止因字典键名拼写错误导致数据丢失。spiders/目录:存放爬虫的核心代码。每个爬虫类需继承自scrapy.Spider(或其子类),定义初始抓取链接(start_urls)与解析回调方法(parse),负责将 HTML 响应转换为 Item 或后续抓取请求。pipelines.py:负责消费爬虫产出的 Item。当 Spider 通过yield item发送数据后,引擎会自动将 Item 顺次投递至已启用的 Pipeline。在此处执行字段清洗、去重验证或写入 MySQL、MongoDB、本地 JSON 文件等持久化操作。middlewares.py:提供拦截并修改 Request 和 Response 的钩子机制。主要包含DownloaderMiddleware(用于统一设置随机 User-Agent、挂载代理 IP、处理重试)与SpiderMiddleware(处理爬虫输出)。settings.py:爬虫运行的“总控开关”。在此配置文件中可调节最大并发请求数(CONCURRENT_REQUESTS)、下载延迟(DOWNLOAD_DELAY)、是否遵守 Robots 协议,以及为各个 Pipeline 和 Middleware 分配权重以激活执行。
二、核心代码实现:从字段建模到管道入库
下面以抓取经典公开练习站点 quotes.toscrape.com 的名言、作者与标签为例,演示一个完整的 Scrapy 核心代码实现链路。
1. 数据模型定义:items.py
在 items.py 中定义抓取数据的结构字段。使用 scrapy.Field() 占位,为整个项目确立统一的数据字段契约:
# myproject/items.py
import scrapy
class QuoteItem(scrapy.Item):
"""定义名言数据项的结构字段"""
text = scrapy.Field() # 名言正文内容
author = scrapy.Field() # 作者姓名
tags = scrapy.Field() # 标签列表(以逗号拼接或数组形式存储)
2. 爬虫解析逻辑:spiders/quotes_spider.py
在 spiders/ 目录下创建 quotes_spider.py。爬虫类必须指定唯一的 name,并在 parse 方法中使用 CSS 或 XPath 选择器提取页面元素。遇到下一页链接时,通过 response.follow 或 scrapy.Request 生成新的抓取任务:
# myproject/spiders/quotes_spider.py
import scrapy
from myproject.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("div.quote")
for block in quote_blocks:
item = QuoteItem()
# 提取名言文本,去除首尾符号
item["text"] = block.css("span.text::text").get(default="").strip("“”")
# 提取作者
item["author"] = block.css("small.author::text").get(default="").strip()
# 提取标签列表
item["tags"] = block.css("div.tags a.tag::text").getall()
# 将提取出的数据结构推送到引擎
yield item
# 查找下一页链接并生成新的爬取请求
next_page = response.css("li.next a::attr(href)").get()
if next_page is not None:
# response.follow 会自动处理相对路径并绑定 parse 回调
yield response.follow(next_page, callback=self.parse)
parse 回调中,绝不可写阻塞式的同步网络请求(如 time.sleep 或 requests.get)。Scrapy 运行在单线程的 Twisted 反应器(Reactor)上,任何阻塞操作都会直接卡死整个异步事件循环,导致其他并发任务全部挂起。如需控制爬取频率,应在 settings.py 中配置 DOWNLOAD_DELAY。
3. 数据清洗与存储:pipelines.py
在 pipelines.py 中编写管道类。每个管道需实现 process_item(item, spider) 方法。数据在管道中链式传递,必须在方法末尾返回 item,或者抛出 DropItem 异常中断废弃数据:
# myproject/pipelines.py
import json
from scrapy.exceptions import DropItem
class CleanQuotePipeline:
"""清洗管道:去除多余空白,过滤无效内容"""
def process_item(self, item, spider):
text = item.get("text", "")
if not text:
raise DropItem(f"发现空名言,丢弃该条数据: {item}")
# 将标签列表转换为逗号分隔的字符串,便于后续存储
item["tags"] = ", ".join(item.get("tags", []))
return item
class JsonWriterPipeline:
"""持久化管道:以 JSON Lines 格式写入本地文件"""
def open_spider(self, spider):
# 爬虫开启时打开文件句柄
self.file = open("quotes_cleaned.jsonl", "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) + "\n"
self.file.write(line)
return item
4. 运行策略配置:settings.py
编写完 Pipeline 后,必须在 settings.py 中显式激活。数字键代表优先级(范围 1~1000,数值越小越先执行):
# myproject/settings.py
BOT_NAME = "myproject"
SPIDER_MODULES = ["myproject.spiders"]
NEWSPIDER_MODULE = "myproject.spiders"
# 尊重目标站点的 robots.txt 规则
ROBOTSTXT_OBEY = True
# 设置下载延迟与并发,减轻目标服务器压力
DOWNLOAD_DELAY = 1.0
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",
"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36",
}
# 激活数据持久化管道及其处理顺序
ITEM_PIPELINES = {
"myproject.pipelines.CleanQuotePipeline": 300, # 先清洗
"myproject.pipelines.JsonWriterPipeline": 400, # 后入库
}
三、Scrapy 爬虫项目的构建、运行与调试步骤
完成上述代码后,项目的标准执行流程涵盖环境准备、爬虫调用与产物核对四个环节:
步骤 1:创建项目与生成模版
在目标工作目录打开终端,依次执行以下命令:
# 1. 创建项目骨架
scrapy startproject quotes_spider
# 2. 进入项目主目录
cd quotes_spider
# 3. 使用 genspider 快速生成爬虫模版文件
scrapy genspider quotes quotes.toscrape.com
步骤 2:启动爬虫并验证输出
运行 quotes 爬虫并查看实时执行日志:
scrapy crawl quotes
控制台将输出 Scrapy 引擎初始化的核心统计日志(示例输出):
2026-11-01 16:30:10 [scrapy.utils.log] INFO: Scrapy 2.11.0 started (bot: myproject)
2026-11-01 16:30:10 [scrapy.core.engine] INFO: Spider opened
2026-11-01 16:30:11 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://quotes.toscrape.com/> (referer: None)
2026-11-01 16:30:12 [scrapy.core.engine] DEBUG: Crawled (200) <GET https://quotes.toscrape.com/page/2/>
...
2026-11-01 16:30:22 [scrapy.statscollectors] INFO: Dumping Scrapy stats:
'item_scraped_count': 100,
'response_received_count': 10,
'downloader/request_count': 10,
2026-11-01 16:30:22 [scrapy.core.engine] INFO: Spider closed (finished)
执行完成后,检查项目根目录下的 quotes_cleaned.jsonl,每行为一条清洗后的完整 JSON 记录:
{"text": "The world as we have created it is a process of our thinking.", "author": "Albert Einstein", "tags": "change, deep-thoughts, thinking"}
{"text": "It is our choices, Harry, that show what we truly are, far more than our abilities.", "author": "J.K. Rowling", "tags": "abilities, choices"}
四、关键差异对比:单文件脚本 vs Scrapy 工程化多层架构
在完成相同的数据采集需求时,开发者常在“单文件脚本”(如 requests + BeautifulSoup 编写的单一 .py 脚本)与“Scrapy 工程化架构”之间选择。二者的核心差异并非仅是代码行数的多少,而在于架构设计对系统维护性与扩展性的深层影响。
| 对比维度 | 传统单文件脚本模式 | Scrapy 标准工程化架构 |
|---|---|---|
| 并发机制 | 同步阻塞循环;若需并发需手动引入 threading 或 multiprocessing,存在死锁与线程安全问题。 |
内置 Twisted 异步 Reactor,单线程非阻塞高并发,自动管理事件循环与连接池。 |
| 模块耦合度 | 网络请求、HTML 解析、数据清洗、文件写入强混杂在单一循环体内,牵一发而动全身。 | 职责严格解耦:Spider 仅负责提取,Pipeline 负责清洗入库,Downloader 负责调度。 |
| 去重与调度 | 需开发者手动维护 URL 集合或 Redis 键,易发生重复爬取或遗漏。 | Scheduler 自动对 Request 计算 SHA1 指纹进行去重,支持队列深度/广度优先切换。 |
| 异常容错能力 | 某次请求异常或单条数据缺失可能导致整段脚本崩溃,需层层包裹 try...except。 |
内置多级容错与重试中间件,单条 Item 异常被 DropItem 拦截,不影响其余任务。 |
| 维护与复用 | 新增爬虫时往往复制粘贴整套请求与入库逻辑,逻辑重复率高。 | 共享 Pipelines、Middlewares 与 Settings,新增数据源只需编写独立的 Spider 类。 |
为什么严禁在 Spider 内部直接写入数据库?
初学者容易在 spiders/quotes_spider.py 的 parse 方法中直接编写 pymysql.connect() 或写入本地文件。这种做法破坏了 Scrapy 的核心设计理念:
- 破坏并发性能:数据库写入或磁盘 I/O 通常是同步阻塞操作。如果在
parse回调中阻塞数毫秒,Twisted 反应器就无法及时响应其他并发请求的网络响应,吞吐量将急剧下跌。 - 阻碍多目标持久化:若将来需要将数据同时输出到 Elasticsearch、Kafka 或进行质量监控,直接写死在 Spider 内部会导致逻辑膨胀失控;而通过配置
ITEM_PIPELINES,只需追加一个独立 Pipeline 即可无侵入式接入。
五、核心要点总结与开发建议
掌握 Scrapy 框架的核心,在于牢记各个组件在数据生命周期中的位置与契约:
- 以数据为核心推进:先在
items.py中确立数据契约,再进入spiders/编写提取规则,最后由pipelines.py实施持久化。 - 善用 yield 机制:Spider 是一个生成器,通过
yield scrapy.Request实现链式翻页与子页面下钻,通过yield item将成果交付管道,保持方法纯粹。 - 全局参数精细化治理:爬虫的礼貌性(下载延迟、并发数)、安全性(User-Agent 轮换、代理中间件)与存储管道的启用顺序,均集中在
settings.py中收敛配置,避免硬编码。
免责声明:本文内容仅供参考,不代表本站观点。转载或引用请注明出处。









