deepspider 0.3.1 → 0.3.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (78) hide show
  1. package/.env.example +3 -0
  2. package/README.md +13 -13
  3. package/package.json +6 -6
  4. package/src/agent/core/PanelBridge.js +28 -76
  5. package/src/agent/core/StreamHandler.js +139 -14
  6. package/src/agent/index.js +51 -12
  7. package/src/agent/logger.js +183 -8
  8. package/src/agent/middleware/report.js +41 -15
  9. package/src/agent/middleware/subagent.js +233 -0
  10. package/src/agent/middleware/toolGuard.js +77 -0
  11. package/src/agent/middleware/validationWorkflow.js +171 -0
  12. package/src/agent/prompts/system.js +181 -59
  13. package/src/agent/run.js +41 -6
  14. package/src/agent/skills/crawler/SKILL.md +64 -3
  15. package/src/agent/skills/crawler/evolved.md +9 -1
  16. package/src/agent/skills/dynamic-analysis/SKILL.md +74 -7
  17. package/src/agent/skills/env/SKILL.md +75 -0
  18. package/src/agent/skills/sandbox/SKILL.md +35 -0
  19. package/src/agent/skills/static-analysis/SKILL.md +98 -2
  20. package/src/agent/subagents/anti-detect.js +10 -20
  21. package/src/agent/subagents/captcha.js +7 -19
  22. package/src/agent/subagents/crawler.js +25 -37
  23. package/src/agent/subagents/factory.js +109 -9
  24. package/src/agent/subagents/index.js +4 -13
  25. package/src/agent/subagents/js2python.js +7 -19
  26. package/src/agent/subagents/reverse.js +180 -0
  27. package/src/agent/tools/analysis.js +84 -1
  28. package/src/agent/tools/anti-detect.js +5 -2
  29. package/src/agent/tools/browser.js +160 -0
  30. package/src/agent/tools/capture.js +24 -3
  31. package/src/agent/tools/correlate.js +129 -15
  32. package/src/agent/tools/crawler.js +2 -1
  33. package/src/agent/tools/crawlerGenerator.js +90 -0
  34. package/src/agent/tools/debug.js +43 -6
  35. package/src/agent/tools/evolve.js +5 -2
  36. package/src/agent/tools/extractor.js +5 -1
  37. package/src/agent/tools/file.js +14 -5
  38. package/src/agent/tools/generateHook.js +66 -0
  39. package/src/agent/tools/hookManager.js +19 -9
  40. package/src/agent/tools/index.js +33 -20
  41. package/src/agent/tools/nodejs.js +41 -6
  42. package/src/agent/tools/sandbox.js +21 -1
  43. package/src/agent/tools/scratchpad.js +70 -0
  44. package/src/agent/tools/tracing.js +26 -0
  45. package/src/agent/tools/verifyAlgorithm.js +117 -0
  46. package/src/browser/EnvBridge.js +27 -13
  47. package/src/browser/client.js +124 -18
  48. package/src/browser/collector.js +101 -22
  49. package/src/browser/defaultHooks.js +3 -1
  50. package/src/browser/hooks/index.js +5 -0
  51. package/src/browser/interceptors/AntiDebugInterceptor.js +132 -0
  52. package/src/browser/interceptors/NetworkInterceptor.js +76 -12
  53. package/src/browser/interceptors/ScriptInterceptor.js +32 -7
  54. package/src/browser/interceptors/index.js +1 -0
  55. package/src/browser/ui/analysisPanel.js +469 -464
  56. package/src/cli/commands/config.js +11 -3
  57. package/src/config/paths.js +9 -1
  58. package/src/config/settings.js +7 -1
  59. package/src/core/PatchGenerator.js +24 -4
  60. package/src/core/Sandbox.js +140 -3
  61. package/src/env/EnvCodeGenerator.js +60 -88
  62. package/src/env/modules/bom/history.js +6 -0
  63. package/src/env/modules/bom/location.js +6 -0
  64. package/src/env/modules/bom/navigator.js +13 -0
  65. package/src/env/modules/bom/screen.js +6 -0
  66. package/src/env/modules/bom/storage.js +7 -0
  67. package/src/env/modules/dom/document.js +14 -0
  68. package/src/env/modules/dom/event.js +4 -0
  69. package/src/env/modules/index.js +27 -10
  70. package/src/env/modules/webapi/fetch.js +4 -0
  71. package/src/env/modules/webapi/url.js +4 -0
  72. package/src/env/modules/webapi/xhr.js +8 -0
  73. package/src/store/DataStore.js +125 -42
  74. package/src/store/Store.js +2 -1
  75. package/src/agent/subagents/dynamic.js +0 -64
  76. package/src/agent/subagents/env-agent.js +0 -82
  77. package/src/agent/subagents/sandbox.js +0 -55
  78. package/src/agent/subagents/static.js +0 -66
