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.
- package/.env.example +3 -0
- package/README.md +13 -13
- package/package.json +6 -6
- package/src/agent/core/PanelBridge.js +28 -76
- package/src/agent/core/StreamHandler.js +139 -14
- package/src/agent/index.js +51 -12
- package/src/agent/logger.js +183 -8
- package/src/agent/middleware/report.js +41 -15
- package/src/agent/middleware/subagent.js +233 -0
- package/src/agent/middleware/toolGuard.js +77 -0
- package/src/agent/middleware/validationWorkflow.js +171 -0
- package/src/agent/prompts/system.js +181 -59
- package/src/agent/run.js +41 -6
- package/src/agent/skills/crawler/SKILL.md +64 -3
- package/src/agent/skills/crawler/evolved.md +9 -1
- package/src/agent/skills/dynamic-analysis/SKILL.md +74 -7
- package/src/agent/skills/env/SKILL.md +75 -0
- package/src/agent/skills/sandbox/SKILL.md +35 -0
- package/src/agent/skills/static-analysis/SKILL.md +98 -2
- package/src/agent/subagents/anti-detect.js +10 -20
- package/src/agent/subagents/captcha.js +7 -19
- package/src/agent/subagents/crawler.js +25 -37
- package/src/agent/subagents/factory.js +109 -9
- package/src/agent/subagents/index.js +4 -13
- package/src/agent/subagents/js2python.js +7 -19
- package/src/agent/subagents/reverse.js +180 -0
- package/src/agent/tools/analysis.js +84 -1
- package/src/agent/tools/anti-detect.js +5 -2
- package/src/agent/tools/browser.js +160 -0
- package/src/agent/tools/capture.js +24 -3
- package/src/agent/tools/correlate.js +129 -15
- package/src/agent/tools/crawler.js +2 -1
- package/src/agent/tools/crawlerGenerator.js +90 -0
- package/src/agent/tools/debug.js +43 -6
- package/src/agent/tools/evolve.js +5 -2
- package/src/agent/tools/extractor.js +5 -1
- package/src/agent/tools/file.js +14 -5
- package/src/agent/tools/generateHook.js +66 -0
- package/src/agent/tools/hookManager.js +19 -9
- package/src/agent/tools/index.js +33 -20
- package/src/agent/tools/nodejs.js +41 -6
- package/src/agent/tools/sandbox.js +21 -1
- package/src/agent/tools/scratchpad.js +70 -0
- package/src/agent/tools/tracing.js +26 -0
- package/src/agent/tools/verifyAlgorithm.js +117 -0
- package/src/browser/EnvBridge.js +27 -13
- package/src/browser/client.js +124 -18
- package/src/browser/collector.js +101 -22
- package/src/browser/defaultHooks.js +3 -1
- package/src/browser/hooks/index.js +5 -0
- package/src/browser/interceptors/AntiDebugInterceptor.js +132 -0
- package/src/browser/interceptors/NetworkInterceptor.js +76 -12
- package/src/browser/interceptors/ScriptInterceptor.js +32 -7
- package/src/browser/interceptors/index.js +1 -0
- package/src/browser/ui/analysisPanel.js +469 -464
- package/src/cli/commands/config.js +11 -3
- package/src/config/paths.js +9 -1
- package/src/config/settings.js +7 -1
- package/src/core/PatchGenerator.js +24 -4
- package/src/core/Sandbox.js +140 -3
- package/src/env/EnvCodeGenerator.js +60 -88
- package/src/env/modules/bom/history.js +6 -0
- package/src/env/modules/bom/location.js +6 -0
- package/src/env/modules/bom/navigator.js +13 -0
- package/src/env/modules/bom/screen.js +6 -0
- package/src/env/modules/bom/storage.js +7 -0
- package/src/env/modules/dom/document.js +14 -0
- package/src/env/modules/dom/event.js +4 -0
- package/src/env/modules/index.js +27 -10
- package/src/env/modules/webapi/fetch.js +4 -0
- package/src/env/modules/webapi/url.js +4 -0
- package/src/env/modules/webapi/xhr.js +8 -0
- package/src/store/DataStore.js +125 -42
- package/src/store/Store.js +2 -1
- package/src/agent/subagents/dynamic.js +0 -64
- package/src/agent/subagents/env-agent.js +0 -82
- package/src/agent/subagents/sandbox.js +0 -55
- 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
|
-
|
|
|
25
|
-
|
|
|
26
|
-
|
|
|
27
|
-
|
|
|
28
|
-
|
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
68
|
-
|
|
69
|
-
3. **检查响应是否包含用户要求的目标数据**
|
|
111
|
+
不管是 Python 代码还是 JS 代码,不管有没有加密,都必须验证:
|
|
112
|
+
1. 运行生成的代码发送请求
|
|
113
|
+
2. 检查响应包含目标数据(与浏览器中看到的一致)
|
|
114
|
+
3. 如果有加密,验证加密参数与浏览器请求中的格式一致
|
|
70
115
|
|
|
71
|
-
|
|
72
|
-
-
|
|
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\`
|
|
87
|
-
2.
|
|
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
|
-
|
|
91
|
-
1.
|
|
92
|
-
2.
|
|
93
|
-
3.
|
|
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
|
-
|
|
99
|
-
|
|
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.
|
|
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
|
-
${
|
|
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
|
|
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:
|
|
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
|
-
|
|
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
|
-
|
|
4
|
+
爬虫编排经验。采集流程设计、数据提取策略、脚本生成。
|
|
5
|
+
触发:爬虫编排、数据采集、脚本生成、请求重放。
|
|
5
6
|
---
|
|
6
7
|
|
|
7
|
-
#
|
|
8
|
+
# 爬虫编排经验
|
|
8
9
|
|
|
9
|
-
|
|
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:
|
|
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 条 -->
|