Peewee在爬虫中易因并发写入、字段类型误用和异常处理不当引发问题:SqliteDatabase默认check_same_thread=True导致多线程报错;CharField长度限制易截断HTML等长内容,应优先用TextField;重复插入需用get_or_create()或on_conflict_ignore()避免IntegrityError;自动时间戳和外键在爬虫场景下宜关闭以提升性能与稳定性。

Peewee 能显著减少爬虫数据入库的模板代码,但直接套用默认配置容易在并发、字段类型和异常处理上翻车。
为什么爬虫场景下 SqliteDatabase 默认不支持多线程写入
Peewee 的 SqliteDatabase 默认启用 check_same_thread=True,而多数爬虫会用 concurrent.futures 或 asyncio 并发采集——此时多个线程共用一个连接会触发 sqlite3.ProgrammingError: SQLite objects created in a thread can only be used in that same thread。
- 必须显式关闭检查:
db = SqliteDatabase('data.db', check_same_thread=False) - 更稳妥的做法是为每个线程/协程分配独立连接,用
db.bind()+db.connect()控制生命周期 - 避免在模型定义前就调用
db.connect(),否则可能提前建立主线程连接,后续子线程仍会报错
TextField 和 CharField 在存网页内容时怎么选
爬虫常存 HTML 片段、JSON 字符串或长文本摘要,CharField 有长度限制(SQLite 默认 255),而 TextField 才是无界字符串的实际载体。
- 标题、URL 等固定短字段用
CharField(max_length=512),便于加索引 - 正文、HTML 源码、评论内容一律用
TextField(),别试图设max_length——SQLite 不强制该参数,但 Peewee 会做 Python 层校验,徒增开销 - 如果字段可能为空且需区分
NULL和空字符串,显式声明null=True,否则默认NOT NULL,插入空值会报IntegrityError
如何避免重复插入导致的 IntegrityError
爬虫去重通常靠 URL 或唯一标识符,但 Peewee 没有内置“存在则更新”逻辑,直接 .create() 遇到主键/唯一约束冲突就会炸。
立即学习“Python免费学习笔记(深入)”;
- 用
get_or_create():适合单字段唯一(如url),返回 (instance,created) 元组,注意它只对主键或unique=True字段生效 - 更灵活的方式是捕获异常:
try: model.create(...) except IntegrityError: pass,但需确保已设置url = CharField(unique=True) - 批量插入时别用循环
create(),改用model.insert_many(...).on_conflict_ignore().execute()(SQLite 3.24+ 支持),性能高且原子
什么时候该关掉 auto_timestamps 和 foreign_key
Peewee 的 BaseModel 若开启自动时间戳或外键约束,在爬虫这种高吞吐、低关系语义的场景下反而拖慢速度、增加出错路径。
-
created_at = DateTimeField(default=datetime.now)比AutoNow更可控,避免时区或序列化问题 - 除非明确需要级联删除(比如文章和评论强绑定),否则定义外键时加
constraints=[SQL('FOREIGN KEY (xxx_id) REFERENCES xxx(id) ON DELETE NO ACTION')],并手动控制关联逻辑 - SQLite 默认不强制外键,即使写了
ForeignKeyField也不生效,得额外调用db.execute_sql('PRAGMA foreign_keys = ON')
真正麻烦的不是写几行 create_table,而是字段类型选错后数据截断、并发写崩数据库、或者唯一性校验漏掉导致脏数据堆积——这些坑往往要跑几天爬虫才暴露。


