@@ -0,0 +1,171 @@
1
+ /**
2
+ * DeepSpider - 验证流程强制中间件
3
+ * 使用 state machine 模式确保两层验证都通过才允许保存报告
4
+ */
5
+
6
+ import { createMiddleware } from 'langchain';
7
+ import { z } from 'zod';
8
+
9
+ // 验证状态 schema
10
+ const validationStateSchema = z.object({
11
+ validationStage: z.enum(['none', 'algorithm', 'end_to_end', 'passed']).default('none'),
12
+ algorithmVerified: z.boolean().default(false),
13
+ endToEndVerified: z.boolean().default(false),
14
+ savedPythonCode: z.boolean().default(false),
15
+ });
16
+
17
+ /**
18
+ * 检测是否调用了 js2python 子代理(算法验证)
19
+ */
20
+ function isAlgorithmVerification(state) {
21
+ // 检查最近的 tool_calls 是否包含 task 且 subagent_type 为 js2python
22
+ const messages = state.messages || [];
23
+ for (let i = messages.length - 1; i >= 0; i--) {
24
+ const msg = messages[i];
25
+ if (msg.type === 'ai' && msg.tool_calls) {
26
+ for (const call of msg.tool_calls) {
27
+ if (call.name === 'task' && call.args?.subagent_type === 'js2python') {
28
+ return true;
29
+ }
30
+ }
31
+ }
32
+ }
33
+ return false;
34
+ }
35
+
36
+ /**
37
+ * 检测是否通过了端到端验证(sandbox_execute 成功返回目标数据)
38
+ */
39
+ function isEndToEndVerification(state) {
40
+ const messages = state.messages || [];
41
+ for (let i = messages.length - 1; i >= 0; i--) {
42
+ const msg = messages[i];
43
+ if (msg.type === 'tool' && msg.name === 'sandbox_execute') {
44
+ try {
45
+ const result = JSON.parse(msg.content);
46
+ // 检查是否成功且包含目标数据
47
+ if (result.success && !result.error) {
48
+ return true;
49
+ }
50
+ } catch {
51
+ // 解析失败,忽略
52
+ }
53
+ }
54
+ }
55
+ return false;
56
+ }
57
+
58
+ /**
59
+ * 检测是否已保存 Python 代码(artifact_save 且路径包含 .py)
60
+ */
61
+ function isPythonCodeSaved(state) {
62
+ const messages = state.messages || [];
63
+ for (let i = messages.length - 1; i >= 0; i--) {
64
+ const msg = messages[i];
65
+ if (msg.type === 'tool' && msg.name === 'artifact_save') {
66
+ try {
67
+ const result = JSON.parse(msg.content);
68
+ if (result.success && result.path?.endsWith('.py')) {
69
+ return true;
70
+ }
71
+ } catch {
72
+ // 解析失败,忽略
73
+ }
74
+ }
75
+ }
76
+ return false;
77
+ }
78
+
79
+ /**
80
+ * 创建验证流程中间件
81
+ * 强制要求:算法验证 → 端到端验证 → 保存代码 → 保存报告
82
+ */
83
+ export function createValidationWorkflowMiddleware() {
84
+ return createMiddleware({
85
+ name: 'validationWorkflow',
86
+ stateSchema: validationStateSchema,
87
+
88
+ // 每次模型调用后检查验证状态
89
+ afterModel: (state) => {
90
+ const updates = {};
91
+
92
+ // 检查当前验证阶段
93
+ if (!state.algorithmVerified && isAlgorithmVerification(state)) {
94
+ updates.algorithmVerified = true;
95
+ updates.validationStage = 'algorithm';
96
+ }
97
+
98
+ if (!state.endToEndVerified && isEndToEndVerification(state)) {
99
+ updates.endToEndVerified = true;
100
+ updates.validationStage = 'end_to_end';
101
+ }
102
+
103
+ if (!state.savedPythonCode && isPythonCodeSaved(state)) {
104
+ updates.savedPythonCode = true;
105
+ }
106
+
107
+ // 所有验证通过
108
+ if (updates.algorithmVerified && updates.endToEndVerified && updates.savedPythonCode) {
109
+ updates.validationStage = 'passed';
110
+ }
111
+
112
+ return Object.keys(updates).length > 0 ? updates : undefined;
113
+ },
114
+
115
+ // 工具调用前检查是否允许
116
+ wrapToolCall: async (request, handler) => {
117
+ const toolName = request.tool?.name ?? request.toolCall?.name;
118
+ const args = request.toolCall?.args || {};
119
+ const state = request.state || {};
120
+
121
+ // 保存 Python 代码前检查算法验证
122
+ if (toolName === 'artifact_save') {
123
+ const filePath = args.file_path || '';
124
+ if (filePath.endsWith('.py')) {
125
+ if (!state.algorithmVerified) {
126
+ return {
127
+ type: 'tool',
128
+ name: 'artifact_save',
129
+ content: JSON.stringify({
130
+ success: false,
131
+ error: '保存 Python 代码前必须先完成算法验证。请委托 js2python 子代理验证加密/解密逻辑。',
132
+ requiredStep: '算法验证',
133
+ hint: '使用 task 工具,指定 subagent_type: "js2python"',
134
+ }),
135
+ tool_call_id: request.toolCall?.id || 'blocked',
136
+ };
137
+ }
138
+ }
139
+ }
140
+
141
+ // 保存报告前强制检查验证状态
142
+ if (toolName === 'save_analysis_report') {
143
+ const stage = state.validationStage || 'none';
144
+
145
+ if (stage !== 'passed') {
146
+ // 构造阻止消息
147
+ const missing = [];
148
+ if (!state.algorithmVerified) missing.push('算法验证(委托 js2python)');
149
+ if (!state.endToEndVerified) missing.push('端到端验证(sandbox_execute)');
150
+ if (!state.savedPythonCode) missing.push('保存 Python 代码');
151
+
152
+ return {
153
+ type: 'tool',
154
+ name: 'save_analysis_report',
155
+ content: JSON.stringify({
156
+ success: false,
157
+ error: `验证未完成,无法保存报告。缺少步骤:${missing.join('、')}`,
158
+ requiredSteps: missing,
159
+ currentStage: stage,
160
+ }),
161
+ tool_call_id: request.toolCall?.id || 'blocked',
162
+ };
163
+ }
164
+ }
165
+
166
+ return handler(request);
167
+ },
168
+ });
169
+ }
170
+
171
+ export default createValidationWorkflowMiddleware;
@@ -15,21 +15,56 @@ export const systemPrompt = `你是 DeepSpider,一个智能爬虫 Agent。你
15
15
  - **Hook 已经注入**,数据已在自动记录中
16
16
  - 直接使用工具获取已捕获的数据
17
17
 
18
+ ## 你的职责
19
+
20
+ 你是调度者,不是执行者。负责分析、决策和委托子代理执行具体任务。
21
+
18
22
  ## 委托子代理
19
23
 
20
- **原则:简单任务自己做,复杂任务委托子代理。**
24
+ **原则:你负责分析和调度,子代理负责执行。**
21
25
 
22
26
  | 场景特征 | 委托给 |
23
27
  |----------|--------|
24
- | 重度混淆 + 环境检测多 | env-agent |
25
- | 混淆代码需要深度反混淆 | static-agent |
26
- | Python转换多次失败 | js2python |
27
- | 需要复杂断点调试 | dynamic-agent |
28
- | 沙箱执行反复报错 | sandbox-agent |
28
+ | 加密分析(反混淆、AST、断点、Hook、沙箱验证) | reverse-agent |
29
+ | 已还原的 JS 转 Python | js2python |
30
+ | 生成完整爬虫脚本 | crawler |
31
+ | 验证码处理 | captcha |
32
+ | 反检测/指纹/代理 | anti-detect |
29
33
 
30
34
  使用 \`task\` 工具委托,指定 \`subagent_type\` 和详细任务描述。
31
35
 
32
- **传递浏览器状态**:如果浏览器已打开,任务描述中必须包含"[浏览器已就绪]"和当前页面 URL。`;
36
+ **传递浏览器状态**:如果浏览器已打开,任务描述中必须包含"[浏览器已就绪]"和当前页面 URL
37
+
38
+ ### 委托最佳实践
39
+
40
+ - 委托前先用最小代价验证关键假设(如一次 run_node_code 快速测试)
41
+ - **description 包含用户原始需求**(子代理看不到用户消息)
42
+ - **委托 reverse-agent 时通过 context 传递**:site, requestId, targetParam, url
43
+ - **description 包含分析结论**:调用栈摘要、可疑参数、加密特征
44
+ - 示例:"调用栈:send@match/1:652 → oo0O0@match/1:960,可疑参数:m=<32位hex>"
45
+
46
+ ## 禁止行为(必须遵守)
47
+
48
+ **你禁止自己编写代码**。当用户需要代码时,你必须:
49
+
50
+ 1. **不要在回复中输出代码片段** — 即使是"示例代码"也不允许
51
+ 2. **必须委托子代理**:
52
+ - Python 加密/解密代码 → \`task\` 委托 js2python
53
+ - 完整爬虫脚本 → \`generate_crawler_code\` 让用户选择框架,然后委托 crawler
54
+ 3. **分析结果用文字描述** — 说明接口、参数、数据结构,不要写代码
55
+
56
+ **为什么?**
57
+ - 你是调度者,代码质量由专业子代理保证
58
+ - 用户需要的是可运行的文件,不是聊天框里的代码片段
59
+ - 子代理会验证代码、保存文件、生成完整报告
60
+
61
+ **正确示例**:
62
+ - ✅ "我发现了 API 接口结构:GET /api/list?page=1,返回 JSON 数据。需要我生成爬虫代码吗?"
63
+ - ✅ 调用 \`generate_crawler_code\` → 用户选择框架 → 委托 crawler 生成完整代码文件
64
+
65
+ **错误示例**:
66
+ - ❌ 在回复中直接写 "\`\`\`python\\nimport requests\\n..." 代码块
67
+ - ❌ 说"这是一个简单的示例代码"然后输出代码`;
33
68
 
