很多新手写爬虫时都会遇到一个诡异现象:CPU 占用率很低,网络也没跑满,但数据入库速度却慢得像蜗牛。如果你用的是 SQLite,99% 的问题出在写入方式上。今天分享一个实战中屡试不爽的优化技巧——批量提交(executemany),帮你把每秒写入性能提升 10 倍以上。
痛点场景
假设你在抓取电商数据,目标是每秒写入几百条商品信息。
如果你像下面这样写代码:
for item in items:
cursor.execute("INSERT INTO products (name, price) VALUES (?, ?)",
(item['name'], item['price']))
conn.commit()
你会发现,随着数据量增加,程序越来越慢。这是因为:每一次 execute 和 commit,SQLite 都要进行一次磁盘 I/O 操作。1 万条数据就是 1 万次磁盘写入,硬盘吃不消,速度自然上不来。
核心技巧:批量插入 + 手动事务
优化的核心思路很简单:减少 I/O 次数。把多次插入合并成一次,把多次提交合并成一次。
操作步骤
1. 关闭自动提交,开启手动事务
默认情况下,SQLite 是自动提交模式。我们需要关闭它,改为手动控制。
2. 使用 executemany 代替 execute
executemany 可以一次性传入一个包含多组参数的列表,SQLite 会将其视为一个批次进行处理。
3. 统一 commit
在所有数据插入完成后,再调用一次 commit()。
优化后的代码示例
import sqlite3
import time
def batch_insert(data_list):
conn = sqlite3.connect('spider_data.db')
cursor = conn.cursor()
# 1. 关闭自动提交(实际上sqlite默认是按需,但显式管理更好)
# 2. 开始事务
cursor.execute("BEGIN TRANSACTION")
try:
# 核心:使用 executemany 批量插入
sql = "INSERT INTO products (name, price) VALUES (?, ?)"
cursor.executemany(sql, data_list)
# 3. 统一提交
conn.commit()
print(f"成功批量插入 {len(data_list)} 条数据")
except Exception as e:
conn.rollback() # 出错回滚
print(f"插入失败: {e}")
finally:
conn.close()
if __name__ == '__main__':
# 模拟抓取到的 5000 条数据
mock_data = [(f'商品_{i}', i * 10.0) for i in range(5000)]
start_time = time.time()
batch_insert(mock_data)
end_time = time.time()
print(f"耗时: {end_time - start_time:.4f} 秒")
功能执行结果对比
我在本地做了一个简单的基准测试,分别用单条插入和批量插入写入 5000 条数据:
写入方式 数据量 耗时 速度
单条循环 (execute) 5000 条 ~3.8 秒 ~1300 条/秒
批量提交 (executemany) 5000 条 ~0.12 秒 ~41600 条/秒
结果分析:
速度提升了 30 多倍。在实际爬虫项目中,由于涉及网络请求和解析,提升幅度通常在 5-10 倍之间,但效果依然惊人。
进阶小贴士
1. 批次大小:建议每批 500~2000 条。太小体现不出优势,太大可能占用过多内存。
2. WAL 模式:在执行插入前,运行 cursor.execute("PRAGMA journal_mode=WAL;")。WAL 模式能大幅提升 SQLite 的并发写入性能,非常适合爬虫场景。
3. 内存缓存:在内存中攒够一批数据再入库,比来一条写一条效率高得多。
这个技巧不仅适用于爬虫,在任何需要高频写入数据库的场景(如日志分析、数据清洗)中都极其有效。下次写脚本时,记得别让你的硬盘"996"了!
------
推荐阅读:
• [Scrapling 框架实战:单机日抓百万数据的配置详解]
• [SSD 硬盘下的高并发写入优化指南]