你想掌握Scrapy框架的数据存储方法,核心是围绕Item数据模型和**Pipeline(数据管道)**展开------Scrapy将爬取到的数据先封装到Item中,再通过Pipeline统一处理存储,支持本地文件、关系型数据库、非关系型数据库等多种存储方式,以下是详细实操教程,无冗余理论,可直接复刻运行。
一、前置基础:数据存储的核心流程
- 定义
Item:在items.py中声明需要存储的字段,规范数据格式(避免杂乱数据); - 解析数据封装到
Item:在爬虫文件中,将解析到的数据存入自定义Item对象并yield; - 启用
Pipeline:在settings.py中配置启用对应的管道(设置优先级,数字越小优先级越高); - 实现存储逻辑:在
pipelines.py中编写具体的存储代码(文件/数据库等); - 运行爬虫:自动触发Pipeline,完成数据存储。
必备前置准备(快速搭建测试环境)
- 已有Scrapy项目(若无,执行
scrapy startproject data_storage_spider创建); - 简单爬虫示例(用于生成测试数据,后续所有存储方式均基于此):
- 爬虫文件
spiders/test_spider.py:
python
import scrapy
from data_storage_spider.items import DataStorageSpiderItem
class TestSpider(scrapy.Spider):
name = "test_spider"
# 测试用静态页面,无需处理动态渲染
start_urls = ["https://quotes.toscrape.com/"]
def parse(self, response):
# 解析名言数据
quote_items = response.xpath("//div[@class='quote']")
for item in quote_items:
# 实例化自定义Item
quote_data = DataStorageSpiderItem()
# 封装数据(字段与items.py中定义一致)
quote_data['content'] = item.xpath(".//span[@class='text']/text()").extract_first() or ""
quote_data['author'] = item.xpath(".//small[@class='author']/text()").extract_first() or ""
quote_data['tags'] = ",".join(item.xpath(".//div[@class='tags']/a/text()").extract()) or ""
# 提交数据到Pipeline(核心:yield Item对象)
yield quote_data
- 定义Item
items.py:
python
import scrapy
class DataStorageSpiderItem(scrapy.Item):
# 定义需要存储的字段,scrapy.Field()仅用于标记,无额外逻辑
content = scrapy.Field() # 名言内容
author = scrapy.Field() # 作者
tags = scrapy.Field() # 标签(用逗号拼接为字符串,方便存储)
二、方式1:本地文件存储(CSV/JSON/JSON Lines,最常用)
本地文件存储无需额外安装依赖,适合小规模数据、快速验证结果,Scrapy提供两种实现方式:内置命令直接导出 (快速便捷)、Pipeline自定义实现(灵活可控,支持复杂逻辑)。
1. 快速实现:Scrapy内置命令导出(无需编写Pipeline)
直接在终端运行爬虫时,通过-o参数指定文件路径和格式,自动生成文件,支持csv、json、jl(JSON Lines)三种常用格式。
(1)导出为CSV文件(推荐,易用于Excel分析)
bash
# 进入项目目录后执行
scrapy crawl test_spider -o quotes.csv -s FEED_EXPORT_ENCODING=utf_8_sig
- 关键参数说明:
-o quotes.csv:指定导出为CSV文件,文件名quotes.csv;-s FEED_EXPORT_ENCODING=utf_8_sig:解决中文乱码问题(核心参数,必加);
- 结果:项目根目录生成
quotes.csv,可用Excel直接打开,数据格式规整。
(2)导出为JSON文件
bash
scrapy crawl test_spider -o quotes.json -s FEED_EXPORT_ENCODING=utf_8_sig
- 特点:数据以数组格式存储,适合后续JSON解析处理,缺点是大文件加载耗时久。
(3)导出为JSON Lines文件(推荐,大文件友好)
bash
scrapy crawl test_spider -o quotes.jl -s FEED_EXPORT_ENCODING=utf_8_sig
- 特点:每行一条JSON数据,无需加载整个文件即可解析,适合大规模本地数据存储,兼容性更强。
2. 灵活实现:Pipeline自定义文件存储(支持复杂逻辑)
内置命令无法满足自定义需求(如:按日期拆分文件、过滤无效数据、追加写入),此时需通过Pipeline实现,以CSV文件为例(JSON格式同理)。
(1)编写Pipeline存储逻辑(pipelines.py)
python
import csv
import os
class CsvFileStoragePipeline:
"""自定义CSV文件存储管道"""
def __init__(self):
# 1. 初始化文件对象和CSV写入器
self.file = None
self.writer = None
# CSV文件路径和表头
self.csv_path = "custom_quotes.csv"
self.fieldnames = ["content", "author", "tags"]
def open_spider(self, spider):
"""爬虫启动时执行(仅执行一次),用于打开文件、初始化写入器"""
# 追加写入模式(a):避免覆盖已有数据;newline="":避免CSV出现空行
self.file = open(self.csv_path, "a", newline="", encoding="utf_8_sig")
# 检查文件是否为空,为空则写入表头
if os.path.getsize(self.csv_path) == 0:
self.writer = csv.DictWriter(self.file, fieldnames=self.fieldnames)
self.writer.writeheader()
else:
self.writer = csv.DictWriter(self.file, fieldnames=self.fieldnames)
def process_item(self, item, spider):
"""核心:处理每个Item数据(爬虫提交一个Item,执行一次)"""
# 过滤无效数据(自定义逻辑:排除空内容的记录)
if not item.get("content") or item.get("content").strip() == "":
spider.logger.warning("无效数据:内容为空,跳过存储")
return item
# 将Item转换为字典,写入CSV文件
self.writer.writerow(dict(item))
spider.logger.info(f"数据已写入CSV:{item.get('author')} - {item.get('content')[:20]}...")
return item
def close_spider(self, spider):
"""爬虫关闭时执行(仅执行一次),用于关闭文件,释放资源"""
if self.file:
self.file.close()
spider.logger.info(f"CSV文件存储完成,文件路径:{self.csv_path}")
(2)启用Pipeline(settings.py)
找到ITEM_PIPELINES配置,取消注释并添加自定义管道(优先级300,可根据需求调整):
python
ITEM_PIPELINES = {
'data_storage_spider.pipelines.CsvFileStoragePipeline': 300,
}
(3)运行爬虫验证结果
bash
scrapy crawl test_spider
- 结果:项目根目录生成
custom_quotes.csv,无中文乱码,已过滤空内容数据,支持追加写入(重复运行爬虫,数据不会覆盖,而是新增到文件末尾)。
三、方式2:关系型数据库存储(以MySQL为例,生产环境常用)
适合大规模结构化数据存储,支持数据查询、更新、关联分析,核心依赖pymysql库,步骤如下:
1. 安装依赖库
bash
pip install pymysql
2. 提前创建MySQL数据库和表
- 创建数据库
scrapy_data(字符集utf8mb4,支持所有中文和特殊字符); - 创建表
quotes(字段与Item对应,结构如下):
sql
CREATE DATABASE IF NOT EXISTS scrapy_data DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE scrapy_data;
CREATE TABLE IF NOT EXISTS quotes (
id INT PRIMARY KEY AUTO_INCREMENT COMMENT '自增主键',
content TEXT NOT NULL COMMENT '名言内容',
author VARCHAR(100) NOT NULL COMMENT '作者',
tags VARCHAR(255) DEFAULT '' COMMENT '标签',
create_time DATETIME DEFAULT CURRENT_TIMESTAMP COMMENT '存储时间'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT '名言数据表';
3. 编写MySQL存储Pipeline(pipelines.py)
python
import pymysql
class MysqlStoragePipeline:
"""MySQL数据库存储管道"""
def __init__(self):
# 数据库连接配置(根据自身环境修改)
self.host = "localhost"
self.port = 3306
self.user = "root"
self.password = "你的MySQL密码"
self.db_name = "scrapy_data"
self.charset = "utf8mb4"
# 初始化连接和游标对象
self.conn = None
self.cursor = None
def open_spider(self, spider):
"""爬虫启动时,建立MySQL连接"""
try:
self.conn = pymysql.connect(
host=self.host,
port=self.port,
user=self.user,
password=self.password,
db=self.db_name,
charset=self.charset
)
self.cursor = self.conn.cursor()
spider.logger.info("MySQL数据库连接成功")
except Exception as e:
spider.logger.error(f"MySQL数据库连接失败:{e}")
raise e
def process_item(self, item, spider):
"""核心:将Item数据插入MySQL数据库"""
if not item.get("content"):
return item
# 1. 定义SQL插入语句(使用占位符%s,避免SQL注入)
insert_sql = """
INSERT INTO quotes (content, author, tags)
VALUES (%s, %s, %s)
"""
# 2. 提取Item数据,组装为参数元组(与SQL占位符顺序对应)
item_data = (
item.get("content"),
item.get("author"),
item.get("tags")
)
try:
# 3. 执行SQL语句
self.cursor.execute(insert_sql, item_data)
# 4. 提交事务(关键:不提交则数据不会写入数据库)
self.conn.commit()
spider.logger.info(f"数据已插入MySQL:{item.get('author')}")
except Exception as e:
# 5. 出错时回滚事务,避免数据混乱
self.conn.rollback()
spider.logger.error(f"数据插入MySQL失败:{e},数据:{item_data}")
return item
def close_spider(self, spider):
"""爬虫关闭时,关闭游标和连接"""
if self.cursor:
self.cursor.close()
if self.conn:
self.conn.close()
spider.logger.info("MySQL数据库连接已关闭")
4. 启用MySQL Pipeline(settings.py)
python
ITEM_PIPELINES = {
# 可同时启用多个Pipeline,分别存储为CSV和MySQL(优先级300<400,CSV先执行)
'data_storage_spider.pipelines.CsvFileStoragePipeline': 300,
'data_storage_spider.pipelines.MysqlStoragePipeline': 400,
}
5. 运行爬虫验证结果
bash
scrapy crawl test_spider
- 验证:登录MySQL,查询
scrapy_data.quotes表,可看到已成功插入数据,无乱码,字段对应完整。
四、方式3:非关系型数据库存储(以MongoDB为例,灵活存储非结构化数据)
适合存储非结构化/半结构化数据(如字段不固定的爬取数据),无需提前定义表结构,核心依赖pymongo库,步骤如下:
1. 安装依赖库
bash
pip install pymongo
2. 提前启动MongoDB服务
- 本地MongoDB:启动
mongod服务(默认端口27017,无需手动创建数据库和集合,插入数据时自动创建); - 远程MongoDB:准备好连接地址、用户名、密码。
3. 编写MongoDB存储Pipeline(pipelines.py)
python
import pymongo
class MongodbStoragePipeline:
"""MongoDB数据库存储管道"""
def __init__(self):
# MongoDB连接配置(本地无密码,远程需添加username、password、authSource)
self.host = "localhost"
self.port = 27017
self.db_name = "scrapy_data"
self.collection_name = "quotes" # 集合(类似MySQL的表)
# 初始化数据库连接和集合对象
self.client = None
self.db = None
self.collection = None
def open_spider(self, spider):
"""爬虫启动时,建立MongoDB连接"""
try:
# 建立连接(本地无密码)
self.client = pymongo.MongoClient(host=self.host, port=self.port)
# 选择数据库(不存在则自动创建)
self.db = self.client[self.db_name]
# 选择集合(不存在则自动创建)
self.collection = self.db[self.collection_name]
spider.logger.info("MongoDB数据库连接成功")
except Exception as e:
spider.logger.error(f"MongoDB数据库连接失败:{e}")
raise e
def process_item(self, item, spider):
"""核心:将Item数据插入MongoDB(转换为字典即可)"""
if not item.get("content"):
return item
# 1. 将Item转换为字典(Scrapy Item对象可直接强转为dict)
item_dict = dict(item)
try:
# 2. 插入数据(insert_one:插入单条数据;insert_many:插入多条数据)
self.collection.insert_one(item_dict)
spider.logger.info(f"数据已插入MongoDB:{item_dict.get('author')}")
except Exception as e:
spider.logger.error(f"数据插入MongoDB失败:{e},数据:{item_dict}")
return item
def close_spider(self, spider):
"""爬虫关闭时,关闭MongoDB连接"""
if self.client:
self.client.close()
spider.logger.info("MongoDB数据库连接已关闭")
4. 启用MongoDB Pipeline(settings.py)
python
ITEM_PIPELINES = {
'data_storage_spider.pipelines.CsvFileStoragePipeline': 300,
'data_storage_spider.pipelines.MysqlStoragePipeline': 400,
'data_storage_spider.pipelines.MongodbStoragePipeline': 500,
}
5. 运行爬虫验证结果
bash
scrapy crawl test_spider
- 验证:使用
mongo终端连接,执行use scrapy_data; db.quotes.find().pretty();,可看到已成功插入数据,字段完整。
五、核心注意事项与实操技巧
- Pipeline优先级 :
ITEM_PIPELINES中的数字(1-1000)越小,优先级越高,先执行的Pipeline处理后的Item会传递给后续Pipeline; - 数据去重 :
- MySQL:可给
content字段添加唯一索引(ALTER TABLE quotes ADD UNIQUE INDEX uk_content (content(255));),避免重复插入; - MongoDB:使用
update_one(),设置upsert=True(存在则更新,不存在则插入),基于唯一字段去重;
- MySQL:可给
- 异常处理与事务 :
- 数据库操作必须添加
try-except,避免单个数据插入失败导致整个爬虫崩溃; - MySQL需手动提交事务(
conn.commit()),出错时回滚(conn.rollback());MongoDB无需手动提交,默认自动写入;
- 数据库操作必须添加
- 中文乱码 :
- 本地文件:必须使用
encoding="utf_8_sig"(CSV/JSON); - MySQL:数据库、表、字段均使用
utf8mb4字符集; - MongoDB:默认支持UTF-8,无需额外配置;
- 本地文件:必须使用
- 资源释放 :
open_spider()建立连接,close_spider()关闭连接,避免资源泄露(尤其长期运行的分布式爬虫); - 大规模数据优化 :
- 本地文件:避免一次性写入大文件,可按批次拆分;
- 数据库:使用批量插入(MySQL
executemany()、MongoDBinsert_many()),减少数据库交互次数,提升效率。