34
69
  /**
35
70
  * 完整分析专用提示 - 仅在用户请求完整分析时使用
@@ -41,83 +76,170 @@ export const fullAnalysisPrompt = `
41
76
 
42
77
  ### 分析思路
43
78
 
44
- 1. **识别加密类型** - 先判断是哪种场景:
79
+ 1. **定位目标请求** - 找到数据来源的 API 接口:
80
+ - 用 search_in_responses 搜索用户选中的文本,定位数据来源请求
81
+ - 或用 get_request_list 浏览所有 XHR/Fetch 请求
82
+ - 记录请求的 site 和 id,委托 reverse-agent 时通过 context 参数传递
83
+
84
+ 2. **识别加密类型** - 查看请求详情,判断场景:
45
85
  - Headers 动态签名(如 X-Sign, X-Token)
46
86
  - Cookie 动态生成(如反爬 Cookie)
47
87
  - 请求参数加密(POST body 加密)
48
88
  - 响应数据解密(接口返回加密数据)
49
89
 
50
- 2. **判断复杂度** - 决定自己做还是委托:
51
- - **简单场景**(自己做):标准加密算法、代码清晰可读
52
- - **复杂场景**(委托子代理):重度混淆、多层嵌套、环境检测多
90
+ 3. **根据加密情况分流**:
91
+
92
+ **🟢 无加密(快速路径)** — 如果请求参数全部为明文、无动态签名、无加密响应:
93
+ - 跳过 reverse-agent 和 js2python 委托
94
+ - 用 \`run_node_code\` 发送一次完整请求,确认接口可用且返回目标数据
95
+ - 调用 \`save_analysis_report\` 保存分析报告(无加密代码时 pythonCodeFile 可省略)
96
+ - 调用 \`generate_crawler_code\` → 用户选择框架 → 委托 crawler 生成爬虫代码
97
+ - 对 crawler 生成的代码做端到端验证(运行代码确认能拿到目标数据)
53
98
 
54
- 3. **验证与输出** - **必须验证代码能正确运行**,才能生成报告
99
+ **🔴 有加密(完整路径)** 存在任何加密特征时:
100
+ - 委托 reverse-agent 分析加密逻辑
101
+ - 委托 js2python 生成 Python 加密代码并验证算法正确
102
+ - 端到端验证:运行生成的代码,确认能正确复现请求并拿到目标数据
103
+ - 验证通过后保存报告并生成爬虫
55
104
 
56
- ### 强制验证流程(必须遵守)
105
+ 4. **验证与输出** - **所有生成的代码都必须经过端到端验证**
57
106
 
58
- **验证分为两个层次,必须全部通过:**
107
+ ### 端到端验证(所有路径都必须执行)
59
108
 
60
- #### 第一层:算法验证(必须)
61
- 验证加密/解密函数本身是否正确:
62
- 1. 使用 \`run_python_code\` 执行加密/解密代码
63
- 2. 检查:encrypt(plaintext) → ciphertext → decrypt() → plaintext
109
+ **端到端验证 = 运行生成的代码,确认能正确复现请求并拿到与浏览器一致的目标数据。**
64
110
 
65
- #### 第二层:端到端验证(必须)
66
- 验证完整请求能否获取到目标数据:
67
- 1. 使用生成的代码构造完整请求(包含正确的 Headers、Cookies、加密参数)
68
- 2. 发送请求到目标接口
69
- 3. **检查响应是否包含用户要求的目标数据**
111
+ 不管是 Python 代码还是 JS 代码,不管有没有加密,都必须验证:
112
+ 1. 运行生成的代码发送请求
113
+ 2. 检查响应包含目标数据(与浏览器中看到的一致)
114
+ 3. 如果有加密,验证加密参数与浏览器请求中的格式一致
70
115
 
71
- **端到端验证的成功标准**:
72
- - 响应状态码正常(200)
73
- - ✅ 响应内容包含目标数据
74
- - ❌ 响应返回错误信息(如"参数错误"、"签名无效")→ 验证失败
116
+ **有加密时额外验证:**
117
+ - 算法验证:委托 js2python 验证加密/解密逻辑能正确还原
75
118
 
76
- **端到端验证失败时的处理**:
77
- 1. 分析错误响应,判断缺少什么
78
- 2. 使用 \`get_request_detail\` 查看原始请求的完整信息
79
- 3. 使用 \`get_cookies\` 获取浏览器当前 Cookie
80
- 4. 补全缺失的参数后重新验证
81
- 5. 如果多次失败,明确告知用户当前进度和遇到的问题
119
+ **验证失败处理**:分析错误 → 用 get_request_detail 比对 → 补全参数重试
82
120
 
83
121
  ### 输出与保存
84
122
 
85
- **推荐流程**(分步保存):
86
- 1. 先用 \`artifact_save\` 保存 Python 代码到文件(如 \`{domain}/decrypt.py\`)
87
- 2. 再调用 \`save_analysis_report\`,传入 \`pythonCodeFile\` 文件路径
88
- 3. **必须在最终输出中告知用户文件保存路径**
123
+ **有加密时**(分步保存):
124
+ 1. 先用 \`artifact_save\` 保存加密代码到文件(如 \`{domain}/decrypt.py\`)
125
+ 2. 端到端验证通过后,调用 \`save_analysis_report\`,传入 \`pythonCodeFile\` 文件路径
126
+ 3. 再调用 \`generate_crawler_code\` → 委托 crawler 生成完整爬虫
89
127
 
90
- **调用 save_analysis_report 的前提条件**(必须全部满足):
91
- 1. 已使用 \`run_python_code\` \`verify_with_python\` 验证代码能正确运行
92
- 2. 验证结果与预期一致
93
- 3. 已用 \`artifact_save\` 保存代码文件
128
+ **无加密时**:
129
+ 1. \`run_node_code\` 验证接口可用后,直接调用 \`save_analysis_report\`(无需 pythonCodeFile)
130
+ 2. 再调用 \`generate_crawler_code\` → 委托 crawler 生成爬虫代码
131
+ 3. crawler 生成的代码做端到端验证
94
132
 
95
- **完成后必须输出文件路径**:
96
- \`\`\`
97
- 📁 生成的文件:
98
- - Python 代码: ~/.deepspider/output/{domain}/decrypt.py
99
- - 分析报告: ~/.deepspider/output/{domain}/report.html
100
- \`\`\`
133
+ **调用 save_analysis_report 的前提条件**:
134
+ - 有加密:加密代码已通过端到端验证,且已用 \`artifact_save\` 保存
135
+ - 无加密:已用 \`run_node_code\` 确认接口可用且返回目标数据
136
+
137
+ **必须在最终输出中告知用户文件保存路径。**
101
138
 
102
139
  ### 任务完成标准
103
140
 
104
- **任务只有在满足以下条件时才算完成:**
105
- 1. 定位数据来源接口
106
- 2. 分析加密/解密算法
107
- 3. 生成可运行的代码
108
- 4. **端到端验证:发送请求能获取到目标数据**
109
- 5. **保存报告:调用 save_analysis_report 保存分析结果**
141
+ **无加密快速路径:**
142
+ 1. 定位数据来源接口
143
+ 2. run_node_code 验证接口可用
144
+ 3. 调用 save_analysis_report 保存报告
145
+ 4. 调用 generate_crawler_code → 用户选择框架 → 委托 crawler 生成代码
146
+ 5. 端到端验证通过(运行 crawler 生成的代码能拿到目标数据)
147
+
148
+ **有加密完整路径:**
149
+ 1. 定位数据来源接口
150
+ 2. 委托 reverse-agent 分析加密逻辑
151
+ 3. 委托 js2python 生成可运行的加密代码
152
+ 4. 端到端验证通过(运行生成的代码能正确复现请求)
153
+ 5. 调用 save_analysis_report 保存报告
154
+ 6. 调用 generate_crawler_code → 用户选择框架 → 委托 crawler 生成爬虫
155
+
156
+ ### 生成完整爬虫脚本
157
+
158
+ **分析报告保存后,必须调用 \`generate_crawler_code\` 工具:**
159
+
160
+ \`\`\`
161
+ 步骤1: 调用 save_analysis_report 保存分析报告
162
+ 步骤2: 调用 generate_crawler_code 工具,传入分析摘要
163
+ - 工具会暂停执行,向面板发送可点击的框架选项
164
+ - 用户点击后,工具自动恢复并返回用户选择的框架名称
165
+ 步骤3: 根据返回值委托 crawler 子代理生成代码("不需要"则结束)
166
+ \`\`\`
110
167
 
111
- **以下情况不算完成**:
112
- - ❌ 只验证了加密算法正确,但请求返回错误
113
- - ❌ 请求返回"参数错误"、"签名无效"等
114
- - 没有实际获取到用户要求的目标数据
115
- - ❌ 验证成功但没有调用 save_analysis_report
168
+ **crawler 子代理任务要求:**
169
+ - 基于分析结果和已有的代码模块(如有加密代码则整合)
170
+ - 生成完整可运行的爬虫脚本(不是片段)
171
+ - 使用 artifact_save 保存到 ~/.deepspider/output/{domain}/crawler.{py/json}
116
172
 
117
173
  ### 禁止行为
118
174
  - 禁止只验证算法正确就认为任务完成
119
175
  - 禁止在端到端验证失败时保存报告
120
176
  - 禁止忽略错误响应
121
- - 禁止假装任务完成`;
177
+ - 禁止假装任务完成
178
+ - **禁止跳过人工确认步骤直接生成爬虫脚本**`;
179
+
180
+ export const tracePrompt = `
181
+ ## 追踪数据来源
182
+
183
+ 找到用户选中数据的来源接口,快速返回结果。
184
+
185
+ ### 步骤
186
+
187
+ 1. 用 search_in_responses 搜索选中文本的关键词,定位数据来源请求
188
+ 2. 如果搜索无结果,用 get_request_list 浏览 XHR/Fetch 请求列表,根据 URL 和时间判断
189
+ 3. 找到后用 get_request_detail 查看接口详情
190
+
191
+ ### 输出格式
192
+
193
+ 简洁总结:
194
+ - 接口地址和方法(GET/POST)
195
+ - 关键请求参数
196
+ - 是否有加密迹象(Headers 签名、加密参数、加密响应)
197
+ - 如需深入分析,建议用户选择"分析加密参数"`;
198
+
199
+ export const decryptPrompt = `
200
+ ## 分析加密参数
201
+
202
+ 定位数据接口,分析加密机制,委托 reverse-agent 破解。
203
+
204
+ ### 步骤
205
+
206
+ 1. **定位接口** — search_in_responses 搜索选中文本,找到数据来源请求
207
+ 2. **识别加密** — get_request_detail 查看请求详情,判断加密类型:
208
+ - Headers 动态签名(如 X-Sign, X-Token)
209
+ - Cookie 动态生成
210
+ - 请求参数加密(POST body)
211
+ - 响应数据解密
212
+ 3. **委托分析** — 定位到目标请求后,立即委托 reverse-agent:
213
+ - 通过 context 传递 site、requestId、targetParam、url
214
+ - description 包含 get_request_initiator 返回的调用栈摘要
215
+ - description 包含可疑加密参数
216
+
217
+ ### 输出
218
+
219
+ 总结分析结果,包括:
220
+ - 加密算法类型和实现方式
221
+ - 关键参数的生成逻辑
222
+ - 如已还原算法,告知用户可选择"完整分析并生成爬虫"生成代码`;
223
+
224
+ export const extractPrompt = `
225
+ ## 提取页面结构
226
+
227
+ 分析用户选中元素的 DOM 结构,生成爬虫所需的选择器和字段配置。
228
+
229
+ ### 步骤
230
+
231
+ 1. **分析选中元素** — 根据 XPath 判断元素在页面中的位置和层级关系
232
+ 2. **识别列表结构** — 判断选中元素是否属于列表项(表格行、卡片列表等)
233
+ 3. **生成选择器** — 为每个字段生成 XPath 和 CSS 选择器
234
+ 4. **检测分页** — 查找分页组件(下一页按钮、页码、滚动加载)
235
+ 5. **检测数据来源** — 用 search_in_responses 快速检查数据是否来自 API(如果是,建议用"追踪数据来源")
236
+
237
+ ### 输出
238
+
239
+ 结构化的字段配置:
240
+ - 每个字段的名称、选择器、数据类型
241
+ - 列表容器选择器
242
+ - 分页方式和选择器(如有)
243
+ - 数据来源判断(SSR 渲染 vs API 接口)`;
122
244
 
123
245
  export default systemPrompt;
package/src/agent/run.js CHANGED
@@ -10,12 +10,14 @@ import readline from 'readline';
10
10
  import { readFileSync } from 'fs';
11
11
  import { marked } from 'marked';
12
12
  import { createDeepSpiderAgent } from './index.js';
13
- import { fullAnalysisPrompt } from './prompts/system.js';
13
+ import { fullAnalysisPrompt, tracePrompt, decryptPrompt, extractPrompt } from './prompts/system.js';
14
14
  import { getBrowser } from '../browser/index.js';
15
15
  import { markHookInjected } from './tools/runtime.js';
16
16
  import { createLogger } from './logger.js';
17
17
  import { browserTools } from './tools/browser.js';
18
18
  import { ensureConfig } from './setup.js';
19
+ import { getConfigValues } from '../config/settings.js';
20
+ import { PATHS, ensureDir } from '../config/paths.js';
19
21
  import { StreamHandler, PanelBridge } from './core/index.js';
20
22
 
21
23
  let rl = null;
@@ -56,6 +58,16 @@ async function showReportFromFile(mdFilePath) {
56
58
  }
57
59
  }
58
60
 
61
+ function getActionPrompt(action) {
62
+ switch (action) {
63
+ case 'trace': return tracePrompt;
64
+ case 'decrypt': return decryptPrompt;
65
+ case 'extract': return extractPrompt;
66
+ case 'full':
67
+ default: return fullAnalysisPrompt;
68
+ }
69
+ }
70
+
59
71
  /**
60
72
  * 处理浏览器消息(通过 CDP binding 接收)
61
73
  */
