scrapy中process_item写入文件丢数据的根本原因是异步执行、文件未缓冲且未正确关闭流;必须用open_spider和close_spider管理文件生命周期,避免并发写入与缓冲区丢失。

Scrapy 中 process_item 写入文件却丢数据,根本不是代码写错了,而是异步执行 + 文件未缓冲 + 没关流导致的。直接用 open() 写文件,在爬虫结束前程序就退出或崩溃,self.file.write() 的内容很可能还卡在内存缓冲区里,根本没落盘。
为什么 process_item 里直接 write() 会丢数据
Scrapy 是异步框架,process_item 被并发调用,但 Python 的普通文件对象不是线程安全的;更关键的是,write() 默认不立即刷盘,靠系统或解释器决定何时 flush。如果爬虫中途异常退出(比如 Ctrl+C、网络中断、内存溢出),缓冲区里还没写出的数据就永远消失了。
-
open(..., 'w')不带buffering=1或flush=True,写入是全缓冲的,小量文本可能攒几十 KB 才写一次 - 没调用
close()或没用with语句,__del__不保证被及时触发 - 多个
process_item并发写同一个文件句柄,可能造成行交错或覆盖(尤其没加锁时)
必须用 open_spider 和 close_spider 管理文件生命周期
Scrapy 提供了明确的生命周期钩子,这是唯一可靠的方式——不能靠 try/finally 或全局变量模拟。
-
open_spider(self, spider):只在爬虫启动时调用一次,适合初始化文件、数据库连接 -
close_spider(self, spider):只在爬虫彻底结束(无论成功或异常)时调用,必须在这里self.file.close() - 中间所有
process_item都复用同一个打开的文件对象,避免反复 open/close 开销和竞态
示例关键片段:
def open_spider(self, spider):
self.file = open('output.txt', 'w', encoding='utf-8')
<p>def process_item(self, item, spider):
line = f"{item.get('title', '')}\n{item.get('content', '')}\n\n"
self.file.write(line) # 不要 flush,留到 close 时统一落盘</p><p>def close_spider(self, spider):
if hasattr(self, 'file') and not self.file.closed:
self.file.close()</p>
遇到重复写入或字段错乱?检查 item 是否被意外复用
Scrapy 默认复用 Item 实例(尤其在 yield 多次修改同一对象时),导致后一个 process_item 看到的是前一个改过的值——看起来像“数据覆盖”或“字段错乱”。
- 不要在
parse中反复修改并yield同一个item对象 - 要么每次
yield scrapy.Item()新实例,要么在process_item开头做深拷贝:item = copy.deepcopy(item) - 记得
import copy,别漏掉
数据量大时,别硬扛——换更稳的输出方式
纯文本写入不是瓶颈,但手动管理文件在高并发下极易出错。真要稳定,优先考虑:
- 用 Scrapy 自带的
Feed Exporters:配置FEEDS = {'output.json': {'format': 'json', 'encoding': 'utf-8'}},它自动处理缓冲、分片、异常恢复 - 写数据库时务必用异步驱动(如
adbapi)+ 连接池,避免阻塞 pipeline - 临时调试可用
logging.info(str(dict(item))),比文件更不易丢
最常被忽略的一点:很多人写了 close_spider 却没验证它是否真的被执行——加一行 print("closed") 或写个标记文件,否则你以为关了,其实没关。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!











