用 queue.queue 做生产者-消费者爬虫完全可行,但必须搭配 threading 且禁用 asyncio;因其非协程安全,与 asyncio.queue 混用会导致死锁或 runtimeerror。

直接说结论:用 queue.Queue 做生产者-消费者爬虫完全可行,但必须搭配线程(threading)且禁用 asyncio —— 因为 queue.Queue 不是协程安全的,和 asyncio.Queue 不能混用。
为什么不能用 queue.Queue 配合 asyncio
常见错误是写了个 async def producer() 往 queue.Queue() 里 put(),结果卡死或抛出 RuntimeError: This event loop is already running。根本原因是:queue.Queue 的 put()/get() 是阻塞式同步调用,会阻塞当前线程;而 asyncio 要求所有 I/O 操作必须是非阻塞、可挂起的。两者运行模型冲突。
如果你要用异步爬虫,必须换 asyncio.Queue,并配合 await queue.put() 和 await queue.get()。
-
queue.Queue→ 只能用于多线程场景(threading.Thread) -
asyncio.Queue→ 只能用于异步任务(asyncio.create_task()) - 混用会导致死锁、异常或静默失败
线程版生产者-消费者的最小可靠结构
核心是:生产者线程只负责发现 URL 并 put() 到队列;消费者线程只负责 get()、请求、解析、存数据;队列本身不参与网络请求逻辑。
关键参数要设对:
SkillSub Pro - Python 题解与代码注释双功能技能功能概述SkillSub Pro - Python 题解与代码注释双功能技能是一项面向实际任务的技能,主要用于SkillSub Pro 是一个 Python 题解生成与代码注释的 双功能合体技能 ,专为学生、算法学习者和开发者设计;✅ 一个技能,两种用途 :;核心要点📝 题解模式 :输入题目/题号,自动生成完整 Python 题解(含详细注释、解题思路、复杂度分析);💬 注释模式 :输入 Python 代码,自动添加详细中。它将相关步骤、
- 初始化用
queue.Queue(maxsize=0)(默认不限大小),避免生产者被意外阻塞 - 消费者用
queue.get(block=True, timeout=1),加timeout防止空队列时永久等待 - 每次
get()后必须配task_done(),否则join()永远不会返回 - 生产者结束前调用
queue.join()等待所有任务完成,再发退出信号(如None)
示例片段(非完整代码,仅示意流程):
import queue
import threading
import requests
<p>q = queue.Queue()</p><p>def producer(urls):
for url in urls:
q.put(url)</p><h1>发送结束信号</h1><pre class="brush:python;toolbar:false;">for _ in range(3): # 假设开3个消费者线程
q.put(None)def consumer(): while True: url = q.get() if url is None: break try: resp = requests.get(url, timeout=5)
解析 resp.text ...
finally:
q.task_done() # 必须有!
容易被忽略的三个坑
实际跑起来常卡住或漏数据,多数栽在这几个点上:
- 没调
q.task_done():导致q.join()永不返回,主程序无法退出 - 消费者异常未捕获:比如
requests.exceptions.Timeout没except,线程直接退出,后续任务无人处理 - 队列满时
put()阻塞:若maxsize > 0且生产过快,生产者线程会被卡住,拖慢整体吞吐;建议保持maxsize=0,靠消费者并发数控速
真正难的不是写通,而是让队列在高并发、异常频发、URL 重复/失效等真实爬虫场景下不丢任务、不卡死、不重复消费——这些得靠日志、重试计数、去重中间件(如 set 或 Redis)来补,queue.Queue 本身只管“排队”这一件事。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










