@skrillex1224/playwright-toolkit 2.1.50 → 2.1.52
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/browser.d.ts +3 -0
- package/dist/browser.js +327 -37
- package/dist/browser.js.map +4 -4
- package/dist/index.cjs +2106 -2137
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +2106 -2133
- package/dist/index.js.map +4 -4
- package/index.d.ts +33 -4
- package/package.json +6 -2
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ var __export = (target, all) => {
|
|
|
5
5
|
};
|
|
6
6
|
|
|
7
7
|
// entrys/node.js
|
|
8
|
-
import { log as
|
|
8
|
+
import { log as crawleeLog } from "crawlee";
|
|
9
9
|
|
|
10
10
|
// src/constants.js
|
|
11
11
|
var constants_exports = {};
|
|
@@ -31,7 +31,7 @@ var Status = {
|
|
|
31
31
|
var FAILED_KEY_SEPARATOR = "::<@>::";
|
|
32
32
|
var PresetOfLiveViewKey = "LIVE_VIEW_SCREENSHOT";
|
|
33
33
|
|
|
34
|
-
// src/logger.js
|
|
34
|
+
// src/internals/logger.js
|
|
35
35
|
var formatLine = (prefix, icon, message) => {
|
|
36
36
|
const parts = [];
|
|
37
37
|
if (prefix) parts.push(`[${prefix}]`);
|
|
@@ -58,9 +58,9 @@ var defaultLogger = null;
|
|
|
58
58
|
var setDefaultLogger = (logger10) => {
|
|
59
59
|
defaultLogger = logger10;
|
|
60
60
|
};
|
|
61
|
-
var resolveLogger = (
|
|
62
|
-
if (
|
|
63
|
-
return
|
|
61
|
+
var resolveLogger = (explicitLogger) => {
|
|
62
|
+
if (explicitLogger && typeof explicitLogger.info === "function") {
|
|
63
|
+
return explicitLogger;
|
|
64
64
|
}
|
|
65
65
|
if (defaultLogger && typeof defaultLogger.info === "function") {
|
|
66
66
|
return defaultLogger;
|
|
@@ -76,2226 +76,2203 @@ var ANSI = {
|
|
|
76
76
|
blue: "\x1B[34m",
|
|
77
77
|
cyan: "\x1B[36m"
|
|
78
78
|
};
|
|
79
|
-
var stripAnsi = (input) => {
|
|
80
|
-
if (!input) return "";
|
|
81
|
-
return String(input).replace(/\x1b\[[0-9;]*m/g, "");
|
|
82
|
-
};
|
|
83
79
|
var colorize = (text, color) => {
|
|
84
80
|
if (!text || !color) return text;
|
|
85
81
|
return `${color}${text}${ANSI.reset}`;
|
|
86
82
|
};
|
|
87
|
-
var createBaseLogger = (prefix = "",
|
|
83
|
+
var createBaseLogger = (prefix = "", explicitLogger) => {
|
|
88
84
|
const name = prefix ? String(prefix) : "";
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
85
|
+
const dispatch = (methodName, icon, message, color) => {
|
|
86
|
+
const logger10 = resolveLogger(explicitLogger);
|
|
87
|
+
const logFn = resolveLogMethod(logger10, methodName);
|
|
88
|
+
logFn(colorize(formatLine(name, icon, message), color));
|
|
89
|
+
};
|
|
94
90
|
return {
|
|
95
|
-
info: (message) => info
|
|
96
|
-
success: (message) => info
|
|
97
|
-
warning: (message) => warning
|
|
98
|
-
warn: (message) => warning
|
|
99
|
-
error: (message) => error
|
|
100
|
-
debug: (message) => debug
|
|
101
|
-
start: (message) => info
|
|
91
|
+
info: (message) => dispatch("info", "\u{1F4D6}", message, ANSI.cyan),
|
|
92
|
+
success: (message) => dispatch("info", "\u2705", message, ANSI.green),
|
|
93
|
+
warning: (message) => dispatch("warning", "\u26A0\uFE0F", message, ANSI.yellow),
|
|
94
|
+
warn: (message) => dispatch("warning", "\u26A0\uFE0F", message, ANSI.yellow),
|
|
95
|
+
error: (message) => dispatch("error", "\u274C", message, ANSI.red),
|
|
96
|
+
debug: (message) => dispatch("debug", "\u{1F539}", message, ANSI.gray),
|
|
97
|
+
start: (message) => dispatch("info", "\u{1F537}", message, ANSI.blue)
|
|
102
98
|
};
|
|
103
99
|
};
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
100
|
+
function createInternalLogger(moduleName, explicitLogger) {
|
|
101
|
+
const baseLogger = createBaseLogger(moduleName, explicitLogger);
|
|
102
|
+
return {
|
|
103
|
+
start(methodName, params = "") {
|
|
104
|
+
const paramStr = params ? ` (${params})` : "";
|
|
105
|
+
baseLogger.start(`${methodName} \u5F00\u59CB${paramStr}`);
|
|
106
|
+
},
|
|
107
|
+
success(methodName, result = "") {
|
|
108
|
+
const resultStr = result ? ` (${result})` : "";
|
|
109
|
+
baseLogger.success(`${methodName} \u5B8C\u6210${resultStr}`);
|
|
110
|
+
},
|
|
111
|
+
fail(methodName, error) {
|
|
112
|
+
const message = error instanceof Error ? error.message : error;
|
|
113
|
+
baseLogger.error(`${methodName} \u5931\u8D25: ${message}`);
|
|
114
|
+
},
|
|
115
|
+
debug(message) {
|
|
116
|
+
baseLogger.debug(message);
|
|
117
|
+
},
|
|
118
|
+
warn(message) {
|
|
119
|
+
baseLogger.warning(message);
|
|
120
|
+
},
|
|
121
|
+
warning(message) {
|
|
122
|
+
baseLogger.warning(message);
|
|
123
|
+
},
|
|
124
|
+
info(message) {
|
|
125
|
+
baseLogger.info(message);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// src/errors.js
|
|
131
|
+
var errors_exports = {};
|
|
132
|
+
__export(errors_exports, {
|
|
133
|
+
CrawlerError: () => CrawlerError
|
|
134
|
+
});
|
|
135
|
+
import { serializeError } from "serialize-error";
|
|
136
|
+
var CrawlerError = class _CrawlerError extends Error {
|
|
137
|
+
/**
|
|
138
|
+
* @param {string|Object} info - 错误信息字符串或配置对象
|
|
139
|
+
* @param {string} info.message - 错误信息
|
|
140
|
+
* @param {number} [info.code] - ErrorKeygen 枚举值(用于错误分类)
|
|
141
|
+
* @param {Object} [info.context] - 上下文信息对象
|
|
142
|
+
*/
|
|
143
|
+
constructor(info) {
|
|
144
|
+
if (typeof info === "string") {
|
|
145
|
+
info = { message: info };
|
|
146
|
+
}
|
|
147
|
+
super(info.message);
|
|
148
|
+
this.name = "CrawlerError";
|
|
149
|
+
this.code = info.code ?? Code.UnknownError;
|
|
150
|
+
this.context = info.context ?? {};
|
|
151
|
+
this.timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
152
|
+
if (Error.captureStackTrace) {
|
|
153
|
+
Error.captureStackTrace(this, _CrawlerError);
|
|
154
|
+
}
|
|
142
155
|
}
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
};
|
|
150
|
-
var normalizeSnippet = (snippet, maxLen = 120) => {
|
|
151
|
-
if (!snippet) return "";
|
|
152
|
-
const text = String(snippet).replace(/\s+/g, " ").trim();
|
|
153
|
-
if (!text) return "";
|
|
154
|
-
const cleaned = text.replace(/"/g, "'");
|
|
155
|
-
if (cleaned.length <= maxLen) return cleaned;
|
|
156
|
-
return `${cleaned.slice(0, maxLen)}...`;
|
|
157
|
-
};
|
|
158
|
-
var LOG_TAG_PREFIX = "[#log:";
|
|
159
|
-
var LOG_TAG_SUFFIX = "]";
|
|
160
|
-
var buildLogTag = (key) => `${LOG_TAG_PREFIX}${key}${LOG_TAG_SUFFIX}`;
|
|
161
|
-
var escapeRegExp = (value) => String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
162
|
-
var buildStepPattern = (step, status) => {
|
|
163
|
-
if (!step) return null;
|
|
164
|
-
let pattern = `\u6B65\u9AA4: .*${escapeRegExp(step)}`;
|
|
165
|
-
if (status) {
|
|
166
|
-
pattern += `.*${escapeRegExp(status)}`;
|
|
156
|
+
/**
|
|
157
|
+
* 转换为可推送的数据对象
|
|
158
|
+
* @returns {Object}
|
|
159
|
+
*/
|
|
160
|
+
toJSON() {
|
|
161
|
+
return serializeError(this);
|
|
167
162
|
}
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
163
|
+
/**
|
|
164
|
+
* 检查一个 error 是否是 CrawlerError
|
|
165
|
+
* @param {Error} error
|
|
166
|
+
* @returns {boolean}
|
|
167
|
+
*/
|
|
168
|
+
static isCrawlerError(error) {
|
|
169
|
+
return error instanceof _CrawlerError || error?.name === "CrawlerError";
|
|
170
|
+
}
|
|
171
|
+
/**
|
|
172
|
+
* 从普通 Error 创建 CrawlerError
|
|
173
|
+
* @param {Error} error - 原始错误
|
|
174
|
+
* @param {Object} [options={}] - 选项对象 (包含 code 和 context)
|
|
175
|
+
* @returns {CrawlerError}
|
|
176
|
+
*/
|
|
177
|
+
static from(error, options = {}) {
|
|
178
|
+
const crawlerError = new _CrawlerError({
|
|
179
|
+
message: error.message,
|
|
180
|
+
...options
|
|
181
|
+
});
|
|
182
|
+
crawlerError.stack = error.stack;
|
|
183
|
+
return crawlerError;
|
|
176
184
|
}
|
|
177
|
-
return patterns;
|
|
178
|
-
};
|
|
179
|
-
var ATTENTION_RANK = {
|
|
180
|
-
low: 1,
|
|
181
|
-
medium: 2,
|
|
182
|
-
high: 3,
|
|
183
|
-
critical: 4
|
|
184
185
|
};
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
},
|
|
366
|
-
{
|
|
367
|
-
key: "response_listen_detected",
|
|
368
|
-
method: "responseListenDetected",
|
|
369
|
-
label: "\u54CD\u5E94\u76D1\u542C\u5DF2\u68C0\u6D4B",
|
|
370
|
-
group: "\u54CD\u5E94\u76D1\u542C",
|
|
371
|
-
step: "\u54CD\u5E94\u76D1\u542C",
|
|
372
|
-
status: "\u5DF2\u68C0\u6D4B",
|
|
373
|
-
level: "success",
|
|
374
|
-
attention: "medium",
|
|
375
|
-
buildDetails: (label) => [label ? `\u76EE\u6807=${label}` : ""]
|
|
376
|
-
},
|
|
377
|
-
{
|
|
378
|
-
key: "response_listen_timeout",
|
|
379
|
-
method: "responseListenTimeout",
|
|
380
|
-
label: "\u54CD\u5E94\u76D1\u542C\u8D85\u65F6",
|
|
381
|
-
group: "\u54CD\u5E94\u76D1\u542C",
|
|
382
|
-
step: "\u54CD\u5E94\u76D1\u542C",
|
|
383
|
-
status: "\u8D85\u65F6",
|
|
384
|
-
level: "error",
|
|
385
|
-
attention: "high",
|
|
386
|
-
buildDetails: (label, err) => [
|
|
387
|
-
label ? `\u76EE\u6807=${label}` : "",
|
|
388
|
-
err ? `err=${toErrorMessage(err)}` : ""
|
|
389
|
-
]
|
|
390
|
-
},
|
|
391
|
-
{
|
|
392
|
-
key: "response_listen_end",
|
|
393
|
-
method: "responseListenEnd",
|
|
394
|
-
label: "\u54CD\u5E94\u76D1\u542C\u7ED3\u675F",
|
|
395
|
-
group: "\u54CD\u5E94\u76D1\u542C",
|
|
396
|
-
step: "\u54CD\u5E94\u76D1\u542C",
|
|
397
|
-
status: "\u7ED3\u675F",
|
|
398
|
-
level: "info",
|
|
399
|
-
attention: "low",
|
|
400
|
-
buildDetails: (label) => [label ? `\u76EE\u6807=${label}` : ""]
|
|
401
|
-
},
|
|
402
|
-
{
|
|
403
|
-
key: "response_wait_start",
|
|
404
|
-
method: "responseWaitStart",
|
|
405
|
-
label: "\u7B49\u5F85\u54CD\u5E94\u5F00\u59CB",
|
|
406
|
-
group: "\u7B49\u5F85\u54CD\u5E94",
|
|
407
|
-
step: "\u7B49\u5F85\u54CD\u5E94",
|
|
408
|
-
status: "\u5F00\u59CB",
|
|
409
|
-
level: "start",
|
|
410
|
-
attention: "low",
|
|
411
|
-
buildDetails: (label) => [label ? `\u76EE\u6807=${label}` : ""]
|
|
412
|
-
},
|
|
413
|
-
{
|
|
414
|
-
key: "response_wait_success",
|
|
415
|
-
method: "responseWaitSuccess",
|
|
416
|
-
label: "\u7B49\u5F85\u54CD\u5E94\u5B8C\u6210",
|
|
417
|
-
group: "\u7B49\u5F85\u54CD\u5E94",
|
|
418
|
-
step: "\u7B49\u5F85\u54CD\u5E94",
|
|
419
|
-
status: "\u5B8C\u6210",
|
|
420
|
-
level: "success",
|
|
421
|
-
attention: "medium",
|
|
422
|
-
buildDetails: (label) => [label ? `\u76EE\u6807=${label}` : ""]
|
|
423
|
-
},
|
|
424
|
-
{
|
|
425
|
-
key: "response_wait_fail",
|
|
426
|
-
method: "responseWaitFail",
|
|
427
|
-
label: "\u7B49\u5F85\u54CD\u5E94\u5931\u8D25",
|
|
428
|
-
group: "\u7B49\u5F85\u54CD\u5E94",
|
|
429
|
-
step: "\u7B49\u5F85\u54CD\u5E94",
|
|
430
|
-
status: "\u5931\u8D25",
|
|
431
|
-
level: "warning",
|
|
432
|
-
attention: "high",
|
|
433
|
-
buildDetails: (label, err) => [
|
|
434
|
-
label ? `\u76EE\u6807=${label}` : "",
|
|
435
|
-
err ? `err=${toErrorMessage(err)}` : ""
|
|
436
|
-
]
|
|
437
|
-
},
|
|
438
|
-
{
|
|
439
|
-
key: "response_wait_retry",
|
|
440
|
-
method: "responseWaitRetry",
|
|
441
|
-
label: "\u7B49\u5F85\u54CD\u5E94\u91CD\u8BD5",
|
|
442
|
-
group: "\u7B49\u5F85\u54CD\u5E94",
|
|
443
|
-
step: "\u7B49\u5F85\u54CD\u5E94",
|
|
444
|
-
status: "\u91CD\u8BD5",
|
|
445
|
-
level: "warning",
|
|
446
|
-
attention: "medium",
|
|
447
|
-
buildDetails: (label, attempt) => [
|
|
448
|
-
label ? `\u76EE\u6807=${label}` : "",
|
|
449
|
-
attempt !== void 0 ? `\u5C1D\u8BD5=${attempt}` : ""
|
|
450
|
-
]
|
|
451
|
-
},
|
|
452
|
-
{
|
|
453
|
-
key: "dom_chunk",
|
|
454
|
-
method: "domChunk",
|
|
455
|
-
label: "DOM\u7247\u6BB5",
|
|
456
|
-
group: "DOM",
|
|
457
|
-
step: "DOM\u7247\u6BB5",
|
|
458
|
-
status: "",
|
|
459
|
-
level: "info",
|
|
460
|
-
attention: "low",
|
|
461
|
-
throttleKey: "dom-chunk",
|
|
462
|
-
throttleMs: 2e3,
|
|
463
|
-
buildDetails: (length, snippet, paused) => [
|
|
464
|
-
length !== void 0 ? `len=${length}` : "",
|
|
465
|
-
snippet ? `preview="${normalizeSnippet(snippet)}"` : "",
|
|
466
|
-
paused ? "paused=1" : ""
|
|
467
|
-
]
|
|
468
|
-
},
|
|
469
|
-
{
|
|
470
|
-
key: "dom_complete",
|
|
471
|
-
method: "domComplete",
|
|
472
|
-
label: "DOM\u7A33\u5B9A\u5B8C\u6210",
|
|
473
|
-
group: "DOM",
|
|
474
|
-
step: "DOM\u7A33\u5B9A",
|
|
475
|
-
status: "\u5B8C\u6210",
|
|
476
|
-
level: "success",
|
|
477
|
-
attention: "medium",
|
|
478
|
-
buildDetails: (mutationCount, stableTime, wasPaused) => [
|
|
479
|
-
mutationCount !== void 0 ? `mutations=${mutationCount}` : "",
|
|
480
|
-
stableTime !== void 0 ? `stableTime=${stableTime}ms` : "",
|
|
481
|
-
wasPaused ? "paused=1" : ""
|
|
482
|
-
]
|
|
483
|
-
},
|
|
484
|
-
{
|
|
485
|
-
key: "stream_chunk",
|
|
486
|
-
method: "streamChunk",
|
|
487
|
-
label: "\u6D41\u5F0F\u7247\u6BB5",
|
|
488
|
-
group: "\u6D41\u5F0F",
|
|
489
|
-
step: "\u6D41\u5F0F\u7247\u6BB5",
|
|
490
|
-
status: "",
|
|
491
|
-
level: "info",
|
|
492
|
-
attention: "low",
|
|
493
|
-
throttleKey: "stream-chunk",
|
|
494
|
-
throttleMs: 2e3,
|
|
495
|
-
buildDetails: (length, snippet) => [
|
|
496
|
-
length !== void 0 ? `len=${length}` : "",
|
|
497
|
-
snippet ? `preview="${normalizeSnippet(snippet)}"` : ""
|
|
498
|
-
]
|
|
499
|
-
},
|
|
500
|
-
{
|
|
501
|
-
key: "stream_events_parsed",
|
|
502
|
-
method: "streamEventsParsed",
|
|
503
|
-
label: "\u6D41\u5F0F\u4E8B\u4EF6\u89E3\u6790\u5B8C\u6210",
|
|
504
|
-
group: "\u6D41\u5F0F",
|
|
505
|
-
step: "\u6D41\u5F0F\u4E8B\u4EF6\u89E3\u6790",
|
|
506
|
-
status: "\u5B8C\u6210",
|
|
507
|
-
level: "info",
|
|
508
|
-
attention: "low",
|
|
509
|
-
throttleKey: "stream-events",
|
|
510
|
-
throttleMs: 4e3,
|
|
511
|
-
buildDetails: (count) => [count !== void 0 ? `count=${count}` : ""]
|
|
512
|
-
},
|
|
513
|
-
{
|
|
514
|
-
key: "stream_complete_event",
|
|
515
|
-
method: "streamCompleteEvent",
|
|
516
|
-
label: "\u6D41\u5F0F\u5B8C\u6210\u4E8B\u4EF6\u6355\u83B7",
|
|
517
|
-
group: "\u6D41\u5F0F",
|
|
518
|
-
step: "\u6D41\u5F0F\u4E8B\u4EF6",
|
|
519
|
-
status: "\u5B8C\u6210\u4E8B\u4EF6\u5DF2\u6355\u83B7",
|
|
520
|
-
level: "success",
|
|
521
|
-
attention: "medium"
|
|
522
|
-
},
|
|
523
|
-
{
|
|
524
|
-
key: "stream_end",
|
|
525
|
-
method: "streamEnd",
|
|
526
|
-
label: "\u6D41\u5F0F\u54CD\u5E94\u7ED3\u675F",
|
|
527
|
-
group: "\u6D41\u5F0F",
|
|
528
|
-
step: "\u6D41\u5F0F\u54CD\u5E94",
|
|
529
|
-
status: "\u7ED3\u675F",
|
|
530
|
-
level: "info",
|
|
531
|
-
attention: "low"
|
|
532
|
-
},
|
|
533
|
-
{
|
|
534
|
-
key: "reference_expand_start",
|
|
535
|
-
method: "referenceExpandStart",
|
|
536
|
-
label: "\u5F15\u7528\u5C55\u5F00\u5F00\u59CB",
|
|
537
|
-
group: "\u5F15\u7528",
|
|
538
|
-
step: "\u5F15\u7528\u5C55\u5F00",
|
|
539
|
-
status: "\u5F00\u59CB",
|
|
540
|
-
level: "start",
|
|
541
|
-
attention: "low",
|
|
542
|
-
buildDetails: (label) => [label ? `\u76EE\u6807=${label}` : ""]
|
|
543
|
-
},
|
|
544
|
-
{
|
|
545
|
-
key: "reference_expand_fail",
|
|
546
|
-
method: "referenceExpandFail",
|
|
547
|
-
label: "\u5F15\u7528\u5C55\u5F00\u5931\u8D25",
|
|
548
|
-
group: "\u5F15\u7528",
|
|
549
|
-
step: "\u5F15\u7528\u5C55\u5F00",
|
|
550
|
-
status: "\u5931\u8D25",
|
|
551
|
-
level: "warning",
|
|
552
|
-
attention: "medium",
|
|
553
|
-
buildDetails: (err) => [err ? `err=${toErrorMessage(err)}` : ""]
|
|
554
|
-
},
|
|
555
|
-
{
|
|
556
|
-
key: "screenshot_start",
|
|
557
|
-
method: "screenshotStart",
|
|
558
|
-
label: "\u622A\u56FE\u5F00\u59CB",
|
|
559
|
-
group: "\u622A\u56FE",
|
|
560
|
-
step: "\u622A\u56FE",
|
|
561
|
-
status: "\u5F00\u59CB",
|
|
562
|
-
level: "start",
|
|
563
|
-
attention: "low"
|
|
186
|
+
|
|
187
|
+
// src/apify-kit.js
|
|
188
|
+
import { serializeError as serializeError2 } from "serialize-error";
|
|
189
|
+
var logger = createInternalLogger("ApifyKit");
|
|
190
|
+
async function createApifyKit() {
|
|
191
|
+
let apify = null;
|
|
192
|
+
try {
|
|
193
|
+
apify = await import("apify");
|
|
194
|
+
} catch (error) {
|
|
195
|
+
throw new Error("\u26A0\uFE0F apify \u5E93\u672A\u5B89\u88C5\uFF0CApifyKit \u7684 Actor \u76F8\u5173\u529F\u80FD\u4E0D\u53EF\u7528");
|
|
196
|
+
}
|
|
197
|
+
const { Actor: Actor2 } = apify;
|
|
198
|
+
return {
|
|
199
|
+
/**
|
|
200
|
+
* 核心封装:执行步骤,带自动日志确认、失败截图处理和重试机制
|
|
201
|
+
*
|
|
202
|
+
* @param {string} step - 步骤名称
|
|
203
|
+
* @param {import('playwright').Page} page - Playwright page 对象
|
|
204
|
+
* @param {Function} actionFn - 执行的异步操作
|
|
205
|
+
* @param {Object} [options] - 配置选项
|
|
206
|
+
* @param {boolean} [options.failActor=true] - 失败时是否调用 Actor.fail
|
|
207
|
+
* @param {Object} [options.retry] - 重试配置
|
|
208
|
+
* @param {number} [options.retry.times=0] - 重试次数
|
|
209
|
+
* @param {'direct'|'refresh'} [options.retry.mode='direct'] - 重试模式
|
|
210
|
+
* @param {Function} [options.retry.before] - 重试前钩子,可覆盖默认等待行为
|
|
211
|
+
*/
|
|
212
|
+
async runStep(step, page, actionFn, options = {}) {
|
|
213
|
+
const { failActor = true, retry = {} } = options;
|
|
214
|
+
const { times: retryTimes = 0, mode: retryMode = "direct", before: beforeRetry } = retry;
|
|
215
|
+
const executeAction = async (attemptNumber) => {
|
|
216
|
+
const attemptLabel = attemptNumber > 0 ? ` (\u91CD\u8BD5 #${attemptNumber})` : "";
|
|
217
|
+
logger.start(`[Step] ${step}${attemptLabel}`);
|
|
218
|
+
try {
|
|
219
|
+
const result = await actionFn();
|
|
220
|
+
logger.success(`[Step] ${step}${attemptLabel}`);
|
|
221
|
+
return { success: true, result };
|
|
222
|
+
} catch (error) {
|
|
223
|
+
logger.fail(`[Step] ${step}${attemptLabel}`, error);
|
|
224
|
+
return { success: false, error };
|
|
225
|
+
}
|
|
226
|
+
};
|
|
227
|
+
const prepareForRetry = async (attemptNumber) => {
|
|
228
|
+
if (typeof beforeRetry === "function") {
|
|
229
|
+
logger.start(`[RetryStep] \u6267\u884C\u81EA\u5B9A\u4E49 before \u94A9\u5B50 (\u7B2C ${attemptNumber} \u6B21\u91CD\u8BD5)`);
|
|
230
|
+
await beforeRetry(page, attemptNumber);
|
|
231
|
+
logger.success(`[RetryStep] before \u94A9\u5B50\u5B8C\u6210`);
|
|
232
|
+
} else if (retryMode === "refresh") {
|
|
233
|
+
logger.start(`[RetryStep] \u5237\u65B0\u9875\u9762 (\u7B2C ${attemptNumber} \u6B21\u91CD\u8BD5)`);
|
|
234
|
+
await page.reload({ waitUntil: "domcontentloaded" });
|
|
235
|
+
logger.success(`[RetryStep] \u9875\u9762\u5237\u65B0\u5B8C\u6210`);
|
|
236
|
+
} else {
|
|
237
|
+
logger.start(`[RetryStep] \u7B49\u5F85 3 \u79D2 (\u7B2C ${attemptNumber} \u6B21\u91CD\u8BD5)`);
|
|
238
|
+
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
239
|
+
logger.success(`[RetryStep] \u7B49\u5F85\u5B8C\u6210`);
|
|
240
|
+
}
|
|
241
|
+
};
|
|
242
|
+
let lastResult = await executeAction(0);
|
|
243
|
+
if (lastResult.success) {
|
|
244
|
+
return lastResult.result;
|
|
245
|
+
}
|
|
246
|
+
for (let attempt = 1; attempt <= retryTimes; attempt++) {
|
|
247
|
+
logger.start(`[RetryStep] \u51C6\u5907\u7B2C ${attempt}/${retryTimes} \u6B21\u91CD\u8BD5: ${step}`);
|
|
248
|
+
try {
|
|
249
|
+
await prepareForRetry(attempt);
|
|
250
|
+
} catch (prepareError) {
|
|
251
|
+
logger.warn(`[RetryStep] \u91CD\u8BD5\u51C6\u5907\u5931\u8D25: ${prepareError.message}`);
|
|
252
|
+
continue;
|
|
253
|
+
}
|
|
254
|
+
lastResult = await executeAction(attempt);
|
|
255
|
+
if (lastResult.success) {
|
|
256
|
+
return lastResult.result;
|
|
257
|
+
}
|
|
258
|
+
}
|
|
259
|
+
const finalError = lastResult.error;
|
|
260
|
+
if (failActor) {
|
|
261
|
+
let base64 = "\u622A\u56FE\u5931\u8D25";
|
|
262
|
+
try {
|
|
263
|
+
if (page) {
|
|
264
|
+
const buffer = await page.screenshot({ fullPage: true, type: "jpeg", quality: 60 });
|
|
265
|
+
base64 = `data:image/jpeg;base64,${buffer.toString("base64")}`;
|
|
266
|
+
}
|
|
267
|
+
} catch (snapErr) {
|
|
268
|
+
logger.warn(`\u622A\u56FE\u751F\u6210\u5931\u8D25: ${snapErr.message}`);
|
|
269
|
+
}
|
|
270
|
+
await this.pushFailed(finalError, {
|
|
271
|
+
step,
|
|
272
|
+
page,
|
|
273
|
+
options,
|
|
274
|
+
base64,
|
|
275
|
+
retryAttempts: retryTimes
|
|
276
|
+
});
|
|
277
|
+
await Actor2.fail(`Run Step ${step} \u5931\u8D25 (\u5DF2\u91CD\u8BD5 ${retryTimes} \u6B21): ${finalError.message}`);
|
|
278
|
+
} else {
|
|
279
|
+
throw finalError;
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
/**
|
|
283
|
+
* 宽松版runStep:失败时不调用Actor.fail,只抛出异常
|
|
284
|
+
*/
|
|
285
|
+
async runStepLoose(step, page, fn) {
|
|
286
|
+
return await this.runStep(step, page, fn, { failActor: false });
|
|
287
|
+
},
|
|
288
|
+
/**
|
|
289
|
+
* 推送成功数据的通用方法
|
|
290
|
+
* @param {Object} data - 要推送的数据对象
|
|
291
|
+
*/
|
|
292
|
+
async pushSuccess(data) {
|
|
293
|
+
await Actor2.pushData({
|
|
294
|
+
// 固定为0
|
|
295
|
+
code: Code.Success,
|
|
296
|
+
status: Status.Success,
|
|
297
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString(),
|
|
298
|
+
data
|
|
299
|
+
});
|
|
300
|
+
logger.success("pushSuccess", "Data pushed");
|
|
301
|
+
},
|
|
302
|
+
/**
|
|
303
|
+
* 推送失败数据的通用方法(私有方法,仅供runStep内部使用)
|
|
304
|
+
* 自动解析 CrawlerError 的 code 和 context
|
|
305
|
+
* @param {Error|CrawlerError} error - 错误对象
|
|
306
|
+
* @param {Object} [meta] - 额外的数据(如failedStep, screenshotBase64等)
|
|
307
|
+
* @private
|
|
308
|
+
*/
|
|
309
|
+
async pushFailed(error, meta = {}) {
|
|
310
|
+
const isCrawlerError = CrawlerError.isCrawlerError(error);
|
|
311
|
+
const code = isCrawlerError ? error.code : Code.UnknownError;
|
|
312
|
+
const context = isCrawlerError ? error.context : {};
|
|
313
|
+
await Actor2.pushData({
|
|
314
|
+
// 如果是 CrawlerError,使用其 code,否则使用默认 Failed code
|
|
315
|
+
code,
|
|
316
|
+
status: Status.Failed,
|
|
317
|
+
error: serializeError2(error),
|
|
318
|
+
meta,
|
|
319
|
+
context,
|
|
320
|
+
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
321
|
+
});
|
|
322
|
+
logger.success("pushFailed", "Error data pushed");
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
var instance = null;
|
|
327
|
+
async function useApifyKit() {
|
|
328
|
+
if (!instance) {
|
|
329
|
+
instance = await createApifyKit();
|
|
330
|
+
}
|
|
331
|
+
return instance;
|
|
332
|
+
}
|
|
333
|
+
var ApifyKit = {
|
|
334
|
+
useApifyKit
|
|
335
|
+
};
|
|
336
|
+
|
|
337
|
+
// src/utils.js
|
|
338
|
+
import delay from "delay";
|
|
339
|
+
var logger2 = createInternalLogger("Utils");
|
|
340
|
+
var Utils = {
|
|
341
|
+
/**
|
|
342
|
+
* 解析 Cookie 字符串为 Playwright 格式的 Cookie 数组
|
|
343
|
+
* @param {string} cookieString - Cookie 字符串
|
|
344
|
+
* @param {string} [domain] - Cookie 域名 (可选)
|
|
345
|
+
* @returns {Array} Cookie 数组
|
|
346
|
+
*/
|
|
347
|
+
parseCookies(cookieString, domain) {
|
|
348
|
+
const cookies = [];
|
|
349
|
+
const pairs = cookieString.split(";").map((c) => c.trim());
|
|
350
|
+
for (const pair of pairs) {
|
|
351
|
+
const [name, ...valueParts] = pair.split("=");
|
|
352
|
+
if (name && valueParts.length > 0) {
|
|
353
|
+
const cookie = {
|
|
354
|
+
name: name.trim(),
|
|
355
|
+
value: valueParts.join("=").trim(),
|
|
356
|
+
path: "/"
|
|
357
|
+
};
|
|
358
|
+
if (domain) {
|
|
359
|
+
cookie.domain = domain;
|
|
360
|
+
}
|
|
361
|
+
cookies.push(cookie);
|
|
362
|
+
}
|
|
363
|
+
}
|
|
364
|
+
logger2.success("parseCookies", `parsed ${cookies.length} cookies`);
|
|
365
|
+
return cookies;
|
|
564
366
|
},
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
367
|
+
/**
|
|
368
|
+
* 全页面滚动截图
|
|
369
|
+
* 自动检测页面所有可滚动元素,取最大高度,强制展开后截图
|
|
370
|
+
*
|
|
371
|
+
* @param {import('playwright').Page} page - Playwright page 对象
|
|
372
|
+
* @param {Object} [options] - 配置选项
|
|
373
|
+
* @param {number} [options.buffer] - 额外缓冲高度 (默认: 视口高度的一半)
|
|
374
|
+
* @param {boolean} [options.restore] - 截图后是否恢复页面高度和样式 (默认: false)
|
|
375
|
+
* @param {number} [options.maxHeight] - 最大截图高度 (默认: 8000px)
|
|
376
|
+
* @returns {Promise<string>} - base64 编码的 PNG 图片
|
|
377
|
+
*/
|
|
378
|
+
async fullPageScreenshot(page, options = {}) {
|
|
379
|
+
logger2.start("fullPageScreenshot", "detecting scrollable elements");
|
|
380
|
+
const originalViewport = page.viewportSize();
|
|
381
|
+
const defaultBuffer = Math.round((originalViewport?.height || 1080) / 2);
|
|
382
|
+
const buffer = options.buffer ?? defaultBuffer;
|
|
383
|
+
const restore = options.restore ?? false;
|
|
384
|
+
const maxHeight = options.maxHeight ?? 8e3;
|
|
385
|
+
try {
|
|
386
|
+
const maxScrollHeight = await page.evaluate(() => {
|
|
387
|
+
let maxHeight2 = document.body.scrollHeight;
|
|
388
|
+
document.querySelectorAll("*").forEach((el) => {
|
|
389
|
+
const style = window.getComputedStyle(el);
|
|
390
|
+
const overflowY = style.overflowY;
|
|
391
|
+
if ((overflowY === "auto" || overflowY === "scroll") && el.scrollHeight > el.clientHeight) {
|
|
392
|
+
if (el.scrollHeight > maxHeight2) {
|
|
393
|
+
maxHeight2 = el.scrollHeight;
|
|
394
|
+
}
|
|
395
|
+
el.dataset.pkOrigOverflow = el.style.overflow;
|
|
396
|
+
el.dataset.pkOrigHeight = el.style.height;
|
|
397
|
+
el.dataset.pkOrigMaxHeight = el.style.maxHeight;
|
|
398
|
+
el.classList.add("__pk_expanded__");
|
|
399
|
+
el.style.overflow = "visible";
|
|
400
|
+
el.style.height = "auto";
|
|
401
|
+
el.style.maxHeight = "none";
|
|
402
|
+
}
|
|
403
|
+
});
|
|
404
|
+
return maxHeight2;
|
|
405
|
+
});
|
|
406
|
+
const targetHeight = Math.min(maxScrollHeight + buffer, maxHeight);
|
|
407
|
+
await page.setViewportSize({
|
|
408
|
+
width: originalViewport?.width || 1280,
|
|
409
|
+
height: targetHeight
|
|
410
|
+
});
|
|
411
|
+
await delay(1e3);
|
|
412
|
+
const buffer_ = await page.screenshot({
|
|
413
|
+
fullPage: true,
|
|
414
|
+
type: "png"
|
|
415
|
+
});
|
|
416
|
+
logger2.success("fullPageScreenshot", `captured ${Math.round(buffer_.length / 1024)} KB`);
|
|
417
|
+
return buffer_.toString("base64");
|
|
418
|
+
} finally {
|
|
419
|
+
if (restore) {
|
|
420
|
+
await page.evaluate(() => {
|
|
421
|
+
document.querySelectorAll(".__pk_expanded__").forEach((el) => {
|
|
422
|
+
el.style.overflow = el.dataset.pkOrigOverflow || "";
|
|
423
|
+
el.style.height = el.dataset.pkOrigHeight || "";
|
|
424
|
+
el.style.maxHeight = el.dataset.pkOrigMaxHeight || "";
|
|
425
|
+
delete el.dataset.pkOrigOverflow;
|
|
426
|
+
delete el.dataset.pkOrigHeight;
|
|
427
|
+
delete el.dataset.pkOrigMaxHeight;
|
|
428
|
+
el.classList.remove("__pk_expanded__");
|
|
429
|
+
});
|
|
430
|
+
});
|
|
431
|
+
if (originalViewport) {
|
|
432
|
+
await page.setViewportSize(originalViewport);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
};
|
|
438
|
+
|
|
439
|
+
// src/anti-cheat.js
|
|
440
|
+
var logger3 = createInternalLogger("AntiCheat");
|
|
441
|
+
var BASE_CONFIG = Object.freeze({
|
|
442
|
+
locale: "zh-CN",
|
|
443
|
+
acceptLanguage: "zh-CN,zh;q=0.9",
|
|
444
|
+
timezoneId: "Asia/Shanghai",
|
|
445
|
+
timezoneOffset: -480,
|
|
446
|
+
geolocation: null
|
|
447
|
+
});
|
|
448
|
+
var DEFAULT_LAUNCH_ARGS = [
|
|
449
|
+
// '--disable-blink-features=AutomationControlled', // Crawlee 可能会自动处理,过多干预反而会被识别
|
|
450
|
+
"--no-sandbox",
|
|
451
|
+
"--disable-setuid-sandbox",
|
|
452
|
+
"--window-position=0,0",
|
|
453
|
+
`--lang=${BASE_CONFIG.locale}`
|
|
454
|
+
];
|
|
455
|
+
function buildFingerprintOptions(locale) {
|
|
456
|
+
return {
|
|
457
|
+
browsers: [{ name: "chrome", minVersion: 110 }],
|
|
458
|
+
devices: ["desktop"],
|
|
459
|
+
operatingSystems: ["windows", "macos", "linux"],
|
|
460
|
+
locales: [locale]
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
var AntiCheat = {
|
|
464
|
+
/**
|
|
465
|
+
* 获取统一的基础配置
|
|
466
|
+
*/
|
|
467
|
+
getBaseConfig() {
|
|
468
|
+
return { ...BASE_CONFIG };
|
|
574
469
|
},
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
step: "\u622A\u56FE",
|
|
581
|
-
status: "\u5931\u8D25",
|
|
582
|
-
level: "warning",
|
|
583
|
-
attention: "high",
|
|
584
|
-
buildDetails: (err) => [err ? `err=${toErrorMessage(err)}` : ""]
|
|
470
|
+
/**
|
|
471
|
+
* 用于 Crawlee fingerprint generator 的统一配置(桌面端)。
|
|
472
|
+
*/
|
|
473
|
+
getFingerprintGeneratorOptions() {
|
|
474
|
+
return buildFingerprintOptions(BASE_CONFIG.locale);
|
|
585
475
|
},
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
step: "\u5206\u4EAB\u94FE\u63A5",
|
|
592
|
-
status: "\u5F00\u59CB",
|
|
593
|
-
level: "start",
|
|
594
|
-
attention: "low"
|
|
476
|
+
/**
|
|
477
|
+
* 获取基础启动参数。
|
|
478
|
+
*/
|
|
479
|
+
getLaunchArgs() {
|
|
480
|
+
return [...DEFAULT_LAUNCH_ARGS];
|
|
595
481
|
},
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
step: "\u5206\u4EAB\u94FE\u63A5",
|
|
602
|
-
status: "\u6B65\u9AA4",
|
|
603
|
-
level: "info",
|
|
604
|
-
attention: "low",
|
|
605
|
-
buildDetails: (label) => [label ? `\u6B65\u9AA4=${label}` : ""]
|
|
482
|
+
/**
|
|
483
|
+
* 为 got-scraping 生成与浏览器一致的 TLS 指纹配置(桌面端)。
|
|
484
|
+
*/
|
|
485
|
+
getTlsFingerprintOptions(userAgent = "", acceptLanguage = "") {
|
|
486
|
+
return buildFingerprintOptions(BASE_CONFIG.locale);
|
|
606
487
|
},
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
|
|
616
|
-
|
|
488
|
+
/**
|
|
489
|
+
* 规范化请求头
|
|
490
|
+
*/
|
|
491
|
+
applyLocaleHeaders(headers, acceptLanguage = "") {
|
|
492
|
+
if (!headers["accept-language"]) {
|
|
493
|
+
headers["accept-language"] = acceptLanguage || BASE_CONFIG.acceptLanguage;
|
|
494
|
+
}
|
|
495
|
+
return headers;
|
|
496
|
+
}
|
|
497
|
+
};
|
|
498
|
+
|
|
499
|
+
// src/humanize.js
|
|
500
|
+
import delay2 from "delay";
|
|
501
|
+
import { createCursor } from "ghost-cursor-playwright";
|
|
502
|
+
var logger4 = createInternalLogger("Humanize");
|
|
503
|
+
var $CursorWeakMap = /* @__PURE__ */ new WeakMap();
|
|
504
|
+
function $GetCursor(page) {
|
|
505
|
+
const cursor = $CursorWeakMap.get(page);
|
|
506
|
+
if (!cursor) {
|
|
507
|
+
throw new Error("Cursor \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 Humanize.initializeCursor(page)");
|
|
508
|
+
}
|
|
509
|
+
return cursor;
|
|
510
|
+
}
|
|
511
|
+
var Humanize = {
|
|
512
|
+
/**
|
|
513
|
+
* 生成带抖动的毫秒数 - 基于基础值添加随机浮动 (±30% 默认)
|
|
514
|
+
* @param {number} base - 基础延迟 (ms)
|
|
515
|
+
* @param {number} [jitterPercent=0.3] - 抖动百分比 (0.3 = ±30%)
|
|
516
|
+
* @returns {number} 抖动后的毫秒数
|
|
517
|
+
*/
|
|
518
|
+
jitterMs(base, jitterPercent = 0.3) {
|
|
519
|
+
const jitter = base * jitterPercent * (Math.random() * 2 - 1);
|
|
520
|
+
return Math.max(10, Math.round(base + jitter));
|
|
617
521
|
},
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
522
|
+
/**
|
|
523
|
+
* 初始化页面的 Ghost Cursor(必须在使用其他 cursor 相关方法前调用)
|
|
524
|
+
*
|
|
525
|
+
* @param {import('playwright').Page} page
|
|
526
|
+
* @returns {Promise<void>}
|
|
527
|
+
*/
|
|
528
|
+
async initializeCursor(page) {
|
|
529
|
+
if ($CursorWeakMap.has(page)) {
|
|
530
|
+
logger4.debug("initializeCursor: cursor already exists, skipping");
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
logger4.start("initializeCursor", "creating cursor");
|
|
534
|
+
const cursor = await createCursor(page);
|
|
535
|
+
$CursorWeakMap.set(page, cursor);
|
|
536
|
+
logger4.success("initializeCursor", "cursor initialized");
|
|
628
537
|
},
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
538
|
+
/**
|
|
539
|
+
* 人类化鼠标移动 - 使用 ghost-cursor 移动到指定位置或元素
|
|
540
|
+
*
|
|
541
|
+
* @param {import('playwright').Page} page
|
|
542
|
+
* @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
|
|
543
|
+
*/
|
|
544
|
+
async humanMove(page, target) {
|
|
545
|
+
const cursor = $GetCursor(page);
|
|
546
|
+
logger4.start("humanMove", `target=${typeof target === "string" ? target : "element/coords"}`);
|
|
547
|
+
try {
|
|
548
|
+
if (typeof target === "string") {
|
|
549
|
+
const element = await page.$(target);
|
|
550
|
+
if (!element) {
|
|
551
|
+
logger4.warn(`humanMove: \u5143\u7D20\u4E0D\u5B58\u5728 ${target}`);
|
|
552
|
+
return false;
|
|
553
|
+
}
|
|
554
|
+
const box = await element.boundingBox();
|
|
555
|
+
if (!box) {
|
|
556
|
+
logger4.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
|
|
557
|
+
return false;
|
|
558
|
+
}
|
|
559
|
+
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
560
|
+
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
561
|
+
await cursor.actions.move({ x, y });
|
|
562
|
+
} else if (target && typeof target.x === "number" && typeof target.y === "number") {
|
|
563
|
+
await cursor.actions.move(target);
|
|
564
|
+
} else if (target && typeof target.boundingBox === "function") {
|
|
565
|
+
const box = await target.boundingBox();
|
|
566
|
+
if (box) {
|
|
567
|
+
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
568
|
+
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
569
|
+
await cursor.actions.move({ x, y });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
logger4.success("humanMove");
|
|
573
|
+
return true;
|
|
574
|
+
} catch (error) {
|
|
575
|
+
logger4.fail("humanMove", error);
|
|
576
|
+
throw error;
|
|
577
|
+
}
|
|
639
578
|
},
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
579
|
+
/**
|
|
580
|
+
* 渐进式滚动到元素可见(仅处理 Y 轴滚动)
|
|
581
|
+
* 返回 restore 方法,用于将滚动容器恢复到原位置
|
|
582
|
+
*
|
|
583
|
+
* @param {import('playwright').Page} page
|
|
584
|
+
* @param {string|import('playwright').ElementHandle} target - CSS 选择器或元素句柄
|
|
585
|
+
* @param {Object} [options]
|
|
586
|
+
* @param {number} [options.maxSteps=25] - 最大滚动步数
|
|
587
|
+
* @param {number} [options.minStep=80] - 单次滚动最小步长
|
|
588
|
+
* @param {number} [options.maxStep=220] - 单次滚动最大步长
|
|
589
|
+
*/
|
|
590
|
+
async humanScroll(page, target, options = {}) {
|
|
591
|
+
const { maxSteps = 30, minStep = 150, maxStep = 400 } = options;
|
|
592
|
+
const targetDesc = typeof target === "string" ? target : "ElementHandle";
|
|
593
|
+
logger4.debug(`humanScroll | \u76EE\u6807=${targetDesc}`);
|
|
594
|
+
let element;
|
|
595
|
+
if (typeof target === "string") {
|
|
596
|
+
element = await page.$(target);
|
|
597
|
+
if (!element) {
|
|
598
|
+
logger4.warn(`humanScroll | \u5143\u7D20\u672A\u627E\u5230: ${target}`);
|
|
599
|
+
return { element: null, didScroll: false };
|
|
600
|
+
}
|
|
601
|
+
} else {
|
|
602
|
+
element = target;
|
|
603
|
+
}
|
|
604
|
+
const cursor = $GetCursor(page);
|
|
605
|
+
let didScroll = false;
|
|
606
|
+
const checkVisibility = async () => {
|
|
607
|
+
return await element.evaluate((el) => {
|
|
608
|
+
const rect = el.getBoundingClientRect();
|
|
609
|
+
if (!rect || rect.width === 0 || rect.height === 0) {
|
|
610
|
+
return { code: "ZERO_DIMENSIONS", reason: "\u5C3A\u5BF8\u4E3A\u96F6" };
|
|
611
|
+
}
|
|
612
|
+
const cx = rect.left + rect.width / 2;
|
|
613
|
+
const cy = rect.top + rect.height / 2;
|
|
614
|
+
const viewH = window.innerHeight;
|
|
615
|
+
const viewW = window.innerWidth;
|
|
616
|
+
if (cy < 0 || cy > viewH || cx < 0 || cx > viewW) {
|
|
617
|
+
const direction = cy < 0 ? "up" : cy > viewH ? "down" : "unknown";
|
|
618
|
+
return { code: "OUT_OF_VIEWPORT", reason: "\u4E0D\u5728\u89C6\u53E3\u5185", direction, cy, viewH };
|
|
619
|
+
}
|
|
620
|
+
const pointElement = document.elementFromPoint(cx, cy);
|
|
621
|
+
if (pointElement && !el.contains(pointElement) && !pointElement.contains(el)) {
|
|
622
|
+
return {
|
|
623
|
+
code: "OBSTRUCTED",
|
|
624
|
+
reason: "\u88AB\u906E\u6321",
|
|
625
|
+
obstruction: {
|
|
626
|
+
tag: pointElement.tagName,
|
|
627
|
+
id: pointElement.id,
|
|
628
|
+
className: pointElement.className
|
|
629
|
+
},
|
|
630
|
+
cy,
|
|
631
|
+
// Return Center Y for smart direction calculation
|
|
632
|
+
viewH
|
|
633
|
+
};
|
|
634
|
+
}
|
|
635
|
+
return { code: "VISIBLE" };
|
|
636
|
+
});
|
|
637
|
+
};
|
|
638
|
+
try {
|
|
639
|
+
for (let i = 0; i < maxSteps; i++) {
|
|
640
|
+
const status = await checkVisibility();
|
|
641
|
+
if (status.code === "VISIBLE") {
|
|
642
|
+
logger4.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
|
|
643
|
+
return { element, didScroll };
|
|
644
|
+
}
|
|
645
|
+
logger4.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
|
|
646
|
+
if (status.code === "OBSTRUCTED" && status.obstruction) {
|
|
647
|
+
logger4.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
|
|
648
|
+
}
|
|
649
|
+
let deltaY = 0;
|
|
650
|
+
if (status.code === "OUT_OF_VIEWPORT") {
|
|
651
|
+
if (status.direction === "down") {
|
|
652
|
+
deltaY = minStep + Math.random() * (maxStep - minStep);
|
|
653
|
+
} else if (status.direction === "up") {
|
|
654
|
+
deltaY = -(minStep + Math.random() * (maxStep - minStep));
|
|
655
|
+
} else {
|
|
656
|
+
deltaY = 100;
|
|
657
|
+
}
|
|
658
|
+
} else if (status.code === "OBSTRUCTED") {
|
|
659
|
+
const isBottomHalf = status.cy > status.viewH / 2;
|
|
660
|
+
const direction = isBottomHalf ? 1 : -1;
|
|
661
|
+
deltaY = direction * (minStep + Math.random() * 50);
|
|
662
|
+
}
|
|
663
|
+
if (i === 0 || Math.random() < 0.2) {
|
|
664
|
+
const viewSize = page.viewportSize();
|
|
665
|
+
if (viewSize) {
|
|
666
|
+
const safeX = viewSize.width * 0.5 + (Math.random() - 0.5) * 100;
|
|
667
|
+
const safeY = viewSize.height * 0.5 + (Math.random() - 0.5) * 100;
|
|
668
|
+
await cursor.actions.move({ x: safeX, y: safeY });
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
await page.mouse.wheel(0, deltaY);
|
|
672
|
+
didScroll = true;
|
|
673
|
+
await delay2(this.jitterMs(100 + Math.random() * 150, 0.2));
|
|
674
|
+
}
|
|
675
|
+
logger4.warn(`humanScroll | \u5728 ${maxSteps} \u6B65\u540E\u65E0\u6CD5\u786E\u4FDD\u53EF\u89C1\u6027`);
|
|
676
|
+
return { element, didScroll };
|
|
677
|
+
} catch (error) {
|
|
678
|
+
logger4.fail("humanScroll", error);
|
|
679
|
+
throw error;
|
|
680
|
+
}
|
|
650
681
|
},
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
682
|
+
/**
|
|
683
|
+
* 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
|
|
684
|
+
*
|
|
685
|
+
* @param {import('playwright').Page} page
|
|
686
|
+
* @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
|
|
687
|
+
* @param {Object} [options]
|
|
688
|
+
* @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
|
|
689
|
+
* @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
|
|
690
|
+
* @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
|
|
691
|
+
*/
|
|
692
|
+
async humanClick(page, target, options = {}) {
|
|
693
|
+
const cursor = $GetCursor(page);
|
|
694
|
+
const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
|
|
695
|
+
const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
|
|
696
|
+
logger4.start("humanClick", `target=${targetDesc}`);
|
|
697
|
+
const restoreOnce = async () => {
|
|
698
|
+
if (restoreOnce.restored) return;
|
|
699
|
+
restoreOnce.restored = true;
|
|
700
|
+
if (typeof restoreOnce.do !== "function") return;
|
|
701
|
+
try {
|
|
702
|
+
await delay2(this.jitterMs(1e3));
|
|
703
|
+
await restoreOnce.do();
|
|
704
|
+
} catch (restoreError) {
|
|
705
|
+
logger4.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
|
|
706
|
+
}
|
|
707
|
+
};
|
|
708
|
+
try {
|
|
709
|
+
if (target == null) {
|
|
710
|
+
await delay2(this.jitterMs(reactionDelay, 0.4));
|
|
711
|
+
await cursor.actions.click();
|
|
712
|
+
logger4.success("humanClick", "Clicked current position");
|
|
713
|
+
return true;
|
|
714
|
+
}
|
|
715
|
+
let element;
|
|
716
|
+
if (typeof target === "string") {
|
|
717
|
+
element = await page.$(target);
|
|
718
|
+
if (!element) {
|
|
719
|
+
if (throwOnMissing) {
|
|
720
|
+
throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${target}`);
|
|
721
|
+
}
|
|
722
|
+
logger4.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
|
|
723
|
+
return false;
|
|
724
|
+
}
|
|
725
|
+
} else {
|
|
726
|
+
element = target;
|
|
727
|
+
}
|
|
728
|
+
if (scrollIfNeeded) {
|
|
729
|
+
const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
|
|
730
|
+
restoreOnce.do = didScroll && restore ? restoreFn : null;
|
|
731
|
+
}
|
|
732
|
+
const box = await element.boundingBox();
|
|
733
|
+
if (!box) {
|
|
734
|
+
await restoreOnce();
|
|
735
|
+
if (throwOnMissing) {
|
|
736
|
+
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
|
|
737
|
+
}
|
|
738
|
+
logger4.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
|
|
739
|
+
return false;
|
|
740
|
+
}
|
|
741
|
+
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.3;
|
|
742
|
+
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.3;
|
|
743
|
+
await cursor.actions.move({ x, y });
|
|
744
|
+
await delay2(this.jitterMs(reactionDelay, 0.4));
|
|
745
|
+
await cursor.actions.click();
|
|
746
|
+
await restoreOnce();
|
|
747
|
+
logger4.success("humanClick");
|
|
748
|
+
return true;
|
|
749
|
+
} catch (error) {
|
|
750
|
+
await restoreOnce();
|
|
751
|
+
logger4.fail("humanClick", error);
|
|
752
|
+
throw error;
|
|
753
|
+
}
|
|
661
754
|
},
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
755
|
+
/**
|
|
756
|
+
* 随机延迟一段毫秒数(带 ±30% 抖动)
|
|
757
|
+
* @param {number} baseMs - 基础延迟毫秒数
|
|
758
|
+
* @param {number} [jitterPercent=0.3] - 抖动百分比
|
|
759
|
+
*/
|
|
760
|
+
async randomSleep(baseMs, jitterPercent = 0.3) {
|
|
761
|
+
const ms = this.jitterMs(baseMs, jitterPercent);
|
|
762
|
+
logger4.start("randomSleep", `base=${baseMs}, actual=${ms}ms`);
|
|
763
|
+
await delay2(ms);
|
|
764
|
+
logger4.success("randomSleep");
|
|
672
765
|
},
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
766
|
+
/**
|
|
767
|
+
* 模拟人类"注视"或"阅读"行为:鼠标在页面上随机微动
|
|
768
|
+
* @param {import('playwright').Page} page
|
|
769
|
+
* @param {number} [baseDurationMs=2500] - 基础持续时间 (±40% 抖动)
|
|
770
|
+
*/
|
|
771
|
+
async simulateGaze(page, baseDurationMs = 2500) {
|
|
772
|
+
const cursor = $GetCursor(page);
|
|
773
|
+
const durationMs = this.jitterMs(baseDurationMs, 0.4);
|
|
774
|
+
logger4.start("simulateGaze", `duration=${durationMs}ms`);
|
|
775
|
+
const startTime = Date.now();
|
|
776
|
+
const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
|
|
777
|
+
while (Date.now() - startTime < durationMs) {
|
|
778
|
+
const x = 100 + Math.random() * (viewportSize.width - 200);
|
|
779
|
+
const y = 100 + Math.random() * (viewportSize.height - 200);
|
|
780
|
+
await cursor.actions.move({ x, y });
|
|
781
|
+
await delay2(this.jitterMs(600, 0.5));
|
|
782
|
+
}
|
|
783
|
+
logger4.success("simulateGaze");
|
|
683
784
|
},
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
785
|
+
/**
|
|
786
|
+
* 人类化输入 - 带节奏变化(快-慢-停顿-偶尔加速)
|
|
787
|
+
* @param {import('playwright').Page} page
|
|
788
|
+
* @param {string} selector - 输入框选择器
|
|
789
|
+
* @param {string} text - 要输入的文本
|
|
790
|
+
* @param {Object} [options]
|
|
791
|
+
* @param {number} [options.baseDelay=180] - 基础按键延迟 (ms),实际 ±40% 抖动
|
|
792
|
+
* @param {number} [options.pauseProbability=0.08] - 停顿概率 (0-1)
|
|
793
|
+
* @param {number} [options.pauseBase=800] - 停顿时长基础值 (ms),实际 ±50% 抖动
|
|
794
|
+
*/
|
|
795
|
+
async humanType(page, selector, text, options = {}) {
|
|
796
|
+
logger4.start("humanType", `selector=${selector}, textLen=${text.length}`);
|
|
797
|
+
const {
|
|
798
|
+
baseDelay = 180,
|
|
799
|
+
pauseProbability = 0.08,
|
|
800
|
+
pauseBase = 800
|
|
801
|
+
} = options;
|
|
802
|
+
try {
|
|
803
|
+
const locator = page.locator(selector);
|
|
804
|
+
await Humanize.humanClick(page, locator);
|
|
805
|
+
await delay2(this.jitterMs(200, 0.4));
|
|
806
|
+
for (let i = 0; i < text.length; i++) {
|
|
807
|
+
const char = text[i];
|
|
808
|
+
let charDelay;
|
|
809
|
+
if (char === " ") {
|
|
810
|
+
charDelay = this.jitterMs(baseDelay * 0.6, 0.3);
|
|
811
|
+
} else if (/[,.!?;:,。!?;:]/.test(char)) {
|
|
812
|
+
charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
|
|
813
|
+
} else {
|
|
814
|
+
charDelay = this.jitterMs(baseDelay, 0.4);
|
|
815
|
+
}
|
|
816
|
+
await page.keyboard.type(char);
|
|
817
|
+
await delay2(charDelay);
|
|
818
|
+
if (Math.random() < pauseProbability && i < text.length - 1) {
|
|
819
|
+
const pauseTime = this.jitterMs(pauseBase, 0.5);
|
|
820
|
+
logger4.debug(`\u505C\u987F ${pauseTime}ms...`);
|
|
821
|
+
await delay2(pauseTime);
|
|
822
|
+
}
|
|
823
|
+
}
|
|
824
|
+
logger4.success("humanType");
|
|
825
|
+
} catch (error) {
|
|
826
|
+
logger4.fail("humanType", error);
|
|
827
|
+
throw error;
|
|
828
|
+
}
|
|
693
829
|
},
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
};
|
|
718
|
-
});
|
|
719
|
-
var buildStepLine = (step, status, details = []) => {
|
|
720
|
-
const parts = [];
|
|
721
|
-
const decoratedStep = step ? decorateLabel(step, STEP_EMOJIS) : "";
|
|
722
|
-
const base = decoratedStep ? `${STEP_PREFIX} ${decoratedStep}` : STEP_PREFIX;
|
|
723
|
-
parts.push(base.trim());
|
|
724
|
-
if (status) parts.push(decorateLabel(status, STATUS_EMOJIS));
|
|
725
|
-
const detailParts = details.filter(Boolean);
|
|
726
|
-
if (detailParts.length > 0) {
|
|
727
|
-
parts.push(...detailParts);
|
|
728
|
-
}
|
|
729
|
-
return parts.join(STEP_SEPARATOR);
|
|
730
|
-
};
|
|
731
|
-
var createThrottle = () => {
|
|
732
|
-
const lastMap = /* @__PURE__ */ new Map();
|
|
733
|
-
return (key, intervalMs, fn) => {
|
|
734
|
-
const now = Date.now();
|
|
735
|
-
const last = lastMap.get(key) || 0;
|
|
736
|
-
if (now - last >= intervalMs) {
|
|
737
|
-
lastMap.set(key, now);
|
|
738
|
-
fn();
|
|
830
|
+
/**
|
|
831
|
+
* 人类化清空输入框 - 模拟人类删除文本的行为
|
|
832
|
+
* @param {import('playwright').Page} page
|
|
833
|
+
* @param {string} selector - 输入框选择器
|
|
834
|
+
*/
|
|
835
|
+
async humanClear(page, selector) {
|
|
836
|
+
logger4.start("humanClear", `selector=${selector}`);
|
|
837
|
+
try {
|
|
838
|
+
const locator = page.locator(selector);
|
|
839
|
+
await locator.click();
|
|
840
|
+
await delay2(this.jitterMs(200, 0.4));
|
|
841
|
+
const currentValue = await locator.inputValue();
|
|
842
|
+
if (!currentValue || currentValue.length === 0) {
|
|
843
|
+
logger4.success("humanClear", "already empty");
|
|
844
|
+
return;
|
|
845
|
+
}
|
|
846
|
+
await page.keyboard.press("Meta+A");
|
|
847
|
+
await delay2(this.jitterMs(100, 0.4));
|
|
848
|
+
await page.keyboard.press("Backspace");
|
|
849
|
+
logger4.success("humanClear");
|
|
850
|
+
} catch (error) {
|
|
851
|
+
logger4.fail("humanClear", error);
|
|
852
|
+
throw error;
|
|
739
853
|
}
|
|
740
|
-
}
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
854
|
+
},
|
|
855
|
+
/**
|
|
856
|
+
* 页面预热浏览 - 模拟人类进入页面后的探索行为
|
|
857
|
+
* @param {import('playwright').Page} page
|
|
858
|
+
* @param {number} [baseDuration=3500] - 预热时长基础值 (±40% 抖动)
|
|
859
|
+
*/
|
|
860
|
+
async warmUpBrowsing(page, baseDuration = 3500) {
|
|
861
|
+
const cursor = $GetCursor(page);
|
|
862
|
+
const durationMs = this.jitterMs(baseDuration, 0.4);
|
|
863
|
+
logger4.start("warmUpBrowsing", `duration=${durationMs}ms`);
|
|
864
|
+
const startTime = Date.now();
|
|
865
|
+
const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
|
|
866
|
+
try {
|
|
867
|
+
while (Date.now() - startTime < durationMs) {
|
|
868
|
+
const action = Math.random();
|
|
869
|
+
if (action < 0.4) {
|
|
870
|
+
const x = 100 + Math.random() * (viewportSize.width - 200);
|
|
871
|
+
const y = 100 + Math.random() * (viewportSize.height - 200);
|
|
872
|
+
await cursor.actions.move({ x, y });
|
|
873
|
+
await delay2(this.jitterMs(350, 0.4));
|
|
874
|
+
} else if (action < 0.7) {
|
|
875
|
+
const scrollY = (Math.random() - 0.5) * 200;
|
|
876
|
+
await page.mouse.wheel(0, scrollY);
|
|
877
|
+
await delay2(this.jitterMs(500, 0.4));
|
|
878
|
+
} else {
|
|
879
|
+
await delay2(this.jitterMs(800, 0.5));
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
logger4.success("warmUpBrowsing");
|
|
883
|
+
} catch (error) {
|
|
884
|
+
logger4.fail("warmUpBrowsing", error);
|
|
885
|
+
throw error;
|
|
769
886
|
}
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
};
|
|
798
|
-
var getDefaultBaseLogger = () => createBaseLogger("", defaultLogger);
|
|
799
|
-
var Logger = {
|
|
800
|
-
setLogger: (logger10) => setDefaultLogger(logger10),
|
|
801
|
-
info: (message) => getDefaultBaseLogger().info(message),
|
|
802
|
-
success: (message) => getDefaultBaseLogger().success(message),
|
|
803
|
-
warning: (message) => getDefaultBaseLogger().warning(message),
|
|
804
|
-
warn: (message) => getDefaultBaseLogger().warning(message),
|
|
805
|
-
error: (message) => getDefaultBaseLogger().error(message),
|
|
806
|
-
debug: (message) => getDefaultBaseLogger().debug(message),
|
|
807
|
-
start: (message) => getDefaultBaseLogger().start(message),
|
|
808
|
-
useTemplate: (logger10) => createTemplateLogger(createBaseLogger("", logger10 || defaultLogger))
|
|
809
|
-
};
|
|
810
|
-
|
|
811
|
-
// src/internal/logger.js
|
|
812
|
-
import { log as crawleeLog } from "crawlee";
|
|
813
|
-
function createLogger(moduleName) {
|
|
814
|
-
const baseLogger = createBaseLogger(moduleName, crawleeLog);
|
|
815
|
-
return {
|
|
816
|
-
/**
|
|
817
|
-
* 方法开始日志
|
|
818
|
-
* @param {string} methodName - 方法名称
|
|
819
|
-
* @param {string} [params] - 参数摘要 (可选)
|
|
820
|
-
*/
|
|
821
|
-
start(methodName, params = "") {
|
|
822
|
-
const paramStr = params ? ` (${params})` : "";
|
|
823
|
-
baseLogger.start(`${methodName} \u5F00\u59CB${paramStr}`);
|
|
824
|
-
},
|
|
825
|
-
/**
|
|
826
|
-
* 方法成功日志
|
|
827
|
-
* @param {string} methodName - 方法名称
|
|
828
|
-
* @param {string} [result] - 结果摘要 (可选)
|
|
829
|
-
*/
|
|
830
|
-
success(methodName, result = "") {
|
|
831
|
-
const resultStr = result ? ` (${result})` : "";
|
|
832
|
-
baseLogger.success(`${methodName} \u5B8C\u6210${resultStr}`);
|
|
833
|
-
},
|
|
834
|
-
/**
|
|
835
|
-
* 方法失败日志
|
|
836
|
-
* @param {string} methodName - 方法名称
|
|
837
|
-
* @param {Error|string} error - 错误对象或信息
|
|
838
|
-
*/
|
|
839
|
-
fail(methodName, error) {
|
|
840
|
-
const message = error instanceof Error ? error.message : error;
|
|
841
|
-
baseLogger.error(`${methodName} \u5931\u8D25: ${message}`);
|
|
842
|
-
},
|
|
843
|
-
/**
|
|
844
|
-
* 调试日志
|
|
845
|
-
* @param {string} message - 详情
|
|
846
|
-
*/
|
|
847
|
-
debug(message) {
|
|
848
|
-
baseLogger.debug(message);
|
|
849
|
-
},
|
|
850
|
-
/**
|
|
851
|
-
* 警告日志
|
|
852
|
-
* @param {string} message - 警告信息
|
|
853
|
-
*/
|
|
854
|
-
warn(message) {
|
|
855
|
-
baseLogger.warning(message);
|
|
856
|
-
},
|
|
857
|
-
warning(message) {
|
|
858
|
-
baseLogger.warning(message);
|
|
859
|
-
},
|
|
860
|
-
/**
|
|
861
|
-
* 普通信息日志
|
|
862
|
-
* @param {string} message - 信息
|
|
863
|
-
*/
|
|
864
|
-
info(message) {
|
|
865
|
-
baseLogger.info(message);
|
|
887
|
+
},
|
|
888
|
+
/**
|
|
889
|
+
* 自然滚动 - 带惯性、减速效果和随机抖动
|
|
890
|
+
* @param {import('playwright').Page} page
|
|
891
|
+
* @param {'up' | 'down'} [direction='down'] - 滚动方向
|
|
892
|
+
* @param {number} [distance=300] - 总滚动距离基础值 (px),±15% 抖动
|
|
893
|
+
* @param {number} [baseSteps=5] - 分几步完成基础值,±1 随机
|
|
894
|
+
*/
|
|
895
|
+
async naturalScroll(page, direction = "down", distance = 300, baseSteps = 5) {
|
|
896
|
+
const steps = Math.max(3, baseSteps + Math.floor(Math.random() * 3) - 1);
|
|
897
|
+
const actualDistance = this.jitterMs(distance, 0.15);
|
|
898
|
+
logger4.start("naturalScroll", `dir=${direction}, dist=${actualDistance}, steps=${steps}`);
|
|
899
|
+
const sign = direction === "down" ? 1 : -1;
|
|
900
|
+
const stepDistance = actualDistance / steps;
|
|
901
|
+
try {
|
|
902
|
+
for (let i = 0; i < steps; i++) {
|
|
903
|
+
const factor = 1 - i / steps * 0.5;
|
|
904
|
+
const jitter = 0.9 + Math.random() * 0.2;
|
|
905
|
+
const scrollAmount = stepDistance * factor * sign * jitter;
|
|
906
|
+
await page.mouse.wheel(0, scrollAmount);
|
|
907
|
+
const baseDelay = 60 + i * 25;
|
|
908
|
+
await delay2(this.jitterMs(baseDelay, 0.3));
|
|
909
|
+
}
|
|
910
|
+
logger4.success("naturalScroll");
|
|
911
|
+
} catch (error) {
|
|
912
|
+
logger4.fail("naturalScroll", error);
|
|
913
|
+
throw error;
|
|
866
914
|
}
|
|
867
|
-
}
|
|
868
|
-
}
|
|
915
|
+
}
|
|
916
|
+
};
|
|
869
917
|
|
|
870
|
-
// src/
|
|
871
|
-
var
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
875
|
-
|
|
876
|
-
|
|
918
|
+
// src/launch.js
|
|
919
|
+
var Launch = {
|
|
920
|
+
getLaunchOptions(customArgs = []) {
|
|
921
|
+
return {
|
|
922
|
+
args: [
|
|
923
|
+
...AntiCheat.getLaunchArgs(),
|
|
924
|
+
...customArgs
|
|
925
|
+
],
|
|
926
|
+
ignoreDefaultArgs: ["--enable-automation"]
|
|
927
|
+
};
|
|
928
|
+
},
|
|
877
929
|
/**
|
|
878
|
-
*
|
|
879
|
-
*
|
|
880
|
-
* @param {number} [info.code] - ErrorKeygen 枚举值(用于错误分类)
|
|
881
|
-
* @param {Object} [info.context] - 上下文信息对象
|
|
930
|
+
* 推荐的 Fingerprint Generator 选项
|
|
931
|
+
* 确保生成的是桌面端、较新的 Chrome,以匹配我们的脚本逻辑
|
|
882
932
|
*/
|
|
883
|
-
|
|
884
|
-
|
|
885
|
-
|
|
933
|
+
getFingerprintGeneratorOptions() {
|
|
934
|
+
return AntiCheat.getFingerprintGeneratorOptions();
|
|
935
|
+
}
|
|
936
|
+
};
|
|
937
|
+
|
|
938
|
+
// src/live-view.js
|
|
939
|
+
import express from "express";
|
|
940
|
+
import { Actor } from "apify";
|
|
941
|
+
var logger5 = createInternalLogger("LiveView");
|
|
942
|
+
async function startLiveViewServer(liveViewKey) {
|
|
943
|
+
const app = express();
|
|
944
|
+
app.get("/", async (req, res) => {
|
|
945
|
+
try {
|
|
946
|
+
const screenshotBuffer = await Actor.getValue(liveViewKey);
|
|
947
|
+
if (!screenshotBuffer) {
|
|
948
|
+
res.send('<html><head><meta http-equiv="refresh" content="2"></head><body>\u7B49\u5F85\u7B2C\u4E00\u4E2A\u5C4F\u5E55\u622A\u56FE...</body></html>');
|
|
949
|
+
return;
|
|
950
|
+
}
|
|
951
|
+
const screenshotBase64 = screenshotBuffer.toString("base64");
|
|
952
|
+
res.send(`
|
|
953
|
+
<html>
|
|
954
|
+
<head>
|
|
955
|
+
<title>Live View (\u622A\u56FE)</title>
|
|
956
|
+
<meta http-equiv="refresh" content="1">
|
|
957
|
+
</head>
|
|
958
|
+
<body style="margin:0; padding:0;">
|
|
959
|
+
<img src="data:image/png;base64,${screenshotBase64}"
|
|
960
|
+
alt="Live View Screenshot"
|
|
961
|
+
style="width: 100%; height: auto;" />
|
|
962
|
+
</body>
|
|
963
|
+
</html>
|
|
964
|
+
`);
|
|
965
|
+
} catch (error) {
|
|
966
|
+
logger5.fail("Live View Server", error);
|
|
967
|
+
res.status(500).send(`\u65E0\u6CD5\u52A0\u8F7D\u5C4F\u5E55\u622A\u56FE: ${error.message}`);
|
|
886
968
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
969
|
+
});
|
|
970
|
+
const port = process.env.APIFY_CONTAINER_PORT || 4321;
|
|
971
|
+
app.listen(port, () => {
|
|
972
|
+
logger5.success("startLiveViewServer", `\u76D1\u542C\u7AEF\u53E3 ${port}`);
|
|
973
|
+
});
|
|
974
|
+
}
|
|
975
|
+
async function takeLiveScreenshot(liveViewKey, page, logMessage) {
|
|
976
|
+
try {
|
|
977
|
+
const buffer = await page.screenshot({ type: "png" });
|
|
978
|
+
await Actor.setValue(liveViewKey, buffer, { contentType: "image/png" });
|
|
979
|
+
if (logMessage) {
|
|
980
|
+
logger5.info(`(\u622A\u56FE): ${logMessage}`);
|
|
894
981
|
}
|
|
982
|
+
} catch (e) {
|
|
983
|
+
logger5.warn(`\u65E0\u6CD5\u6355\u83B7 Live View \u5C4F\u5E55\u622A\u56FE: ${e.message}`);
|
|
895
984
|
}
|
|
896
|
-
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
return error instanceof _CrawlerError || error?.name === "CrawlerError";
|
|
910
|
-
}
|
|
911
|
-
/**
|
|
912
|
-
* 从普通 Error 创建 CrawlerError
|
|
913
|
-
* @param {Error} error - 原始错误
|
|
914
|
-
* @param {Object} [options={}] - 选项对象 (包含 code 和 context)
|
|
915
|
-
* @returns {CrawlerError}
|
|
916
|
-
*/
|
|
917
|
-
static from(error, options = {}) {
|
|
918
|
-
const crawlerError = new _CrawlerError({
|
|
919
|
-
message: error.message,
|
|
920
|
-
...options
|
|
921
|
-
});
|
|
922
|
-
crawlerError.stack = error.stack;
|
|
923
|
-
return crawlerError;
|
|
924
|
-
}
|
|
985
|
+
}
|
|
986
|
+
var useLiveView = (liveViewKey = PresetOfLiveViewKey) => {
|
|
987
|
+
return {
|
|
988
|
+
takeLiveScreenshot: async (page, logMessage) => {
|
|
989
|
+
return await takeLiveScreenshot(liveViewKey, page, logMessage);
|
|
990
|
+
},
|
|
991
|
+
startLiveViewServer: async () => {
|
|
992
|
+
return await startLiveViewServer(liveViewKey);
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
};
|
|
996
|
+
var LiveView = {
|
|
997
|
+
useLiveView
|
|
925
998
|
};
|
|
926
999
|
|
|
927
|
-
// src/
|
|
928
|
-
import {
|
|
929
|
-
var
|
|
930
|
-
|
|
931
|
-
|
|
932
|
-
|
|
933
|
-
|
|
934
|
-
} catch (error) {
|
|
935
|
-
throw new Error("\u26A0\uFE0F apify \u5E93\u672A\u5B89\u88C5\uFF0CApifyKit \u7684 Actor \u76F8\u5173\u529F\u80FD\u4E0D\u53EF\u7528");
|
|
1000
|
+
// src/captcha-monitor.js
|
|
1001
|
+
import { v4 as uuidv4 } from "uuid";
|
|
1002
|
+
var logger6 = createInternalLogger("Captcha");
|
|
1003
|
+
function useCaptchaMonitor(page, options) {
|
|
1004
|
+
const { domSelector, urlPattern, onDetected } = options;
|
|
1005
|
+
if (!domSelector && !urlPattern) {
|
|
1006
|
+
throw new Error("[CaptchaMonitor] \u5FC5\u987B\u63D0\u4F9B domSelector \u6216 urlPattern \u81F3\u5C11\u4E00\u4E2A");
|
|
936
1007
|
}
|
|
937
|
-
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
|
|
949
|
-
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
970
|
-
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
1008
|
+
if (!onDetected || typeof onDetected !== "function") {
|
|
1009
|
+
throw new Error("[CaptchaMonitor] onDetected \u5FC5\u987B\u662F\u4E00\u4E2A\u51FD\u6570");
|
|
1010
|
+
}
|
|
1011
|
+
let isHandled = false;
|
|
1012
|
+
let frameHandler = null;
|
|
1013
|
+
let exposedFunctionName = null;
|
|
1014
|
+
const triggerDetected = async () => {
|
|
1015
|
+
if (isHandled) return;
|
|
1016
|
+
isHandled = true;
|
|
1017
|
+
await onDetected();
|
|
1018
|
+
};
|
|
1019
|
+
const cleanupFns = [];
|
|
1020
|
+
if (domSelector) {
|
|
1021
|
+
exposedFunctionName = `__c_d_${uuidv4().replace(/-/g, "_")}`;
|
|
1022
|
+
const cleanerName = `__c_cleaner_${uuidv4().replace(/-/g, "_")}`;
|
|
1023
|
+
page.exposeFunction(exposedFunctionName, triggerDetected).catch(() => {
|
|
1024
|
+
});
|
|
1025
|
+
page.addInitScript(({ selector, callbackName, cleanerName: cleanerName2 }) => {
|
|
1026
|
+
(() => {
|
|
1027
|
+
let observer = null;
|
|
1028
|
+
const checkAndReport = () => {
|
|
1029
|
+
const element = document.querySelector(selector);
|
|
1030
|
+
if (element) {
|
|
1031
|
+
if (observer) {
|
|
1032
|
+
observer.disconnect();
|
|
1033
|
+
observer = null;
|
|
1034
|
+
}
|
|
1035
|
+
if (window[callbackName]) {
|
|
1036
|
+
window[callbackName]();
|
|
1037
|
+
}
|
|
1038
|
+
return true;
|
|
1039
|
+
}
|
|
1040
|
+
return false;
|
|
1041
|
+
};
|
|
1042
|
+
if (checkAndReport()) return;
|
|
1043
|
+
observer = new MutationObserver((mutations) => {
|
|
1044
|
+
let shouldCheck = false;
|
|
1045
|
+
for (const mutation of mutations) {
|
|
1046
|
+
if (mutation.addedNodes.length > 0) {
|
|
1047
|
+
shouldCheck = true;
|
|
1048
|
+
break;
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
if (shouldCheck && observer) {
|
|
1052
|
+
checkAndReport();
|
|
1053
|
+
}
|
|
1054
|
+
});
|
|
1055
|
+
const mountObserver = () => {
|
|
1056
|
+
const target = document.documentElement;
|
|
1057
|
+
if (target && observer) {
|
|
1058
|
+
observer.observe(target, { childList: true, subtree: true });
|
|
1059
|
+
}
|
|
1060
|
+
};
|
|
1061
|
+
if (document.readyState === "loading") {
|
|
1062
|
+
window.addEventListener("DOMContentLoaded", mountObserver);
|
|
976
1063
|
} else {
|
|
977
|
-
|
|
978
|
-
await new Promise((resolve) => setTimeout(resolve, 3e3));
|
|
979
|
-
logger.success(`[RetryStep] \u7B49\u5F85\u5B8C\u6210`);
|
|
980
|
-
}
|
|
981
|
-
};
|
|
982
|
-
let lastResult = await executeAction(0);
|
|
983
|
-
if (lastResult.success) {
|
|
984
|
-
return lastResult.result;
|
|
985
|
-
}
|
|
986
|
-
for (let attempt = 1; attempt <= retryTimes; attempt++) {
|
|
987
|
-
logger.start(`[RetryStep] \u51C6\u5907\u7B2C ${attempt}/${retryTimes} \u6B21\u91CD\u8BD5: ${step}`);
|
|
988
|
-
try {
|
|
989
|
-
await prepareForRetry(attempt);
|
|
990
|
-
} catch (prepareError) {
|
|
991
|
-
logger.warn(`[RetryStep] \u91CD\u8BD5\u51C6\u5907\u5931\u8D25: ${prepareError.message}`);
|
|
992
|
-
continue;
|
|
993
|
-
}
|
|
994
|
-
lastResult = await executeAction(attempt);
|
|
995
|
-
if (lastResult.success) {
|
|
996
|
-
return lastResult.result;
|
|
1064
|
+
mountObserver();
|
|
997
1065
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
try {
|
|
1003
|
-
if (page) {
|
|
1004
|
-
const buffer = await page.screenshot({ fullPage: true, type: "jpeg", quality: 60 });
|
|
1005
|
-
base64 = `data:image/jpeg;base64,${buffer.toString("base64")}`;
|
|
1066
|
+
window[cleanerName2] = () => {
|
|
1067
|
+
if (observer) {
|
|
1068
|
+
observer.disconnect();
|
|
1069
|
+
observer = null;
|
|
1006
1070
|
}
|
|
1007
|
-
}
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
|
|
1012
|
-
|
|
1013
|
-
|
|
1014
|
-
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1019
|
-
|
|
1020
|
-
}
|
|
1021
|
-
}
|
|
1022
|
-
|
|
1023
|
-
|
|
1024
|
-
|
|
1025
|
-
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
* @param {Error|CrawlerError} error - 错误对象
|
|
1046
|
-
* @param {Object} [meta] - 额外的数据(如failedStep, screenshotBase64等)
|
|
1047
|
-
* @private
|
|
1048
|
-
*/
|
|
1049
|
-
async pushFailed(error, meta = {}) {
|
|
1050
|
-
const isCrawlerError = CrawlerError.isCrawlerError(error);
|
|
1051
|
-
const code = isCrawlerError ? error.code : Code.UnknownError;
|
|
1052
|
-
const context = isCrawlerError ? error.context : {};
|
|
1053
|
-
await Actor2.pushData({
|
|
1054
|
-
// 如果是 CrawlerError,使用其 code,否则使用默认 Failed code
|
|
1055
|
-
code,
|
|
1056
|
-
status: Status.Failed,
|
|
1057
|
-
error: serializeError2(error),
|
|
1058
|
-
meta,
|
|
1059
|
-
context,
|
|
1060
|
-
timestamp: (/* @__PURE__ */ new Date()).toISOString()
|
|
1061
|
-
});
|
|
1062
|
-
logger.success("pushFailed", "Error data pushed");
|
|
1071
|
+
};
|
|
1072
|
+
})();
|
|
1073
|
+
}, { selector: domSelector, callbackName: exposedFunctionName, cleanerName });
|
|
1074
|
+
logger6.success("useCaptchaMonitor", `DOM \u76D1\u63A7\u5DF2\u542F\u7528: ${domSelector}`);
|
|
1075
|
+
cleanupFns.push(async () => {
|
|
1076
|
+
try {
|
|
1077
|
+
await page.evaluate((name) => {
|
|
1078
|
+
if (window[name]) {
|
|
1079
|
+
window[name]();
|
|
1080
|
+
delete window[name];
|
|
1081
|
+
}
|
|
1082
|
+
}, cleanerName);
|
|
1083
|
+
} catch (e) {
|
|
1084
|
+
}
|
|
1085
|
+
});
|
|
1086
|
+
}
|
|
1087
|
+
if (urlPattern) {
|
|
1088
|
+
frameHandler = async (frame) => {
|
|
1089
|
+
if (frame === page.mainFrame()) {
|
|
1090
|
+
const currentUrl = page.url();
|
|
1091
|
+
if (currentUrl.includes(urlPattern)) {
|
|
1092
|
+
await triggerDetected();
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
};
|
|
1096
|
+
page.on("framenavigated", frameHandler);
|
|
1097
|
+
logger6.success("useCaptchaMonitor", `URL \u76D1\u63A7\u5DF2\u542F\u7528: ${urlPattern}`);
|
|
1098
|
+
cleanupFns.push(async () => {
|
|
1099
|
+
page.off("framenavigated", frameHandler);
|
|
1100
|
+
});
|
|
1101
|
+
}
|
|
1102
|
+
return {
|
|
1103
|
+
stop: async () => {
|
|
1104
|
+
logger6.info("useCaptchaMonitor", "\u6B63\u5728\u505C\u6B62\u76D1\u63A7...");
|
|
1105
|
+
for (const fn of cleanupFns) {
|
|
1106
|
+
await fn();
|
|
1107
|
+
}
|
|
1108
|
+
isHandled = true;
|
|
1063
1109
|
}
|
|
1064
1110
|
};
|
|
1065
1111
|
}
|
|
1066
|
-
var
|
|
1067
|
-
|
|
1068
|
-
if (!instance) {
|
|
1069
|
-
instance = await createApifyKit();
|
|
1070
|
-
}
|
|
1071
|
-
return instance;
|
|
1072
|
-
}
|
|
1073
|
-
var ApifyKit = {
|
|
1074
|
-
useApifyKit
|
|
1112
|
+
var Captcha = {
|
|
1113
|
+
useCaptchaMonitor
|
|
1075
1114
|
};
|
|
1076
1115
|
|
|
1077
|
-
// src/
|
|
1078
|
-
import
|
|
1079
|
-
|
|
1080
|
-
var
|
|
1116
|
+
// src/sse.js
|
|
1117
|
+
import https from "https";
|
|
1118
|
+
import { URL as URL2 } from "url";
|
|
1119
|
+
var logger7 = createInternalLogger("Sse");
|
|
1120
|
+
var Sse = {
|
|
1081
1121
|
/**
|
|
1082
|
-
* 解析
|
|
1083
|
-
*
|
|
1084
|
-
* @param {string}
|
|
1085
|
-
* @returns {Array}
|
|
1122
|
+
* 解析 SSE 流文本
|
|
1123
|
+
* 支持 `data: {...}` 和 `data:{...}` 两种格式
|
|
1124
|
+
* @param {string} sseStreamText
|
|
1125
|
+
* @returns {Array<Object>} events
|
|
1086
1126
|
*/
|
|
1087
|
-
|
|
1088
|
-
const
|
|
1089
|
-
const
|
|
1090
|
-
for (const
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
}
|
|
1098
|
-
|
|
1099
|
-
cookie.domain = domain;
|
|
1127
|
+
parseSseStream(sseStreamText) {
|
|
1128
|
+
const events = [];
|
|
1129
|
+
const lines = sseStreamText.split("\n");
|
|
1130
|
+
for (const line of lines) {
|
|
1131
|
+
if (line.startsWith("data:")) {
|
|
1132
|
+
try {
|
|
1133
|
+
const jsonContent = line.substring(5).trim();
|
|
1134
|
+
if (jsonContent) {
|
|
1135
|
+
events.push(JSON.parse(jsonContent));
|
|
1136
|
+
}
|
|
1137
|
+
} catch (e) {
|
|
1138
|
+
logger7.debug("parseSseStream", `JSON \u89E3\u6790\u5931\u8D25: ${e.message}, line: ${line.substring(0, 100)}...`);
|
|
1100
1139
|
}
|
|
1101
|
-
cookies.push(cookie);
|
|
1102
1140
|
}
|
|
1103
1141
|
}
|
|
1104
|
-
|
|
1105
|
-
return
|
|
1142
|
+
logger7.success("parseSseStream", `\u89E3\u6790\u5B8C\u6210, events \u6570\u91CF: ${events.length}`);
|
|
1143
|
+
return events;
|
|
1106
1144
|
},
|
|
1107
1145
|
/**
|
|
1108
|
-
*
|
|
1109
|
-
*
|
|
1110
|
-
*
|
|
1111
|
-
* @param {
|
|
1112
|
-
* @param {
|
|
1113
|
-
* @param {
|
|
1114
|
-
* @param {
|
|
1115
|
-
* @param {number} [options.
|
|
1116
|
-
* @
|
|
1146
|
+
* 拦截网络请求并使用 Node.js 原生 https 模块转发,以解决流式数据捕获问题。
|
|
1147
|
+
* @param {import('playwright').Page} page
|
|
1148
|
+
* @param {string|RegExp} urlPattern - 拦截的 URL 模式
|
|
1149
|
+
* @param {object} options
|
|
1150
|
+
* @param {function(string, function, string): void} [options.onData] - (textChunk, resolve, accumulatedText) => void
|
|
1151
|
+
* @param {function(string, function): void} [options.onEnd] - (fullText, resolve) => void
|
|
1152
|
+
* @param {function(Error, function): void} [options.onTimeout] - (error, reject) => void
|
|
1153
|
+
* @param {number} [options.initialTimeout=90000] - 初始数据接收超时 (ms),默认 90s
|
|
1154
|
+
* @param {number} [options.timeout=180000] - 整体请求超时时间 (ms),默认 180s
|
|
1155
|
+
* @returns {Promise<any>} - 返回 Promise,当流满足条件时 resolve
|
|
1117
1156
|
*/
|
|
1118
|
-
async
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
|
|
1131
|
-
|
|
1132
|
-
|
|
1133
|
-
|
|
1134
|
-
|
|
1135
|
-
|
|
1136
|
-
|
|
1137
|
-
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1157
|
+
async intercept(page, urlPattern, options = {}) {
|
|
1158
|
+
const {
|
|
1159
|
+
onData,
|
|
1160
|
+
onEnd,
|
|
1161
|
+
onTimeout,
|
|
1162
|
+
initialTimeout = 9e4,
|
|
1163
|
+
overallTimeout = 18e4
|
|
1164
|
+
} = options;
|
|
1165
|
+
let initialTimer = null;
|
|
1166
|
+
let overallTimer = null;
|
|
1167
|
+
let hasReceivedInitialData = false;
|
|
1168
|
+
const clearAllTimers = () => {
|
|
1169
|
+
if (initialTimer) clearTimeout(initialTimer);
|
|
1170
|
+
if (overallTimer) clearTimeout(overallTimer);
|
|
1171
|
+
initialTimer = null;
|
|
1172
|
+
overallTimer = null;
|
|
1173
|
+
};
|
|
1174
|
+
const workPromise = new Promise((resolve, reject) => {
|
|
1175
|
+
page.route(urlPattern, async (route) => {
|
|
1176
|
+
const request = route.request();
|
|
1177
|
+
logger7.info(`[MITM] \u5DF2\u62E6\u622A\u8BF7\u6C42: ${request.url()}`);
|
|
1178
|
+
try {
|
|
1179
|
+
const headers = await request.allHeaders();
|
|
1180
|
+
const postData = request.postData();
|
|
1181
|
+
const urlObj = new URL2(request.url());
|
|
1182
|
+
delete headers["accept-encoding"];
|
|
1183
|
+
delete headers["content-length"];
|
|
1184
|
+
const reqOptions = {
|
|
1185
|
+
hostname: urlObj.hostname,
|
|
1186
|
+
port: 443,
|
|
1187
|
+
path: urlObj.pathname + urlObj.search,
|
|
1188
|
+
method: request.method(),
|
|
1189
|
+
headers,
|
|
1190
|
+
timeout: overallTimeout
|
|
1191
|
+
};
|
|
1192
|
+
const req = https.request(reqOptions, (res) => {
|
|
1193
|
+
const chunks = [];
|
|
1194
|
+
let accumulatedText = "";
|
|
1195
|
+
res.on("data", (chunk) => {
|
|
1196
|
+
if (!hasReceivedInitialData) {
|
|
1197
|
+
hasReceivedInitialData = true;
|
|
1198
|
+
if (initialTimer) {
|
|
1199
|
+
clearTimeout(initialTimer);
|
|
1200
|
+
initialTimer = null;
|
|
1201
|
+
}
|
|
1202
|
+
logger7.debug("[Intercept] \u5DF2\u63A5\u6536\u521D\u59CB\u6570\u636E");
|
|
1203
|
+
}
|
|
1204
|
+
chunks.push(chunk);
|
|
1205
|
+
const textChunk = chunk.toString("utf-8");
|
|
1206
|
+
accumulatedText += textChunk;
|
|
1207
|
+
if (onData) {
|
|
1208
|
+
try {
|
|
1209
|
+
onData(textChunk, resolve, accumulatedText);
|
|
1210
|
+
} catch (e) {
|
|
1211
|
+
logger7.fail(`onData \u9519\u8BEF`, e);
|
|
1212
|
+
}
|
|
1213
|
+
}
|
|
1214
|
+
});
|
|
1215
|
+
res.on("end", () => {
|
|
1216
|
+
logger7.info("[MITM] \u4E0A\u6E38\u54CD\u5E94\u7ED3\u675F");
|
|
1217
|
+
clearAllTimers();
|
|
1218
|
+
if (onEnd) {
|
|
1219
|
+
try {
|
|
1220
|
+
onEnd(accumulatedText, resolve);
|
|
1221
|
+
} catch (e) {
|
|
1222
|
+
logger7.fail(`onEnd \u9519\u8BEF`, e);
|
|
1223
|
+
}
|
|
1224
|
+
} else if (!onData) {
|
|
1225
|
+
resolve(accumulatedText);
|
|
1226
|
+
}
|
|
1227
|
+
route.fulfill({
|
|
1228
|
+
status: res.statusCode,
|
|
1229
|
+
headers: res.headers,
|
|
1230
|
+
body: Buffer.concat(chunks)
|
|
1231
|
+
}).catch(() => {
|
|
1232
|
+
});
|
|
1233
|
+
});
|
|
1234
|
+
});
|
|
1235
|
+
req.on("error", (e) => {
|
|
1236
|
+
clearAllTimers();
|
|
1237
|
+
route.abort().catch(() => {
|
|
1238
|
+
});
|
|
1239
|
+
reject(e);
|
|
1240
|
+
});
|
|
1241
|
+
if (postData) req.write(postData);
|
|
1242
|
+
req.end();
|
|
1243
|
+
} catch (e) {
|
|
1244
|
+
clearAllTimers();
|
|
1245
|
+
route.continue().catch(() => {
|
|
1246
|
+
});
|
|
1247
|
+
reject(e);
|
|
1248
|
+
}
|
|
1249
|
+
}).catch(reject);
|
|
1250
|
+
});
|
|
1251
|
+
const timeoutPromise = new Promise((_, reject) => {
|
|
1252
|
+
initialTimer = setTimeout(() => {
|
|
1253
|
+
if (!hasReceivedInitialData) {
|
|
1254
|
+
const error = new CrawlerError({
|
|
1255
|
+
message: `\u521D\u59CB\u6570\u636E\u63A5\u6536\u8D85\u65F6 (${initialTimeout}ms)`,
|
|
1256
|
+
code: Code.InitialTimeout,
|
|
1257
|
+
context: { timeout: initialTimeout }
|
|
1169
1258
|
});
|
|
1259
|
+
clearAllTimers();
|
|
1260
|
+
if (onTimeout) {
|
|
1261
|
+
try {
|
|
1262
|
+
onTimeout(error, reject);
|
|
1263
|
+
} catch (e) {
|
|
1264
|
+
reject(e);
|
|
1265
|
+
}
|
|
1266
|
+
} else {
|
|
1267
|
+
reject(error);
|
|
1268
|
+
}
|
|
1269
|
+
}
|
|
1270
|
+
}, initialTimeout);
|
|
1271
|
+
overallTimer = setTimeout(() => {
|
|
1272
|
+
const error = new CrawlerError({
|
|
1273
|
+
message: `\u6574\u4F53\u8BF7\u6C42\u8D85\u65F6 (${overallTimeout}ms)`,
|
|
1274
|
+
code: Code.OverallTimeout,
|
|
1275
|
+
context: { timeout: overallTimeout }
|
|
1170
1276
|
});
|
|
1171
|
-
|
|
1172
|
-
|
|
1277
|
+
clearAllTimers();
|
|
1278
|
+
if (onTimeout) {
|
|
1279
|
+
try {
|
|
1280
|
+
onTimeout(error, reject);
|
|
1281
|
+
} catch (e) {
|
|
1282
|
+
reject(e);
|
|
1283
|
+
}
|
|
1284
|
+
} else {
|
|
1285
|
+
reject(error);
|
|
1173
1286
|
}
|
|
1174
|
-
}
|
|
1175
|
-
}
|
|
1287
|
+
}, overallTimeout);
|
|
1288
|
+
});
|
|
1289
|
+
workPromise.catch(() => {
|
|
1290
|
+
});
|
|
1291
|
+
timeoutPromise.catch(() => {
|
|
1292
|
+
});
|
|
1293
|
+
const racePromise = Promise.race([workPromise, timeoutPromise]);
|
|
1294
|
+
racePromise.catch(() => {
|
|
1295
|
+
});
|
|
1296
|
+
return racePromise;
|
|
1176
1297
|
}
|
|
1177
1298
|
};
|
|
1178
1299
|
|
|
1179
|
-
// src/
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
"
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1300
|
+
// src/interception.js
|
|
1301
|
+
import { gotScraping } from "got-scraping";
|
|
1302
|
+
import { Agent as HttpAgent } from "http";
|
|
1303
|
+
import { Agent as HttpsAgent } from "https";
|
|
1304
|
+
var logger8 = createInternalLogger("Interception");
|
|
1305
|
+
var SHARED_HTTP_AGENT = new HttpAgent({ keepAlive: false });
|
|
1306
|
+
var SHARED_HTTPS_AGENT = new HttpsAgent({ keepAlive: false, rejectUnauthorized: false });
|
|
1307
|
+
var DirectConfig = {
|
|
1308
|
+
/** 直连请求超时时间(秒) */
|
|
1309
|
+
directTimeout: 12,
|
|
1310
|
+
/** 静默扩展名:这些扩展名的直连成功日志用 debug 级别 */
|
|
1311
|
+
silentExtensions: [".js"]
|
|
1312
|
+
};
|
|
1313
|
+
var ARCHIVE_EXTENSIONS = [".7z", ".zip", ".rar", ".gz", ".bz2", ".tar", ".zst"];
|
|
1314
|
+
var EXECUTABLE_EXTENSIONS = [".exe", ".apk", ".bin", ".dmg", ".jar", ".class"];
|
|
1315
|
+
var DOCUMENT_EXTENSIONS = [".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".csv"];
|
|
1316
|
+
var IMAGE_EXTENSIONS = [
|
|
1317
|
+
".jpg",
|
|
1318
|
+
".jpeg",
|
|
1319
|
+
".png",
|
|
1320
|
+
".gif",
|
|
1321
|
+
".bmp",
|
|
1322
|
+
".ico",
|
|
1323
|
+
".svg",
|
|
1324
|
+
".svgz",
|
|
1325
|
+
".webp",
|
|
1326
|
+
".avif",
|
|
1327
|
+
".pis",
|
|
1328
|
+
".pict",
|
|
1329
|
+
".tif",
|
|
1330
|
+
".tiff",
|
|
1331
|
+
".eps",
|
|
1332
|
+
".ejs",
|
|
1333
|
+
".eot"
|
|
1194
1334
|
];
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
|
|
1204
|
-
/**
|
|
1205
|
-
|
|
1206
|
-
|
|
1207
|
-
|
|
1208
|
-
|
|
1209
|
-
|
|
1210
|
-
/**
|
|
1211
|
-
|
|
1212
|
-
|
|
1213
|
-
|
|
1214
|
-
|
|
1215
|
-
|
|
1216
|
-
/**
|
|
1217
|
-
|
|
1218
|
-
*/
|
|
1219
|
-
getLaunchArgs() {
|
|
1220
|
-
return [...DEFAULT_LAUNCH_ARGS];
|
|
1221
|
-
},
|
|
1222
|
-
/**
|
|
1223
|
-
* 为 got-scraping 生成与浏览器一致的 TLS 指纹配置(桌面端)。
|
|
1224
|
-
*/
|
|
1225
|
-
getTlsFingerprintOptions(userAgent = "", acceptLanguage = "") {
|
|
1226
|
-
return buildFingerprintOptions(BASE_CONFIG.locale);
|
|
1227
|
-
},
|
|
1228
|
-
/**
|
|
1229
|
-
* 规范化请求头
|
|
1230
|
-
*/
|
|
1231
|
-
applyLocaleHeaders(headers, acceptLanguage = "") {
|
|
1232
|
-
if (!headers["accept-language"]) {
|
|
1233
|
-
headers["accept-language"] = acceptLanguage || BASE_CONFIG.acceptLanguage;
|
|
1234
|
-
}
|
|
1235
|
-
return headers;
|
|
1236
|
-
}
|
|
1335
|
+
var MEDIA_EXTENSIONS = [".mp3", ".mp4", ".avi", ".mkv", ".webm", ".midi", ".mid", ".ogg", ".flac", ".swf"];
|
|
1336
|
+
var FONT_EXTENSIONS = [".woff", ".woff2", ".ttf", ".otf"];
|
|
1337
|
+
var CSS_EXTENSIONS = [".css"];
|
|
1338
|
+
var OTHER_EXTENSIONS = [".ps", ".iso"];
|
|
1339
|
+
var DEFAULT_BLOCKING_CONFIG = {
|
|
1340
|
+
/** 屏蔽压缩包 */
|
|
1341
|
+
blockArchive: true,
|
|
1342
|
+
/** 屏蔽可执行文件 */
|
|
1343
|
+
blockExecutable: true,
|
|
1344
|
+
/** 屏蔽办公文档 */
|
|
1345
|
+
blockDocument: true,
|
|
1346
|
+
/** 屏蔽图片 */
|
|
1347
|
+
blockImage: true,
|
|
1348
|
+
/** 屏蔽音视频 */
|
|
1349
|
+
blockMedia: true,
|
|
1350
|
+
/** 屏蔽字体 */
|
|
1351
|
+
blockFont: true,
|
|
1352
|
+
/** 屏蔽 CSS (注意:可能影响页面视觉效果) */
|
|
1353
|
+
blockCss: false,
|
|
1354
|
+
/** 屏蔽其他资源 */
|
|
1355
|
+
blockOther: true,
|
|
1356
|
+
/** 额外自定义扩展名列表 */
|
|
1357
|
+
customExtensions: []
|
|
1237
1358
|
};
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
throw new Error("Cursor \u672A\u521D\u59CB\u5316\uFF0C\u8BF7\u5148\u8C03\u7528 Humanize.initializeCursor(page)");
|
|
1248
|
-
}
|
|
1249
|
-
return cursor;
|
|
1250
|
-
}
|
|
1251
|
-
var Humanize = {
|
|
1252
|
-
/**
|
|
1253
|
-
* 生成带抖动的毫秒数 - 基于基础值添加随机浮动 (±30% 默认)
|
|
1254
|
-
* @param {number} base - 基础延迟 (ms)
|
|
1255
|
-
* @param {number} [jitterPercent=0.3] - 抖动百分比 (0.3 = ±30%)
|
|
1256
|
-
* @returns {number} 抖动后的毫秒数
|
|
1257
|
-
*/
|
|
1258
|
-
jitterMs(base, jitterPercent = 0.3) {
|
|
1259
|
-
const jitter = base * jitterPercent * (Math.random() * 2 - 1);
|
|
1260
|
-
return Math.max(10, Math.round(base + jitter));
|
|
1261
|
-
},
|
|
1359
|
+
var SHARED_GOT_OPTIONS = {
|
|
1360
|
+
http2: false,
|
|
1361
|
+
// 禁用 HTTP2 避免在拦截场景下的握手兼容性问题
|
|
1362
|
+
retry: { limit: 0 },
|
|
1363
|
+
// 让 Playwright 或外层逻辑处理重试
|
|
1364
|
+
throwHttpErrors: false
|
|
1365
|
+
// 404/500 等错误不抛出异常,直接透传给浏览器
|
|
1366
|
+
};
|
|
1367
|
+
var Interception = {
|
|
1262
1368
|
/**
|
|
1263
|
-
*
|
|
1369
|
+
* 根据配置生成需要屏蔽的扩展名列表
|
|
1264
1370
|
*
|
|
1265
|
-
* @param {
|
|
1266
|
-
* @returns {
|
|
1371
|
+
* @param {Object} [config] - 屏蔽配置
|
|
1372
|
+
* @returns {string[]} 需要屏蔽的扩展名列表
|
|
1267
1373
|
*/
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1374
|
+
getBlockedExtensions(config = {}) {
|
|
1375
|
+
const mergedConfig = { ...DEFAULT_BLOCKING_CONFIG, ...config };
|
|
1376
|
+
const extensions = [];
|
|
1377
|
+
if (mergedConfig.blockArchive) extensions.push(...ARCHIVE_EXTENSIONS);
|
|
1378
|
+
if (mergedConfig.blockExecutable) extensions.push(...EXECUTABLE_EXTENSIONS);
|
|
1379
|
+
if (mergedConfig.blockDocument) extensions.push(...DOCUMENT_EXTENSIONS);
|
|
1380
|
+
if (mergedConfig.blockImage) extensions.push(...IMAGE_EXTENSIONS);
|
|
1381
|
+
if (mergedConfig.blockMedia) extensions.push(...MEDIA_EXTENSIONS);
|
|
1382
|
+
if (mergedConfig.blockFont) extensions.push(...FONT_EXTENSIONS);
|
|
1383
|
+
if (mergedConfig.blockCss) extensions.push(...CSS_EXTENSIONS);
|
|
1384
|
+
if (mergedConfig.blockOther) extensions.push(...OTHER_EXTENSIONS);
|
|
1385
|
+
if (mergedConfig.customExtensions?.length > 0) {
|
|
1386
|
+
extensions.push(...mergedConfig.customExtensions);
|
|
1272
1387
|
}
|
|
1273
|
-
|
|
1274
|
-
const cursor = await createCursor(page);
|
|
1275
|
-
$CursorWeakMap.set(page, cursor);
|
|
1276
|
-
logger4.success("initializeCursor", "cursor initialized");
|
|
1388
|
+
return [...new Set(extensions)];
|
|
1277
1389
|
},
|
|
1278
1390
|
/**
|
|
1279
|
-
*
|
|
1391
|
+
* 获取所有可用的扩展名分类信息
|
|
1280
1392
|
*
|
|
1281
|
-
* @
|
|
1282
|
-
* @param {string|{x: number, y: number}|import('playwright').ElementHandle} target - CSS选择器、坐标对象或元素句柄
|
|
1393
|
+
* @returns {Object} 分类信息
|
|
1283
1394
|
*/
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1288
|
-
|
|
1289
|
-
|
|
1290
|
-
|
|
1291
|
-
|
|
1292
|
-
|
|
1293
|
-
|
|
1294
|
-
|
|
1295
|
-
if (!box) {
|
|
1296
|
-
logger4.warn(`humanMove: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E ${target}`);
|
|
1297
|
-
return false;
|
|
1298
|
-
}
|
|
1299
|
-
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
1300
|
-
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
1301
|
-
await cursor.actions.move({ x, y });
|
|
1302
|
-
} else if (target && typeof target.x === "number" && typeof target.y === "number") {
|
|
1303
|
-
await cursor.actions.move(target);
|
|
1304
|
-
} else if (target && typeof target.boundingBox === "function") {
|
|
1305
|
-
const box = await target.boundingBox();
|
|
1306
|
-
if (box) {
|
|
1307
|
-
const x = box.x + box.width / 2 + (Math.random() - 0.5) * box.width * 0.2;
|
|
1308
|
-
const y = box.y + box.height / 2 + (Math.random() - 0.5) * box.height * 0.2;
|
|
1309
|
-
await cursor.actions.move({ x, y });
|
|
1310
|
-
}
|
|
1311
|
-
}
|
|
1312
|
-
logger4.success("humanMove");
|
|
1313
|
-
return true;
|
|
1314
|
-
} catch (error) {
|
|
1315
|
-
logger4.fail("humanMove", error);
|
|
1316
|
-
throw error;
|
|
1317
|
-
}
|
|
1395
|
+
getExtensionCategories() {
|
|
1396
|
+
return {
|
|
1397
|
+
archive: { name: "\u538B\u7F29\u5305", extensions: ARCHIVE_EXTENSIONS },
|
|
1398
|
+
executable: { name: "\u53EF\u6267\u884C\u6587\u4EF6", extensions: EXECUTABLE_EXTENSIONS },
|
|
1399
|
+
document: { name: "\u529E\u516C\u6587\u6863", extensions: DOCUMENT_EXTENSIONS },
|
|
1400
|
+
image: { name: "\u56FE\u7247", extensions: IMAGE_EXTENSIONS },
|
|
1401
|
+
media: { name: "\u97F3\u89C6\u9891", extensions: MEDIA_EXTENSIONS },
|
|
1402
|
+
font: { name: "\u5B57\u4F53", extensions: FONT_EXTENSIONS },
|
|
1403
|
+
css: { name: "CSS \u6837\u5F0F", extensions: CSS_EXTENSIONS },
|
|
1404
|
+
other: { name: "\u5176\u4ED6\u8D44\u6E90", extensions: OTHER_EXTENSIONS }
|
|
1405
|
+
};
|
|
1318
1406
|
},
|
|
1319
1407
|
/**
|
|
1320
|
-
*
|
|
1321
|
-
* 返回 restore 方法,用于将滚动容器恢复到原位置
|
|
1408
|
+
* 设置网络拦截规则(资源屏蔽 + CDN 直连)
|
|
1322
1409
|
*
|
|
1323
|
-
* @param {import('playwright').Page} page
|
|
1324
|
-
* @param {
|
|
1325
|
-
* @param {
|
|
1326
|
-
* @param {
|
|
1327
|
-
* @param {
|
|
1328
|
-
* @
|
|
1410
|
+
* @param {import('playwright').Page} page - Playwright Page 对象
|
|
1411
|
+
* @param {Object} [options] - 配置选项
|
|
1412
|
+
* @param {string[]} [options.directDomains] - 需要直连的域名列表
|
|
1413
|
+
* @param {Object} [options.blockingConfig] - 资源屏蔽配置
|
|
1414
|
+
* @param {boolean} [options.fallbackToProxy] - 直连失败时是否回退到代理(默认 true)
|
|
1415
|
+
* @returns {Promise<void>}
|
|
1329
1416
|
*/
|
|
1330
|
-
async
|
|
1331
|
-
const {
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
const
|
|
1353
|
-
const
|
|
1354
|
-
const
|
|
1355
|
-
const
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
return {
|
|
1363
|
-
code: "OBSTRUCTED",
|
|
1364
|
-
reason: "\u88AB\u906E\u6321",
|
|
1365
|
-
obstruction: {
|
|
1366
|
-
tag: pointElement.tagName,
|
|
1367
|
-
id: pointElement.id,
|
|
1368
|
-
className: pointElement.className
|
|
1369
|
-
},
|
|
1370
|
-
cy,
|
|
1371
|
-
// Return Center Y for smart direction calculation
|
|
1372
|
-
viewH
|
|
1373
|
-
};
|
|
1374
|
-
}
|
|
1375
|
-
return { code: "VISIBLE" };
|
|
1376
|
-
});
|
|
1377
|
-
};
|
|
1378
|
-
try {
|
|
1379
|
-
for (let i = 0; i < maxSteps; i++) {
|
|
1380
|
-
const status = await checkVisibility();
|
|
1381
|
-
if (status.code === "VISIBLE") {
|
|
1382
|
-
logger4.debug("humanScroll | \u5143\u7D20\u53EF\u89C1\u4E14\u65E0\u906E\u6321");
|
|
1383
|
-
return { element, didScroll };
|
|
1384
|
-
}
|
|
1385
|
-
logger4.debug(`humanScroll | \u6B65\u9AA4 ${i + 1}/${maxSteps}: ${status.reason} ${status.direction ? `(${status.direction})` : ""}`);
|
|
1386
|
-
if (status.code === "OBSTRUCTED" && status.obstruction) {
|
|
1387
|
-
logger4.debug(`humanScroll | \u88AB\u4EE5\u4E0B\u5143\u7D20\u906E\u6321 <${status.obstruction.tag} id="${status.obstruction.id}">`);
|
|
1417
|
+
async setup(page, options = {}) {
|
|
1418
|
+
const {
|
|
1419
|
+
directDomains = [],
|
|
1420
|
+
blockingConfig = {},
|
|
1421
|
+
fallbackToProxy = true
|
|
1422
|
+
} = options;
|
|
1423
|
+
const mergedBlockingConfig = { ...DEFAULT_BLOCKING_CONFIG, ...blockingConfig };
|
|
1424
|
+
const blockedExtensions = this.getBlockedExtensions(mergedBlockingConfig);
|
|
1425
|
+
const hasDirectDomains = directDomains.length > 0;
|
|
1426
|
+
const enabledCategories = [];
|
|
1427
|
+
if (mergedBlockingConfig.blockArchive) enabledCategories.push("\u538B\u7F29\u5305");
|
|
1428
|
+
if (mergedBlockingConfig.blockExecutable) enabledCategories.push("\u53EF\u6267\u884C\u6587\u4EF6");
|
|
1429
|
+
if (mergedBlockingConfig.blockDocument) enabledCategories.push("\u529E\u516C\u6587\u6863");
|
|
1430
|
+
if (mergedBlockingConfig.blockImage) enabledCategories.push("\u56FE\u7247");
|
|
1431
|
+
if (mergedBlockingConfig.blockMedia) enabledCategories.push("\u97F3\u89C6\u9891");
|
|
1432
|
+
if (mergedBlockingConfig.blockFont) enabledCategories.push("\u5B57\u4F53");
|
|
1433
|
+
if (mergedBlockingConfig.blockCss) enabledCategories.push("CSS");
|
|
1434
|
+
if (mergedBlockingConfig.blockOther) enabledCategories.push("\u5176\u4ED6");
|
|
1435
|
+
logger8.start("setup", hasDirectDomains ? `\u76F4\u8FDE\u57DF\u540D: [${directDomains.length} \u4E2A] | \u5C4F\u853D: [${enabledCategories.join(", ")}]` : `\u4EC5\u8D44\u6E90\u5C4F\u853D\u6A21\u5F0F | \u5C4F\u853D: [${enabledCategories.join(", ")}]`);
|
|
1436
|
+
await page.route("**/*", async (route) => {
|
|
1437
|
+
let handled = false;
|
|
1438
|
+
try {
|
|
1439
|
+
const request = route.request();
|
|
1440
|
+
const url = request.url();
|
|
1441
|
+
const urlLower = url.toLowerCase();
|
|
1442
|
+
const urlPath = urlLower.split("?")[0];
|
|
1443
|
+
const isSilent = DirectConfig.silentExtensions.some((ext) => urlPath.endsWith(ext));
|
|
1444
|
+
const shouldBlock = blockedExtensions.some((ext) => urlPath.endsWith(ext));
|
|
1445
|
+
if (shouldBlock) {
|
|
1446
|
+
await route.abort();
|
|
1447
|
+
handled = true;
|
|
1448
|
+
return;
|
|
1388
1449
|
}
|
|
1389
|
-
let
|
|
1390
|
-
if (
|
|
1391
|
-
|
|
1392
|
-
|
|
1393
|
-
|
|
1394
|
-
|
|
1395
|
-
} else {
|
|
1396
|
-
deltaY = 100;
|
|
1450
|
+
let isDirect = false;
|
|
1451
|
+
if (hasDirectDomains) {
|
|
1452
|
+
try {
|
|
1453
|
+
const hostname = new URL(url).hostname;
|
|
1454
|
+
isDirect = directDomains.some((domain) => hostname.startsWith(domain));
|
|
1455
|
+
} catch (e) {
|
|
1397
1456
|
}
|
|
1398
|
-
} else if (status.code === "OBSTRUCTED") {
|
|
1399
|
-
const isBottomHalf = status.cy > status.viewH / 2;
|
|
1400
|
-
const direction = isBottomHalf ? 1 : -1;
|
|
1401
|
-
deltaY = direction * (minStep + Math.random() * 50);
|
|
1402
1457
|
}
|
|
1403
|
-
if (
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
const
|
|
1408
|
-
|
|
1458
|
+
if (isDirect) {
|
|
1459
|
+
try {
|
|
1460
|
+
const reqHeaders = await request.allHeaders();
|
|
1461
|
+
delete reqHeaders["host"];
|
|
1462
|
+
const resolvedAcceptLanguage = reqHeaders["accept-language"] || "";
|
|
1463
|
+
const userAgent = reqHeaders["user-agent"] || "";
|
|
1464
|
+
const method = request.method();
|
|
1465
|
+
const postData = method !== "GET" && method !== "HEAD" ? request.postDataBuffer() : void 0;
|
|
1466
|
+
const response = await gotScraping({
|
|
1467
|
+
...SHARED_GOT_OPTIONS,
|
|
1468
|
+
// 应用通用配置
|
|
1469
|
+
url,
|
|
1470
|
+
method,
|
|
1471
|
+
headers: reqHeaders,
|
|
1472
|
+
body: postData,
|
|
1473
|
+
responseType: "buffer",
|
|
1474
|
+
// 强制获取 Buffer
|
|
1475
|
+
// 移除手动 TLS 指纹配置,使用 got-scraping 默认的高质量指纹
|
|
1476
|
+
// headerGeneratorOptions: ...
|
|
1477
|
+
// 使用共享的 Agent 单例(keepAlive: false,不会池化连接)
|
|
1478
|
+
agent: {
|
|
1479
|
+
http: SHARED_HTTP_AGENT,
|
|
1480
|
+
https: SHARED_HTTPS_AGENT
|
|
1481
|
+
},
|
|
1482
|
+
// 超时时间
|
|
1483
|
+
timeout: { request: DirectConfig.directTimeout * 1e3 }
|
|
1484
|
+
});
|
|
1485
|
+
const resHeaders = {};
|
|
1486
|
+
for (const [key, value] of Object.entries(response.headers)) {
|
|
1487
|
+
if (Array.isArray(value)) {
|
|
1488
|
+
resHeaders[key] = value.join(", ");
|
|
1489
|
+
} else if (value) {
|
|
1490
|
+
resHeaders[key] = String(value);
|
|
1491
|
+
}
|
|
1492
|
+
}
|
|
1493
|
+
delete resHeaders["content-encoding"];
|
|
1494
|
+
delete resHeaders["content-length"];
|
|
1495
|
+
delete resHeaders["transfer-encoding"];
|
|
1496
|
+
delete resHeaders["connection"];
|
|
1497
|
+
delete resHeaders["keep-alive"];
|
|
1498
|
+
isSilent ? logger8.debug(`\u76F4\u8FDE\u6210\u529F: ${urlPath}`) : logger8.info(`\u76F4\u8FDE\u6210\u529F: ${urlPath}`);
|
|
1499
|
+
await safeFulfill(route, {
|
|
1500
|
+
status: response.statusCode,
|
|
1501
|
+
headers: resHeaders,
|
|
1502
|
+
body: response.body
|
|
1503
|
+
});
|
|
1504
|
+
handled = true;
|
|
1505
|
+
return;
|
|
1506
|
+
} catch (e) {
|
|
1507
|
+
const isTimeout = e.code === "ETIMEDOUT" || e.message.toLowerCase().includes("timeout");
|
|
1508
|
+
const action = fallbackToProxy ? "\u56DE\u9000\u4EE3\u7406" : "\u5DF2\u653E\u5F03";
|
|
1509
|
+
const reason = isTimeout ? `\u8D85\u65F6(${DirectConfig.directTimeout}s)` : `\u5F02\u5E38: ${e.message}`;
|
|
1510
|
+
logger8.warn(`\u76F4\u8FDE${reason}\uFF0C${action}: ${urlPath}`);
|
|
1511
|
+
if (fallbackToProxy) {
|
|
1512
|
+
await safeContinue(route);
|
|
1513
|
+
} else {
|
|
1514
|
+
await route.abort();
|
|
1515
|
+
}
|
|
1516
|
+
handled = true;
|
|
1517
|
+
return;
|
|
1409
1518
|
}
|
|
1410
1519
|
}
|
|
1411
|
-
await
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
throw error;
|
|
1420
|
-
}
|
|
1421
|
-
},
|
|
1422
|
-
/**
|
|
1423
|
-
* 人类化点击 - 使用 ghost-cursor 模拟人类鼠标移动轨迹并点击
|
|
1424
|
-
*
|
|
1425
|
-
* @param {import('playwright').Page} page
|
|
1426
|
-
* @param {string|import('playwright').ElementHandle} [target] - CSS 选择器或元素句柄。如果为空,则点击当前鼠标位置
|
|
1427
|
-
* @param {Object} [options]
|
|
1428
|
-
* @param {number} [options.reactionDelay=250] - 反应延迟基础值 (ms),实际 ±30% 抖动
|
|
1429
|
-
* @param {boolean} [options.throwOnMissing=true] - 元素不存在时是否抛出错误
|
|
1430
|
-
* @param {boolean} [options.scrollIfNeeded=true] - 元素不在视口时是否自动滚动
|
|
1431
|
-
*/
|
|
1432
|
-
async humanClick(page, target, options = {}) {
|
|
1433
|
-
const cursor = $GetCursor(page);
|
|
1434
|
-
const { reactionDelay = 250, throwOnMissing = true, scrollIfNeeded = true, restore = false } = options;
|
|
1435
|
-
const targetDesc = target == null ? "Current Position" : typeof target === "string" ? target : "ElementHandle";
|
|
1436
|
-
logger4.start("humanClick", `target=${targetDesc}`);
|
|
1437
|
-
const restoreOnce = async () => {
|
|
1438
|
-
if (restoreOnce.restored) return;
|
|
1439
|
-
restoreOnce.restored = true;
|
|
1440
|
-
if (typeof restoreOnce.do !== "function") return;
|
|
1441
|
-
try {
|
|
1442
|
-
await delay2(this.jitterMs(1e3));
|
|
1443
|
-
await restoreOnce.do();
|
|
1444
|
-
} catch (restoreError) {
|
|
1445
|
-
logger4.warn(`humanClick: \u6062\u590D\u6EDA\u52A8\u4F4D\u7F6E\u5931\u8D25: ${restoreError.message}`);
|
|
1446
|
-
}
|
|
1447
|
-
};
|
|
1448
|
-
try {
|
|
1449
|
-
if (target == null) {
|
|
1450
|
-
await delay2(this.jitterMs(reactionDelay, 0.4));
|
|
1451
|
-
await cursor.actions.click();
|
|
1452
|
-
logger4.success("humanClick", "Clicked current position");
|
|
1453
|
-
return true;
|
|
1454
|
-
}
|
|
1455
|
-
let element;
|
|
1456
|
-
if (typeof target === "string") {
|
|
1457
|
-
element = await page.$(target);
|
|
1458
|
-
if (!element) {
|
|
1459
|
-
if (throwOnMissing) {
|
|
1460
|
-
throw new Error(`\u627E\u4E0D\u5230\u5143\u7D20 ${target}`);
|
|
1520
|
+
await safeContinue(route);
|
|
1521
|
+
handled = true;
|
|
1522
|
+
} catch (err) {
|
|
1523
|
+
logger8.warn(`\u8DEF\u7531\u5904\u7406\u5F02\u5E38: ${err.message}`);
|
|
1524
|
+
if (!handled) {
|
|
1525
|
+
try {
|
|
1526
|
+
await route.continue();
|
|
1527
|
+
} catch (_) {
|
|
1461
1528
|
}
|
|
1462
|
-
logger4.warn(`humanClick: \u5143\u7D20\u4E0D\u5B58\u5728\uFF0C\u8DF3\u8FC7\u70B9\u51FB ${target}`);
|
|
1463
|
-
return false;
|
|
1464
|
-
}
|
|
1465
|
-
} else {
|
|
1466
|
-
element = target;
|
|
1467
|
-
}
|
|
1468
|
-
if (scrollIfNeeded) {
|
|
1469
|
-
const { restore: restoreFn, didScroll } = await this.humanScroll(page, element);
|
|
1470
|
-
restoreOnce.do = didScroll && restore ? restoreFn : null;
|
|
1471
|
-
}
|
|
1472
|
-
const box = await element.boundingBox();
|
|
1473
|
-
if (!box) {
|
|
1474
|
-
await restoreOnce();
|
|
1475
|
-
if (throwOnMissing) {
|
|
1476
|
-
throw new Error("\u65E0\u6CD5\u83B7\u53D6\u5143\u7D20\u4F4D\u7F6E");
|
|
1477
1529
|
}
|
|
1478
|
-
logger4.warn("humanClick: \u65E0\u6CD5\u83B7\u53D6\u4F4D\u7F6E\uFF0C\u8DF3\u8FC7\u70B9\u51FB");
|
|
1479
|
-
return false;
|
|
1480
1530
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1531
|
+
});
|
|
1532
|
+
}
|
|
1533
|
+
};
|
|
1534
|
+
async function safeFulfill(route, options) {
|
|
1535
|
+
try {
|
|
1536
|
+
await route.fulfill(options);
|
|
1537
|
+
} catch (error) {
|
|
1538
|
+
if (!isIgnorableError(error)) {
|
|
1539
|
+
console.error(`[Interception] Fulfill Error: ${error.message}`);
|
|
1540
|
+
}
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
async function safeContinue(route) {
|
|
1544
|
+
try {
|
|
1545
|
+
await route.continue();
|
|
1546
|
+
} catch (error) {
|
|
1547
|
+
if (!isIgnorableError(error)) {
|
|
1493
1548
|
}
|
|
1549
|
+
}
|
|
1550
|
+
}
|
|
1551
|
+
function isIgnorableError(error) {
|
|
1552
|
+
const msg = error.message;
|
|
1553
|
+
return msg.includes("already handled") || msg.includes("Target closed") || msg.includes("closed");
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
// src/mutation.js
|
|
1557
|
+
import { v4 as uuidv42 } from "uuid";
|
|
1558
|
+
|
|
1559
|
+
// src/logger.js
|
|
1560
|
+
var stripAnsi = (input) => {
|
|
1561
|
+
if (!input) return "";
|
|
1562
|
+
return String(input).replace(/\x1b\[[0-9;]*m/g, "");
|
|
1563
|
+
};
|
|
1564
|
+
var STEP_PREFIX = "\u6B65\u9AA4:";
|
|
1565
|
+
var STEP_SEPARATOR = " | ";
|
|
1566
|
+
var STEP_EMOJIS = [
|
|
1567
|
+
{ match: "\u4EFB\u52A1", emoji: "\u{1F9ED}" },
|
|
1568
|
+
{ match: "\u8FD0\u884C\u6A21\u5F0F", emoji: "\u{1F9E9}" },
|
|
1569
|
+
{ match: "\u767B\u5F55", emoji: "\u{1F510}" },
|
|
1570
|
+
{ match: "\u73AF\u5883", emoji: "\u{1F9EA}" },
|
|
1571
|
+
{ match: "\u8F93\u5165", emoji: "\u2328\uFE0F" },
|
|
1572
|
+
{ match: "\u53D1\u9001", emoji: "\u{1F4E4}" },
|
|
1573
|
+
{ match: "\u54CD\u5E94\u76D1\u542C", emoji: "\u{1F4E1}" },
|
|
1574
|
+
{ match: "\u7B49\u5F85\u54CD\u5E94", emoji: "\u23F3" },
|
|
1575
|
+
{ match: "\u6D41\u5F0F", emoji: "\u{1F9F5}" },
|
|
1576
|
+
{ match: "\u5F15\u7528", emoji: "\u{1F4CE}" },
|
|
1577
|
+
{ match: "\u622A\u56FE", emoji: "\u{1F5BC}\uFE0F" },
|
|
1578
|
+
{ match: "\u5206\u4EAB\u94FE\u63A5", emoji: "\u{1F517}" },
|
|
1579
|
+
{ match: "\u6570\u636E\u63A8\u9001", emoji: "\u{1F4E6}" },
|
|
1580
|
+
{ match: "\u5F39\u7A97", emoji: "\u{1FA9F}" }
|
|
1581
|
+
];
|
|
1582
|
+
var STATUS_EMOJIS = [
|
|
1583
|
+
{ match: "\u5F00\u59CB", emoji: "\u{1F680}" },
|
|
1584
|
+
{ match: "\u5B8C\u6210", emoji: "\u2705" },
|
|
1585
|
+
{ match: "\u6210\u529F", emoji: "\u2705" },
|
|
1586
|
+
{ match: "\u5931\u8D25", emoji: "\u274C" },
|
|
1587
|
+
{ match: "\u8DF3\u8FC7", emoji: "\u23ED\uFE0F" },
|
|
1588
|
+
{ match: "\u8D85\u65F6", emoji: "\u23F1\uFE0F" },
|
|
1589
|
+
{ match: "\u91CD\u8BD5", emoji: "\u{1F501}" },
|
|
1590
|
+
{ match: "\u7ED3\u675F", emoji: "\u{1F3C1}" },
|
|
1591
|
+
{ match: "\u5DF2\u914D\u7F6E", emoji: "\u{1F9F7}" },
|
|
1592
|
+
{ match: "\u5DF2\u68C0\u6D4B", emoji: "\u{1F50E}" }
|
|
1593
|
+
];
|
|
1594
|
+
var toErrorMessage = (error) => {
|
|
1595
|
+
if (!error) return "";
|
|
1596
|
+
if (error instanceof Error) return error.message;
|
|
1597
|
+
if (typeof error === "string") return error;
|
|
1598
|
+
try {
|
|
1599
|
+
return JSON.stringify(error);
|
|
1600
|
+
} catch {
|
|
1601
|
+
return String(error);
|
|
1602
|
+
}
|
|
1603
|
+
};
|
|
1604
|
+
var decorateLabel = (label, mappings) => {
|
|
1605
|
+
if (!label) return "";
|
|
1606
|
+
const mapping = mappings.find((item) => label.includes(item.match));
|
|
1607
|
+
if (!mapping) return label;
|
|
1608
|
+
return `${mapping.emoji} ${label}`;
|
|
1609
|
+
};
|
|
1610
|
+
var normalizeSnippet = (snippet, maxLen = 120) => {
|
|
1611
|
+
if (!snippet) return "";
|
|
1612
|
+
const text = String(snippet).replace(/\s+/g, " ").trim();
|
|
1613
|
+
if (!text) return "";
|
|
1614
|
+
const cleaned = text.replace(/"/g, "'");
|
|
1615
|
+
if (cleaned.length <= maxLen) return cleaned;
|
|
1616
|
+
return `${cleaned.slice(0, maxLen)}...`;
|
|
1617
|
+
};
|
|
1618
|
+
var LOG_TAG_PREFIX = "[#log:";
|
|
1619
|
+
var LOG_TAG_SUFFIX = "]";
|
|
1620
|
+
var buildLogTag = (key) => `${LOG_TAG_PREFIX}${key}${LOG_TAG_SUFFIX}`;
|
|
1621
|
+
var escapeRegExp = (value) => String(value).replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
1622
|
+
var buildStepPattern = (step, status) => {
|
|
1623
|
+
if (!step) return null;
|
|
1624
|
+
let pattern = `\u6B65\u9AA4: .*${escapeRegExp(step)}`;
|
|
1625
|
+
if (status) {
|
|
1626
|
+
pattern += `.*${escapeRegExp(status)}`;
|
|
1627
|
+
}
|
|
1628
|
+
return new RegExp(pattern);
|
|
1629
|
+
};
|
|
1630
|
+
var buildDefinitionPatterns = (definition) => {
|
|
1631
|
+
const patterns = [new RegExp(`\\[#log:${escapeRegExp(definition.key)}\\]`)];
|
|
1632
|
+
const fallback = buildStepPattern(definition.step, definition.status);
|
|
1633
|
+
if (fallback) patterns.push(fallback);
|
|
1634
|
+
if (Array.isArray(definition.extraPatterns)) {
|
|
1635
|
+
patterns.push(...definition.extraPatterns);
|
|
1636
|
+
}
|
|
1637
|
+
return patterns;
|
|
1638
|
+
};
|
|
1639
|
+
var ATTENTION_RANK = {
|
|
1640
|
+
low: 1,
|
|
1641
|
+
medium: 2,
|
|
1642
|
+
high: 3,
|
|
1643
|
+
critical: 4
|
|
1644
|
+
};
|
|
1645
|
+
var DEFAULT_ATTENTION_RANK = ATTENTION_RANK.high;
|
|
1646
|
+
var LOG_DEFINITIONS = [
|
|
1647
|
+
{
|
|
1648
|
+
key: "task_start",
|
|
1649
|
+
method: "taskStart",
|
|
1650
|
+
label: "\u4EFB\u52A1\u5F00\u59CB",
|
|
1651
|
+
group: "\u4EFB\u52A1",
|
|
1652
|
+
step: "\u4EFB\u52A1",
|
|
1653
|
+
status: "\u5F00\u59CB",
|
|
1654
|
+
level: "start",
|
|
1655
|
+
attention: "low",
|
|
1656
|
+
buildDetails: (url) => [url ? `url=${url}` : ""]
|
|
1657
|
+
},
|
|
1658
|
+
{
|
|
1659
|
+
key: "task_success",
|
|
1660
|
+
method: "taskSuccess",
|
|
1661
|
+
label: "\u4EFB\u52A1\u5B8C\u6210",
|
|
1662
|
+
group: "\u4EFB\u52A1",
|
|
1663
|
+
step: "\u4EFB\u52A1",
|
|
1664
|
+
status: "\u5B8C\u6210",
|
|
1665
|
+
level: "success",
|
|
1666
|
+
attention: "high"
|
|
1667
|
+
},
|
|
1668
|
+
{
|
|
1669
|
+
key: "task_fail",
|
|
1670
|
+
method: "taskFail",
|
|
1671
|
+
label: "\u4EFB\u52A1\u5931\u8D25",
|
|
1672
|
+
group: "\u4EFB\u52A1",
|
|
1673
|
+
step: "\u4EFB\u52A1",
|
|
1674
|
+
status: "\u5931\u8D25",
|
|
1675
|
+
level: "error",
|
|
1676
|
+
attention: "critical",
|
|
1677
|
+
buildDetails: (url, err) => [
|
|
1678
|
+
url ? `url=${url}` : "",
|
|
1679
|
+
err ? `err=${toErrorMessage(err)}` : ""
|
|
1680
|
+
]
|
|
1681
|
+
},
|
|
1682
|
+
{
|
|
1683
|
+
key: "runtime_headless",
|
|
1684
|
+
method: "runtimeHeadless",
|
|
1685
|
+
label: "\u8FD0\u884C\u6A21\u5F0F\u5F3A\u5236\u65E0\u5934",
|
|
1686
|
+
group: "\u8FD0\u884C\u6A21\u5F0F",
|
|
1687
|
+
step: "\u8FD0\u884C\u6A21\u5F0F",
|
|
1688
|
+
status: "Apify \u73AF\u5883\u5F3A\u5236\u65E0\u5934",
|
|
1689
|
+
level: "warning",
|
|
1690
|
+
attention: "medium"
|
|
1691
|
+
},
|
|
1692
|
+
{
|
|
1693
|
+
key: "login_inject_success",
|
|
1694
|
+
method: "loginInjectSuccess",
|
|
1695
|
+
label: "\u767B\u5F55\u6001\u6CE8\u5165\u6210\u529F",
|
|
1696
|
+
group: "\u767B\u5F55",
|
|
1697
|
+
step: "\u767B\u5F55\u6001\u6CE8\u5165",
|
|
1698
|
+
status: "\u6210\u529F",
|
|
1699
|
+
level: "success",
|
|
1700
|
+
attention: "medium",
|
|
1701
|
+
buildDetails: (detail) => [detail ? `detail=${detail}` : ""]
|
|
1702
|
+
},
|
|
1703
|
+
{
|
|
1704
|
+
key: "login_inject_skip",
|
|
1705
|
+
method: "loginInjectSkip",
|
|
1706
|
+
label: "\u767B\u5F55\u6001\u6CE8\u5165\u8DF3\u8FC7",
|
|
1707
|
+
group: "\u767B\u5F55",
|
|
1708
|
+
step: "\u767B\u5F55\u6001\u6CE8\u5165",
|
|
1709
|
+
status: "\u8DF3\u8FC7",
|
|
1710
|
+
level: "warning",
|
|
1711
|
+
attention: "medium",
|
|
1712
|
+
buildDetails: (reason) => [reason ? `\u539F\u56E0=${reason}` : ""]
|
|
1713
|
+
},
|
|
1714
|
+
{
|
|
1715
|
+
key: "login_inject_fail",
|
|
1716
|
+
method: "loginInjectFail",
|
|
1717
|
+
label: "\u767B\u5F55\u6001\u6CE8\u5165\u5931\u8D25",
|
|
1718
|
+
group: "\u767B\u5F55",
|
|
1719
|
+
step: "\u767B\u5F55\u6001\u6CE8\u5165",
|
|
1720
|
+
status: "\u5931\u8D25",
|
|
1721
|
+
level: "error",
|
|
1722
|
+
attention: "high",
|
|
1723
|
+
buildDetails: (err) => [err ? `err=${toErrorMessage(err)}` : ""]
|
|
1724
|
+
},
|
|
1725
|
+
{
|
|
1726
|
+
key: "login_verify_success",
|
|
1727
|
+
method: "loginVerifySuccess",
|
|
1728
|
+
label: "\u767B\u5F55\u9A8C\u8BC1\u6210\u529F",
|
|
1729
|
+
group: "\u767B\u5F55",
|
|
1730
|
+
step: "\u767B\u5F55\u9A8C\u8BC1",
|
|
1731
|
+
status: "\u6210\u529F",
|
|
1732
|
+
level: "success",
|
|
1733
|
+
attention: "high",
|
|
1734
|
+
buildDetails: (detail) => [detail ? `detail=${detail}` : ""]
|
|
1735
|
+
},
|
|
1736
|
+
{
|
|
1737
|
+
key: "login_verify_skip",
|
|
1738
|
+
method: "loginVerifySkip",
|
|
1739
|
+
label: "\u767B\u5F55\u9A8C\u8BC1\u8DF3\u8FC7",
|
|
1740
|
+
group: "\u767B\u5F55",
|
|
1741
|
+
step: "\u767B\u5F55\u9A8C\u8BC1",
|
|
1742
|
+
status: "\u8DF3\u8FC7",
|
|
1743
|
+
level: "warning",
|
|
1744
|
+
attention: "medium",
|
|
1745
|
+
buildDetails: (reason) => [reason ? `\u539F\u56E0=${reason}` : ""]
|
|
1746
|
+
},
|
|
1747
|
+
{
|
|
1748
|
+
key: "login_verify_fail",
|
|
1749
|
+
method: "loginVerifyFail",
|
|
1750
|
+
label: "\u767B\u5F55\u9A8C\u8BC1\u5931\u8D25",
|
|
1751
|
+
group: "\u767B\u5F55",
|
|
1752
|
+
step: "\u767B\u5F55\u9A8C\u8BC1",
|
|
1753
|
+
status: "\u5931\u8D25",
|
|
1754
|
+
level: "error",
|
|
1755
|
+
attention: "critical",
|
|
1756
|
+
buildDetails: (err) => [err ? `err=${toErrorMessage(err)}` : ""]
|
|
1494
1757
|
},
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1758
|
+
{
|
|
1759
|
+
key: "env_check_success",
|
|
1760
|
+
method: "envCheckSuccess",
|
|
1761
|
+
label: "\u73AF\u5883\u68C0\u67E5\u6210\u529F",
|
|
1762
|
+
group: "\u73AF\u5883",
|
|
1763
|
+
step: "\u73AF\u5883\u68C0\u67E5",
|
|
1764
|
+
status: "\u6210\u529F",
|
|
1765
|
+
level: "success",
|
|
1766
|
+
attention: "medium",
|
|
1767
|
+
buildDetails: (detail) => [detail ? `detail=${detail}` : ""]
|
|
1505
1768
|
},
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
const viewportSize = page.viewportSize() || { width: 1920, height: 1080 };
|
|
1517
|
-
while (Date.now() - startTime < durationMs) {
|
|
1518
|
-
const x = 100 + Math.random() * (viewportSize.width - 200);
|
|
1519
|
-
const y = 100 + Math.random() * (viewportSize.height - 200);
|
|
1520
|
-
await cursor.actions.move({ x, y });
|
|
1521
|
-
await delay2(this.jitterMs(600, 0.5));
|
|
1522
|
-
}
|
|
1523
|
-
logger4.success("simulateGaze");
|
|
1769
|
+
{
|
|
1770
|
+
key: "env_check_fail",
|
|
1771
|
+
method: "envCheckFail",
|
|
1772
|
+
label: "\u73AF\u5883\u68C0\u67E5\u5931\u8D25",
|
|
1773
|
+
group: "\u73AF\u5883",
|
|
1774
|
+
step: "\u73AF\u5883\u68C0\u67E5",
|
|
1775
|
+
status: "\u5931\u8D25",
|
|
1776
|
+
level: "error",
|
|
1777
|
+
attention: "high",
|
|
1778
|
+
buildDetails: (err) => [err ? `err=${toErrorMessage(err)}` : ""]
|
|
1524
1779
|
},
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
async humanType(page, selector, text, options = {}) {
|
|
1536
|
-
logger4.start("humanType", `selector=${selector}, textLen=${text.length}`);
|
|
1537
|
-
const {
|
|
1538
|
-
baseDelay = 180,
|
|
1539
|
-
pauseProbability = 0.08,
|
|
1540
|
-
pauseBase = 800
|
|
1541
|
-
} = options;
|
|
1542
|
-
try {
|
|
1543
|
-
const locator = page.locator(selector);
|
|
1544
|
-
await Humanize.humanClick(page, locator);
|
|
1545
|
-
await delay2(this.jitterMs(200, 0.4));
|
|
1546
|
-
for (let i = 0; i < text.length; i++) {
|
|
1547
|
-
const char = text[i];
|
|
1548
|
-
let charDelay;
|
|
1549
|
-
if (char === " ") {
|
|
1550
|
-
charDelay = this.jitterMs(baseDelay * 0.6, 0.3);
|
|
1551
|
-
} else if (/[,.!?;:,。!?;:]/.test(char)) {
|
|
1552
|
-
charDelay = this.jitterMs(baseDelay * 1.5, 0.4);
|
|
1553
|
-
} else {
|
|
1554
|
-
charDelay = this.jitterMs(baseDelay, 0.4);
|
|
1555
|
-
}
|
|
1556
|
-
await page.keyboard.type(char);
|
|
1557
|
-
await delay2(charDelay);
|
|
1558
|
-
if (Math.random() < pauseProbability && i < text.length - 1) {
|
|
1559
|
-
const pauseTime = this.jitterMs(pauseBase, 0.5);
|
|
1560
|
-
logger4.debug(`\u505C\u987F ${pauseTime}ms...`);
|
|
1561
|
-
await delay2(pauseTime);
|
|
1562
|
-
}
|
|
1563
|
-
}
|
|
1564
|
-
logger4.success("humanType");
|
|
1565
|
-
} catch (error) {
|
|
1566
|
-
logger4.fail("humanType", error);
|
|
1567
|
-
throw error;
|
|
1568
|
-
}
|
|
1780
|
+
{
|
|
1781
|
+
key: "input_query_start",
|
|
1782
|
+
method: "inputQuery",
|
|
1783
|
+
label: "\u8F93\u5165\u67E5\u8BE2\u5F00\u59CB",
|
|
1784
|
+
group: "\u8F93\u5165",
|
|
1785
|
+
step: "\u8F93\u5165\u67E5\u8BE2",
|
|
1786
|
+
status: "\u5F00\u59CB",
|
|
1787
|
+
level: "start",
|
|
1788
|
+
attention: "low",
|
|
1789
|
+
buildDetails: (query) => [query ? `query=${query}` : ""]
|
|
1569
1790
|
},
|
|
1570
|
-
|
|
1571
|
-
|
|
1572
|
-
|
|
1573
|
-
|
|
1574
|
-
|
|
1575
|
-
|
|
1576
|
-
|
|
1577
|
-
|
|
1578
|
-
|
|
1579
|
-
await locator.click();
|
|
1580
|
-
await delay2(this.jitterMs(200, 0.4));
|
|
1581
|
-
const currentValue = await locator.inputValue();
|
|
1582
|
-
if (!currentValue || currentValue.length === 0) {
|
|
1583
|
-
logger4.success("humanClear", "already empty");
|
|
1584
|
-
return;
|
|
1585
|
-
}
|
|
1586
|
-
await page.keyboard.press("Meta+A");
|
|
1587
|
-
await delay2(this.jitterMs(100, 0.4));
|
|
1588
|
-
await page.keyboard.press("Backspace");
|
|
1589
|
-
logger4.success("humanClear");
|
|
1590
|
-
} catch (error) {
|
|
1591
|
-
logger4.fail("humanClear", error);
|
|
1592
|
-
throw error;
|
|
1593
|
-
}
|
|
1791
|
+
{
|
|
1792
|
+
key: "send_action",
|
|
1793
|
+
method: "sendAction",
|
|
1794
|
+
label: "\u53D1\u9001\u8BF7\u6C42",
|
|
1795
|
+
group: "\u53D1\u9001",
|
|
1796
|
+
step: "\u53D1\u9001\u8BF7\u6C42",
|
|
1797
|
+
status: "\u70B9\u51FB\u53D1\u9001",
|
|
1798
|
+
level: "info",
|
|
1799
|
+
attention: "low"
|
|
1594
1800
|
},
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
const action = Math.random();
|
|
1609
|
-
if (action < 0.4) {
|
|
1610
|
-
const x = 100 + Math.random() * (viewportSize.width - 200);
|
|
1611
|
-
const y = 100 + Math.random() * (viewportSize.height - 200);
|
|
1612
|
-
await cursor.actions.move({ x, y });
|
|
1613
|
-
await delay2(this.jitterMs(350, 0.4));
|
|
1614
|
-
} else if (action < 0.7) {
|
|
1615
|
-
const scrollY = (Math.random() - 0.5) * 200;
|
|
1616
|
-
await page.mouse.wheel(0, scrollY);
|
|
1617
|
-
await delay2(this.jitterMs(500, 0.4));
|
|
1618
|
-
} else {
|
|
1619
|
-
await delay2(this.jitterMs(800, 0.5));
|
|
1620
|
-
}
|
|
1621
|
-
}
|
|
1622
|
-
logger4.success("warmUpBrowsing");
|
|
1623
|
-
} catch (error) {
|
|
1624
|
-
logger4.fail("warmUpBrowsing", error);
|
|
1625
|
-
throw error;
|
|
1626
|
-
}
|
|
1801
|
+
{
|
|
1802
|
+
key: "response_listen_start",
|
|
1803
|
+
method: "responseListenStart",
|
|
1804
|
+
label: "\u54CD\u5E94\u76D1\u542C\u5F00\u59CB",
|
|
1805
|
+
group: "\u54CD\u5E94\u76D1\u542C",
|
|
1806
|
+
step: "\u54CD\u5E94\u76D1\u542C",
|
|
1807
|
+
status: "\u5F00\u59CB",
|
|
1808
|
+
level: "start",
|
|
1809
|
+
attention: "low",
|
|
1810
|
+
buildDetails: (label, timeoutSec) => [
|
|
1811
|
+
label ? `\u76EE\u6807=${label}` : "",
|
|
1812
|
+
timeoutSec ? `timeout=${timeoutSec}s` : ""
|
|
1813
|
+
]
|
|
1627
1814
|
},
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1660
|
-
|
|
1661
|
-
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1815
|
+
{
|
|
1816
|
+
key: "response_listen_ready",
|
|
1817
|
+
method: "responseListenReady",
|
|
1818
|
+
label: "\u54CD\u5E94\u76D1\u542C\u5DF2\u914D\u7F6E",
|
|
1819
|
+
group: "\u54CD\u5E94\u76D1\u542C",
|
|
1820
|
+
step: "\u54CD\u5E94\u76D1\u542C",
|
|
1821
|
+
status: "\u5DF2\u914D\u7F6E",
|
|
1822
|
+
level: "info",
|
|
1823
|
+
attention: "medium",
|
|
1824
|
+
buildDetails: (label) => [label ? `\u76EE\u6807=${label}` : ""]
|
|
1825
|
+
},
|
|
1826
|
+
{
|
|
1827
|
+
key: "response_listen_detected",
|
|
1828
|
+
method: "responseListenDetected",
|
|
1829
|
+
label: "\u54CD\u5E94\u76D1\u542C\u5DF2\u68C0\u6D4B",
|
|
1830
|
+
group: "\u54CD\u5E94\u76D1\u542C",
|
|
1831
|
+
step: "\u54CD\u5E94\u76D1\u542C",
|
|
1832
|
+
status: "\u5DF2\u68C0\u6D4B",
|
|
1833
|
+
level: "success",
|
|
1834
|
+
attention: "medium",
|
|
1835
|
+
buildDetails: (label) => [label ? `\u76EE\u6807=${label}` : ""]
|
|
1836
|
+
},
|
|
1837
|
+
{
|
|
1838
|
+
key: "response_listen_timeout",
|
|
1839
|
+
method: "responseListenTimeout",
|
|
1840
|
+
label: "\u54CD\u5E94\u76D1\u542C\u8D85\u65F6",
|
|
1841
|
+
group: "\u54CD\u5E94\u76D1\u542C",
|
|
1842
|
+
step: "\u54CD\u5E94\u76D1\u542C",
|
|
1843
|
+
status: "\u8D85\u65F6",
|
|
1844
|
+
level: "error",
|
|
1845
|
+
attention: "high",
|
|
1846
|
+
buildDetails: (label, err) => [
|
|
1847
|
+
label ? `\u76EE\u6807=${label}` : "",
|
|
1848
|
+
err ? `err=${toErrorMessage(err)}` : ""
|
|
1849
|
+
]
|
|
1850
|
+
},
|
|
1851
|
+
{
|
|
1852
|
+
key: "response_listen_end",
|
|
1853
|
+
method: "responseListenEnd",
|
|
1854
|
+
label: "\u54CD\u5E94\u76D1\u542C\u7ED3\u675F",
|
|
1855
|
+
group: "\u54CD\u5E94\u76D1\u542C",
|
|
1856
|
+
step: "\u54CD\u5E94\u76D1\u542C",
|
|
1857
|
+
status: "\u7ED3\u675F",
|
|
1858
|
+
level: "info",
|
|
1859
|
+
attention: "low",
|
|
1860
|
+
buildDetails: (label) => [label ? `\u76EE\u6807=${label}` : ""]
|
|
1668
1861
|
},
|
|
1669
|
-
|
|
1670
|
-
|
|
1671
|
-
|
|
1672
|
-
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
|
|
1678
|
-
|
|
1679
|
-
|
|
1680
|
-
|
|
1681
|
-
|
|
1682
|
-
|
|
1683
|
-
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
`);
|
|
1705
|
-
} catch (error) {
|
|
1706
|
-
logger5.fail("Live View Server", error);
|
|
1707
|
-
res.status(500).send(`\u65E0\u6CD5\u52A0\u8F7D\u5C4F\u5E55\u622A\u56FE: ${error.message}`);
|
|
1708
|
-
}
|
|
1709
|
-
});
|
|
1710
|
-
const port = process.env.APIFY_CONTAINER_PORT || 4321;
|
|
1711
|
-
app.listen(port, () => {
|
|
1712
|
-
logger5.success("startLiveViewServer", `\u76D1\u542C\u7AEF\u53E3 ${port}`);
|
|
1713
|
-
});
|
|
1714
|
-
}
|
|
1715
|
-
async function takeLiveScreenshot(liveViewKey, page, logMessage) {
|
|
1716
|
-
try {
|
|
1717
|
-
const buffer = await page.screenshot({ type: "png" });
|
|
1718
|
-
await Actor.setValue(liveViewKey, buffer, { contentType: "image/png" });
|
|
1719
|
-
if (logMessage) {
|
|
1720
|
-
logger5.info(`(\u622A\u56FE): ${logMessage}`);
|
|
1721
|
-
}
|
|
1722
|
-
} catch (e) {
|
|
1723
|
-
logger5.warn(`\u65E0\u6CD5\u6355\u83B7 Live View \u5C4F\u5E55\u622A\u56FE: ${e.message}`);
|
|
1724
|
-
}
|
|
1725
|
-
}
|
|
1726
|
-
var useLiveView = (liveViewKey = PresetOfLiveViewKey) => {
|
|
1727
|
-
return {
|
|
1728
|
-
takeLiveScreenshot: async (page, logMessage) => {
|
|
1729
|
-
return await takeLiveScreenshot(liveViewKey, page, logMessage);
|
|
1730
|
-
},
|
|
1731
|
-
startLiveViewServer: async () => {
|
|
1732
|
-
return await startLiveViewServer(liveViewKey);
|
|
1733
|
-
}
|
|
1734
|
-
};
|
|
1735
|
-
};
|
|
1736
|
-
var LiveView = {
|
|
1737
|
-
useLiveView
|
|
1738
|
-
};
|
|
1739
|
-
|
|
1740
|
-
// src/captcha-monitor.js
|
|
1741
|
-
import { v4 as uuidv4 } from "uuid";
|
|
1742
|
-
var logger6 = createLogger("Captcha");
|
|
1743
|
-
function useCaptchaMonitor(page, options) {
|
|
1744
|
-
const { domSelector, urlPattern, onDetected } = options;
|
|
1745
|
-
if (!domSelector && !urlPattern) {
|
|
1746
|
-
throw new Error("[CaptchaMonitor] \u5FC5\u987B\u63D0\u4F9B domSelector \u6216 urlPattern \u81F3\u5C11\u4E00\u4E2A");
|
|
1747
|
-
}
|
|
1748
|
-
if (!onDetected || typeof onDetected !== "function") {
|
|
1749
|
-
throw new Error("[CaptchaMonitor] onDetected \u5FC5\u987B\u662F\u4E00\u4E2A\u51FD\u6570");
|
|
1750
|
-
}
|
|
1751
|
-
let isHandled = false;
|
|
1752
|
-
let frameHandler = null;
|
|
1753
|
-
let exposedFunctionName = null;
|
|
1754
|
-
const triggerDetected = async () => {
|
|
1755
|
-
if (isHandled) return;
|
|
1756
|
-
isHandled = true;
|
|
1757
|
-
await onDetected();
|
|
1758
|
-
};
|
|
1759
|
-
const cleanupFns = [];
|
|
1760
|
-
if (domSelector) {
|
|
1761
|
-
exposedFunctionName = `__c_d_${uuidv4().replace(/-/g, "_")}`;
|
|
1762
|
-
const cleanerName = `__c_cleaner_${uuidv4().replace(/-/g, "_")}`;
|
|
1763
|
-
page.exposeFunction(exposedFunctionName, triggerDetected).catch(() => {
|
|
1764
|
-
});
|
|
1765
|
-
page.addInitScript(({ selector, callbackName, cleanerName: cleanerName2 }) => {
|
|
1766
|
-
(() => {
|
|
1767
|
-
let observer = null;
|
|
1768
|
-
const checkAndReport = () => {
|
|
1769
|
-
const element = document.querySelector(selector);
|
|
1770
|
-
if (element) {
|
|
1771
|
-
if (observer) {
|
|
1772
|
-
observer.disconnect();
|
|
1773
|
-
observer = null;
|
|
1774
|
-
}
|
|
1775
|
-
if (window[callbackName]) {
|
|
1776
|
-
window[callbackName]();
|
|
1777
|
-
}
|
|
1778
|
-
return true;
|
|
1779
|
-
}
|
|
1780
|
-
return false;
|
|
1781
|
-
};
|
|
1782
|
-
if (checkAndReport()) return;
|
|
1783
|
-
observer = new MutationObserver((mutations) => {
|
|
1784
|
-
let shouldCheck = false;
|
|
1785
|
-
for (const mutation of mutations) {
|
|
1786
|
-
if (mutation.addedNodes.length > 0) {
|
|
1787
|
-
shouldCheck = true;
|
|
1788
|
-
break;
|
|
1789
|
-
}
|
|
1790
|
-
}
|
|
1791
|
-
if (shouldCheck && observer) {
|
|
1792
|
-
checkAndReport();
|
|
1793
|
-
}
|
|
1794
|
-
});
|
|
1795
|
-
const mountObserver = () => {
|
|
1796
|
-
const target = document.documentElement;
|
|
1797
|
-
if (target && observer) {
|
|
1798
|
-
observer.observe(target, { childList: true, subtree: true });
|
|
1799
|
-
}
|
|
1800
|
-
};
|
|
1801
|
-
if (document.readyState === "loading") {
|
|
1802
|
-
window.addEventListener("DOMContentLoaded", mountObserver);
|
|
1803
|
-
} else {
|
|
1804
|
-
mountObserver();
|
|
1805
|
-
}
|
|
1806
|
-
window[cleanerName2] = () => {
|
|
1807
|
-
if (observer) {
|
|
1808
|
-
observer.disconnect();
|
|
1809
|
-
observer = null;
|
|
1810
|
-
}
|
|
1811
|
-
};
|
|
1812
|
-
})();
|
|
1813
|
-
}, { selector: domSelector, callbackName: exposedFunctionName, cleanerName });
|
|
1814
|
-
logger6.success("useCaptchaMonitor", `DOM \u76D1\u63A7\u5DF2\u542F\u7528: ${domSelector}`);
|
|
1815
|
-
cleanupFns.push(async () => {
|
|
1816
|
-
try {
|
|
1817
|
-
await page.evaluate((name) => {
|
|
1818
|
-
if (window[name]) {
|
|
1819
|
-
window[name]();
|
|
1820
|
-
delete window[name];
|
|
1821
|
-
}
|
|
1822
|
-
}, cleanerName);
|
|
1823
|
-
} catch (e) {
|
|
1824
|
-
}
|
|
1825
|
-
});
|
|
1826
|
-
}
|
|
1827
|
-
if (urlPattern) {
|
|
1828
|
-
frameHandler = async (frame) => {
|
|
1829
|
-
if (frame === page.mainFrame()) {
|
|
1830
|
-
const currentUrl = page.url();
|
|
1831
|
-
if (currentUrl.includes(urlPattern)) {
|
|
1832
|
-
await triggerDetected();
|
|
1833
|
-
}
|
|
1834
|
-
}
|
|
1835
|
-
};
|
|
1836
|
-
page.on("framenavigated", frameHandler);
|
|
1837
|
-
logger6.success("useCaptchaMonitor", `URL \u76D1\u63A7\u5DF2\u542F\u7528: ${urlPattern}`);
|
|
1838
|
-
cleanupFns.push(async () => {
|
|
1839
|
-
page.off("framenavigated", frameHandler);
|
|
1840
|
-
});
|
|
1841
|
-
}
|
|
1842
|
-
return {
|
|
1843
|
-
stop: async () => {
|
|
1844
|
-
logger6.info("useCaptchaMonitor", "\u6B63\u5728\u505C\u6B62\u76D1\u63A7...");
|
|
1845
|
-
for (const fn of cleanupFns) {
|
|
1846
|
-
await fn();
|
|
1847
|
-
}
|
|
1848
|
-
isHandled = true;
|
|
1849
|
-
}
|
|
1850
|
-
};
|
|
1851
|
-
}
|
|
1852
|
-
var Captcha = {
|
|
1853
|
-
useCaptchaMonitor
|
|
1854
|
-
};
|
|
1855
|
-
|
|
1856
|
-
// src/sse.js
|
|
1857
|
-
import https from "https";
|
|
1858
|
-
import { URL as URL2 } from "url";
|
|
1859
|
-
var logger7 = createLogger("Sse");
|
|
1860
|
-
var Sse = {
|
|
1861
|
-
/**
|
|
1862
|
-
* 解析 SSE 流文本
|
|
1863
|
-
* 支持 `data: {...}` 和 `data:{...}` 两种格式
|
|
1864
|
-
* @param {string} sseStreamText
|
|
1865
|
-
* @returns {Array<Object>} events
|
|
1866
|
-
*/
|
|
1867
|
-
parseSseStream(sseStreamText) {
|
|
1868
|
-
const events = [];
|
|
1869
|
-
const lines = sseStreamText.split("\n");
|
|
1870
|
-
for (const line of lines) {
|
|
1871
|
-
if (line.startsWith("data:")) {
|
|
1872
|
-
try {
|
|
1873
|
-
const jsonContent = line.substring(5).trim();
|
|
1874
|
-
if (jsonContent) {
|
|
1875
|
-
events.push(JSON.parse(jsonContent));
|
|
1876
|
-
}
|
|
1877
|
-
} catch (e) {
|
|
1878
|
-
logger7.debug("parseSseStream", `JSON \u89E3\u6790\u5931\u8D25: ${e.message}, line: ${line.substring(0, 100)}...`);
|
|
1879
|
-
}
|
|
1880
|
-
}
|
|
1881
|
-
}
|
|
1882
|
-
logger7.success("parseSseStream", `\u89E3\u6790\u5B8C\u6210, events \u6570\u91CF: ${events.length}`);
|
|
1883
|
-
return events;
|
|
1862
|
+
{
|
|
1863
|
+
key: "response_wait_start",
|
|
1864
|
+
method: "responseWaitStart",
|
|
1865
|
+
label: "\u7B49\u5F85\u54CD\u5E94\u5F00\u59CB",
|
|
1866
|
+
group: "\u7B49\u5F85\u54CD\u5E94",
|
|
1867
|
+
step: "\u7B49\u5F85\u54CD\u5E94",
|
|
1868
|
+
status: "\u5F00\u59CB",
|
|
1869
|
+
level: "start",
|
|
1870
|
+
attention: "low",
|
|
1871
|
+
buildDetails: (label) => [label ? `\u76EE\u6807=${label}` : ""]
|
|
1872
|
+
},
|
|
1873
|
+
{
|
|
1874
|
+
key: "response_wait_success",
|
|
1875
|
+
method: "responseWaitSuccess",
|
|
1876
|
+
label: "\u7B49\u5F85\u54CD\u5E94\u5B8C\u6210",
|
|
1877
|
+
group: "\u7B49\u5F85\u54CD\u5E94",
|
|
1878
|
+
step: "\u7B49\u5F85\u54CD\u5E94",
|
|
1879
|
+
status: "\u5B8C\u6210",
|
|
1880
|
+
level: "success",
|
|
1881
|
+
attention: "medium",
|
|
1882
|
+
buildDetails: (label) => [label ? `\u76EE\u6807=${label}` : ""]
|
|
1883
|
+
},
|
|
1884
|
+
{
|
|
1885
|
+
key: "response_wait_fail",
|
|
1886
|
+
method: "responseWaitFail",
|
|
1887
|
+
label: "\u7B49\u5F85\u54CD\u5E94\u5931\u8D25",
|
|
1888
|
+
group: "\u7B49\u5F85\u54CD\u5E94",
|
|
1889
|
+
step: "\u7B49\u5F85\u54CD\u5E94",
|
|
1890
|
+
status: "\u5931\u8D25",
|
|
1891
|
+
level: "warning",
|
|
1892
|
+
attention: "high",
|
|
1893
|
+
buildDetails: (label, err) => [
|
|
1894
|
+
label ? `\u76EE\u6807=${label}` : "",
|
|
1895
|
+
err ? `err=${toErrorMessage(err)}` : ""
|
|
1896
|
+
]
|
|
1884
1897
|
},
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
1891
|
-
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
const {
|
|
1899
|
-
onData,
|
|
1900
|
-
onEnd,
|
|
1901
|
-
onTimeout,
|
|
1902
|
-
initialTimeout = 9e4,
|
|
1903
|
-
overallTimeout = 18e4
|
|
1904
|
-
} = options;
|
|
1905
|
-
let initialTimer = null;
|
|
1906
|
-
let overallTimer = null;
|
|
1907
|
-
let hasReceivedInitialData = false;
|
|
1908
|
-
const clearAllTimers = () => {
|
|
1909
|
-
if (initialTimer) clearTimeout(initialTimer);
|
|
1910
|
-
if (overallTimer) clearTimeout(overallTimer);
|
|
1911
|
-
initialTimer = null;
|
|
1912
|
-
overallTimer = null;
|
|
1913
|
-
};
|
|
1914
|
-
const workPromise = new Promise((resolve, reject) => {
|
|
1915
|
-
page.route(urlPattern, async (route) => {
|
|
1916
|
-
const request = route.request();
|
|
1917
|
-
logger7.info(`[MITM] \u5DF2\u62E6\u622A\u8BF7\u6C42: ${request.url()}`);
|
|
1918
|
-
try {
|
|
1919
|
-
const headers = await request.allHeaders();
|
|
1920
|
-
const postData = request.postData();
|
|
1921
|
-
const urlObj = new URL2(request.url());
|
|
1922
|
-
delete headers["accept-encoding"];
|
|
1923
|
-
delete headers["content-length"];
|
|
1924
|
-
const reqOptions = {
|
|
1925
|
-
hostname: urlObj.hostname,
|
|
1926
|
-
port: 443,
|
|
1927
|
-
path: urlObj.pathname + urlObj.search,
|
|
1928
|
-
method: request.method(),
|
|
1929
|
-
headers,
|
|
1930
|
-
timeout: overallTimeout
|
|
1931
|
-
};
|
|
1932
|
-
const req = https.request(reqOptions, (res) => {
|
|
1933
|
-
const chunks = [];
|
|
1934
|
-
let accumulatedText = "";
|
|
1935
|
-
res.on("data", (chunk) => {
|
|
1936
|
-
if (!hasReceivedInitialData) {
|
|
1937
|
-
hasReceivedInitialData = true;
|
|
1938
|
-
if (initialTimer) {
|
|
1939
|
-
clearTimeout(initialTimer);
|
|
1940
|
-
initialTimer = null;
|
|
1941
|
-
}
|
|
1942
|
-
logger7.debug("[Intercept] \u5DF2\u63A5\u6536\u521D\u59CB\u6570\u636E");
|
|
1943
|
-
}
|
|
1944
|
-
chunks.push(chunk);
|
|
1945
|
-
const textChunk = chunk.toString("utf-8");
|
|
1946
|
-
accumulatedText += textChunk;
|
|
1947
|
-
if (onData) {
|
|
1948
|
-
try {
|
|
1949
|
-
onData(textChunk, resolve, accumulatedText);
|
|
1950
|
-
} catch (e) {
|
|
1951
|
-
logger7.fail(`onData \u9519\u8BEF`, e);
|
|
1952
|
-
}
|
|
1953
|
-
}
|
|
1954
|
-
});
|
|
1955
|
-
res.on("end", () => {
|
|
1956
|
-
logger7.info("[MITM] \u4E0A\u6E38\u54CD\u5E94\u7ED3\u675F");
|
|
1957
|
-
clearAllTimers();
|
|
1958
|
-
if (onEnd) {
|
|
1959
|
-
try {
|
|
1960
|
-
onEnd(accumulatedText, resolve);
|
|
1961
|
-
} catch (e) {
|
|
1962
|
-
logger7.fail(`onEnd \u9519\u8BEF`, e);
|
|
1963
|
-
}
|
|
1964
|
-
} else if (!onData) {
|
|
1965
|
-
resolve(accumulatedText);
|
|
1966
|
-
}
|
|
1967
|
-
route.fulfill({
|
|
1968
|
-
status: res.statusCode,
|
|
1969
|
-
headers: res.headers,
|
|
1970
|
-
body: Buffer.concat(chunks)
|
|
1971
|
-
}).catch(() => {
|
|
1972
|
-
});
|
|
1973
|
-
});
|
|
1974
|
-
});
|
|
1975
|
-
req.on("error", (e) => {
|
|
1976
|
-
clearAllTimers();
|
|
1977
|
-
route.abort().catch(() => {
|
|
1978
|
-
});
|
|
1979
|
-
reject(e);
|
|
1980
|
-
});
|
|
1981
|
-
if (postData) req.write(postData);
|
|
1982
|
-
req.end();
|
|
1983
|
-
} catch (e) {
|
|
1984
|
-
clearAllTimers();
|
|
1985
|
-
route.continue().catch(() => {
|
|
1986
|
-
});
|
|
1987
|
-
reject(e);
|
|
1988
|
-
}
|
|
1989
|
-
}).catch(reject);
|
|
1990
|
-
});
|
|
1991
|
-
const timeoutPromise = new Promise((_, reject) => {
|
|
1992
|
-
initialTimer = setTimeout(() => {
|
|
1993
|
-
if (!hasReceivedInitialData) {
|
|
1994
|
-
const error = new CrawlerError({
|
|
1995
|
-
message: `\u521D\u59CB\u6570\u636E\u63A5\u6536\u8D85\u65F6 (${initialTimeout}ms)`,
|
|
1996
|
-
code: Code.InitialTimeout,
|
|
1997
|
-
context: { timeout: initialTimeout }
|
|
1998
|
-
});
|
|
1999
|
-
clearAllTimers();
|
|
2000
|
-
if (onTimeout) {
|
|
2001
|
-
try {
|
|
2002
|
-
onTimeout(error, reject);
|
|
2003
|
-
} catch (e) {
|
|
2004
|
-
reject(e);
|
|
2005
|
-
}
|
|
2006
|
-
} else {
|
|
2007
|
-
reject(error);
|
|
2008
|
-
}
|
|
2009
|
-
}
|
|
2010
|
-
}, initialTimeout);
|
|
2011
|
-
overallTimer = setTimeout(() => {
|
|
2012
|
-
const error = new CrawlerError({
|
|
2013
|
-
message: `\u6574\u4F53\u8BF7\u6C42\u8D85\u65F6 (${overallTimeout}ms)`,
|
|
2014
|
-
code: Code.OverallTimeout,
|
|
2015
|
-
context: { timeout: overallTimeout }
|
|
2016
|
-
});
|
|
2017
|
-
clearAllTimers();
|
|
2018
|
-
if (onTimeout) {
|
|
2019
|
-
try {
|
|
2020
|
-
onTimeout(error, reject);
|
|
2021
|
-
} catch (e) {
|
|
2022
|
-
reject(e);
|
|
2023
|
-
}
|
|
2024
|
-
} else {
|
|
2025
|
-
reject(error);
|
|
2026
|
-
}
|
|
2027
|
-
}, overallTimeout);
|
|
2028
|
-
});
|
|
2029
|
-
workPromise.catch(() => {
|
|
2030
|
-
});
|
|
2031
|
-
timeoutPromise.catch(() => {
|
|
2032
|
-
});
|
|
2033
|
-
const racePromise = Promise.race([workPromise, timeoutPromise]);
|
|
2034
|
-
racePromise.catch(() => {
|
|
2035
|
-
});
|
|
2036
|
-
return racePromise;
|
|
2037
|
-
}
|
|
2038
|
-
};
|
|
2039
|
-
|
|
2040
|
-
// src/interception.js
|
|
2041
|
-
import { gotScraping } from "got-scraping";
|
|
2042
|
-
import { Agent as HttpAgent } from "http";
|
|
2043
|
-
import { Agent as HttpsAgent } from "https";
|
|
2044
|
-
var logger8 = createLogger("Interception");
|
|
2045
|
-
var SHARED_HTTP_AGENT = new HttpAgent({ keepAlive: false });
|
|
2046
|
-
var SHARED_HTTPS_AGENT = new HttpsAgent({ keepAlive: false, rejectUnauthorized: false });
|
|
2047
|
-
var DirectConfig = {
|
|
2048
|
-
/** 直连请求超时时间(秒) */
|
|
2049
|
-
directTimeout: 12,
|
|
2050
|
-
/** 静默扩展名:这些扩展名的直连成功日志用 debug 级别 */
|
|
2051
|
-
silentExtensions: [".js"]
|
|
2052
|
-
};
|
|
2053
|
-
var ARCHIVE_EXTENSIONS = [".7z", ".zip", ".rar", ".gz", ".bz2", ".tar", ".zst"];
|
|
2054
|
-
var EXECUTABLE_EXTENSIONS = [".exe", ".apk", ".bin", ".dmg", ".jar", ".class"];
|
|
2055
|
-
var DOCUMENT_EXTENSIONS = [".doc", ".docx", ".xls", ".xlsx", ".ppt", ".pptx", ".pdf", ".csv"];
|
|
2056
|
-
var IMAGE_EXTENSIONS = [
|
|
2057
|
-
".jpg",
|
|
2058
|
-
".jpeg",
|
|
2059
|
-
".png",
|
|
2060
|
-
".gif",
|
|
2061
|
-
".bmp",
|
|
2062
|
-
".ico",
|
|
2063
|
-
".svg",
|
|
2064
|
-
".svgz",
|
|
2065
|
-
".webp",
|
|
2066
|
-
".avif",
|
|
2067
|
-
".pis",
|
|
2068
|
-
".pict",
|
|
2069
|
-
".tif",
|
|
2070
|
-
".tiff",
|
|
2071
|
-
".eps",
|
|
2072
|
-
".ejs",
|
|
2073
|
-
".eot"
|
|
2074
|
-
];
|
|
2075
|
-
var MEDIA_EXTENSIONS = [".mp3", ".mp4", ".avi", ".mkv", ".webm", ".midi", ".mid", ".ogg", ".flac", ".swf"];
|
|
2076
|
-
var FONT_EXTENSIONS = [".woff", ".woff2", ".ttf", ".otf"];
|
|
2077
|
-
var CSS_EXTENSIONS = [".css"];
|
|
2078
|
-
var OTHER_EXTENSIONS = [".ps", ".iso"];
|
|
2079
|
-
var DEFAULT_BLOCKING_CONFIG = {
|
|
2080
|
-
/** 屏蔽压缩包 */
|
|
2081
|
-
blockArchive: true,
|
|
2082
|
-
/** 屏蔽可执行文件 */
|
|
2083
|
-
blockExecutable: true,
|
|
2084
|
-
/** 屏蔽办公文档 */
|
|
2085
|
-
blockDocument: true,
|
|
2086
|
-
/** 屏蔽图片 */
|
|
2087
|
-
blockImage: true,
|
|
2088
|
-
/** 屏蔽音视频 */
|
|
2089
|
-
blockMedia: true,
|
|
2090
|
-
/** 屏蔽字体 */
|
|
2091
|
-
blockFont: true,
|
|
2092
|
-
/** 屏蔽 CSS (注意:可能影响页面视觉效果) */
|
|
2093
|
-
blockCss: false,
|
|
2094
|
-
/** 屏蔽其他资源 */
|
|
2095
|
-
blockOther: true,
|
|
2096
|
-
/** 额外自定义扩展名列表 */
|
|
2097
|
-
customExtensions: []
|
|
2098
|
-
};
|
|
2099
|
-
var SHARED_GOT_OPTIONS = {
|
|
2100
|
-
http2: false,
|
|
2101
|
-
// 禁用 HTTP2 避免在拦截场景下的握手兼容性问题
|
|
2102
|
-
retry: { limit: 0 },
|
|
2103
|
-
// 让 Playwright 或外层逻辑处理重试
|
|
2104
|
-
throwHttpErrors: false
|
|
2105
|
-
// 404/500 等错误不抛出异常,直接透传给浏览器
|
|
2106
|
-
};
|
|
2107
|
-
var Interception = {
|
|
2108
|
-
/**
|
|
2109
|
-
* 根据配置生成需要屏蔽的扩展名列表
|
|
2110
|
-
*
|
|
2111
|
-
* @param {Object} [config] - 屏蔽配置
|
|
2112
|
-
* @returns {string[]} 需要屏蔽的扩展名列表
|
|
2113
|
-
*/
|
|
2114
|
-
getBlockedExtensions(config = {}) {
|
|
2115
|
-
const mergedConfig = { ...DEFAULT_BLOCKING_CONFIG, ...config };
|
|
2116
|
-
const extensions = [];
|
|
2117
|
-
if (mergedConfig.blockArchive) extensions.push(...ARCHIVE_EXTENSIONS);
|
|
2118
|
-
if (mergedConfig.blockExecutable) extensions.push(...EXECUTABLE_EXTENSIONS);
|
|
2119
|
-
if (mergedConfig.blockDocument) extensions.push(...DOCUMENT_EXTENSIONS);
|
|
2120
|
-
if (mergedConfig.blockImage) extensions.push(...IMAGE_EXTENSIONS);
|
|
2121
|
-
if (mergedConfig.blockMedia) extensions.push(...MEDIA_EXTENSIONS);
|
|
2122
|
-
if (mergedConfig.blockFont) extensions.push(...FONT_EXTENSIONS);
|
|
2123
|
-
if (mergedConfig.blockCss) extensions.push(...CSS_EXTENSIONS);
|
|
2124
|
-
if (mergedConfig.blockOther) extensions.push(...OTHER_EXTENSIONS);
|
|
2125
|
-
if (mergedConfig.customExtensions?.length > 0) {
|
|
2126
|
-
extensions.push(...mergedConfig.customExtensions);
|
|
2127
|
-
}
|
|
2128
|
-
return [...new Set(extensions)];
|
|
1898
|
+
{
|
|
1899
|
+
key: "response_wait_retry",
|
|
1900
|
+
method: "responseWaitRetry",
|
|
1901
|
+
label: "\u7B49\u5F85\u54CD\u5E94\u91CD\u8BD5",
|
|
1902
|
+
group: "\u7B49\u5F85\u54CD\u5E94",
|
|
1903
|
+
step: "\u7B49\u5F85\u54CD\u5E94",
|
|
1904
|
+
status: "\u91CD\u8BD5",
|
|
1905
|
+
level: "warning",
|
|
1906
|
+
attention: "medium",
|
|
1907
|
+
buildDetails: (label, attempt) => [
|
|
1908
|
+
label ? `\u76EE\u6807=${label}` : "",
|
|
1909
|
+
attempt !== void 0 ? `\u5C1D\u8BD5=${attempt}` : ""
|
|
1910
|
+
]
|
|
2129
1911
|
},
|
|
2130
|
-
|
|
2131
|
-
|
|
2132
|
-
|
|
2133
|
-
|
|
2134
|
-
|
|
2135
|
-
|
|
2136
|
-
|
|
2137
|
-
|
|
2138
|
-
|
|
2139
|
-
|
|
2140
|
-
|
|
2141
|
-
|
|
2142
|
-
|
|
2143
|
-
|
|
2144
|
-
|
|
2145
|
-
|
|
1912
|
+
{
|
|
1913
|
+
key: "dom_chunk",
|
|
1914
|
+
method: "domChunk",
|
|
1915
|
+
label: "DOM\u7247\u6BB5",
|
|
1916
|
+
group: "DOM",
|
|
1917
|
+
step: "DOM\u7247\u6BB5",
|
|
1918
|
+
status: "",
|
|
1919
|
+
level: "info",
|
|
1920
|
+
attention: "low",
|
|
1921
|
+
throttleKey: "dom-chunk",
|
|
1922
|
+
throttleMs: 2e3,
|
|
1923
|
+
buildDetails: (length, snippet, paused) => [
|
|
1924
|
+
length !== void 0 ? `len=${length}` : "",
|
|
1925
|
+
snippet ? `preview="${normalizeSnippet(snippet)}"` : "",
|
|
1926
|
+
paused ? "paused=1" : ""
|
|
1927
|
+
]
|
|
1928
|
+
},
|
|
1929
|
+
{
|
|
1930
|
+
key: "dom_complete",
|
|
1931
|
+
method: "domComplete",
|
|
1932
|
+
label: "DOM\u7A33\u5B9A\u5B8C\u6210",
|
|
1933
|
+
group: "DOM",
|
|
1934
|
+
step: "DOM\u7A33\u5B9A",
|
|
1935
|
+
status: "\u5B8C\u6210",
|
|
1936
|
+
level: "success",
|
|
1937
|
+
attention: "medium",
|
|
1938
|
+
buildDetails: (mutationCount, stableTime, wasPaused) => [
|
|
1939
|
+
mutationCount !== void 0 ? `mutations=${mutationCount}` : "",
|
|
1940
|
+
stableTime !== void 0 ? `stableTime=${stableTime}ms` : "",
|
|
1941
|
+
wasPaused ? "paused=1" : ""
|
|
1942
|
+
]
|
|
1943
|
+
},
|
|
1944
|
+
{
|
|
1945
|
+
key: "stream_chunk",
|
|
1946
|
+
method: "streamChunk",
|
|
1947
|
+
label: "\u6D41\u5F0F\u7247\u6BB5",
|
|
1948
|
+
group: "\u6D41\u5F0F",
|
|
1949
|
+
step: "\u6D41\u5F0F\u7247\u6BB5",
|
|
1950
|
+
status: "",
|
|
1951
|
+
level: "info",
|
|
1952
|
+
attention: "low",
|
|
1953
|
+
throttleKey: "stream-chunk",
|
|
1954
|
+
throttleMs: 2e3,
|
|
1955
|
+
buildDetails: (length, snippet) => [
|
|
1956
|
+
length !== void 0 ? `len=${length}` : "",
|
|
1957
|
+
snippet ? `preview="${normalizeSnippet(snippet)}"` : ""
|
|
1958
|
+
]
|
|
1959
|
+
},
|
|
1960
|
+
{
|
|
1961
|
+
key: "stream_events_parsed",
|
|
1962
|
+
method: "streamEventsParsed",
|
|
1963
|
+
label: "\u6D41\u5F0F\u4E8B\u4EF6\u89E3\u6790\u5B8C\u6210",
|
|
1964
|
+
group: "\u6D41\u5F0F",
|
|
1965
|
+
step: "\u6D41\u5F0F\u4E8B\u4EF6\u89E3\u6790",
|
|
1966
|
+
status: "\u5B8C\u6210",
|
|
1967
|
+
level: "info",
|
|
1968
|
+
attention: "low",
|
|
1969
|
+
throttleKey: "stream-events",
|
|
1970
|
+
throttleMs: 4e3,
|
|
1971
|
+
buildDetails: (count) => [count !== void 0 ? `count=${count}` : ""]
|
|
1972
|
+
},
|
|
1973
|
+
{
|
|
1974
|
+
key: "stream_complete_event",
|
|
1975
|
+
method: "streamCompleteEvent",
|
|
1976
|
+
label: "\u6D41\u5F0F\u5B8C\u6210\u4E8B\u4EF6\u6355\u83B7",
|
|
1977
|
+
group: "\u6D41\u5F0F",
|
|
1978
|
+
step: "\u6D41\u5F0F\u4E8B\u4EF6",
|
|
1979
|
+
status: "\u5B8C\u6210\u4E8B\u4EF6\u5DF2\u6355\u83B7",
|
|
1980
|
+
level: "success",
|
|
1981
|
+
attention: "medium"
|
|
1982
|
+
},
|
|
1983
|
+
{
|
|
1984
|
+
key: "stream_end",
|
|
1985
|
+
method: "streamEnd",
|
|
1986
|
+
label: "\u6D41\u5F0F\u54CD\u5E94\u7ED3\u675F",
|
|
1987
|
+
group: "\u6D41\u5F0F",
|
|
1988
|
+
step: "\u6D41\u5F0F\u54CD\u5E94",
|
|
1989
|
+
status: "\u7ED3\u675F",
|
|
1990
|
+
level: "info",
|
|
1991
|
+
attention: "low"
|
|
1992
|
+
},
|
|
1993
|
+
{
|
|
1994
|
+
key: "reference_expand_start",
|
|
1995
|
+
method: "referenceExpandStart",
|
|
1996
|
+
label: "\u5F15\u7528\u5C55\u5F00\u5F00\u59CB",
|
|
1997
|
+
group: "\u5F15\u7528",
|
|
1998
|
+
step: "\u5F15\u7528\u5C55\u5F00",
|
|
1999
|
+
status: "\u5F00\u59CB",
|
|
2000
|
+
level: "start",
|
|
2001
|
+
attention: "low",
|
|
2002
|
+
buildDetails: (label) => [label ? `\u76EE\u6807=${label}` : ""]
|
|
2003
|
+
},
|
|
2004
|
+
{
|
|
2005
|
+
key: "reference_expand_fail",
|
|
2006
|
+
method: "referenceExpandFail",
|
|
2007
|
+
label: "\u5F15\u7528\u5C55\u5F00\u5931\u8D25",
|
|
2008
|
+
group: "\u5F15\u7528",
|
|
2009
|
+
step: "\u5F15\u7528\u5C55\u5F00",
|
|
2010
|
+
status: "\u5931\u8D25",
|
|
2011
|
+
level: "warning",
|
|
2012
|
+
attention: "medium",
|
|
2013
|
+
buildDetails: (err) => [err ? `err=${toErrorMessage(err)}` : ""]
|
|
2014
|
+
},
|
|
2015
|
+
{
|
|
2016
|
+
key: "screenshot_start",
|
|
2017
|
+
method: "screenshotStart",
|
|
2018
|
+
label: "\u622A\u56FE\u5F00\u59CB",
|
|
2019
|
+
group: "\u622A\u56FE",
|
|
2020
|
+
step: "\u622A\u56FE",
|
|
2021
|
+
status: "\u5F00\u59CB",
|
|
2022
|
+
level: "start",
|
|
2023
|
+
attention: "low"
|
|
2024
|
+
},
|
|
2025
|
+
{
|
|
2026
|
+
key: "screenshot_success",
|
|
2027
|
+
method: "screenshotSuccess",
|
|
2028
|
+
label: "\u622A\u56FE\u6210\u529F",
|
|
2029
|
+
group: "\u622A\u56FE",
|
|
2030
|
+
step: "\u622A\u56FE",
|
|
2031
|
+
status: "\u6210\u529F",
|
|
2032
|
+
level: "success",
|
|
2033
|
+
attention: "high"
|
|
2034
|
+
},
|
|
2035
|
+
{
|
|
2036
|
+
key: "screenshot_fail",
|
|
2037
|
+
method: "screenshotFail",
|
|
2038
|
+
label: "\u622A\u56FE\u5931\u8D25",
|
|
2039
|
+
group: "\u622A\u56FE",
|
|
2040
|
+
step: "\u622A\u56FE",
|
|
2041
|
+
status: "\u5931\u8D25",
|
|
2042
|
+
level: "warning",
|
|
2043
|
+
attention: "high",
|
|
2044
|
+
buildDetails: (err) => [err ? `err=${toErrorMessage(err)}` : ""]
|
|
2045
|
+
},
|
|
2046
|
+
{
|
|
2047
|
+
key: "share_start",
|
|
2048
|
+
method: "shareStart",
|
|
2049
|
+
label: "\u5206\u4EAB\u94FE\u63A5\u5F00\u59CB",
|
|
2050
|
+
group: "\u5206\u4EAB",
|
|
2051
|
+
step: "\u5206\u4EAB\u94FE\u63A5",
|
|
2052
|
+
status: "\u5F00\u59CB",
|
|
2053
|
+
level: "start",
|
|
2054
|
+
attention: "low"
|
|
2055
|
+
},
|
|
2056
|
+
{
|
|
2057
|
+
key: "share_progress",
|
|
2058
|
+
method: "shareProgress",
|
|
2059
|
+
label: "\u5206\u4EAB\u94FE\u63A5\u6B65\u9AA4",
|
|
2060
|
+
group: "\u5206\u4EAB",
|
|
2061
|
+
step: "\u5206\u4EAB\u94FE\u63A5",
|
|
2062
|
+
status: "\u6B65\u9AA4",
|
|
2063
|
+
level: "info",
|
|
2064
|
+
attention: "low",
|
|
2065
|
+
buildDetails: (label) => [label ? `\u6B65\u9AA4=${label}` : ""]
|
|
2066
|
+
},
|
|
2067
|
+
{
|
|
2068
|
+
key: "share_success",
|
|
2069
|
+
method: "shareSuccess",
|
|
2070
|
+
label: "\u5206\u4EAB\u94FE\u63A5\u6210\u529F",
|
|
2071
|
+
group: "\u5206\u4EAB",
|
|
2072
|
+
step: "\u5206\u4EAB\u94FE\u63A5",
|
|
2073
|
+
status: "\u6210\u529F",
|
|
2074
|
+
level: "success",
|
|
2075
|
+
attention: "high",
|
|
2076
|
+
buildDetails: (link) => [link ? `\u94FE\u63A5=${link}` : ""]
|
|
2077
|
+
},
|
|
2078
|
+
{
|
|
2079
|
+
key: "share_fail",
|
|
2080
|
+
method: "shareFail",
|
|
2081
|
+
label: "\u5206\u4EAB\u94FE\u63A5\u5931\u8D25",
|
|
2082
|
+
group: "\u5206\u4EAB",
|
|
2083
|
+
step: "\u5206\u4EAB\u94FE\u63A5",
|
|
2084
|
+
status: "\u5931\u8D25",
|
|
2085
|
+
level: "warning",
|
|
2086
|
+
attention: "high",
|
|
2087
|
+
buildDetails: (err) => [err ? `err=${toErrorMessage(err)}` : ""]
|
|
2088
|
+
},
|
|
2089
|
+
{
|
|
2090
|
+
key: "share_skip",
|
|
2091
|
+
method: "shareSkip",
|
|
2092
|
+
label: "\u5206\u4EAB\u94FE\u63A5\u8DF3\u8FC7",
|
|
2093
|
+
group: "\u5206\u4EAB",
|
|
2094
|
+
step: "\u5206\u4EAB\u94FE\u63A5",
|
|
2095
|
+
status: "\u8DF3\u8FC7",
|
|
2096
|
+
level: "warning",
|
|
2097
|
+
attention: "medium",
|
|
2098
|
+
buildDetails: (reason) => [reason ? `\u539F\u56E0=${reason}` : ""]
|
|
2099
|
+
},
|
|
2100
|
+
{
|
|
2101
|
+
key: "data_push_success",
|
|
2102
|
+
method: "dataPushSuccess",
|
|
2103
|
+
label: "\u6570\u636E\u63A8\u9001\u6210\u529F",
|
|
2104
|
+
group: "\u6570\u636E\u63A8\u9001",
|
|
2105
|
+
step: "\u6570\u636E\u63A8\u9001",
|
|
2106
|
+
status: "\u6210\u529F",
|
|
2107
|
+
level: "success",
|
|
2108
|
+
attention: "medium",
|
|
2109
|
+
buildDetails: (label) => [label ? `\u8BF4\u660E=${label}` : ""]
|
|
2110
|
+
},
|
|
2111
|
+
{
|
|
2112
|
+
key: "data_push_fail",
|
|
2113
|
+
method: "dataPushFail",
|
|
2114
|
+
label: "\u6570\u636E\u63A8\u9001\u5931\u8D25",
|
|
2115
|
+
group: "\u6570\u636E\u63A8\u9001",
|
|
2116
|
+
step: "\u6570\u636E\u63A8\u9001",
|
|
2117
|
+
status: "\u5931\u8D25",
|
|
2118
|
+
level: "error",
|
|
2119
|
+
attention: "high",
|
|
2120
|
+
buildDetails: (err) => [err ? `err=${toErrorMessage(err)}` : ""]
|
|
2121
|
+
},
|
|
2122
|
+
{
|
|
2123
|
+
key: "popup_detected",
|
|
2124
|
+
method: "popupDetected",
|
|
2125
|
+
label: "\u5F39\u7A97\u68C0\u6D4B",
|
|
2126
|
+
group: "\u5F39\u7A97",
|
|
2127
|
+
step: "\u5F39\u7A97\u5904\u7406",
|
|
2128
|
+
status: "\u68C0\u6D4B\u5230\u906E\u7F69",
|
|
2129
|
+
level: "warning",
|
|
2130
|
+
attention: "medium",
|
|
2131
|
+
buildDetails: (detail) => [detail ? `detail=${detail}` : ""]
|
|
2132
|
+
},
|
|
2133
|
+
{
|
|
2134
|
+
key: "popup_close_attempt",
|
|
2135
|
+
method: "popupCloseAttempt",
|
|
2136
|
+
label: "\u5F39\u7A97\u5173\u95ED\u5C1D\u8BD5",
|
|
2137
|
+
group: "\u5F39\u7A97",
|
|
2138
|
+
step: "\u5F39\u7A97\u5904\u7406",
|
|
2139
|
+
status: "\u5C1D\u8BD5\u5173\u95ED",
|
|
2140
|
+
level: "info",
|
|
2141
|
+
attention: "low",
|
|
2142
|
+
buildDetails: (detail) => [detail ? `detail=${detail}` : ""]
|
|
2143
|
+
},
|
|
2144
|
+
{
|
|
2145
|
+
key: "popup_close_success",
|
|
2146
|
+
method: "popupCloseSuccess",
|
|
2147
|
+
label: "\u5F39\u7A97\u5173\u95ED\u5B8C\u6210",
|
|
2148
|
+
group: "\u5F39\u7A97",
|
|
2149
|
+
step: "\u5F39\u7A97\u5904\u7406",
|
|
2150
|
+
status: "\u5173\u95ED\u5B8C\u6210",
|
|
2151
|
+
level: "success",
|
|
2152
|
+
attention: "medium"
|
|
2146
2153
|
},
|
|
2147
|
-
|
|
2148
|
-
|
|
2149
|
-
|
|
2150
|
-
|
|
2151
|
-
|
|
2152
|
-
|
|
2153
|
-
|
|
2154
|
-
|
|
2155
|
-
|
|
2156
|
-
|
|
2157
|
-
|
|
2158
|
-
|
|
2159
|
-
|
|
2160
|
-
|
|
2161
|
-
|
|
2162
|
-
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
2169
|
-
|
|
2170
|
-
|
|
2171
|
-
|
|
2172
|
-
|
|
2173
|
-
|
|
2174
|
-
|
|
2175
|
-
|
|
2176
|
-
|
|
2177
|
-
|
|
2178
|
-
|
|
2179
|
-
|
|
2180
|
-
|
|
2181
|
-
const urlLower = url.toLowerCase();
|
|
2182
|
-
const urlPath = urlLower.split("?")[0];
|
|
2183
|
-
const isSilent = DirectConfig.silentExtensions.some((ext) => urlPath.endsWith(ext));
|
|
2184
|
-
const shouldBlock = blockedExtensions.some((ext) => urlPath.endsWith(ext));
|
|
2185
|
-
if (shouldBlock) {
|
|
2186
|
-
await route.abort();
|
|
2187
|
-
handled = true;
|
|
2188
|
-
return;
|
|
2189
|
-
}
|
|
2190
|
-
let isDirect = false;
|
|
2191
|
-
if (hasDirectDomains) {
|
|
2192
|
-
try {
|
|
2193
|
-
const hostname = new URL(url).hostname;
|
|
2194
|
-
isDirect = directDomains.some((domain) => hostname.startsWith(domain));
|
|
2195
|
-
} catch (e) {
|
|
2196
|
-
}
|
|
2197
|
-
}
|
|
2198
|
-
if (isDirect) {
|
|
2199
|
-
try {
|
|
2200
|
-
const reqHeaders = await request.allHeaders();
|
|
2201
|
-
delete reqHeaders["host"];
|
|
2202
|
-
const resolvedAcceptLanguage = reqHeaders["accept-language"] || "";
|
|
2203
|
-
const userAgent = reqHeaders["user-agent"] || "";
|
|
2204
|
-
const method = request.method();
|
|
2205
|
-
const postData = method !== "GET" && method !== "HEAD" ? request.postDataBuffer() : void 0;
|
|
2206
|
-
const response = await gotScraping({
|
|
2207
|
-
...SHARED_GOT_OPTIONS,
|
|
2208
|
-
// 应用通用配置
|
|
2209
|
-
url,
|
|
2210
|
-
method,
|
|
2211
|
-
headers: reqHeaders,
|
|
2212
|
-
body: postData,
|
|
2213
|
-
responseType: "buffer",
|
|
2214
|
-
// 强制获取 Buffer
|
|
2215
|
-
// 移除手动 TLS 指纹配置,使用 got-scraping 默认的高质量指纹
|
|
2216
|
-
// headerGeneratorOptions: ...
|
|
2217
|
-
// 使用共享的 Agent 单例(keepAlive: false,不会池化连接)
|
|
2218
|
-
agent: {
|
|
2219
|
-
http: SHARED_HTTP_AGENT,
|
|
2220
|
-
https: SHARED_HTTPS_AGENT
|
|
2221
|
-
},
|
|
2222
|
-
// 超时时间
|
|
2223
|
-
timeout: { request: DirectConfig.directTimeout * 1e3 }
|
|
2224
|
-
});
|
|
2225
|
-
const resHeaders = {};
|
|
2226
|
-
for (const [key, value] of Object.entries(response.headers)) {
|
|
2227
|
-
if (Array.isArray(value)) {
|
|
2228
|
-
resHeaders[key] = value.join(", ");
|
|
2229
|
-
} else if (value) {
|
|
2230
|
-
resHeaders[key] = String(value);
|
|
2231
|
-
}
|
|
2232
|
-
}
|
|
2233
|
-
delete resHeaders["content-encoding"];
|
|
2234
|
-
delete resHeaders["content-length"];
|
|
2235
|
-
delete resHeaders["transfer-encoding"];
|
|
2236
|
-
delete resHeaders["connection"];
|
|
2237
|
-
delete resHeaders["keep-alive"];
|
|
2238
|
-
isSilent ? logger8.debug(`\u76F4\u8FDE\u6210\u529F: ${urlPath}`) : logger8.info(`\u76F4\u8FDE\u6210\u529F: ${urlPath}`);
|
|
2239
|
-
await safeFulfill(route, {
|
|
2240
|
-
status: response.statusCode,
|
|
2241
|
-
headers: resHeaders,
|
|
2242
|
-
body: response.body
|
|
2243
|
-
});
|
|
2244
|
-
handled = true;
|
|
2245
|
-
return;
|
|
2246
|
-
} catch (e) {
|
|
2247
|
-
const isTimeout = e.code === "ETIMEDOUT" || e.message.toLowerCase().includes("timeout");
|
|
2248
|
-
const action = fallbackToProxy ? "\u56DE\u9000\u4EE3\u7406" : "\u5DF2\u653E\u5F03";
|
|
2249
|
-
const reason = isTimeout ? `\u8D85\u65F6(${DirectConfig.directTimeout}s)` : `\u5F02\u5E38: ${e.message}`;
|
|
2250
|
-
logger8.warn(`\u76F4\u8FDE${reason}\uFF0C${action}: ${urlPath}`);
|
|
2251
|
-
if (fallbackToProxy) {
|
|
2252
|
-
await safeContinue(route);
|
|
2253
|
-
} else {
|
|
2254
|
-
await route.abort();
|
|
2255
|
-
}
|
|
2256
|
-
handled = true;
|
|
2257
|
-
return;
|
|
2258
|
-
}
|
|
2259
|
-
}
|
|
2260
|
-
await safeContinue(route);
|
|
2261
|
-
handled = true;
|
|
2262
|
-
} catch (err) {
|
|
2263
|
-
logger8.warn(`\u8DEF\u7531\u5904\u7406\u5F02\u5E38: ${err.message}`);
|
|
2264
|
-
if (!handled) {
|
|
2265
|
-
try {
|
|
2266
|
-
await route.continue();
|
|
2267
|
-
} catch (_) {
|
|
2268
|
-
}
|
|
2269
|
-
}
|
|
2270
|
-
}
|
|
2271
|
-
});
|
|
2154
|
+
{
|
|
2155
|
+
key: "popup_close_fail",
|
|
2156
|
+
method: "popupCloseFail",
|
|
2157
|
+
label: "\u5F39\u7A97\u5173\u95ED\u5931\u8D25",
|
|
2158
|
+
group: "\u5F39\u7A97",
|
|
2159
|
+
step: "\u5F39\u7A97\u5904\u7406",
|
|
2160
|
+
status: "\u5173\u95ED\u5931\u8D25",
|
|
2161
|
+
level: "warning",
|
|
2162
|
+
attention: "medium",
|
|
2163
|
+
buildDetails: (err) => [err ? `err=${toErrorMessage(err)}` : ""]
|
|
2164
|
+
}
|
|
2165
|
+
];
|
|
2166
|
+
var LOG_TEMPLATES = LOG_DEFINITIONS.map((definition) => {
|
|
2167
|
+
const attention = definition.attention || "medium";
|
|
2168
|
+
const attentionRank = ATTENTION_RANK[attention] || ATTENTION_RANK.medium;
|
|
2169
|
+
const defaultSelected = attentionRank >= DEFAULT_ATTENTION_RANK;
|
|
2170
|
+
return {
|
|
2171
|
+
key: definition.key,
|
|
2172
|
+
label: definition.label,
|
|
2173
|
+
group: definition.group,
|
|
2174
|
+
attention,
|
|
2175
|
+
patterns: buildDefinitionPatterns(definition),
|
|
2176
|
+
defaultSelected
|
|
2177
|
+
};
|
|
2178
|
+
});
|
|
2179
|
+
var buildStepLine = (step, status, details = []) => {
|
|
2180
|
+
const parts = [];
|
|
2181
|
+
const decoratedStep = step ? decorateLabel(step, STEP_EMOJIS) : "";
|
|
2182
|
+
const base = decoratedStep ? `${STEP_PREFIX} ${decoratedStep}` : STEP_PREFIX;
|
|
2183
|
+
parts.push(base.trim());
|
|
2184
|
+
if (status) parts.push(decorateLabel(status, STATUS_EMOJIS));
|
|
2185
|
+
const detailParts = details.filter(Boolean);
|
|
2186
|
+
if (detailParts.length > 0) {
|
|
2187
|
+
parts.push(...detailParts);
|
|
2272
2188
|
}
|
|
2189
|
+
return parts.join(STEP_SEPARATOR);
|
|
2273
2190
|
};
|
|
2274
|
-
|
|
2275
|
-
|
|
2276
|
-
|
|
2277
|
-
|
|
2278
|
-
|
|
2279
|
-
|
|
2191
|
+
var createThrottle = () => {
|
|
2192
|
+
const lastMap = /* @__PURE__ */ new Map();
|
|
2193
|
+
return (key, intervalMs, fn) => {
|
|
2194
|
+
const now = Date.now();
|
|
2195
|
+
const last = lastMap.get(key) || 0;
|
|
2196
|
+
if (now - last >= intervalMs) {
|
|
2197
|
+
lastMap.set(key, now);
|
|
2198
|
+
fn();
|
|
2280
2199
|
}
|
|
2281
|
-
}
|
|
2282
|
-
}
|
|
2283
|
-
|
|
2284
|
-
|
|
2285
|
-
|
|
2286
|
-
|
|
2287
|
-
|
|
2200
|
+
};
|
|
2201
|
+
};
|
|
2202
|
+
var createTemplateLogger = (baseLogger = createBaseLogger()) => {
|
|
2203
|
+
const throttle = createThrottle();
|
|
2204
|
+
const info = (line) => baseLogger.info(line);
|
|
2205
|
+
const success = (line) => baseLogger.success(line);
|
|
2206
|
+
const warning = (line) => baseLogger.warning(line);
|
|
2207
|
+
const error = (line) => baseLogger.error(line);
|
|
2208
|
+
const debug = (line) => baseLogger.debug(line);
|
|
2209
|
+
const start = (line) => baseLogger.start(line);
|
|
2210
|
+
const stepInfo = (step, status, details = []) => info(buildStepLine(step, status, details));
|
|
2211
|
+
const stepSuccess = (step, status, details = []) => success(buildStepLine(step, status, details));
|
|
2212
|
+
const stepWarn = (step, status, details = []) => warning(buildStepLine(step, status, details));
|
|
2213
|
+
const stepError = (step, status, details = []) => error(buildStepLine(step, status, details));
|
|
2214
|
+
const stepStart = (step, status, details = []) => start(buildStepLine(step, status, details));
|
|
2215
|
+
const stepHandlers = {
|
|
2216
|
+
info: stepInfo,
|
|
2217
|
+
success: stepSuccess,
|
|
2218
|
+
warning: stepWarn,
|
|
2219
|
+
error: stepError,
|
|
2220
|
+
start: stepStart
|
|
2221
|
+
};
|
|
2222
|
+
const logFromDefinition = (definition, details = []) => {
|
|
2223
|
+
const handler = stepHandlers[definition.level] || stepInfo;
|
|
2224
|
+
const payload = [...details, buildLogTag(definition.key)];
|
|
2225
|
+
const emit = () => handler(definition.step, definition.status, payload);
|
|
2226
|
+
if (definition.throttleMs) {
|
|
2227
|
+
throttle(definition.throttleKey || definition.key, definition.throttleMs, emit);
|
|
2228
|
+
return;
|
|
2288
2229
|
}
|
|
2230
|
+
emit();
|
|
2231
|
+
};
|
|
2232
|
+
const definitionMethods = {};
|
|
2233
|
+
LOG_DEFINITIONS.forEach((definition) => {
|
|
2234
|
+
if (!definition.method) return;
|
|
2235
|
+
definitionMethods[definition.method] = (...args) => {
|
|
2236
|
+
const details = definition.buildDetails ? definition.buildDetails(...args) : [];
|
|
2237
|
+
logFromDefinition(definition, details);
|
|
2238
|
+
};
|
|
2239
|
+
});
|
|
2240
|
+
return {
|
|
2241
|
+
step: (step, status, details, level = "info") => {
|
|
2242
|
+
if (level === "error") return stepError(step, status, details);
|
|
2243
|
+
if (level === "warn" || level === "warning") return stepWarn(step, status, details);
|
|
2244
|
+
if (level === "start") return stepStart(step, status, details);
|
|
2245
|
+
if (level === "success") return stepSuccess(step, status, details);
|
|
2246
|
+
return stepInfo(step, status, details);
|
|
2247
|
+
},
|
|
2248
|
+
info: (message) => info(message),
|
|
2249
|
+
success: (message) => success(message),
|
|
2250
|
+
warning: (message) => warning(message),
|
|
2251
|
+
warn: (message) => warning(message),
|
|
2252
|
+
error: (message) => error(message),
|
|
2253
|
+
debug: (message) => debug(message),
|
|
2254
|
+
start: (message) => start(message),
|
|
2255
|
+
...definitionMethods
|
|
2256
|
+
};
|
|
2257
|
+
};
|
|
2258
|
+
var getDefaultBaseLogger = () => createBaseLogger("");
|
|
2259
|
+
var Logger = {
|
|
2260
|
+
setLogger: (logger10) => setDefaultLogger(logger10),
|
|
2261
|
+
info: (message) => getDefaultBaseLogger().info(message),
|
|
2262
|
+
success: (message) => getDefaultBaseLogger().success(message),
|
|
2263
|
+
warning: (message) => getDefaultBaseLogger().warning(message),
|
|
2264
|
+
warn: (message) => getDefaultBaseLogger().warning(message),
|
|
2265
|
+
error: (message) => getDefaultBaseLogger().error(message),
|
|
2266
|
+
debug: (message) => getDefaultBaseLogger().debug(message),
|
|
2267
|
+
start: (message) => getDefaultBaseLogger().start(message),
|
|
2268
|
+
useTemplate: (logger10) => {
|
|
2269
|
+
if (logger10) return createTemplateLogger(createBaseLogger("", logger10));
|
|
2270
|
+
return createTemplateLogger();
|
|
2289
2271
|
}
|
|
2290
|
-
}
|
|
2291
|
-
function isIgnorableError(error) {
|
|
2292
|
-
const msg = error.message;
|
|
2293
|
-
return msg.includes("already handled") || msg.includes("Target closed") || msg.includes("closed");
|
|
2294
|
-
}
|
|
2272
|
+
};
|
|
2295
2273
|
|
|
2296
2274
|
// src/mutation.js
|
|
2297
|
-
|
|
2298
|
-
var logger9 = createLogger("Mutation");
|
|
2275
|
+
var logger9 = createInternalLogger("Mutation");
|
|
2299
2276
|
function generateKey(prefix) {
|
|
2300
2277
|
return `__${prefix}_${uuidv42().replace(/-/g, "_")}`;
|
|
2301
2278
|
}
|
|
@@ -2527,7 +2504,7 @@ var Mutation = {
|
|
|
2527
2504
|
};
|
|
2528
2505
|
|
|
2529
2506
|
// entrys/node.js
|
|
2530
|
-
Logger.setLogger(
|
|
2507
|
+
Logger.setLogger(crawleeLog);
|
|
2531
2508
|
var usePlaywrightToolKit = () => {
|
|
2532
2509
|
return {
|
|
2533
2510
|
ApifyKit,
|
|
@@ -2542,15 +2519,11 @@ var usePlaywrightToolKit = () => {
|
|
|
2542
2519
|
Errors: errors_exports,
|
|
2543
2520
|
Interception,
|
|
2544
2521
|
Mutation,
|
|
2545
|
-
Logger
|
|
2522
|
+
Logger,
|
|
2523
|
+
$Internals: { LOG_TEMPLATES, stripAnsi }
|
|
2546
2524
|
};
|
|
2547
2525
|
};
|
|
2548
|
-
var browser = { Logger, LOG_TEMPLATES, stripAnsi };
|
|
2549
2526
|
export {
|
|
2550
|
-
LOG_TEMPLATES,
|
|
2551
|
-
Logger,
|
|
2552
|
-
browser,
|
|
2553
|
-
stripAnsi,
|
|
2554
2527
|
usePlaywrightToolKit
|
|
2555
2528
|
};
|
|
2556
2529
|
//# sourceMappingURL=index.js.map
|