news 2026/9/9 22:02:27

如何为 Crawl4AI 自托管 Docker 服务配置 webhook 回调接收爬取完成通知

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
如何为 Crawl4AI 自托管 Docker 服务配置 webhook 回调接收爬取完成通知

如何为 Crawl4AI 自托管 Docker 服务配置 webhook 回调接收爬取完成通知

【免费下载链接】crawl4ai🚀🤖 Crawl4AI: Open-source LLM Friendly Web Crawler & Scraper. Don't be shy, join here: https://discord.gg/jP8KfhDhyN项目地址: https://gitcode.com/GitHub_Trending/craw/crawl4ai

Crawl4AI 的 Docker 版 API 服务(deploy/docker)通过POST /crawl/job提交异步爬取任务。默认用法是客户端拿回task_id后反复轮询GET /crawl/job/{task_id}检查状态;对于长时间运行的爬取或需要避免占用连接的场景,服务内置了 webhook 功能:任务完成或失败后,服务端会主动向你的回调端点 POST 一条通知,不再需要轮询。本文基于deploy/docker目录下的部署文档,完成三件事:配置回调地址(按任务或全局默认两种粒度)、编写一个能接收通知的回调端点、以及验证通知确实送达并排查投递问题。前提条件:API 服务已按 deploy/docker/README.md 启动,默认监听127.0.0.1:11235;回调端点必须能被服务端访问到,并返回 HTTP 2xx 状态码。

webhook 的工作流程

按 README.md 的 “Asynchronous Jobs with Webhooks” 一节,整个过程是五步:

  1. 提交任务:POST /crawl/job,请求体中携带可选的webhook_config
  2. 立即拿到task_id
  3. 任务在后台执行;
  4. 服务端把完成通知 POST 到你的 webhook 地址;
  5. 如果通知里没有带数据,再用GET /crawl/job/{task_id}拉取结果。

同一套 webhook 机制同时支持/crawl/job(爬取)和/llm/job(LLM 抽取)两个端点。

全局配置:config.yml 的 webhooks 段

发布版 config.yml 已经自带webhooks段,无需手工创建:

webhooks: enabled: true default_url: null # Optional: default webhook URL for all jobs data_in_payload: false # Optional: default behavior for including data retry: max_attempts: 5 initial_delay_ms: 1000 # 1s, 2s, 4s, 8s, 16s exponential backoff max_delay_ms: 32000 timeout_ms: 30000 # 30s timeout per webhook call headers: # Optional: default headers to include User-Agent: "Crawl4AI-Webhook/1.0"

各字段的作用:

  • enabled:总开关。设为false时任务照常运行,但不会发送任何通知;
  • default_url:全局默认回调地址,null表示不设置。只有当请求体没带webhook_config(或其中没有webhook_url)时才会用到它;
  • data_in_payload:通知里是否携带爬取结果的默认值,可被单个任务的webhook_data_in_payload覆盖;
  • retry:重试策略,默认最多 5 次,指数退避 1s → 2s → 4s → 8s → 16s,单次调用超时 30 秒;
  • headers:每次投递默认附加的请求头,任务级webhook_headers会与它合并后一起发送。

如果希望所有没单独指定回调的任务都通知到同一地址,只需把default_url改成你的回调端点:

webhooks: enabled: true default_url: "https://myapp.com/webhooks/default" data_in_payload: false

之后不带webhook_config的任务也会自动发 webhook。配置改动后需要让服务重新加载config.yml

主路径:按任务提交 webhook_config

最常用的是在提交任务的请求体里直接带上webhook_config,粒度是单个任务:

curl -X POST http://localhost:11235/crawl/job \ -H "Content-Type: application/json" \ -d '{ "urls": ["https://example.com"], "webhook_config": { "webhook_url": "https://your-app.example.com/webhooks/crawl-complete", "webhook_data_in_payload": false } }'