@@ -72,12 +84,13 @@ async function handleBrowserMessage(data, page) {
72
84
  ).join('\n');
73
85
 
74
86
  const supplementText = data.text ? `\n\n用户补充说明: ${data.text}` : '';
87
+ const action = data.action || 'full';
75
88
 
76
- userPrompt = `${browserReadyPrefix}用户选中了以下数据要求完整分析:
89
+ userPrompt = `${browserReadyPrefix}用户选中了以下数据:
77
90
 
78
91
  ${elementsDesc}${supplementText}
79
92
 
80
- ${fullAnalysisPrompt}`;
93
+ ${getActionPrompt(action)}`;
81
94
  } else if (data.type === 'generate-config') {
82
95
  const config = data.config;
83
96
  userPrompt = `${browserReadyPrefix}请使用 crawler 子代理生成爬虫。
@@ -117,6 +130,20 @@ ${elementsDesc}`;
117
130
  });
118
131
  }
119
132
  return;
133
+ } else if (data.type === 'choice') {
134
+ // interrupt 恢复:用户点击了选项
135
+ console.log('\n[浏览器] 用户选择: ' + data.value);
136
+ await streamHandler.resumeInterrupt(data.value);
137
+ console.log('\n');
138
+ process.stdout.write('> ');
139
+ return;
140
+ } else if (data.type === 'confirm-result') {
141
+ // interrupt 恢复:用户点击了确认/取消
142
+ console.log('\n[浏览器] 用户' + (data.confirmed ? '确认' : '取消'));
143
+ await streamHandler.resumeInterrupt(data.confirmed);
144
+ console.log('\n');
145
+ process.stdout.write('> ');
146
+ return;
120
147
  } else {
121
148
  return;
122
149
  }
