7.1 KiB
7.1 KiB
它采用了 “衔尾蛇 (Ouroboros)” 模式:工作流通过 API 自我调用,利用 Dify 的短生命周期特性,实现无限长度的任务队列处理。
Wiki Crawler RAG - 全自动递归爬虫架构设计文档 V1.2
1. 概述 (Overview)
本项目旨在构建一个基于 URL 锚定的增量式、全自动爬虫系统。 为了突破 Dify 单次运行的超时限制(Timeout)和内存瓶颈(OOM),本设计采用了 Map + Scrape + Recursion (递归) 架构。
核心特性:
- 一次点击,自动托管:用户仅需输入根 URL,工作流自动完成从发现到入库的全过程。
- 分批吞噬:每次运行只处理固定数量(如 50 个)页面,处理完毕后自动触发下一轮运行。
- 断点续传:基于数据库状态(Pending/Completed),任何时候中断都可接力继续。
2. 架构流程图 (Architecture Diagram)
graph TD
Start([开始节点<br>Input: url, run_mode]) --> Condition{判断模式<br>run_mode?}
%% 分支 A: 初始化模式
Condition -- "init (默认)" --> Map[HTTP: Firecrawl Map<br>获取全量 URL]
Map --> InitDB[SQL: init_crawl_queue<br>批量插入 Pending 队列]
InitDB --> TriggerWorker1[HTTP: Call Self<br>模式切换为 worker]
TriggerWorker1 --> End1([结束: 初始化完成])
%% 分支 B: 打工模式
Condition -- "worker" --> FetchBatch[SQL: Fetch Pending<br>LIMIT 50]
FetchBatch --> Iterator[迭代器<br>并发处理 50 个任务]
subgraph Iteration Loop
Iterator --> Scrape[HTTP: Firecrawl Scrape]
Scrape --> Clean[Python: 清洗 & 提取]
Clean --> Save[SQL: save_scrape_result<br>入库 & 标记 Completed]
end
Iterator --> CheckLeft[SQL: Count Remaining]
CheckLeft --> IfLeft{还有剩余吗?<br>Count > 0}
IfLeft -- "Yes" --> TriggerWorker2[HTTP: Call Self<br>递归调用 worker]
TriggerWorker2 --> End2([结束: 本轮批次完成])
IfLeft -- "No" --> EndSuccess([结束: 全部爬取完成])
3. 数据流动 (Data Flow)
3.1 状态流转
- Init 阶段:
Firecrawl Map->JSON List->DB (crawl_queue)。此时所有 URL 状态为'pending'。 - Worker 阶段:
DB (pending)->Dify List->Firecrawl Scrape->DB (knowledge_chunks)&DB (status='completed')。
3.2 递归逻辑
- Run 1:
run_mode='init'-> 发现 1000 个 URL -> 存库 -> 触发 Run 2。 - Run 2:
run_mode='worker'-> 取前 50 个 -> 抓取 -> 剩余 950 -> 触发 Run 3。 - Run ...: ...
- Run 21:
run_mode='worker'-> 取最后 50 个 -> 抓取 -> 剩余 0 -> 停止。
4. 数据库层准备 (Database Layer)
在部署工作流前,必须确保以下 SQL 函数已在 PostgreSQL 中执行。
4.1 核心表结构 (回顾)
crawl_tasks: 存储根任务信息。crawl_queue: 存储待爬取 URL 及其状态。knowledge_chunks: 存储切片后的文档内容。
4.2 新增初始化函数 (必需)
用于 Map 阶段结束后批量写入队列。
CREATE OR REPLACE FUNCTION init_crawl_queue(
p_urls JSONB,
p_root_url TEXT
)
RETURNS VOID AS $$
BEGIN
-- 1. 注册/更新主任务
INSERT INTO crawl_tasks (root_url) VALUES (p_root_url)
ON CONFLICT (root_url) DO UPDATE SET updated_at = DEFAULT;
-- 2. 批量插入待爬取队列 (忽略已存在的)
INSERT INTO crawl_queue (url, root_url, status)
SELECT x, p_root_url, 'pending'
FROM jsonb_array_elements_text(p_urls) AS x
ON CONFLICT (url) DO NOTHING;
END;
$$ LANGUAGE plpgsql;
5. 详细节点定义 (Node Definitions)
以下是 Dify 工作流中每个节点的详细配置参数。
5.1 开始节点 (Start)
- 变量 1:
url(Text, 必填) - 目标网站 URL。 - 变量 2:
run_mode(Select, 选填) - 运行模式。- 选项:
init,worker - 默认值:
init(保证手动运行时从头开始)
- 选项:
5.2 逻辑分支 (If-Else)
- 条件:
run_modeisinit - True 路径: 进入初始化流程。
- False 路径: 进入打工流程。
分支 A:初始化流程 (Init)
Node A1: HTTP 请求 (Firecrawl Map)
- API:
POST https://api.firecrawl.dev/v1/map - Body:
{ "url": "{{#start.url#}}", "limit": 5000, "includeSubdomains": true, "ignoreSitemap": false }
Node A2: SQL (Init Queue)
- Query:
SELECT init_crawl_queue($arg0::jsonb, $arg1); - arg0:
{{#NodeA1.body.links#}}(注意:Map 接口返回的是 links 数组) - arg1:
{{#start.url#}}
Node A3: HTTP 请求 (Trigger Self)
- API:
POST https://api.dify.ai/v1/workflows/run(替换为您的私有部署域名) - Headers:
Authorization: Bearer app-xxxxxxxx(使用本应用的 API Key) - Body:
{ "inputs": { "url": "{{#start.url#}}", "run_mode": "worker" }, "response_mode": "blocking", "user": "system-recursion-trigger" }
分支 B:打工流程 (Worker)
Node B1: SQL (Fetch Batch)
- Query:
SELECT url FROM crawl_queue WHERE root_url = $arg0 AND status = 'pending' LIMIT 50; - arg0:
{{#start.url#}} - Output: 记为
batch_list
Node B2: 迭代器 (Iterator)
-
Input:
{{#NodeB1.result#}} -
Parallelism: 开启 (推荐 5-10 并发)
内部节点 B2-1: HTTP (Scrape)
- API:
POST https://api.firecrawl.dev/v1/scrape - Body:
{"url": "{{#item.url#}}", "formats": ["markdown"]}
内部节点 B2-2: Python (Clean)
- Code: 清洗 Markdown,去除图片,截取正文,返回标准 JSON 结构 (含 content, title, url)。
内部节点 B2-3: SQL (Save)
- Query:
SELECT save_scrape_result($arg0::jsonb, $arg1, $arg2); - arg0:
{{#NodeB2-2.json_string#}} - arg1:
{{#item.url#}} - arg2:
{{#start.url#}}
- API:
Node B3: SQL (Check Remaining)
- Query:
SELECT count(*) as count FROM crawl_queue WHERE root_url = $arg0 AND status = 'pending'; - arg0:
{{#start.url#}}
Node B4: 逻辑分支 (Recursion Check)
- 条件:
{{#NodeB3.result[0].count#}}>0
Node B5: HTTP 请求 (Trigger Self - Recursion)
- (配置同 Node A3)
- 作用: 当检测到还有剩余任务时,再次调用自己,开启下一轮 50 个页面的抓取。
6. 异常处理与安全机制
-
死循环熔断:
- 建议在 HTTP Trigger Body 中增加一个
loop_count字段。 inputs: { "loop_count": {{#start.loop_count#}} + 1 }- 在 Start 节点后增加校验:如果
loop_count > 100,强制停止,防止意外消耗过多额度。
- 建议在 HTTP Trigger Body 中增加一个
-
API Rate Limit:
- 如果 Firecrawl 报错 429,迭代器内的 SQL 节点不会执行
UPDATE ... SET completed。 - 该 URL 状态仍为
pending。 - 下一轮 Worker 运行时,会再次尝试抓取该 URL(自动重试机制)。
- 如果 Firecrawl 报错 429,迭代器内的 SQL 节点不会执行
-
超时控制:
- 每个 Worker 批次处理 50 个页面,以单页 5秒计算,并发 10的情况下,耗时约 25-30秒。
- 远低于 Dify 默认的 300秒/600秒 超时限制,极其安全。