其中webhook_url需要替换成你自己的回调端点(文档示例中的https://myapp.com/webhooks/crawl-complete只是样例值)。webhook_url是必填字段,会被校验为合法 URL;webhook_data_in_payload默认false,决定通知里是否携带完整结果;webhook_headers可选,见下文。

提交成功后,响应立即返回任务 ID(文档示例输出):

{ "task_id": "crawl_a1b2c3d4" }

模式一:只发通知,凭 task_id 拉结果

webhook_data_in_payload: false时,回调收到的通知体(文档示例):

{ "task_id": "crawl_a1b2c3d4", "task_type": "crawl", "status": "completed", "timestamp": "2025-10-21T10:30:00.000000+00:00", "urls": ["https://example.com"] }

你的回调处理器拿到task_id后,再调用结果接口取数据:

curl http://localhost:11235/crawl/job/crawl_a1b2c3d4

模式二:把爬取结果直接放进通知

webhook_data_in_payload设为true,通知体会多出一个data字段,包含完整结果(文档示例):

{ "task_id": "crawl_a1b2c3d4", "task_type": "crawl", "status": "completed", "timestamp": "2025-10-21T10:30:00.000000+00:00", "urls": ["https://example.com"], "data": { "markdown": "...", "html": "...", "links": {...}, "metadata": {...} } }

两种模式按团队习惯选:只发通知、处理器再拉取,实现最简单;直接带数据则省去一次往返,但通知体可能较大。

可选:自定义请求头做鉴权

webhook_headers用于在通知里附加鉴权或标识头,服务端会把它与config.yml的默认头合并后发送:

{ "urls": ["https://example.com"], "webhook_config": { "webhook_url": "https://myapp.com/webhooks/crawl", "webhook_data_in_payload": false, "webhook_headers": { "X-Webhook-Secret": "your-secret-token", "X-Service-ID": "crawl4ai-prod" } } }

头字段有校验限制(schemas.py 中WebhookConfig在提交时执行,违规请求会被 422 拒绝):最多 20 个头;头名只允许字母、数字、连字符且不超过 64 字符;hostcontent-lengthtransfer-encodingconnectioncontent-typeproxy-authorizationauthorizationcookieexpectupgradetetrailer这些名称被禁止;头值不超过 2048 字符且不能包含\r\n\0

回调端点示例

WEBHOOK_EXAMPLES.md 提供了一个 Flask 处理器示例,下面保留其中处理爬取任务的部分(文档中的完整版本还处理 LLM 抽取任务):

from flask import Flask, request, jsonify import requests app = Flask(__name__) @app.route('/webhooks/crawl-complete', methods=['POST']) def handle_crawl_webhook(): payload = request.json task_id = payload['task_id'] status = payload['status'] if status == 'completed': # If data not in payload, fetch it if 'data' not in payload: response = requests.get(f'http://localhost:11235/crawl/job/{task_id}') data = response.json() else: data = payload['data'] results = data.get('results', []) for result in results: print(f" - {result.get('url')}: {len(result.get('markdown', ''))} chars") elif status == 'failed': error = payload.get('error', 'Unknown error') print(f"crawl job {task_id} failed: {error}") return jsonify({"status": "received"}), 200 if __name__ == '__main__': app.run(port=8080)

两个注意点:

  • 示例里用http://localhost:11235拉取结果,只在回调服务与 API 服务运行在同一主机上成立;回调端点在别的机器时,要替换成实际可达的 API 地址;
  • 最后一行返回 200 是关键:只有收到 2xx,服务端才认为投递成功。返回 4xx 会被视为拒绝且不再重试,见下文重试机制。

验证通知是否送达

1. 提交返回 task_id,回调端点收到 POST

任务完成(或失败)后,你的端点应收到一次 POST。status只有completedfailed两种值;失败时通知体会多一个error字段(文档示例):

{ "task_id": "crawl_a1b2c3d4", "task_type": "crawl", "status": "failed", "timestamp": "2025-10-21T10:30:00.000000+00:00", "urls": ["https://example.com"], "error": "Connection timeout after 30s" }

2. 检查服务端投递日志

投递过程会写入应用日志(INFO 级别记录成功投递、重试及最终失败)。按文档给出的方式过滤:

docker logs crawl4ai-container | grep -i webhook

其中crawl4ai-container需替换为你的实际容器名。webhook.py 中对应的日志信息包括:Webhook delivered successfully(投递成功)、Webhook rejected with status {status}(被 4xx 拒绝,不重试)、Webhook failed with status {status}, will retry(5xx 将重试)、Webhook blocked (SSRF protection)(出站校验拦截,不重试)。如果两端都没收到通知,先确认webhooks.enabledtrue,且任务请求带了webhook_config或全局配置了default_url——两者都没有时服务会静默跳过通知(任务本身照常运行)。

3. 重试机制决定了“收不到”的语义

webhook 投递采用指数退避重试(WEBHOOK_EXAMPLES.md “Retry Logic” 一节):

尝试次数默认最多 5 次
退避间隔1s → 2s → 4s → 8s → 16s
单次超时30 秒
会重试的情况5xx 状态码、网络错误、超时
不重试的情况4xx 状态码(记为拒绝)、2xx(投递成功)

也就是说,回调端点如果暂时不可用,服务端最长会在约 31 秒内尝试 5 次;如果端点返回 4xx,服务端会立即放弃,不会再次投递。

回调地址可达性:出站校验限制

服务端对 webhook 目标有出站校验(webhook.py 与 egress_broker.py):发送前会解析目标主机名,若解析结果不是全局可路由地址(回环、私有网段、链路本地等),或主机名属于localhostmetadatakubernetes.defaulthost.docker.internal等被拒名单,通知会被直接丢弃并记录Webhook blocked (SSRF protection),且不重试。重定向会被逐跳重新校验,最多跟随 5 跳。

实际影响:回调端点应部署在可被公网(或至少是全局可路由地址)访问的位置;把webhook_url指向内网 IP 会静默收不到通知,只能从日志里发现。源码中提供了CRAWL4AI_ALLOW_INTERNAL_URLS环境变量(默认false)可跳过该限制,注释明确它只适用于受信任的内部部署。

可选:LLM 抽取任务

/llm/job端点使用同一套webhook_config(WEBHOOK_EXAMPLES.md Example 6):

curl -X POST http://localhost:11235/llm/job \ -H "Content-Type: application/json" \ -d '{ "url": "https://example.com/article", "q": "Extract the article title, author, and publication date", "schema": "{\"type\": \"object\", \"properties\": {\"title\": {\"type\": \"string\"}, \"author\": {\"type\": \"string\"}, \"date\": {\"type\": \"string\"}}}", "cache": false, "provider": "openai/gpt-4o-mini", "webhook_config": { "webhook_url": "https://myapp.com/webhooks/llm-complete", "webhook_data_in_payload": true } }'

与爬取任务的区别:task_type"llm_extraction"url是单值而非数组;抽取结果在data.extracted_content中。文档特别提醒:webhook 通知里的键是extracted_content,而通过 API 拉取结果时对应的键是result,处理器里两者都要兼容。

限制与注意事项

  • 任务数据存放在 Redis 中,config.yml默认task_ttl_seconds: 3600(1 小时)后过期。采用“只发通知、稍后再拉结果”的模式时,拉取应发生在 TTL 之内,否则GET /crawl/job/{task_id}可能已经查不到数据。
  • 不配置 webhook 时,轮询方式依然可用:请求体省略webhook_config,直接轮询GET /crawl/job/{task_id},响应中status字段取值为"processing""completed""failed"
  • 若回调端点在别的机器上,示例中的http://localhost:11235结果拉取地址、以及服务自身监听地址(默认127.0.0.1:11235,对外暴露需配置CRAWL4AI_API_TOKEN并加反向代理,见config.yml注释)都要按实际部署调整。

配置完成后,用一次真实任务闭环验证:提交带webhook_config的任务 → 回调端点收到status: "completed"的通知 →docker logs中能看到Webhook delivered successfully。这三点都满足,说明 webhook 回调链路已经打通。更完整的处理器示例(含 TypeScript 客户端、完整 Flask 代码)可参考 WEBHOOK_EXAMPLES.md。

【免费下载链接】crawl4ai🚀🤖 Crawl4AI: Open-source LLM Friendly Web Crawler & Scraper. Don't be shy, join here: https://discord.gg/jP8KfhDhyN项目地址: https://gitcode.com/GitHub_Trending/craw/crawl4ai

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/9 22:02:13

制糖厂告别“人盯屏”:TDengine+IDMP如何实现主动告警与闭环管理

制糖季一到,最让我犯怵的其实不是工艺问题,而是夜班值班室里那排监控屏。每到榨季高峰期,中控室十几个屏幕轮播着压榨、清净、蒸发、煮糖各个工序的实时曲线,值班师傅们的眼睛几乎要长在屏幕上——生怕哪个罐的液位悄悄越了红线、…

作者头像 李华
网站建设 2026/9/9 22:02:01

Vitamio jar包实战:从so库配套到反编译与Linux替换打包

简介:这是一份面向Android开发者的Vitamio视频播放框架jar包资源,用于解决应用内多格式视频播放与流媒体处理需求。包内共173个文件,约11.64MB,包含88个class字节码(如MediaPlayer、VideoView、MediaController等核心播…

作者头像 李华
网站建设 2026/9/9 22:01:58

2026发动机工厂MES选型指南:从概念到落地的五层评估与厂商路线

2026年要操心的事不少,但最让我头疼的,还是发动机工厂的MES选型。才开完需求会,车间主任说要卡住漏装螺栓,质量部长说要能调出每一台缸体的加工追溯曲线,设备科说要看到每台机床的真实利用率,IT那边开口就是…

作者头像 李华
网站建设 2026/9/9 22:01:43

Windows CPU使用率控制工具:原理、实现与调优

简介:Windows刷CPU使用率工具是一款面向开发者、系统管理员与硬件爱好者的轻量级压力测试小工具,针对Windows平台设计,通过浏览器即可按需设定CPU占用比例与持续时间,模拟高负载运行场景,适用于性能调优、稳定性验证与…

作者头像 李华
网站建设 2026/9/9 22:01:28

ARM64架构源码编译MySQL 5.7:从环境准备到问题排查

最近在ARM64架构的机器上部署 CentOS 7 MySQL 5.7,网上搜了一圈,资料大多针对 x86_64,真正能在硬件平台上直接照抄的其实不多。这篇文章把我完整的实操过程整理出来,从环境确认、方案选型到依赖安装、源码编译、初始化配置以及最…

作者头像 李华
网站建设 2026/9/9 22:01:00

如何在 Windows 上安装 PPT Master 并跑通最小生成测试

如何在 Windows 上安装 PPT Master 并跑通最小生成测试 【免费下载链接】ppt-master AI turns documents or topics into real, native PowerPoint decks—with native shapes, transitions and animations, data-backed charts and tables on demand, audio narration from sp…

作者头像 李华