直接用celery.send_task()会丢任务,根本原因是broker连接未就绪或task名未注册进worker的导入列表,该方法绕过注册校验,导致任务发出后无人认领;应改用已装饰函数的delay()调用,并确保worker启动时正确加载对应模块。

为什么直接用 celery.send_task() 会丢任务?
很多刚接入 Celery 的爬虫项目,一上来就用 celery.send_task("crawl_task", args=[url]) 发任务,结果日志里没报错,但 worker 就是不消费——根本原因是 broker(比如 Redis)连接未就绪,或 task 名字没注册进 worker 的 CELERY_IMPORTS 或自动发现机制里。worker 启动时只加载它知道的 task 模块,而 send_task() 绕过了 import 检查,发出去了也无人认领。
实操建议:
- 改用
crawl_task.delay(url),确保crawl_task是已装饰的函数(@app.task),这样调用前会做注册校验 - 检查 worker 启动命令是否包含对应模块路径:例如
celery -A tasks worker --loglevel=info,其中tasks.py必须能被 Python 导入 - 在发送端加简单连通性验证:
app.control.inspect().ping()返回非空 dict 再发任务
爬虫任务里怎么安全传参又不爆内存?
Celery 默认用 pickle 序列化参数,但爬虫常要传 headers、proxy、甚至小段 HTML 片段——这些结构深、含不可序列化对象(如 requests.Session)时会直接失败,或把 session 对象整个塞进消息体,撑爆 Redis 内存。
实操建议:
- 所有参数必须是 JSON 可序列化的:用
dict、str、int、list,别传函数、类实例、文件句柄 - 大文本(如预抓取的 HTML)不要当参数传,改用外部存储(Redis String / S3 URL),只传 key 或 url
- 启用 JSON 序列化(比 pickle 更安全):
app.conf.task_serializer = "json",并确保 worker 端也配置一致
如何让同一个 URL 不被重复抓取?
分布式下多个 worker 可能同时拿到相同 URL,尤其重试或定时任务场景。Celery 本身不提供去重,得自己加一层判断。
仅使用 Python 将 Claw Code 虚拟化框架与 OpenClaw 集成,提供 184 种工具和 200 多条命令,用于安全分析和代码质量...
实操建议:
- 在 task 函数开头用 Redis 做原子 setnx:
redis_client.setex(f"seen:{url_hash}", 3600, "1"),返回 True 才继续抓取 - URL 需标准化后再哈希:去掉 fragment、统一 scheme 和 trailing slash,避免
http://a.com/和http://a.com被当成两个 - 别依赖 task 名 + args 去重(
ignore_result=True也不行),Celery 的 ETA 或重试机制会让同一逻辑多次触发
Worker 抓到一半崩溃,任务状态怎么恢复?
爬虫常因网络超时、反爬封 IP、解析异常而中断,如果没设 acks_late=True,worker 进程挂掉时任务就丢了;设了又可能重复执行——关键在「幂等性」和「可恢复断点」。
实操建议:
- 启动 worker 时强制延迟确认:
celery -A tasks worker --acks-late --reject-on-worker-loss - 每个任务内拆出「获取响应」和「解析保存」两阶段,前者成功后立即写 checkpoint 到 Redis(如
redis_client.hset("crawl_state", url, "fetched")),后者失败不影响重试 - 避免在 task 里写死数据库连接,用连接池或每次新建短生命周期连接,防止 worker 复用损坏的连接
真正难的不是发任务,而是让每个 URL 在网络抖动、worker 重启、代码更新后仍能准确识别“我上次做到哪了”。这需要把状态外置、把逻辑切薄、把失败当作常态来设计。
Python免费学习笔记(深入):立即使用
在学习笔记中,你将探索 Python 的核心概念和高级技巧!










