因为open()是同步阻塞调用,会阻塞事件循环,导致并发性能崩溃;aiofiles通过线程池委托实现非阻塞IO,需用await aiofiles.open()并显式指定模式,避免RuntimeWarning和响应延迟。

为什么不能直接用 open() 在 async 函数里写文件
因为 open() 是同步阻塞调用,放进 async 函数里会拖垮整个事件循环——哪怕只写几百 KB,也可能让其他并发请求卡住几百毫秒。aiofiles 就是为解决这个而生的,它把文件操作包装成 awaitable 对象,真正实现非阻塞 IO。
常见错误现象:RuntimeWarning: coroutine 'open' was never awaited,或者程序看似在跑,但 CPU 占用低、响应慢、并发数上不去。
- 必须用
await aiofiles.open(...),不能只写aiofiles.open(...) - 打开模式要显式指定,比如
'w'或'a';'wb'用于二进制,此时内容必须是bytes - 别漏掉
await f.write(...)——f.write()返回的是 coroutine,不 await 就等于没写
如何安全地批量写入爬虫结果到 JSONL 文件
JSONL(每行一个 JSON)是爬虫最常用的本地存储格式,适合流式写入、后续用 pandas.read_json(..., lines=True) 直接读取。用 aiofiles 写 JSONL 要注意编码和换行一致性。
示例场景:10 个协程并发抓取页面,各自解析出一条字典数据,统一追加到 output.jsonl。
立即学习“Python免费学习笔记(深入)”;
- 所有协程共用一个文件路径,但不要共用同一个
aiofiles.open()句柄——每个写入必须独立 open + close,或用async with管理 - 写入前用
json.dumps(item, ensure_ascii=False)转字符串,末尾手动加,避免多行粘连 - 如果担心高并发下文件竞争,可加简单锁:
async with asyncio.Lock()包裹 write 块(仅当写入频率极高时需要)
import aiofiles
import json
async def save_to_jsonl(item, filepath="output.jsonl"):
data = json.dumps(item, ensure_ascii=False)
async with aiofiles.open(filepath, "a", encoding="utf-8") as f:
await f.write(data + "
")
aiofiles 的性能瓶颈在哪?什么时候该换方案
它本身不缓存、不批处理,每次 await f.write() 都是一次系统调用。当单次写入量小(如每条记录 50 路)时,磁盘 IO 会成为瓶颈,甚至比同步写还慢。
真实影响:
- SSD 上单次写延迟约 0.1–0.3ms,但 Python 层调度开销叠加后,100 并发可能压出 5–10ms 平均延迟
- 频繁小写入触发更多 fsync(尤其 macOS/HFS+),进一步拖慢
- Windows 上部分 antivirus 软件会拦截异步文件句柄,导致
OSError: [Errno 22]
可考虑换方案的情况:
- 写入总量 >1GB 且结构固定 → 改用
sqlite3异步封装(如aiosqlite) - 纯日志类追加 → 先写内存 buffer(
io.StringIO),满 1MB 再 flush 到文件 - 需要原子写入或防中断 → 用临时文件 +
os.replace()提交,但需同步调用,得用loop.run_in_executor
Windows 下 aiofiles 常见报错与绕过方式
Windows 默认文件系统对异步 IO 支持有限,尤其在 NTFS 上启用“压缩”或“加密”属性时,aiofiles.open() 可能直接抛 NotImplementedError 或 PermissionError。
典型错误信息:NotImplementedError: asyncio.WindowsProactorEventLoopPolicy does not support subprocesses(间接触发)或 OSError: [WinError 87] 参数不正确。
- 确保使用
asyncio.SelectorEventLoopPolicy(而非默认 Proactor),尤其在 Python - 禁用文件压缩/加密属性:
attrib -C -E your_file.txt - 路径尽量用绝对路径,避免 UNC 路径(
\servershare)和长路径(>260 字符),否则 Windows API 调用失败 - 实在不行,退回到
loop.run_in_executor(None, lambda: open(...).write(...)),虽不是纯异步,但比全同步好


















