🔥本期内容已收录至专栏《Python爬虫实战》,持续完善知识体系与项目实战,建议先订阅收藏,后续查阅更方便~持续更新中!!

全文目录:
-
-
- [🌟 开篇语](#🌟 开篇语)
- [📌 上期回顾](#📌 上期回顾)
- [🎯 本节目标](#🎯 本节目标)
- 一、三种存储方案对比
-
- [1.1 快速对比表](#1.1 快速对比表)
- [1.2 选择决策树](#1.2 选择决策树)
- [二、CSV 存储:简单高效](#二、CSV 存储:简单高效)
-
- [2.1 基础写入](#2.1 基础写入)
- [2.2 使用 pandas(推荐)](#2.2 使用 pandas(推荐))
- [2.3 增量追加](#2.3 增量追加)
- [2.4 处理复杂字段](#2.4 处理复杂字段)
- [三、JSON 存储:灵活通用](#三、JSON 存储:灵活通用)
-
- [3.1 标准 JSON 格式](#3.1 标准 JSON 格式)
- [3.2 JSON Lines 格式(推荐用于大数据)](#3.2 JSON Lines 格式(推荐用于大数据))
- [3.3 压缩存储(节省空间)](#3.3 压缩存储(节省空间))
- [四、SQLite 存储:结构化查询](#四、SQLite 存储:结构化查询)
-
- [4.1 为什么选择 SQLite?](#4.1 为什么选择 SQLite?)
- [4.2 创建数据库和表](#4.2 创建数据库和表)
- [4.3 使用 pandas 操作 SQLite](#4.3 使用 pandas 操作 SQLite)
- 五、存储方案选择指南
-
- [5.1 根据数据量选择](#5.1 根据数据量选择)
- [5.2 多格式保存(保险策略)](#5.2 多格式保存(保险策略))
- 六、性能对比测试
- 七、最佳实践建议
-
- [7.1 存储策略组合](#7.1 存储策略组合)
- [7.2 数据备份策略](#7.2 数据备份策略)
- 八、本节小结
- 九、课后作业(必做,验收进入第四章)
- [🔮 下期预告](#🔮 下期预告)
- [🌟 文末](#🌟 文末)
-
- [📌 专栏持续更新中|建议收藏 + 订阅](#📌 专栏持续更新中|建议收藏 + 订阅)
- [✅ 互动征集](#✅ 互动征集)
-
🌟 开篇语
哈喽,各位小伙伴们你们好呀~我是【喵手】。
运营社区: C站 / 掘金 / 腾讯云 / 阿里云 / 华为云 / 51CTO
欢迎大家常来逛逛,一起学习,一起进步~🌟
我长期专注 Python 爬虫工程化实战 ,主理专栏 👉 《Python爬虫实战》:从采集策略 到反爬对抗 ,从数据清洗 到分布式调度 ,持续输出可复用的方法论与可落地案例。内容主打一个"能跑、能用、能扩展 ",让数据价值真正做到------抓得到、洗得净、用得上。
📌 专栏食用指南(建议收藏)
- ✅ 入门基础:环境搭建 / 请求与解析 / 数据落库
- ✅ 进阶提升:登录鉴权 / 动态渲染 / 反爬对抗
- ✅ 工程实战:异步并发 / 分布式调度 / 监控与容错
- ✅ 项目落地:数据治理 / 可视化分析 / 场景化应用
📣 专栏推广时间 :如果你想系统学爬虫,而不是碎片化东拼西凑,欢迎订阅/关注专栏《Python爬虫实战》
订阅后更新会优先推送,按目录学习更高效~
📌 上期回顾
在上一节《列表页→详情页:两段式采集(90%项目都这样)》中,我们完成了两段式采集的完整流程:从列表页提取 URL,再逐个采集详情页。你已经能够获取结构化的数据了。
但数据采集完成后,如何高效地存储和管理?这是爬虫工程化的最后一公里!📦
这一节,我们将学习三种最常用的数据存储方案:CSV(轻量简单)、JSON(灵活通用)、SQLite(结构化查询)。选对存储方案,能让后续的数据分析事半功倍!
🎯 本节目标
通过本节学习,你将能够:
- 理解三种存储方案的优缺点和适用场景
- 使用 pandas 高效读写 CSV 文件
- 掌握 JSON Lines 格式(适合增量追加)
- 使用 SQLite 存储和查询数据
- 设计存储方案的决策树
- 交付验收:为采集器添加多种存储方式,并对比性能
一、三种存储方案对比
1.1 快速对比表
| 特性 | CSV | JSON | SQLite |
|---|---|---|---|
| 学习曲线 | ⭐ 简单 | ⭐⭐ 简单 | ⭐⭐⭐ 中等 |
| 数据结构 | 表格(二维) | 树形(嵌套) | 关系型(多表) |
| 可读性 | ✅ Excel打开 | ✅ 文本编辑器 | ❌ 需工具查看 |
| 查询能力 | ❌ 需加载全部 | ❌ 需加载全部 | ✅ SQL查询 |
| 增量追加 | ⚠️ 需重写 | ✅ 逐行追加 | ✅ INSERT |
| 大数据 | ⚠️ 内存限制 | ⚠️ 内存限制 | ✅ 磁盘操作 |
| 字段类型 | ❌ 都是文本 | ✅ 支持类型 | ✅ 强类型 |
| 适用场景 | 表格数据分析 | 配置、日志 | 数据库应用 |
1.2 选择决策树
json
你的数据特点是什么?
│
├─ 简单表格(每行结构相同)
│ └─ 需要用Excel打开?
│ ├─ 是 → CSV
│ └─ 否 → 需要查询?
│ ├─ 是 → SQLite
│ └─ 否 → CSV
│
├─ 嵌套结构(数组、对象)
│ └─ 数据量大(>10万条)?
│ ├─ 是 → SQLite(JSON字段)
│ └─ 否 → JSON
│
└─ 需要关联查询(多表JOIN)
└─ SQLite
二、CSV 存储:简单高效
2.1 基础写入
python
import csv
from datetime import datetime
def save_to_csv(data_list, filename):
"""
保存数据到 CSV
Args:
data_list: 字典列表
filename: 文件名
"""
if not data_list:
print("跳过保存")
return
# 提取字段名(从第一条数据)
fieldnames = data_list[0].keys()
with open(filename, 'w', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
# 写入表头
writer.writeheader()
# 写入数据
writer.writerows(data_list)
print(f"✅ CSV 已保存: {filename}")
print(f"📊 共 {len(data_list)} 条记录")
# 使用示例
data = [
{'title': '新闻A', 'author': '张三', 'views': 1000},
{'title': '新闻B', 'author': '李四', 'views': 2000},
]
save_to_csv(data, 'news.csv')
2.2 使用 pandas(推荐)
python
import pandas as pd
def save_to_csv_pandas(data_list, filename):
"""
使用 pandas 保存 CSV
优势:
1. 自动处理缺失字段
2. 支持数据类型推断
3. 性能更好
"""
if not data_list:
print("⚠️ 数据为空")
return
# 转为 DataFrame
df = pd.DataFrame(data_list)
# 保存(utf-8-sig 让 Excel 不乱码)
df.to_csv(filename, index=False, encoding='utf-8-sig')
print(f"✅ CSV 已保存: {filename}")
print(f"📊 形状: {df.shape} (行×列)")
print(f"\n字段: {', '.join(df.columns)}")
# 使用
save_to_csv_pandas(data, 'news_pandas.csv')
2.3 增量追加
python
def append_to_csv(data_list, filename):
"""
追加数据到已存在的 CSV
注意:确保字段顺序一致
"""
import os
# 检查文件是否存在
file_exists = os.path.exists(filename)
if not data_list:
return
fieldnames = data_list[0].keys()
with open(filename, 'a', newline='', encoding='utf-8-sig') as f:
writer = csv.DictWriter(f, fieldnames=fieldnames)
# 如果是新文件,写入表头
if not file_exists:
writer.writeheader()
# 追加数据
writer.writerows(data_list)
print(f"✅ 已追加 {len(data_list)} 条记录到 {filename}")
# pandas 方式
def append_to_csv_pandas(data_list, filename):
"""pandas 追加(更可靠)"""
new_df = pd.DataFrame(data_list)
if os.path.exists(filename):
# 读取现有数据
existing_df = pd.read_csv(filename, encoding='utf-8-sig')
# 合并
combined_df = pd.concat([existing_df, new_df], ignore_index=True)
else:
combined_df = new_df
# 保存
combined_df.to_csv(filename, index=False, encoding='utf-8-sig')
print(f"✅ 已保存 {len(combined_df)} 条记录")
2.4 处理复杂字段
python
def prepare_for_csv(data):
"""
预处理数据以适配 CSV
处理:
1. 数组 → 逗号分隔字符串
2. 嵌套对象 → JSON 字符串
3. None → 空字符串
"""
import json
processed = {}
for key, value in data.items():
if value is None:
processed[key] = ''
elif isinstance(value, list):
# 数组转字符串
if all(isinstance(x, str) for x in value):
processed[key] = ', '.join(value)
else:
processed[key] = json.dumps(value, ensure_ascii=False)
elif isinstance(value, dict):
# 字典转 JSON
processed[key] = json.dumps(value, ensure_ascii=False)
else:
processed[key] = value
return processed
# 使用
data = {
'title': '新闻标题',
'tags': ['科技', '互联网'], # 数组
'meta': {'author': '张三', 'dept': '编辑部'}, # 嵌套对象
}
csv_ready = prepare_for_csv(data)
print(csv_ready)
# {'title': '新闻标题', 'tags': '科技, 互联网', 'meta': '{"author": "张三", ...}'}
三、JSON 存储:灵活通用
3.1 标准 JSON 格式
python
import json
def save_to_json(data_list, filename, indent=2):
"""
保存为标准 JSON 文件
Args:
data_list: 数据列表
filename: 文件名
indent: 缩进空格数(None=压缩,2=易读)
"""
with open(filename, 'w', encoding='utf-8') as f:
json.dump(data_list, f, ensure_ascii=False, indent=indent)
print(f"✅ JSON 已保存: {filename}")
print(f"📊 共 {len(data_list)} 条记录")
# 使用
save_to_json(data, 'news.json', indent=2)
生成的文件格式:
json
[
{
"title": "新闻A",
"author": "张三",
"tags": ["科技", "互联网"]
},
{
"title": "新闻B",
"author": "李四",
"tags": ["财经"]
}
]
3.2 JSON Lines 格式(推荐用于大数据)
什么是 JSON Lines?
每行一个 JSON 对象,适合:
- ✅ 流式处理(逐行读取)
- ✅ 增量追加(直接写入)
- ✅ 大文件处理(不需要全部加载)
python
def save_to_jsonl(data_list, filename):
"""
保存为 JSON Lines 格式
格式:每行一个 JSON 对象
"""
with open(filename, 'w', encoding='utf-8') as f:
for item in data_list:
json_line = json.dumps(item, ensure_ascii=False)
f.write(json_line + '\n')
print(f"✅ JSONL 已保存: {filename}")
def append_to_jsonl(data_list, filename):
"""追加到 JSON Lines 文件"""
with open(filename, 'a', encoding='utf-8') as f:
for item in data_list:
json_line = json.dumps(item, ensure_ascii=False)
f.write(json_line + '\n')
print(f"✅ 已追加 {len(data_list)} 条记录")
def read_jsonl(filename):
"""读取 JSON Lines 文件(逐行)"""
data = []
with open(filename, 'r', encoding='utf-8') as f:
for line in f:
data.append(json.loads(line))
return data
# 使用
save_to_jsonl(data, 'news.jsonl')
生成的文件格式:
json
{"title": "新闻A", "author": "张三", "tags": ["科技", "互联网"]}
{"title": "新闻B", "author": "李四", "tags": ["财经"]}
3.3 压缩存储(节省空间)
python
import gzip
def save_to_json_gz(data_list, filename):
"""保存为压缩的 JSON(节省 70-90% 空间)"""
json_str = json.dumps(data_list, ensure_ascii=False)
with gzip.open(filename, 'wt', encoding='utf-8') as f:
f.write(json_str)
# 对比文件大小
import os
normal_size = len(json_str.encode())
compressed_size = os.path.getsize(filename)
ratio = (1 - compressed_size / normal_size) * 100
print(f"✅ 压缩 JSON 已保存: {filename}")
print(f"📊 压缩率: {ratio:.1f}% ({normal_size} → {compressed_size} 字节)")
def read_json_gz(filename):
"""读取压缩的 JSON"""
with gzip.open(filename, 'rt', encoding='utf-8') as f:
return json.load(f)
# 使用
save_to_json_gz(data, 'news.json.gz')
四、SQLite 存储:结构化查询
4.1 为什么选择 SQLite?
优势:
- ✅ 零配置(Python 内置)
- ✅ 支持 SQL 查询(WHERE、JOIN、GROUP BY)
- ✅ 事务支持(ACID)
- ✅ 适合中小型数据(<100GB)
适用场景:
- 需要按条件查询数据
- 需要更新/删除记录
- 多表关联查询
- 数据分析和统计
4.2 创建数据库和表
python
import sqlite3
from datetime import datetime
class NewsDatabase:
"""新闻数据库管理器"""
def __init__(self, db_file='news.db'):
"""初始化数据库连接"""
self.db_file = db_file
self.conn = sqlite3.connect(db_file)
self.cursor = self.conn.cursor()
self._create_tables()
def _create_tables(self):
"""创建表结构"""
# 新闻表
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS news (
id INTEGER PRIMARY KEY AUTOINCREMENT,
url TEXT UNIQUE NOT NULL,
title TEXT NOT NULL,
author TEXT,
publish_time TEXT,
content TEXT,
views INTEGER DEFAULT 0,
crawl_time TEXT,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
)
''')
# 标签表(多对多关系示例)
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS tags (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT UNIQUE NOT NULL
)
''')
self.cursor.execute('''
CREATE TABLE IF NOT EXISTS news_tags (
news_id INTEGER,
tag_id INTEGER,
FOREIGN KEY (news_id) REFERENCES news(id),
FOREIGN KEY (tag_id) REFERENCES tags(id),
PRIMARY KEY (news_id, tag_id)
)
''')
# 创建索引(提升查询性能)
self.cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_news_url
ON news(url)
''')
self.cursor.execute('''
CREATE INDEX IF NOT EXISTS idx_news_publish_time
ON news(publish_time)
''')
self.conn.commit()
print(f"✅ 数据库已初始化: {self.db_file}")
def insert_news(self, news_data):
"""
插入新闻数据
Args:
news_data: 新闻字典
Returns:
int: 插入的记录 ID,失败返回 None
"""
try:
self.cursor.execute('''
INSERT INTO news (url, title, author, publish_time,
content, views, crawl_time)
VALUES (?, ?, ?, ?, ?, ?, ?)
''', (
news_data.get('url'),
news_data.get('title'),
news_data.get('author'),
news_data.get('publish_time'),
news_data.get('content'),
news_data.get('views', 0),
news_data.get('crawl_time', datetime.now().isoformat())
))
self.conn.commit()
return self.cursor.lastrowid
except sqlite3.IntegrityError as e:
print(f"⚠️ 重复 URL,跳过: {news_data.get('url')}")
return None
except Exception as e:
print(f"❌ 插入失败: {e}")
return None
def batch_insert(self, news_list):
"""
批量插入(性能更好)
Args:
news_list: 新闻列表
Returns:
dict: 统计信息
"""
success = 0
failed = 0
duplicates = 0
for news in news_list:
news_id = self.insert_news(news)
if news_id:
success += 1
# 处理标签
if 'tags' in news and news['tags']:
self._insert_tags(news_id, news['tags'])
elif news_id is None:
duplicates += 1
else:
failed += 1
return {
'success': success,
'duplicates': duplicates,
'failed': failed
}
def _insert_tags(self, news_id, tags):
"""插入标签(多对多关系)"""
for tag_name in tags:
# 插入标签(如果不存在)
self.cursor.execute('''
INSERT OR IGNORE INTO tags (name) VALUES (?)
''', (tag_name,))
# 获取标签 ID
self.cursor.execute('''
SELECT id FROM tags WHERE name = ?
''', (tag_name,))
tag_id = self.cursor.fetchone()[0]
# 建立关联
self.cursor.execute('''
INSERT OR IGNORE INTO news_tags (news_id, tag_id)
VALUES (?, ?)
''', (news_id, tag_id))
self.conn.commit()
def query_news(self, limit=10, offset=0):
"""查询新闻列表"""
self.cursor.execute('''
SELECT id, url, title, author, publish_time, views
FROM news
ORDER BY id DESC
LIMIT ? OFFSET ?
''', (limit, offset))
columns = [desc[0] for desc in self.cursor.description]
rows = self.cursor.fetchall()
return [dict(zip(columns, row)) for row in rows]
def search_by_keyword(self, keyword):
"""按关键词搜索"""
self.cursor.execute('''
SELECT id, url, title, author, publish_time
FROM news
WHERE title LIKE ? OR content LIKE ?
ORDER BY publish_time DESC
''', (f'%{keyword}%', f'%{keyword}%'))
columns = [desc[0] for desc in self.cursor.description]
return [dict(zip(columns, row)) for row in self.cursor.fetchall()]
def get_stats(self):
"""获取统计信息"""
# 总记录数
self.cursor.execute('SELECT COUNT(*) FROM news')
total = self.cursor.fetchone()[0]日新增
self.cursor.execute('''
SELECT COUNT(*) FROM news
WHERE DATE(created_at) = DATE('now')
''')
today = self.cursor.fetchone()[0]
# 热门标签
self.cursor.execute('''
SELECT t.name, COUNT(*) as count
FROM tags t
JOIN news_tags nt ON t.id = nt.tag_id
GROUP BY t.name
ORDER BY count DESC
LIMIT 5
''')
top_tags = self.cursor.fetchall()
return {
'total_news': total,
'today_news': today,
'top_tags': top_tags
}
def close(self):
"""关闭数据库连接"""
self.conn.close()
print("🔒 数据库已关闭")
# ========== 使用示例 ==========
if __name__ == "__main__":
# 创建数据库
db = NewsDatabase('news.db')
# 插入数据
news_data = [
{
'url': 'https://example.com/news/1',
'title': '重大科技突破',
'author': '张三',
'publish_time': '2025-01-21 10:00:00',
'content': '正文内容...',
'views': 1000,
'tags': ['科技', '创新']
},
{
'url': 'https://example.com/news/2',
'title': '经济政策解读',
'author': '李四',
'publish_time': '2025-01-21 09:00:00',
'content': '正文内容...',
'views': 800,
'tags': ['财经', '政策']
}
]
# 批量插入
stats = db.batch_insert(news_data)
print(f"\n📊 插入统计: {stats}")
# 查询数据
print("\n📋 最新新闻:")
news_list = db.query_news(limit=5)
for news in news_list:
print(f" {news['title']} - {news['author']}")
# 关键词搜索
print("\n🔍 搜索'科技':")
results = db.search_by_keyword('科技')
for item in results:
print(f" {item['title']}")
# 统计信息
print("\n📊 数据库统计:")
stats = db.get_stats()
print(f" 总新闻数: {stats['total_news']}")
print(f" 今日新增: {stats['today_news']}")
print(f" 热门标签: {stats['top_tags']}")
# 关闭
db.close()
4.3 使用 pandas 操作 SQLite
python
import pandas as pd
import sqlite3
def save_to_sqlite_pandas(data_list, db_file, table_name='news'):
"""
使用 pandas 保存到 SQLite(更简单)
Args:
data_list: 数据列表
db_file: 数据库文件
table_name: 表名
"""
df = pd.DataFrame(data_list)
conn = sqlite3.connect(db_file)
# if_exists='append': 追加数据
# if_exists='replace': 替换表
df.to_sql(table_name, conn, if_exists='append', index=False)
conn.close()
print(f"✅ 已保存 {len(df)} 条记录到 {db_file}")
def read_from_sqlite_pandas(db_file, query):
"""从 SQLite 读取数据到 DataFrame"""
conn = sqlite3.connect(db_file)
df = pd.read_sql_query(query, conn)
conn.close()
return df
# 使用
save_to_sqlite_pandas(data, 'news_pandas.db')
# 查询
df = read_from_sqlite_pandas('news_pandas.db', 'SELECT * FROM news LIMIT 10')
print(df.head())
五、存储方案选择指南
5.1 根据数据量选择
python
def recommend_storage(record_count, has_nested=False, need_query=False):
"""
推荐存储方案
Args:
record_count: 预计记录数
has_nested: 是否有嵌套结构
need_query: 是否需要查询功能
Returns:
str: 推荐方案
"""
if record_count < 1000:
if has_nested:
return "JSON(易读,便于检查)"
else:
return "CSV(可用 Excel 打开)"
elif record_count < 100000:
if need_query:
return "SQLite(支持查询)"
elif has_nested:
return "JSON Lines(增量友好)"
else:
return "CSV(pandas 处理快)"
else: # > 100000
if need_query:
return "SQLite(必须用数据库)"
else:
return "JSON Lines + 压缩(节省空间)"
# 测试
print(recommend_storage(500, has_nested=True, need_query=False))
# "JSON(易读,便于检查)"
print(recommend_storage(50000, has_nested=False, need_query=True))
# "SQLite(支持查询)"
5.2 多格式保存(保险策略)
python
class MultiFormatSaver:
"""多格式保存器(同时保存多种格式)"""
def __init__(self, base_name='data'):
"""
初始化
Args:
base_name: 文件名前缀
"""
self.base_name = base_name
self.timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
def save_all(self, data_list):
"""保存为所有格式"""
if not data_list:
print("⚠️ 数据为空")
return
filename_base = f"{self.base_name}_{self.timestamp}"
# 1. CSV(用于 Excel 分析)
csv_file = f"{filename_base}.csv"
df = pd.DataFrame(data_list)
df.to_csv(csv_file, index=False, encoding='utf-8-sig')
print(f"✅ CSV: {csv_file}")
# 2. JSON(完整格式,易读)
json_file = f"{filename_base}.json"
with open(json_file, 'w', encoding='utf-8') as f:
json.dump(data_list, f, ensure_ascii=False, indent=2)
print(f"✅ JSON: {json_file}")
# 3. JSON Lines(增量友好)
jsonl_file = f"{filename_base}.jsonl"
with open(jsonl_file, 'w', encoding='utf-8') as f:
for item in data_list:
f.write(json.dumps(item, ensure_ascii=False) + '\n')
print(f"✅ JSONL: {jsonl_file}")
# 4. SQLite(可查询)
db_file = f"{self.base_name}.db"
conn = sqlite3.connect(db_file)
df.to_sql('news', conn, if_exists='append', index=False)
conn.close()
print(f"✅ SQLite: {db_file}")
print(f"\n📦 共保存 {len(data_list)} 条记录(4 base_name='news')
saver.save_all(data)
六、性能对比测试
python
import time
def benchmark_storage(data_list, iterations=3):
"""
对比不同存储方案的性能
Args:
data_list: 测试数据
iterations: 测试次数
"""
results = {}
# 测试 CSV
times = []
for _ in range(iterations):
start = time.time()
df = pd.DataFrame(data_list)
df.to_csv('benchmark.csv', index=False)
times.append(time.time() - start)
results['CSV (pandas)'] = sum(times) / iterations
# 测试 JSON
times = []
for _ in range(iterations):
start = time.time()
with open('benchmark.json', 'w', encoding='utf-8') as f:
json.dump(data_list, f)
times.append(time.time() - start)
results['JSON'] = sum(times) / iterations
# 测试 JSON Lines
times = []
for _ in range(iterations):
start = time.time()
with open('benchmark.jsonl', 'w', encoding='utf-8') as f:
for item in data_list:
f.write(json.dumps(item) + '\n')
times.append(time.time() - start)
results['JSON Lines'] = sum(times) / iterations
# 测试 SQLite
times = []
for _ in range(iterations):
start = time.time()
conn = sqlite3.connect(':memory:') # 内存数据库
df.to_sql('test', conn, if_exists='replace', index=False)
conn.close()
times.append(time.time() - start)
results['SQLite'] = sum(times) / iterations
# 打印结果
print(f"\n⏱️ 性能测试({len(data_list)} 条记录,平均 {iterations} 次)")
print("=" * 50)
sorted_results = sorted(results.items(), key=lambda x: x[1])
for method, avg_time in sorted_results:
print(f"{method:20s}: {avg_time:.4f}s")
# 清理测试文件
import os
for f in ['benchmark.csv', 'benchmark.json', 'benchmark.jsonl']:
if os.path.exists(f):
os.remove(f)
# 生成测试数据
test_data = [
{
'id': i,
'title': f'新闻标题{i}',
'content': '这是正文内容...' * 10,
'tags': ['tag1', 'tag2']
}
for i in range(1000)
]
benchmark_storage(test_data)
七、最佳实践建议
7.1 存储策略组合
python
class SmartStorage:
"""智能存储策略"""
def __init__(self, project_name='spider'):
self.project_name = project_name
# 原始数据(JSON Lines,便于追加)
self.raw_file = f"{project_name}_raw.jsonl"
# 清洗后数据(SQLite,便于查询)
self.db = NewsDatabase(f"{project_name}_clean.db")
# 导出数据(CSV,便于分析)
self.export_dir = f"export/{project_name}"
os.makedirs(self.export_dir, exist_ok=True)
def save_raw(self, data_list):
"""保存原始数据(不经处理)"""
with open(self.raw_file, 'a', encoding='utf-8') as f:
for item in data_list:
f.write(json.dumps(item, ensure_ascii=False) + '\n')
print(f"✅ 原始数据已保存: {self.raw_file}")
def save_clean(self, data_list):
"""保存清洗后的数据"""
stats = self.db.batch_insert(data_list)
print(f"✅ 清洗数据已入库: {stats}")
def export_csv(self, query=None):
"""导出 CSV 用于分析"""
if query is None:
query = "SELECT * FROM news"
conn = sqlite3.connect(self.db.db_file)
df = pd.read_sql_query(query, conn)
conn.close()
timestamp = datetime.now().strftime('%Y%m%d_%H%M%S')
csv_file = f"{self.export_dir}/export_{timestamp}.csv"
df.to_csv(csv_file, index=False, encoding='utf-8-sig')
print(f"✅ 已导出 CSV: {csv_file}")
return csv_file
7.2 数据备份策略
python
import shutil
def backup_data(source_dir, backup_dir):
"""
备份数据文件
建议:每天定时备份
"""
timestamp = datetime.now().strftime('%Y%m%d')
backup_path = f"{backup_dir}/backup_{timestamp}"
if not os.path.exists(backup_path):
shutil.copytree(source_dir, backup_path)
print(f"✅ 已备份到: {backup_path}")
else:
print(f"⏭️ 今日已备份")
八、本节小结
本节我们学习了三种数据存储方案:
✅ CSV 存储 :简单直观,适合表格数据和 Excel 分析
✅ JSON 存储 :灵活通用,支持嵌套结构和增量追加
✅ SQLite 存储 :结构化查询,适合中大型数据和复杂查询
✅ 性能对比 :了解不同方案的速度差异
✅ 组合策略:原始+清洗+导出的三层存储架构
核心原则:
- 原始数据保留:用 JSON Lines 保存未处理的原始数据
- 清洗数据入库:用 SQLite 存储结构化的干净数据
- 按需导出:用 CSV 导出用于分析的数据子集
- 定期备份:重要数据要有备份机制
九、课后作业(必做,验收进入第四章)
任务1:对比三种存储方式
使用你之前采集的新闻数据(至少 50 条),完成:
- 分别保存为 CSV、JSON、SQLite 三种格式
- 对比文件大小
- 测试读取速度
- 生成对比报告
任务2:设计数据库表结构
为一个电商商品采集项目设计 SQLite 表结构:
- 商品表(id、名称、价格、库存...)
- 分类表(一对多关系)
- 评论表(一对多关系)
- 编写建表 SQL 和插入示例
任务3:实现智能存储
为你的采集器添加 SmartStorage 类:
- 原始数据保存为 JSON Lines
- 清洗后数据存入 SQLite
- 提供 CSV 导出功能
- 实现数据统计和查询接口
验收方式:在留言区提交:式的对比报告(含文件大小、速度截图)
- 电商数据库设计文档(ER 图或建表 SQL)
- SmartStorage 实现代码和测试结果
- 学习心得
🔮 下期预告
恭喜完成第三章!🎉 下一章《动态网站爬取:Selenium 基础》,我们将进入新的领域:
- 什么是动态网站?为什么 requests 抓不到数据?
- Selenium 的安装和基本使用
- 定位元素的八种方法
- 等待策略:隐式等待 vs 显式等待
- 执行 JavaScript 脚本
预习建议:
安装 Selenium 和 Chrome 浏览器驱动(ChromeDriver)。了解什么是 JavaScript 渲染的网页。
💬 选对存储方案,事半功倍!数据是爬虫的成果! 📦✨
记住:原始数据是最宝贵的资产。即使清洗逻辑改变,有了原始数据就能重新处理。工程师要有"数据第一"的意识!
🌟 文末
好啦~以上就是本期 《Python爬虫实战》的全部内容啦!如果你在实践过程中遇到任何疑问,欢迎在评论区留言交流,我看到都会尽量回复~咱们下期见!
小伙伴们在批阅的过程中,如果觉得文章不错,欢迎点赞、收藏、关注哦~
三连就是对我写作道路上最好的鼓励与支持! ❤️🔥
📌 专栏持续更新中|建议收藏 + 订阅
专栏 👉 《Python爬虫实战》,我会按照"入门 → 进阶 → 工程化 → 项目落地"的路线持续更新,争取让每一篇都做到:
✅ 讲得清楚(原理)|✅ 跑得起来(代码)|✅ 用得上(场景)|✅ 扛得住(工程化)
📣 想系统提升的小伙伴:强烈建议先订阅专栏,再按目录顺序学习,效率会高很多~

✅ 互动征集
想让我把【某站点/某反爬/某验证码/某分布式方案】写成专栏实战?
评论区留言告诉我你的需求,我会优先安排更新 ✅
⭐️ 若喜欢我,就请关注我叭~(更新不迷路)
⭐️ 若对你有用,就请点赞支持一下叭~(给我一点点动力)
⭐️ 若有疑问,就请评论留言告诉我叭~(我会补坑 & 更新迭代)
免责声明:本文仅用于学习与技术研究,请在合法合规、遵守站点规则与 Robots 协议的前提下使用相关技术。严禁将技术用于任何非法用途或侵害他人权益的行为。