@@ -151,6 +178,7 @@ async function init() {
151
178
  const args = process.argv.slice(2);
152
179
  targetUrl = args.find(arg => arg.startsWith('http://') || arg.startsWith('https://'));
153
180
  DEBUG = process.env.DEBUG === 'true' || args.includes('--debug');
181
+ const PERSIST = args.includes('--persist');
154
182
  debugFn = (...a) => { if (DEBUG) console.log('[DEBUG]', ...a); };
155
183
 
156
184
  debugFn('init: 启动');
@@ -169,7 +197,7 @@ async function init() {
169
197
  output: process.stdout,
170
198
  });
171
199
 
172
- const logger = createLogger({ enabled: DEBUG, verbose: false });
200
+ const loggerCallbacks = createLogger();
173
201
 
174
202
  async function onReportReady(mdFilePath) {
175
203
  console.log('[report] 中间件触发报告显示:', mdFilePath);
@@ -181,7 +209,7 @@ async function init() {
181
209
  agentConfig = {
182
210
  configurable: { thread_id: `deepspider-${Date.now()}` },
183
211
  recursionLimit: 5000,
184
- callbacks: logger ? [logger] : [],
212
+ callbacks: loggerCallbacks,
185
213
  };
186
214
 
187
215
  // 初始化流处理器
@@ -201,7 +229,14 @@ async function init() {
201
229
  console.log(`正在打开: ${targetUrl}\n`);
202
230
  try {
203
231
  debugFn('init: 获取浏览器实例');
204
- browser = await getBrowser();
232
+ const browserOptions = {};
233
+ const config = getConfigValues();
234
+ if (PERSIST || config.persistBrowserData) {
235
+ ensureDir(PATHS.BROWSER_DATA_DIR);
236
+ browserOptions.userDataDir = PATHS.BROWSER_DATA_DIR;
237
+ console.log(`[持久化模式] 浏览器数据保存在 ${PATHS.BROWSER_DATA_DIR}`);
238
+ }
239
+ browser = await getBrowser(browserOptions);
205
240
  browser.onMessage = handleBrowserMessage;
206
241
  debugFn('init: 导航到目标URL');
207
242
  await browser.navigate(targetUrl);
@@ -1,9 +1,70 @@
1
1
  ---
2
2
  name: crawler
3
3
  description: |
4
- crawler 相关经验。
4
+ 爬虫编排经验。采集流程设计、数据提取策略、脚本生成。
5
+ 触发:爬虫编排、数据采集、脚本生成、请求重放。
5
6
  ---
6
7
 
7
- # crawler
8
+ # 爬虫编排经验
8
9
 
9
- 自动创建的 skill 目录。
10
+ ## 采集流程设计
11
+
12
+ ### 请求重放型(优先)
13
+ 适用于:API 接口明确、参数已破解的场景。
14
+ ```
15
+ 1. 分析目标接口(URL、Method、Headers、参数)
16
+ 2. 确认加密参数已还原(由 reverse-agent 完成)
17
+ 3. 构造请求:固定 Headers + 动态参数生成
18
+ 4. 翻页/遍历逻辑
19
+ 5. 数据解析 + 存储
20
+ ```
21
+
22
+ ### 浏览器渲染型
23
+ 适用于:SPA 页面、数据由 JS 动态渲染、无法直接请求 API。
24
+ ```
25
+ 1. 页面加载 + 等待渲染完成
26
+ 2. 元素定位 + 数据提取
27
+ 3. 翻页操作(点击/滚动)
28
+ 4. 循环采集
29
+ ```
30
+
31
+ ## 数据提取策略
32
+
33
+ | 数据位置 | 提取方式 |
34
+ |----------|----------|
35
+ | API JSON 响应 | JSONPath / 字段映射 |
36
+ | HTML 结构化 | CSS Selector / XPath |
37
+ | JS 渲染内容 | element.innerText(渲染后) |
38
+ | 表格数据 | 按行列索引提取 |
39
+
40
+ ## 脚本生成规范
41
+
42
+ ### Python 脚本结构
43
+ ```python
44
+ import requests
45
+
46
+ class Crawler:
47
+ def __init__(self):
48
+ self.session = requests.Session()
49
+ self.session.headers.update({...})
50
+
51
+ def get_sign(self, params):
52
+ """加密参数生成(由逆向分析提供)"""
53
+ pass
54
+
55
+ def fetch_page(self, page):
56
+ """单页采集"""
57
+ pass
58
+
59
+ def run(self, max_pages):
60
+ """主流程"""
61
+ for page in range(1, max_pages + 1):
62
+ data = self.fetch_page(page)
63
+ self.save(data)
64
+ ```
65
+
66
+ ### 关键注意事项
67
+ - Headers 要完整(User-Agent、Referer、Cookie)
68
+ - 请求间隔随机化(1-3s)
69
+ - 异常重试(网络超时、频控返回)
70
+ - 数据去重(基于唯一标识)
@@ -1,5 +1,5 @@
1
1
  ---
2
- total: 1
2
+ total: 3
3
3
  last_merged: null
4
4
  ---
5
5
 
@@ -11,6 +11,14 @@ last_merged: null
11
11
  **场景**: 招标采购网站使用 AES-CFB 加密请求参数,Key和IV为 "jinrun2024secret",需要实现 Python 加密模块并集成到爬虫中
12
12
  **经验**: AES-CFB 加密网站爬虫开发时,使用 pycryptodome 的 AES.MODE_CFB 模式,segment_size=128,密钥和IV需要截取16字节。加密后Base64编码,请求体直接发送密文字符串。
13
13
 
14
+ ### [2026-02-13] 猿人学题目m参数生成规范
15
+ **场景**: 猿人学第1题,需要生成m参数:timestamp = Date.now() + 100000000,md5 = MD5(timestamp),m = md5 + '丨' + timestamp
16
+ **经验**: 注意使用中文竖线'丨'而非英文竖线'|',需要对m参数进行URL编码,第1页不传page参数,第2-5页需要page参数
17
+
18
+ ### [2026-02-14] 政府公示网站爬虫最佳实践
19
+ **场景**: 金昌市信用公示网站行政处罚数据爬取,采用列表+详情两步爬取策略
20
+ **经验**: 政府公示网站通常采用列表接口+详情接口的分离设计,应先获取所有ID再批量请求详情,并添加0.5-1秒随机延迟以示友好
21
+
14
22
  ## 近期发现
15
23
 
16
24
  <!-- 最近发现,FIFO 滚动,最多保留 10 条 -->