直接用文件记录url续传易出错,因并发写入会导致数据丢失、重复抓取或文件损坏;sqlite凭借事务、行级锁及insert or ignore可天然防重,且支持高效进度查询。

为什么直接用文件记录URL续传容易出错
用纯文本或JSON存已抓取URL看似简单,但并发写入时会丢数据、重复抓取,甚至损坏文件。SQLite自带事务和行级锁,INSERT OR IGNORE能天然防重,SELECT COUNT(*)查进度也快——这不是过度设计,而是爬虫跑一两天后不崩溃的底线。
建表语句必须包含这三列
只存URL不够。断点续传依赖三个关键状态字段:url(主键)、status('pending'/'done'/'error')、updated_at(时间戳)。示例:
CREATE TABLE IF NOT EXISTS crawl_queue (
url TEXT PRIMARY KEY,
status TEXT NOT NULL DEFAULT 'pending',
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
漏掉PRIMARY KEY会导致重复插入失败;没DEFAULT 'pending'会让新URL状态为空,后续WHERE status = 'pending'查不到;时间戳不设默认值,就无法判断哪批URL是上一次中断前插入的。
每次请求前必须先标记为 pending
不能“先请求再入库”,否则程序崩在中间,这个URL就永远卡在“未记录”状态,下次启动直接重抓。正确顺序是:
- 从数据库取一条
status = 'pending'的URL - 用
UPDATE crawl_queue SET status = 'pending', updated_at = CURRENT_TIMESTAMP WHERE url = ?抢占式标记(避免多进程抢同一URL) - 真正发起HTTP请求
- 成功则
UPDATE ... SET status = 'done',失败则SET status = 'error'
注意:UPDATE必须带WHERE url = ?且用参数化查询,否则可能误标其他URL;updated_at每次更新都要重置,方便按时间排查卡住的任务。
重启时如何安全跳过已抓取内容
别用SELECT * FROM crawl_queue WHERE status != 'done'当待抓队列——如果某URL状态是'error',你可能还想重试,但直接过滤掉就丢了。更稳妥的做法是:
- 启动时执行
UPDATE crawl_queue SET status = 'pending' WHERE status = 'error' AND updated_at (给错误项1小时冷却) - 然后只取
status = 'pending'的URL - 如果要彻底跳过历史成功项,确保解析逻辑里有
if row['status'] == 'done': continue,而不是靠SQL过滤
很多人忽略updated_at的时间条件,导致网络超时的错误任务被无限重试,压垮目标站点。
实际跑起来后,最麻烦的不是SQL怎么写,而是网络IO和数据库写入速度不匹配——加个time.sleep(0.1)比优化SQL更能防止被封,而SQLite的WAL模式开启与否,直接影响并发读写时的锁等待时间。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











