@skrillex1224/playwright-toolkit 2.0.1 → 2.0.3

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/dist/index.cjs ADDED
@@ -0,0 +1,380 @@
1
+ var __create = Object.create;
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __getProtoOf = Object.getPrototypeOf;
6
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
7
+ var __export = (target, all) => {
8
+ for (var name in all)
9
+ __defProp(target, name, { get: all[name], enumerable: true });
10
+ };
11
+ var __copyProps = (to, from, except, desc) => {
12
+ if (from && typeof from === "object" || typeof from === "function") {
13
+ for (let key of __getOwnPropNames(from))
14
+ if (!__hasOwnProp.call(to, key) && key !== except)
15
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
16
+ }
17
+ return to;
18
+ };
19
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
20
+ // If the importer is in node compatibility mode or this is not an ESM
21
+ // file that has been converted to a CommonJS file using a Babel-
22
+ // compatible transform (i.e. "__esModule" has not been set), then set
23
+ // "default" to the CommonJS "module.exports" for node compatibility.
24
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
25
+ mod
26
+ ));
27
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
28
+
29
+ // index.js
30
+ var index_exports = {};
31
+ __export(index_exports, {
32
+ usePlaywrightToolKit: () => usePlaywrightToolKit
33
+ });
34
+ module.exports = __toCommonJS(index_exports);
35
+
36
+ // src/apify-kit.js
37
+ var import_crawlee = require("crawlee");
38
+ var import_apify = require("apify");
39
+
40
+ // src/constants.js
41
+ var constants_exports = {};
42
+ __export(constants_exports, {
43
+ ErrorKeygen: () => ErrorKeygen,
44
+ FAILED_KEY_SEPARATOR: () => FAILED_KEY_SEPARATOR,
45
+ PresetOfLiveViewKey: () => PresetOfLiveViewKey,
46
+ Status: () => Status,
47
+ StatusCode: () => StatusCode
48
+ });
49
+ var ErrorKeygen = {
50
+ NotLogin: 30000001,
51
+ Chaptcha: 30000002
52
+ };
53
+ var Status = {
54
+ Success: "SUCCESS",
55
+ Failed: "FAILED"
56
+ };
57
+ var StatusCode = {
58
+ Success: 0,
59
+ Failed: -1
60
+ };
61
+ var FAILED_KEY_SEPARATOR = "::<@>::";
62
+ var PresetOfLiveViewKey = "LIVE_VIEW_SCREENSHOT";
63
+
64
+ // src/apify-kit.js
65
+ var ApifyKit = {
66
+ /**
67
+ * 包装 Step Name
68
+ */
69
+ wrapStepNameWithFailedKey(key, stepName) {
70
+ return `${key}${FAILED_KEY_SEPARATOR}${stepName}`;
71
+ },
72
+ /**
73
+ * 解包 Step Name
74
+ */
75
+ unwrapStepName(stepName) {
76
+ const splitIndex = stepName.indexOf(FAILED_KEY_SEPARATOR);
77
+ if (splitIndex === -1) {
78
+ return ["-", stepName];
79
+ }
80
+ const key = stepName.substring(0, splitIndex);
81
+ const value = stepName.substring(splitIndex + FAILED_KEY_SEPARATOR.length);
82
+ return [key, value];
83
+ },
84
+ /**
85
+ * 核心封装:执行步骤,带自动日志确认和失败截图处理
86
+ */
87
+ async runStep(pendingStepName, page, actionFn, options = {}) {
88
+ const { failActor = true } = options;
89
+ const [failedKey, stepName] = this.unwrapStepName(pendingStepName);
90
+ import_crawlee.log.info(`\u{1F504} [\u6B63\u5728\u6267\u884C] ${stepName}...`);
91
+ try {
92
+ const result = await actionFn();
93
+ import_crawlee.log.info(`\u2705 [\u6267\u884C\u6210\u529F] ${stepName}`);
94
+ return result;
95
+ } catch (error) {
96
+ import_crawlee.log.error(`\u274C [\u6267\u884C\u5931\u8D25] ${stepName}: ${error.message}`);
97
+ let screenshotBase64 = "\u622A\u56FE\u5931\u8D25";
98
+ try {
99
+ if (page) {
100
+ const buffer = await page.screenshot({ fullPage: true, type: "jpeg", quality: 60 });
101
+ screenshotBase64 = `data:image/jpeg;base64,${buffer.toString("base64")}`;
102
+ }
103
+ } catch (snapErr) {
104
+ import_crawlee.log.warning(`\u622A\u56FE\u751F\u6210\u5931\u8D25: ${snapErr.message}`);
105
+ }
106
+ await this.pushFailed(error, {
107
+ failedStep: stepName,
108
+ failedKey,
109
+ errorMessage: error.message,
110
+ errorStack: error.stack,
111
+ screenshotBase64
112
+ });
113
+ if (failActor) {
114
+ await import_apify.Actor.fail(`Run Step ${stepName} \u5931\u8D25: ${error.message}`);
115
+ } else {
116
+ throw error;
117
+ }
118
+ }
119
+ },
120
+ /**
121
+ * 宽松版runStep:失败时不调用Actor.fail,只抛出异常
122
+ */
123
+ async runStepLoose(stepName, page, fn) {
124
+ return await this.runStep(stepName, page, fn, { failActor: false });
125
+ },
126
+ /**
127
+ * 推送成功数据的通用方法
128
+ * @param {Object} data - 要推送的数据对象
129
+ */
130
+ async pushSuccess(data) {
131
+ await import_apify.Actor.pushData({
132
+ code: StatusCode.Success,
133
+ status: Status.Success,
134
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
135
+ ...data
136
+ });
137
+ },
138
+ /**
139
+ * 推送失败数据的通用方法(私有方法,仅供runStep内部使用)
140
+ * @param {Error|Object} error - 错误对象(可包含其他的错误/或部分处理成功的额外信息)
141
+ * @param {Object} [meta] - 额外的数据(如failedStep, screenshotBase64等,仅runStep使用)
142
+ * @private
143
+ */
144
+ async pushFailed(error, meta = {}) {
145
+ await import_apify.Actor.pushData({
146
+ code: StatusCode.Failed,
147
+ status: Status.Failed,
148
+ // 这里可能带其他错误信息
149
+ error,
150
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
151
+ ...meta
152
+ });
153
+ }
154
+ };
155
+
156
+ // src/utils.js
157
+ var Utils = {
158
+ /**
159
+ * 解析 SSE 流文本
160
+ */
161
+ parseSseStream(sseStreamText) {
162
+ const events = [];
163
+ const lines = sseStreamText.split("\n");
164
+ for (const line of lines) {
165
+ if (line.startsWith("data: ")) {
166
+ try {
167
+ const jsonContent = line.substring(6).trim();
168
+ if (jsonContent && jsonContent !== "[DONE]") {
169
+ events.push(JSON.parse(jsonContent));
170
+ }
171
+ } catch (e) {
172
+ }
173
+ }
174
+ }
175
+ return events;
176
+ }
177
+ };
178
+
179
+ // src/stealth.js
180
+ var import_crawlee2 = require("crawlee");
181
+ var Stealth = {
182
+ /**
183
+ * 关键修复:将 Page 视口调整为与浏览器指纹 (window.screen) 一致。
184
+ * 防止 "Viewport Mismatch" 类型的反爬检测。
185
+ * @param {import('playwright').Page} page
186
+ */
187
+ async syncViewportWithScreen(page) {
188
+ try {
189
+ const screen = await page.evaluate(() => ({
190
+ width: window.screen.width,
191
+ height: window.screen.height,
192
+ availWidth: window.screen.availWidth,
193
+ availHeight: window.screen.availHeight
194
+ }));
195
+ await page.setViewportSize({
196
+ width: screen.width,
197
+ height: screen.height
198
+ });
199
+ import_crawlee2.log.info(`[Stealth] Viewport synced to fingerprint: ${screen.width}x${screen.height}`);
200
+ } catch (e) {
201
+ import_crawlee2.log.warning(`[Stealth] Failed to sync viewport: ${e.message}. Fallback to 1920x1080.`);
202
+ await page.setViewportSize({ width: 1920, height: 1080 });
203
+ }
204
+ },
205
+ /**
206
+ * 确保 navigator.webdriver 隐藏 (通常 Playwright Stealth 插件已处理,但双重保险)
207
+ */
208
+ async hideWebdriver(page) {
209
+ await page.addInitScript(() => {
210
+ Object.defineProperty(navigator, "webdriver", {
211
+ get: () => false
212
+ });
213
+ });
214
+ },
215
+ /**
216
+ * 通用的 Playwright 资源拦截器,用于屏蔽不必要的资源以加速加载
217
+ * @param {import('playwright').Page} page
218
+ * @param {string[]} [resourceTypes] - 要屏蔽的资源类型,默认为 ['font', 'image', 'media']
219
+ */
220
+ async setupBlockingResources(page, resourceTypes = ["font", "image", "media"]) {
221
+ await page.route("**/*", (route) => {
222
+ const request = route.request();
223
+ const type = request.resourceType();
224
+ if (resourceTypes.includes(type)) {
225
+ return route.abort();
226
+ }
227
+ return route.continue();
228
+ });
229
+ },
230
+ /**
231
+ * 获取推荐的 Stealth 启动参数
232
+ */
233
+ getStealthLaunchArgs() {
234
+ return [
235
+ "--disable-blink-features=AutomationControlled",
236
+ "--no-sandbox",
237
+ "--disable-setuid-sandbox",
238
+ "--disable-infobars",
239
+ "--window-position=0,0",
240
+ "--ignore-certificate-errors",
241
+ "--disable-web-security"
242
+ // 注意:不建议这里强制指定 window-size,让 syncViewportWithScreen 去动态调整
243
+ // '--window-size=1920,1080'
244
+ ];
245
+ }
246
+ };
247
+
248
+ // src/humanize.js
249
+ var import_delay = __toESM(require("delay"), 1);
250
+ var import_crawlee3 = require("crawlee");
251
+ var Humanize = {
252
+ /**
253
+ * 随机延迟一段毫秒数 (API Wrapper for 'delay' package)
254
+ * @param {number} min - 最小毫秒
255
+ * @param {number} max - 最大毫秒
256
+ */
257
+ async randomSleep(min, max) {
258
+ const ms = typeof max === "number" ? import_delay.default.range(min, max) : (0, import_delay.default)(min);
259
+ await ms;
260
+ },
261
+ /**
262
+ * 模拟人类“注视”或“阅读”行为:鼠标在页面上随机微动。
263
+ * @param {import('ghost-cursor-playwright').GhostCursor} cursor
264
+ * @param {number} durationMs - 持续时间
265
+ */
266
+ async simulateGaze(cursor, durationMs = 2e3) {
267
+ const startTime = Date.now();
268
+ while (Date.now() - startTime < durationMs) {
269
+ const x = Math.random() * 800;
270
+ const y = Math.random() * 600;
271
+ await cursor.moveTo({ x, y });
272
+ await import_delay.default.range(200, 800);
273
+ }
274
+ }
275
+ };
276
+
277
+ // src/launch.js
278
+ var Launch = {
279
+ getLaunchOptions(customArgs = []) {
280
+ return {
281
+ args: [
282
+ ...Stealth.getStealthLaunchArgs(),
283
+ ...customArgs
284
+ ],
285
+ ignoreDefaultArgs: ["--enable-automation"]
286
+ };
287
+ },
288
+ /**
289
+ * 推荐的 Fingerprint Generator 选项
290
+ * 确保生成的是桌面端、较新的 Chrome,以匹配我们的脚本逻辑
291
+ */
292
+ getFingerprintGeneratorOptions() {
293
+ return {
294
+ browsers: [{ name: "chrome", minVersion: 110 }],
295
+ devices: ["desktop"],
296
+ operatingSystems: ["windows", "linux"]
297
+ // 包含 Linux 兼容容器
298
+ };
299
+ }
300
+ };
301
+
302
+ // src/live-view.js
303
+ var import_express = __toESM(require("express"), 1);
304
+ var import_crawlee4 = require("crawlee");
305
+ var import_apify2 = require("apify");
306
+ async function startLiveViewServer(liveViewKey) {
307
+ const app = (0, import_express.default)();
308
+ app.get("/", async (req, res) => {
309
+ try {
310
+ const screenshotBuffer = await import_apify2.Actor.getValue(liveViewKey);
311
+ if (!screenshotBuffer) {
312
+ res.send('<html><head><meta http-equiv="refresh" content="2"></head><body>\u7B49\u5F85\u7B2C\u4E00\u4E2A\u5C4F\u5E55\u622A\u56FE...</body></html>');
313
+ return;
314
+ }
315
+ const screenshotBase64 = screenshotBuffer.toString("base64");
316
+ res.send(`
317
+ <html>
318
+ <head>
319
+ <title>Live View (\u622A\u56FE)</title>
320
+ <meta http-equiv="refresh" content="1">
321
+ </head>
322
+ <body style="margin:0; padding:0;">
323
+ <img src="data:image/png;base64,${screenshotBase64}"
324
+ alt="Live View Screenshot"
325
+ style="width: 100%; height: auto;" />
326
+ </body>
327
+ </html>
328
+ `);
329
+ } catch (error) {
330
+ import_crawlee4.log.error(`Live View \u670D\u52A1\u5668\u9519\u8BEF: ${error.message}`);
331
+ res.status(500).send(`\u65E0\u6CD5\u52A0\u8F7D\u5C4F\u5E55\u622A\u56FE: ${error.message}`);
332
+ }
333
+ });
334
+ const port = process.env.APIFY_CONTAINER_PORT || 4321;
335
+ app.listen(port, () => {
336
+ import_crawlee4.log.info(`Live View \u670D\u52A1\u5668\u5DF2\u542F\u52A8\uFF0C\u76D1\u542C\u7AEF\u53E3 ${port}\u3002\u8BF7\u6253\u5F00 "Live View" \u9009\u9879\u5361\u67E5\u770B\u3002`);
337
+ });
338
+ }
339
+ async function takeLiveScreenshot(liveViewKey, page, logMessage) {
340
+ try {
341
+ const buffer = await page.screenshot({ type: "png" });
342
+ await import_apify2.Actor.setValue(liveViewKey, buffer, { contentType: "image/png" });
343
+ if (logMessage) {
344
+ import_crawlee4.log.info(`(\u622A\u56FE): ${logMessage}`);
345
+ }
346
+ } catch (e) {
347
+ import_crawlee4.log.warning(`\u65E0\u6CD5\u6355\u83B7 Live View \u5C4F\u5E55\u622A\u56FE: ${e.message}`);
348
+ }
349
+ }
350
+ var useLiveView = (liveViewKey = PresetOfLiveViewKey) => {
351
+ return {
352
+ takeLiveScreenshot: async (page, logMessage) => {
353
+ return await takeLiveScreenshot(liveViewKey, page, logMessage);
354
+ },
355
+ startLiveViewServer: async () => {
356
+ return await startLiveViewServer(liveViewKey);
357
+ }
358
+ };
359
+ };
360
+ var LiveView = {
361
+ useLiveView
362
+ };
363
+
364
+ // index.js
365
+ var usePlaywrightToolKit = () => {
366
+ return {
367
+ ApifyKit,
368
+ Stealth,
369
+ Humanize,
370
+ Launch,
371
+ LiveView,
372
+ Constants: constants_exports,
373
+ Utils
374
+ };
375
+ };
376
+ // Annotate the CommonJS export names for ESM import in node:
377
+ 0 && (module.exports = {
378
+ usePlaywrightToolKit
379
+ });
380
+ //# sourceMappingURL=index.cjs.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../index.js", "../src/apify-kit.js", "../src/constants.js", "../src/utils.js", "../src/stealth.js", "../src/humanize.js", "../src/launch.js", "../src/live-view.js"],
4
+ "sourcesContent": ["import { ApifyKit } from './src/apify-kit.js';\nimport { Utils } from './src/utils.js';\nimport { Stealth } from './src/stealth.js';\nimport { Humanize } from './src/humanize.js';\nimport { Launch } from './src/launch.js';\nimport { LiveView } from './src/live-view.js';\nimport * as Constants from './src/constants.js';\n\n// Unified Entry Point\nexport const usePlaywrightToolKit = () => {\n return {\n ApifyKit,\n Stealth,\n Humanize,\n Launch,\n LiveView,\n Constants,\n Utils\n };\n};\n", "import { log } from 'crawlee';\nimport { Actor } from 'apify';\nimport { Status, FAILED_KEY_SEPARATOR, StatusCode } from './constants.js';\n\nexport const ApifyKit = {\n\n /**\n * \u5305\u88C5 Step Name\n */\n wrapStepNameWithFailedKey(key, stepName) {\n return `${key}${FAILED_KEY_SEPARATOR}${stepName}`;\n },\n\n /**\n * \u89E3\u5305 Step Name\n */\n unwrapStepName(stepName) {\n const splitIndex = stepName.indexOf(FAILED_KEY_SEPARATOR);\n if (splitIndex === -1) {\n return ['-', stepName];\n }\n const key = stepName.substring(0, splitIndex);\n const value = stepName.substring(splitIndex + FAILED_KEY_SEPARATOR.length);\n return [key, value];\n },\n\n /**\n * \u6838\u5FC3\u5C01\u88C5\uFF1A\u6267\u884C\u6B65\u9AA4\uFF0C\u5E26\u81EA\u52A8\u65E5\u5FD7\u786E\u8BA4\u548C\u5931\u8D25\u622A\u56FE\u5904\u7406\n */\n async runStep(pendingStepName, page, actionFn, options = {}) {\n const { failActor = true } = options; // \u9ED8\u8BA4\u8C03\u7528 Actor.fail\n const [failedKey, stepName] = this.unwrapStepName(pendingStepName);\n\n log.info(`\uD83D\uDD04 [\u6B63\u5728\u6267\u884C] ${stepName}...`);\n\n try {\n const result = await actionFn();\n log.info(`\u2705 [\u6267\u884C\u6210\u529F] ${stepName}`);\n return result;\n } catch (error) {\n log.error(`\u274C [\u6267\u884C\u5931\u8D25] ${stepName}: ${error.message}`);\n\n let screenshotBase64 = '\u622A\u56FE\u5931\u8D25';\n try {\n if (page) {\n const buffer = await page.screenshot({ fullPage: true, type: 'jpeg', quality: 60 });\n screenshotBase64 = `data:image/jpeg;base64,${buffer.toString('base64')}`;\n }\n } catch (snapErr) {\n log.warning(`\u622A\u56FE\u751F\u6210\u5931\u8D25: ${snapErr.message}`);\n }\n\n // \u4F7F\u7528 pushFailed \u65B9\u6CD5\u63A8\u9001\u5931\u8D25\u6570\u636E\uFF08\u79C1\u6709\u4F7F\u7528\uFF09\n await this.pushFailed(error, {\n failedStep: stepName,\n failedKey: failedKey,\n errorMessage: error.message,\n errorStack: error.stack,\n screenshotBase64: screenshotBase64\n });\n\n // \u6839\u636E failActor \u51B3\u5B9A\u662F\u5426\u8C03\u7528 Actor.fail\n if (failActor) {\n await Actor.fail(`Run Step ${stepName} \u5931\u8D25: ${error.message}`);\n } else {\n // \u4E0D\u8C03\u7528 Actor.fail\uFF0C\u76F4\u63A5\u629B\u51FA\u9519\u8BEF\n throw error;\n }\n }\n },\n\n /**\n * \u5BBD\u677E\u7248runStep\uFF1A\u5931\u8D25\u65F6\u4E0D\u8C03\u7528Actor.fail\uFF0C\u53EA\u629B\u51FA\u5F02\u5E38\n */\n async runStepLoose(stepName, page, fn) {\n return await this.runStep(stepName, page, fn, { failActor: false });\n },\n\n /**\n * \u63A8\u9001\u6210\u529F\u6570\u636E\u7684\u901A\u7528\u65B9\u6CD5\n * @param {Object} data - \u8981\u63A8\u9001\u7684\u6570\u636E\u5BF9\u8C61\n */\n async pushSuccess(data) {\n await Actor.pushData({\n code: StatusCode.Success,\n status: Status.Success,\n timestamp: new Date().toISOString(),\n ...data\n });\n },\n\n /**\n * \u63A8\u9001\u5931\u8D25\u6570\u636E\u7684\u901A\u7528\u65B9\u6CD5\uFF08\u79C1\u6709\u65B9\u6CD5\uFF0C\u4EC5\u4F9BrunStep\u5185\u90E8\u4F7F\u7528\uFF09\n * @param {Error|Object} error - \u9519\u8BEF\u5BF9\u8C61\uFF08\u53EF\u5305\u542B\u5176\u4ED6\u7684\u9519\u8BEF/\u6216\u90E8\u5206\u5904\u7406\u6210\u529F\u7684\u989D\u5916\u4FE1\u606F\uFF09\n * @param {Object} [meta] - \u989D\u5916\u7684\u6570\u636E\uFF08\u5982failedStep, screenshotBase64\u7B49\uFF0C\u4EC5runStep\u4F7F\u7528\uFF09\n * @private\n */\n async pushFailed(error, meta = {}) {\n await Actor.pushData({\n code: StatusCode.Failed,\n status: Status.Failed,\n // \u8FD9\u91CC\u53EF\u80FD\u5E26\u5176\u4ED6\u9519\u8BEF\u4FE1\u606F\n error,\n timestamp: new Date().toISOString(),\n ...meta\n });\n }\n}\n", "export const ErrorKeygen = {\n NotLogin: 30000001,\n Chaptcha: 30000002,\n}\n\nexport const Status = {\n Success: 'SUCCESS',\n Failed: 'FAILED'\n}\n\nexport const StatusCode = {\n Success: 0,\n Failed: -1\n}\n\nexport const FAILED_KEY_SEPARATOR = '::<@>::';\n\nexport const PresetOfLiveViewKey = 'LIVE_VIEW_SCREENSHOT';\n", "export const Utils = {\n /**\n * \u89E3\u6790 SSE \u6D41\u6587\u672C\n */\n parseSseStream(sseStreamText) {\n const events = [];\n const lines = sseStreamText.split('\\n');\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const jsonContent = line.substring(6).trim();\n if (jsonContent && jsonContent !== '[DONE]') {\n events.push(JSON.parse(jsonContent));\n }\n } catch (e) {\n // Ignore lines that are not valid JSON\n }\n }\n }\n return events;\n }\n}\n", "import { log } from 'crawlee';\n\nexport const Stealth = {\n /**\n * \u5173\u952E\u4FEE\u590D\uFF1A\u5C06 Page \u89C6\u53E3\u8C03\u6574\u4E3A\u4E0E\u6D4F\u89C8\u5668\u6307\u7EB9 (window.screen) \u4E00\u81F4\u3002\n * \u9632\u6B62 \"Viewport Mismatch\" \u7C7B\u578B\u7684\u53CD\u722C\u68C0\u6D4B\u3002\n * @param {import('playwright').Page} page \n */\n async syncViewportWithScreen(page) {\n try {\n // \u83B7\u53D6\u6307\u7EB9\u4E2D\u7684\u5C4F\u5E55\u5C3A\u5BF8\n const screen = await page.evaluate(() => ({\n width: window.screen.width,\n height: window.screen.height,\n availWidth: window.screen.availWidth,\n availHeight: window.screen.availHeight,\n }));\n\n // \u8C03\u6574\u89C6\u53E3\n await page.setViewportSize({\n width: screen.width,\n height: screen.height\n });\n\n log.info(`[Stealth] Viewport synced to fingerprint: ${screen.width}x${screen.height}`);\n } catch (e) {\n log.warning(`[Stealth] Failed to sync viewport: ${e.message}. Fallback to 1920x1080.`);\n await page.setViewportSize({ width: 1920, height: 1080 });\n }\n },\n\n /**\n * \u786E\u4FDD navigator.webdriver \u9690\u85CF (\u901A\u5E38 Playwright Stealth \u63D2\u4EF6\u5DF2\u5904\u7406\uFF0C\u4F46\u53CC\u91CD\u4FDD\u9669)\n */\n async hideWebdriver(page) {\n await page.addInitScript(() => {\n Object.defineProperty(navigator, 'webdriver', {\n get: () => false,\n });\n });\n },\n\n /**\n * \u901A\u7528\u7684 Playwright \u8D44\u6E90\u62E6\u622A\u5668\uFF0C\u7528\u4E8E\u5C4F\u853D\u4E0D\u5FC5\u8981\u7684\u8D44\u6E90\u4EE5\u52A0\u901F\u52A0\u8F7D\n * @param {import('playwright').Page} page\n * @param {string[]} [resourceTypes] - \u8981\u5C4F\u853D\u7684\u8D44\u6E90\u7C7B\u578B\uFF0C\u9ED8\u8BA4\u4E3A ['font', 'image', 'media']\n */\n async setupBlockingResources(page, resourceTypes = ['font', 'image', 'media']) {\n await page.route('**/*', (route) => {\n const request = route.request();\n const type = request.resourceType();\n if (resourceTypes.includes(type)) {\n return route.abort();\n }\n return route.continue();\n });\n },\n\n /**\n * \u83B7\u53D6\u63A8\u8350\u7684 Stealth \u542F\u52A8\u53C2\u6570\n */\n getStealthLaunchArgs() {\n return [\n '--disable-blink-features=AutomationControlled',\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-infobars',\n '--window-position=0,0',\n '--ignore-certificate-errors',\n '--disable-web-security',\n // \u6CE8\u610F\uFF1A\u4E0D\u5EFA\u8BAE\u8FD9\u91CC\u5F3A\u5236\u6307\u5B9A window-size\uFF0C\u8BA9 syncViewportWithScreen \u53BB\u52A8\u6001\u8C03\u6574\n // '--window-size=1920,1080' \n ];\n }\n}\n", "import delay from 'delay';\nimport { log } from 'crawlee';\n\nexport const Humanize = {\n /**\n * \u968F\u673A\u5EF6\u8FDF\u4E00\u6BB5\u6BEB\u79D2\u6570 (API Wrapper for 'delay' package)\n * @param {number} min - \u6700\u5C0F\u6BEB\u79D2\n * @param {number} max - \u6700\u5927\u6BEB\u79D2\n */\n async randomSleep(min, max) {\n const ms = typeof max === 'number'\n ? delay.range(min, max)\n : delay(min); // \u5982\u679C\u53EA\u4F20\u4E00\u4E2A\u53C2\u6570\uFF0C\u89C6\u4E3A\u56FA\u5B9A\u5EF6\u8FDF\u6216\u6700\u5C0F\u5EF6\u8FDF\n\n // log.debug(`[Humanize] Sleeping for ${await ms} ms...`); // delay return promise acts like number somewhat but best await it\n // The delay package returns a promise that resolves after the delay.\n // delay.range() returns a promise too.\n\n await ms;\n },\n\n /**\n * \u6A21\u62DF\u4EBA\u7C7B\u201C\u6CE8\u89C6\u201D\u6216\u201C\u9605\u8BFB\u201D\u884C\u4E3A\uFF1A\u9F20\u6807\u5728\u9875\u9762\u4E0A\u968F\u673A\u5FAE\u52A8\u3002\n * @param {import('ghost-cursor-playwright').GhostCursor} cursor \n * @param {number} durationMs - \u6301\u7EED\u65F6\u95F4\n */\n async simulateGaze(cursor, durationMs = 2000) {\n const startTime = Date.now();\n while (Date.now() - startTime < durationMs) {\n // \u968F\u673A\u5C0F\u5E45\u5EA6\u79FB\u52A8\n const x = Math.random() * 800;\n const y = Math.random() * 600;\n await cursor.moveTo({ x, y });\n await delay.range(200, 800);\n }\n }\n}\n", "// \u96C6\u4E2D\u7BA1\u7406\u542F\u52A8\u914D\u7F6E\uFF0C\u6682\u65F6\u4E3B\u8981\u7531 Stealth \u6A21\u5757\u63D0\u4F9B Args\uFF0C\u8FD9\u91CC\u4F5C\u4E3A\u6269\u5C55\u70B9\nimport { Stealth } from './stealth.js';\n\nexport const Launch = {\n getLaunchOptions(customArgs = []) {\n return {\n args: [\n ...Stealth.getStealthLaunchArgs(),\n ...customArgs\n ],\n ignoreDefaultArgs: ['--enable-automation'],\n };\n },\n\n /**\n * \u63A8\u8350\u7684 Fingerprint Generator \u9009\u9879\n * \u786E\u4FDD\u751F\u6210\u7684\u662F\u684C\u9762\u7AEF\u3001\u8F83\u65B0\u7684 Chrome\uFF0C\u4EE5\u5339\u914D\u6211\u4EEC\u7684\u811A\u672C\u903B\u8F91\n */\n getFingerprintGeneratorOptions() {\n return {\n browsers: [{ name: 'chrome', minVersion: 110 }],\n devices: ['desktop'],\n operatingSystems: ['windows', 'linux'], // \u5305\u542B Linux \u517C\u5BB9\u5BB9\u5668\n };\n }\n}\n", "import express from 'express';\nimport { log } from 'crawlee';\nimport { Actor } from 'apify';\nimport { PresetOfLiveViewKey } from './constants';\n\n/**\n * \u542F\u52A8\u4E00\u4E2A Web \u670D\u52A1\u5668\u4EE5\u5728 Live View \u9009\u9879\u5361\u4E2D\u663E\u793A\u6700\u65B0\u7684\u5C4F\u5E55\u622A\u56FE\u3002\n */\nasync function startLiveViewServer(liveViewKey) {\n const app = express();\n\n app.get('/', async (req, res) => {\n try {\n // \u4ECE\u9ED8\u8BA4\u7684 Key-Value Store \u4E2D\u8BFB\u53D6\u6700\u65B0\u7684\u5C4F\u5E55\u622A\u56FE\n const screenshotBuffer = await Actor.getValue(liveViewKey);\n\n if (!screenshotBuffer) {\n // \u5982\u679C\u8FD8\u6CA1\u6709\u622A\u56FE\uFF0C\u53D1\u9001\u4E00\u4E2A\u81EA\u52A8\u5237\u65B0\u7684\u5360\u4F4D\u9875\u9762\n res.send('<html><head><meta http-equiv=\"refresh\" content=\"2\"></head><body>\u7B49\u5F85\u7B2C\u4E00\u4E2A\u5C4F\u5E55\u622A\u56FE...</body></html>');\n return;\n }\n\n // \u5C06 Buffer \u8F6C\u6362\u4E3A Base64 \u5B57\u7B26\u4E32\n const screenshotBase64 = screenshotBuffer.toString('base64');\n\n // \u53D1\u9001\u4E00\u4E2A HTML \u9875\u9762\uFF0C\u8BE5\u9875\u9762\u6BCF 1 \u79D2\u81EA\u52A8\u5237\u65B0\u4E00\u6B21\uFF0C\u5E76\u663E\u793A\u622A\u56FE\n res.send(`\n <html>\n <head>\n <title>Live View (\u622A\u56FE)</title>\n <meta http-equiv=\"refresh\" content=\"1\">\n </head>\n <body style=\"margin:0; padding:0;\">\n <img src=\"data:image/png;base64,${screenshotBase64}\" \n alt=\"Live View Screenshot\" \n style=\"width: 100%; height: auto;\" />\n </body>\n </html>\n `);\n } catch (error) {\n log.error(`Live View \u670D\u52A1\u5668\u9519\u8BEF: ${error.message}`);\n res.status(500).send(`\u65E0\u6CD5\u52A0\u8F7D\u5C4F\u5E55\u622A\u56FE: ${error.message}`);\n }\n });\n\n // \u76D1\u542C Apify \u5BB9\u5668\u7AEF\u53E3 \n const port = process.env.APIFY_CONTAINER_PORT || 4321;\n app.listen(port, () => { log.info(`Live View \u670D\u52A1\u5668\u5DF2\u542F\u52A8\uFF0C\u76D1\u542C\u7AEF\u53E3 ${port}\u3002\u8BF7\u6253\u5F00 \"Live View\" \u9009\u9879\u5361\u67E5\u770B\u3002`); });\n}\n\n/**\n * \u62CD\u6444\u5F53\u524D\u9875\u9762\u7684\u5C4F\u5E55\u622A\u56FE\u5E76\u5C06\u5176\u4FDD\u5B58\u5230 Key-Value Store\u3002\n * @param {import('playwright').Page} page\n * @param {string} [logMessage] - \u53EF\u9009\u7684\u65E5\u5FD7\u6D88\u606F\u3002\n */\nasync function takeLiveScreenshot(liveViewKey, page, logMessage) {\n try {\n const buffer = await page.screenshot({ type: 'png' });\n await Actor.setValue(liveViewKey, buffer, { contentType: 'image/png' });\n if (logMessage) {\n log.info(`(\u622A\u56FE): ${logMessage}`);\n }\n } catch (e) {\n log.warning(`\u65E0\u6CD5\u6355\u83B7 Live View \u5C4F\u5E55\u622A\u56FE: ${e.message}`);\n }\n}\n\nconst useLiveView = (liveViewKey = PresetOfLiveViewKey) => {\n return {\n takeLiveScreenshot: async (page, logMessage) => {\n return await takeLiveScreenshot(liveViewKey, page, logMessage)\n },\n startLiveViewServer: async () => {\n return await startLiveViewServer(liveViewKey);\n }\n }\n}\n\nexport const LiveView = {\n useLiveView,\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["import_crawlee", "import_crawlee", "delay", "import_crawlee", "import_apify", "express"]
7
+ }
package/dist/index.js ADDED
@@ -0,0 +1,350 @@
1
+ var __defProp = Object.defineProperty;
2
+ var __export = (target, all) => {
3
+ for (var name in all)
4
+ __defProp(target, name, { get: all[name], enumerable: true });
5
+ };
6
+
7
+ // src/apify-kit.js
8
+ import { log } from "crawlee";
9
+ import { Actor } from "apify";
10
+
11
+ // src/constants.js
12
+ var constants_exports = {};
13
+ __export(constants_exports, {
14
+ ErrorKeygen: () => ErrorKeygen,
15
+ FAILED_KEY_SEPARATOR: () => FAILED_KEY_SEPARATOR,
16
+ PresetOfLiveViewKey: () => PresetOfLiveViewKey,
17
+ Status: () => Status,
18
+ StatusCode: () => StatusCode
19
+ });
20
+ var ErrorKeygen = {
21
+ NotLogin: 30000001,
22
+ Chaptcha: 30000002
23
+ };
24
+ var Status = {
25
+ Success: "SUCCESS",
26
+ Failed: "FAILED"
27
+ };
28
+ var StatusCode = {
29
+ Success: 0,
30
+ Failed: -1
31
+ };
32
+ var FAILED_KEY_SEPARATOR = "::<@>::";
33
+ var PresetOfLiveViewKey = "LIVE_VIEW_SCREENSHOT";
34
+
35
+ // src/apify-kit.js
36
+ var ApifyKit = {
37
+ /**
38
+ * 包装 Step Name
39
+ */
40
+ wrapStepNameWithFailedKey(key, stepName) {
41
+ return `${key}${FAILED_KEY_SEPARATOR}${stepName}`;
42
+ },
43
+ /**
44
+ * 解包 Step Name
45
+ */
46
+ unwrapStepName(stepName) {
47
+ const splitIndex = stepName.indexOf(FAILED_KEY_SEPARATOR);
48
+ if (splitIndex === -1) {
49
+ return ["-", stepName];
50
+ }
51
+ const key = stepName.substring(0, splitIndex);
52
+ const value = stepName.substring(splitIndex + FAILED_KEY_SEPARATOR.length);
53
+ return [key, value];
54
+ },
55
+ /**
56
+ * 核心封装:执行步骤,带自动日志确认和失败截图处理
57
+ */
58
+ async runStep(pendingStepName, page, actionFn, options = {}) {
59
+ const { failActor = true } = options;
60
+ const [failedKey, stepName] = this.unwrapStepName(pendingStepName);
61
+ log.info(`\u{1F504} [\u6B63\u5728\u6267\u884C] ${stepName}...`);
62
+ try {
63
+ const result = await actionFn();
64
+ log.info(`\u2705 [\u6267\u884C\u6210\u529F] ${stepName}`);
65
+ return result;
66
+ } catch (error) {
67
+ log.error(`\u274C [\u6267\u884C\u5931\u8D25] ${stepName}: ${error.message}`);
68
+ let screenshotBase64 = "\u622A\u56FE\u5931\u8D25";
69
+ try {
70
+ if (page) {
71
+ const buffer = await page.screenshot({ fullPage: true, type: "jpeg", quality: 60 });
72
+ screenshotBase64 = `data:image/jpeg;base64,${buffer.toString("base64")}`;
73
+ }
74
+ } catch (snapErr) {
75
+ log.warning(`\u622A\u56FE\u751F\u6210\u5931\u8D25: ${snapErr.message}`);
76
+ }
77
+ await this.pushFailed(error, {
78
+ failedStep: stepName,
79
+ failedKey,
80
+ errorMessage: error.message,
81
+ errorStack: error.stack,
82
+ screenshotBase64
83
+ });
84
+ if (failActor) {
85
+ await Actor.fail(`Run Step ${stepName} \u5931\u8D25: ${error.message}`);
86
+ } else {
87
+ throw error;
88
+ }
89
+ }
90
+ },
91
+ /**
92
+ * 宽松版runStep:失败时不调用Actor.fail,只抛出异常
93
+ */
94
+ async runStepLoose(stepName, page, fn) {
95
+ return await this.runStep(stepName, page, fn, { failActor: false });
96
+ },
97
+ /**
98
+ * 推送成功数据的通用方法
99
+ * @param {Object} data - 要推送的数据对象
100
+ */
101
+ async pushSuccess(data) {
102
+ await Actor.pushData({
103
+ code: StatusCode.Success,
104
+ status: Status.Success,
105
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
106
+ ...data
107
+ });
108
+ },
109
+ /**
110
+ * 推送失败数据的通用方法(私有方法,仅供runStep内部使用)
111
+ * @param {Error|Object} error - 错误对象(可包含其他的错误/或部分处理成功的额外信息)
112
+ * @param {Object} [meta] - 额外的数据(如failedStep, screenshotBase64等,仅runStep使用)
113
+ * @private
114
+ */
115
+ async pushFailed(error, meta = {}) {
116
+ await Actor.pushData({
117
+ code: StatusCode.Failed,
118
+ status: Status.Failed,
119
+ // 这里可能带其他错误信息
120
+ error,
121
+ timestamp: (/* @__PURE__ */ new Date()).toISOString(),
122
+ ...meta
123
+ });
124
+ }
125
+ };
126
+
127
+ // src/utils.js
128
+ var Utils = {
129
+ /**
130
+ * 解析 SSE 流文本
131
+ */
132
+ parseSseStream(sseStreamText) {
133
+ const events = [];
134
+ const lines = sseStreamText.split("\n");
135
+ for (const line of lines) {
136
+ if (line.startsWith("data: ")) {
137
+ try {
138
+ const jsonContent = line.substring(6).trim();
139
+ if (jsonContent && jsonContent !== "[DONE]") {
140
+ events.push(JSON.parse(jsonContent));
141
+ }
142
+ } catch (e) {
143
+ }
144
+ }
145
+ }
146
+ return events;
147
+ }
148
+ };
149
+
150
+ // src/stealth.js
151
+ import { log as log2 } from "crawlee";
152
+ var Stealth = {
153
+ /**
154
+ * 关键修复:将 Page 视口调整为与浏览器指纹 (window.screen) 一致。
155
+ * 防止 "Viewport Mismatch" 类型的反爬检测。
156
+ * @param {import('playwright').Page} page
157
+ */
158
+ async syncViewportWithScreen(page) {
159
+ try {
160
+ const screen = await page.evaluate(() => ({
161
+ width: window.screen.width,
162
+ height: window.screen.height,
163
+ availWidth: window.screen.availWidth,
164
+ availHeight: window.screen.availHeight
165
+ }));
166
+ await page.setViewportSize({
167
+ width: screen.width,
168
+ height: screen.height
169
+ });
170
+ log2.info(`[Stealth] Viewport synced to fingerprint: ${screen.width}x${screen.height}`);
171
+ } catch (e) {
172
+ log2.warning(`[Stealth] Failed to sync viewport: ${e.message}. Fallback to 1920x1080.`);
173
+ await page.setViewportSize({ width: 1920, height: 1080 });
174
+ }
175
+ },
176
+ /**
177
+ * 确保 navigator.webdriver 隐藏 (通常 Playwright Stealth 插件已处理,但双重保险)
178
+ */
179
+ async hideWebdriver(page) {
180
+ await page.addInitScript(() => {
181
+ Object.defineProperty(navigator, "webdriver", {
182
+ get: () => false
183
+ });
184
+ });
185
+ },
186
+ /**
187
+ * 通用的 Playwright 资源拦截器,用于屏蔽不必要的资源以加速加载
188
+ * @param {import('playwright').Page} page
189
+ * @param {string[]} [resourceTypes] - 要屏蔽的资源类型,默认为 ['font', 'image', 'media']
190
+ */
191
+ async setupBlockingResources(page, resourceTypes = ["font", "image", "media"]) {
192
+ await page.route("**/*", (route) => {
193
+ const request = route.request();
194
+ const type = request.resourceType();
195
+ if (resourceTypes.includes(type)) {
196
+ return route.abort();
197
+ }
198
+ return route.continue();
199
+ });
200
+ },
201
+ /**
202
+ * 获取推荐的 Stealth 启动参数
203
+ */
204
+ getStealthLaunchArgs() {
205
+ return [
206
+ "--disable-blink-features=AutomationControlled",
207
+ "--no-sandbox",
208
+ "--disable-setuid-sandbox",
209
+ "--disable-infobars",
210
+ "--window-position=0,0",
211
+ "--ignore-certificate-errors",
212
+ "--disable-web-security"
213
+ // 注意:不建议这里强制指定 window-size,让 syncViewportWithScreen 去动态调整
214
+ // '--window-size=1920,1080'
215
+ ];
216
+ }
217
+ };
218
+
219
+ // src/humanize.js
220
+ import delay from "delay";
221
+ import { log as log3 } from "crawlee";
222
+ var Humanize = {
223
+ /**
224
+ * 随机延迟一段毫秒数 (API Wrapper for 'delay' package)
225
+ * @param {number} min - 最小毫秒
226
+ * @param {number} max - 最大毫秒
227
+ */
228
+ async randomSleep(min, max) {
229
+ const ms = typeof max === "number" ? delay.range(min, max) : delay(min);
230
+ await ms;
231
+ },
232
+ /**
233
+ * 模拟人类“注视”或“阅读”行为:鼠标在页面上随机微动。
234
+ * @param {import('ghost-cursor-playwright').GhostCursor} cursor
235
+ * @param {number} durationMs - 持续时间
236
+ */
237
+ async simulateGaze(cursor, durationMs = 2e3) {
238
+ const startTime = Date.now();
239
+ while (Date.now() - startTime < durationMs) {
240
+ const x = Math.random() * 800;
241
+ const y = Math.random() * 600;
242
+ await cursor.moveTo({ x, y });
243
+ await delay.range(200, 800);
244
+ }
245
+ }
246
+ };
247
+
248
+ // src/launch.js
249
+ var Launch = {
250
+ getLaunchOptions(customArgs = []) {
251
+ return {
252
+ args: [
253
+ ...Stealth.getStealthLaunchArgs(),
254
+ ...customArgs
255
+ ],
256
+ ignoreDefaultArgs: ["--enable-automation"]
257
+ };
258
+ },
259
+ /**
260
+ * 推荐的 Fingerprint Generator 选项
261
+ * 确保生成的是桌面端、较新的 Chrome,以匹配我们的脚本逻辑
262
+ */
263
+ getFingerprintGeneratorOptions() {
264
+ return {
265
+ browsers: [{ name: "chrome", minVersion: 110 }],
266
+ devices: ["desktop"],
267
+ operatingSystems: ["windows", "linux"]
268
+ // 包含 Linux 兼容容器
269
+ };
270
+ }
271
+ };
272
+
273
+ // src/live-view.js
274
+ import express from "express";
275
+ import { log as log4 } from "crawlee";
276
+ import { Actor as Actor2 } from "apify";
277
+ async function startLiveViewServer(liveViewKey) {
278
+ const app = express();
279
+ app.get("/", async (req, res) => {
280
+ try {
281
+ const screenshotBuffer = await Actor2.getValue(liveViewKey);
282
+ if (!screenshotBuffer) {
283
+ res.send('<html><head><meta http-equiv="refresh" content="2"></head><body>\u7B49\u5F85\u7B2C\u4E00\u4E2A\u5C4F\u5E55\u622A\u56FE...</body></html>');
284
+ return;
285
+ }
286
+ const screenshotBase64 = screenshotBuffer.toString("base64");
287
+ res.send(`
288
+ <html>
289
+ <head>
290
+ <title>Live View (\u622A\u56FE)</title>
291
+ <meta http-equiv="refresh" content="1">
292
+ </head>
293
+ <body style="margin:0; padding:0;">
294
+ <img src="data:image/png;base64,${screenshotBase64}"
295
+ alt="Live View Screenshot"
296
+ style="width: 100%; height: auto;" />
297
+ </body>
298
+ </html>
299
+ `);
300
+ } catch (error) {
301
+ log4.error(`Live View \u670D\u52A1\u5668\u9519\u8BEF: ${error.message}`);
302
+ res.status(500).send(`\u65E0\u6CD5\u52A0\u8F7D\u5C4F\u5E55\u622A\u56FE: ${error.message}`);
303
+ }
304
+ });
305
+ const port = process.env.APIFY_CONTAINER_PORT || 4321;
306
+ app.listen(port, () => {
307
+ log4.info(`Live View \u670D\u52A1\u5668\u5DF2\u542F\u52A8\uFF0C\u76D1\u542C\u7AEF\u53E3 ${port}\u3002\u8BF7\u6253\u5F00 "Live View" \u9009\u9879\u5361\u67E5\u770B\u3002`);
308
+ });
309
+ }
310
+ async function takeLiveScreenshot(liveViewKey, page, logMessage) {
311
+ try {
312
+ const buffer = await page.screenshot({ type: "png" });
313
+ await Actor2.setValue(liveViewKey, buffer, { contentType: "image/png" });
314
+ if (logMessage) {
315
+ log4.info(`(\u622A\u56FE): ${logMessage}`);
316
+ }
317
+ } catch (e) {
318
+ log4.warning(`\u65E0\u6CD5\u6355\u83B7 Live View \u5C4F\u5E55\u622A\u56FE: ${e.message}`);
319
+ }
320
+ }
321
+ var useLiveView = (liveViewKey = PresetOfLiveViewKey) => {
322
+ return {
323
+ takeLiveScreenshot: async (page, logMessage) => {
324
+ return await takeLiveScreenshot(liveViewKey, page, logMessage);
325
+ },
326
+ startLiveViewServer: async () => {
327
+ return await startLiveViewServer(liveViewKey);
328
+ }
329
+ };
330
+ };
331
+ var LiveView = {
332
+ useLiveView
333
+ };
334
+
335
+ // index.js
336
+ var usePlaywrightToolKit = () => {
337
+ return {
338
+ ApifyKit,
339
+ Stealth,
340
+ Humanize,
341
+ Launch,
342
+ LiveView,
343
+ Constants: constants_exports,
344
+ Utils
345
+ };
346
+ };
347
+ export {
348
+ usePlaywrightToolKit
349
+ };
350
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1,7 @@
1
+ {
2
+ "version": 3,
3
+ "sources": ["../src/apify-kit.js", "../src/constants.js", "../src/utils.js", "../src/stealth.js", "../src/humanize.js", "../src/launch.js", "../src/live-view.js", "../index.js"],
4
+ "sourcesContent": ["import { log } from 'crawlee';\nimport { Actor } from 'apify';\nimport { Status, FAILED_KEY_SEPARATOR, StatusCode } from './constants.js';\n\nexport const ApifyKit = {\n\n /**\n * \u5305\u88C5 Step Name\n */\n wrapStepNameWithFailedKey(key, stepName) {\n return `${key}${FAILED_KEY_SEPARATOR}${stepName}`;\n },\n\n /**\n * \u89E3\u5305 Step Name\n */\n unwrapStepName(stepName) {\n const splitIndex = stepName.indexOf(FAILED_KEY_SEPARATOR);\n if (splitIndex === -1) {\n return ['-', stepName];\n }\n const key = stepName.substring(0, splitIndex);\n const value = stepName.substring(splitIndex + FAILED_KEY_SEPARATOR.length);\n return [key, value];\n },\n\n /**\n * \u6838\u5FC3\u5C01\u88C5\uFF1A\u6267\u884C\u6B65\u9AA4\uFF0C\u5E26\u81EA\u52A8\u65E5\u5FD7\u786E\u8BA4\u548C\u5931\u8D25\u622A\u56FE\u5904\u7406\n */\n async runStep(pendingStepName, page, actionFn, options = {}) {\n const { failActor = true } = options; // \u9ED8\u8BA4\u8C03\u7528 Actor.fail\n const [failedKey, stepName] = this.unwrapStepName(pendingStepName);\n\n log.info(`\uD83D\uDD04 [\u6B63\u5728\u6267\u884C] ${stepName}...`);\n\n try {\n const result = await actionFn();\n log.info(`\u2705 [\u6267\u884C\u6210\u529F] ${stepName}`);\n return result;\n } catch (error) {\n log.error(`\u274C [\u6267\u884C\u5931\u8D25] ${stepName}: ${error.message}`);\n\n let screenshotBase64 = '\u622A\u56FE\u5931\u8D25';\n try {\n if (page) {\n const buffer = await page.screenshot({ fullPage: true, type: 'jpeg', quality: 60 });\n screenshotBase64 = `data:image/jpeg;base64,${buffer.toString('base64')}`;\n }\n } catch (snapErr) {\n log.warning(`\u622A\u56FE\u751F\u6210\u5931\u8D25: ${snapErr.message}`);\n }\n\n // \u4F7F\u7528 pushFailed \u65B9\u6CD5\u63A8\u9001\u5931\u8D25\u6570\u636E\uFF08\u79C1\u6709\u4F7F\u7528\uFF09\n await this.pushFailed(error, {\n failedStep: stepName,\n failedKey: failedKey,\n errorMessage: error.message,\n errorStack: error.stack,\n screenshotBase64: screenshotBase64\n });\n\n // \u6839\u636E failActor \u51B3\u5B9A\u662F\u5426\u8C03\u7528 Actor.fail\n if (failActor) {\n await Actor.fail(`Run Step ${stepName} \u5931\u8D25: ${error.message}`);\n } else {\n // \u4E0D\u8C03\u7528 Actor.fail\uFF0C\u76F4\u63A5\u629B\u51FA\u9519\u8BEF\n throw error;\n }\n }\n },\n\n /**\n * \u5BBD\u677E\u7248runStep\uFF1A\u5931\u8D25\u65F6\u4E0D\u8C03\u7528Actor.fail\uFF0C\u53EA\u629B\u51FA\u5F02\u5E38\n */\n async runStepLoose(stepName, page, fn) {\n return await this.runStep(stepName, page, fn, { failActor: false });\n },\n\n /**\n * \u63A8\u9001\u6210\u529F\u6570\u636E\u7684\u901A\u7528\u65B9\u6CD5\n * @param {Object} data - \u8981\u63A8\u9001\u7684\u6570\u636E\u5BF9\u8C61\n */\n async pushSuccess(data) {\n await Actor.pushData({\n code: StatusCode.Success,\n status: Status.Success,\n timestamp: new Date().toISOString(),\n ...data\n });\n },\n\n /**\n * \u63A8\u9001\u5931\u8D25\u6570\u636E\u7684\u901A\u7528\u65B9\u6CD5\uFF08\u79C1\u6709\u65B9\u6CD5\uFF0C\u4EC5\u4F9BrunStep\u5185\u90E8\u4F7F\u7528\uFF09\n * @param {Error|Object} error - \u9519\u8BEF\u5BF9\u8C61\uFF08\u53EF\u5305\u542B\u5176\u4ED6\u7684\u9519\u8BEF/\u6216\u90E8\u5206\u5904\u7406\u6210\u529F\u7684\u989D\u5916\u4FE1\u606F\uFF09\n * @param {Object} [meta] - \u989D\u5916\u7684\u6570\u636E\uFF08\u5982failedStep, screenshotBase64\u7B49\uFF0C\u4EC5runStep\u4F7F\u7528\uFF09\n * @private\n */\n async pushFailed(error, meta = {}) {\n await Actor.pushData({\n code: StatusCode.Failed,\n status: Status.Failed,\n // \u8FD9\u91CC\u53EF\u80FD\u5E26\u5176\u4ED6\u9519\u8BEF\u4FE1\u606F\n error,\n timestamp: new Date().toISOString(),\n ...meta\n });\n }\n}\n", "export const ErrorKeygen = {\n NotLogin: 30000001,\n Chaptcha: 30000002,\n}\n\nexport const Status = {\n Success: 'SUCCESS',\n Failed: 'FAILED'\n}\n\nexport const StatusCode = {\n Success: 0,\n Failed: -1\n}\n\nexport const FAILED_KEY_SEPARATOR = '::<@>::';\n\nexport const PresetOfLiveViewKey = 'LIVE_VIEW_SCREENSHOT';\n", "export const Utils = {\n /**\n * \u89E3\u6790 SSE \u6D41\u6587\u672C\n */\n parseSseStream(sseStreamText) {\n const events = [];\n const lines = sseStreamText.split('\\n');\n for (const line of lines) {\n if (line.startsWith('data: ')) {\n try {\n const jsonContent = line.substring(6).trim();\n if (jsonContent && jsonContent !== '[DONE]') {\n events.push(JSON.parse(jsonContent));\n }\n } catch (e) {\n // Ignore lines that are not valid JSON\n }\n }\n }\n return events;\n }\n}\n", "import { log } from 'crawlee';\n\nexport const Stealth = {\n /**\n * \u5173\u952E\u4FEE\u590D\uFF1A\u5C06 Page \u89C6\u53E3\u8C03\u6574\u4E3A\u4E0E\u6D4F\u89C8\u5668\u6307\u7EB9 (window.screen) \u4E00\u81F4\u3002\n * \u9632\u6B62 \"Viewport Mismatch\" \u7C7B\u578B\u7684\u53CD\u722C\u68C0\u6D4B\u3002\n * @param {import('playwright').Page} page \n */\n async syncViewportWithScreen(page) {\n try {\n // \u83B7\u53D6\u6307\u7EB9\u4E2D\u7684\u5C4F\u5E55\u5C3A\u5BF8\n const screen = await page.evaluate(() => ({\n width: window.screen.width,\n height: window.screen.height,\n availWidth: window.screen.availWidth,\n availHeight: window.screen.availHeight,\n }));\n\n // \u8C03\u6574\u89C6\u53E3\n await page.setViewportSize({\n width: screen.width,\n height: screen.height\n });\n\n log.info(`[Stealth] Viewport synced to fingerprint: ${screen.width}x${screen.height}`);\n } catch (e) {\n log.warning(`[Stealth] Failed to sync viewport: ${e.message}. Fallback to 1920x1080.`);\n await page.setViewportSize({ width: 1920, height: 1080 });\n }\n },\n\n /**\n * \u786E\u4FDD navigator.webdriver \u9690\u85CF (\u901A\u5E38 Playwright Stealth \u63D2\u4EF6\u5DF2\u5904\u7406\uFF0C\u4F46\u53CC\u91CD\u4FDD\u9669)\n */\n async hideWebdriver(page) {\n await page.addInitScript(() => {\n Object.defineProperty(navigator, 'webdriver', {\n get: () => false,\n });\n });\n },\n\n /**\n * \u901A\u7528\u7684 Playwright \u8D44\u6E90\u62E6\u622A\u5668\uFF0C\u7528\u4E8E\u5C4F\u853D\u4E0D\u5FC5\u8981\u7684\u8D44\u6E90\u4EE5\u52A0\u901F\u52A0\u8F7D\n * @param {import('playwright').Page} page\n * @param {string[]} [resourceTypes] - \u8981\u5C4F\u853D\u7684\u8D44\u6E90\u7C7B\u578B\uFF0C\u9ED8\u8BA4\u4E3A ['font', 'image', 'media']\n */\n async setupBlockingResources(page, resourceTypes = ['font', 'image', 'media']) {\n await page.route('**/*', (route) => {\n const request = route.request();\n const type = request.resourceType();\n if (resourceTypes.includes(type)) {\n return route.abort();\n }\n return route.continue();\n });\n },\n\n /**\n * \u83B7\u53D6\u63A8\u8350\u7684 Stealth \u542F\u52A8\u53C2\u6570\n */\n getStealthLaunchArgs() {\n return [\n '--disable-blink-features=AutomationControlled',\n '--no-sandbox',\n '--disable-setuid-sandbox',\n '--disable-infobars',\n '--window-position=0,0',\n '--ignore-certificate-errors',\n '--disable-web-security',\n // \u6CE8\u610F\uFF1A\u4E0D\u5EFA\u8BAE\u8FD9\u91CC\u5F3A\u5236\u6307\u5B9A window-size\uFF0C\u8BA9 syncViewportWithScreen \u53BB\u52A8\u6001\u8C03\u6574\n // '--window-size=1920,1080' \n ];\n }\n}\n", "import delay from 'delay';\nimport { log } from 'crawlee';\n\nexport const Humanize = {\n /**\n * \u968F\u673A\u5EF6\u8FDF\u4E00\u6BB5\u6BEB\u79D2\u6570 (API Wrapper for 'delay' package)\n * @param {number} min - \u6700\u5C0F\u6BEB\u79D2\n * @param {number} max - \u6700\u5927\u6BEB\u79D2\n */\n async randomSleep(min, max) {\n const ms = typeof max === 'number'\n ? delay.range(min, max)\n : delay(min); // \u5982\u679C\u53EA\u4F20\u4E00\u4E2A\u53C2\u6570\uFF0C\u89C6\u4E3A\u56FA\u5B9A\u5EF6\u8FDF\u6216\u6700\u5C0F\u5EF6\u8FDF\n\n // log.debug(`[Humanize] Sleeping for ${await ms} ms...`); // delay return promise acts like number somewhat but best await it\n // The delay package returns a promise that resolves after the delay.\n // delay.range() returns a promise too.\n\n await ms;\n },\n\n /**\n * \u6A21\u62DF\u4EBA\u7C7B\u201C\u6CE8\u89C6\u201D\u6216\u201C\u9605\u8BFB\u201D\u884C\u4E3A\uFF1A\u9F20\u6807\u5728\u9875\u9762\u4E0A\u968F\u673A\u5FAE\u52A8\u3002\n * @param {import('ghost-cursor-playwright').GhostCursor} cursor \n * @param {number} durationMs - \u6301\u7EED\u65F6\u95F4\n */\n async simulateGaze(cursor, durationMs = 2000) {\n const startTime = Date.now();\n while (Date.now() - startTime < durationMs) {\n // \u968F\u673A\u5C0F\u5E45\u5EA6\u79FB\u52A8\n const x = Math.random() * 800;\n const y = Math.random() * 600;\n await cursor.moveTo({ x, y });\n await delay.range(200, 800);\n }\n }\n}\n", "// \u96C6\u4E2D\u7BA1\u7406\u542F\u52A8\u914D\u7F6E\uFF0C\u6682\u65F6\u4E3B\u8981\u7531 Stealth \u6A21\u5757\u63D0\u4F9B Args\uFF0C\u8FD9\u91CC\u4F5C\u4E3A\u6269\u5C55\u70B9\nimport { Stealth } from './stealth.js';\n\nexport const Launch = {\n getLaunchOptions(customArgs = []) {\n return {\n args: [\n ...Stealth.getStealthLaunchArgs(),\n ...customArgs\n ],\n ignoreDefaultArgs: ['--enable-automation'],\n };\n },\n\n /**\n * \u63A8\u8350\u7684 Fingerprint Generator \u9009\u9879\n * \u786E\u4FDD\u751F\u6210\u7684\u662F\u684C\u9762\u7AEF\u3001\u8F83\u65B0\u7684 Chrome\uFF0C\u4EE5\u5339\u914D\u6211\u4EEC\u7684\u811A\u672C\u903B\u8F91\n */\n getFingerprintGeneratorOptions() {\n return {\n browsers: [{ name: 'chrome', minVersion: 110 }],\n devices: ['desktop'],\n operatingSystems: ['windows', 'linux'], // \u5305\u542B Linux \u517C\u5BB9\u5BB9\u5668\n };\n }\n}\n", "import express from 'express';\nimport { log } from 'crawlee';\nimport { Actor } from 'apify';\nimport { PresetOfLiveViewKey } from './constants';\n\n/**\n * \u542F\u52A8\u4E00\u4E2A Web \u670D\u52A1\u5668\u4EE5\u5728 Live View \u9009\u9879\u5361\u4E2D\u663E\u793A\u6700\u65B0\u7684\u5C4F\u5E55\u622A\u56FE\u3002\n */\nasync function startLiveViewServer(liveViewKey) {\n const app = express();\n\n app.get('/', async (req, res) => {\n try {\n // \u4ECE\u9ED8\u8BA4\u7684 Key-Value Store \u4E2D\u8BFB\u53D6\u6700\u65B0\u7684\u5C4F\u5E55\u622A\u56FE\n const screenshotBuffer = await Actor.getValue(liveViewKey);\n\n if (!screenshotBuffer) {\n // \u5982\u679C\u8FD8\u6CA1\u6709\u622A\u56FE\uFF0C\u53D1\u9001\u4E00\u4E2A\u81EA\u52A8\u5237\u65B0\u7684\u5360\u4F4D\u9875\u9762\n res.send('<html><head><meta http-equiv=\"refresh\" content=\"2\"></head><body>\u7B49\u5F85\u7B2C\u4E00\u4E2A\u5C4F\u5E55\u622A\u56FE...</body></html>');\n return;\n }\n\n // \u5C06 Buffer \u8F6C\u6362\u4E3A Base64 \u5B57\u7B26\u4E32\n const screenshotBase64 = screenshotBuffer.toString('base64');\n\n // \u53D1\u9001\u4E00\u4E2A HTML \u9875\u9762\uFF0C\u8BE5\u9875\u9762\u6BCF 1 \u79D2\u81EA\u52A8\u5237\u65B0\u4E00\u6B21\uFF0C\u5E76\u663E\u793A\u622A\u56FE\n res.send(`\n <html>\n <head>\n <title>Live View (\u622A\u56FE)</title>\n <meta http-equiv=\"refresh\" content=\"1\">\n </head>\n <body style=\"margin:0; padding:0;\">\n <img src=\"data:image/png;base64,${screenshotBase64}\" \n alt=\"Live View Screenshot\" \n style=\"width: 100%; height: auto;\" />\n </body>\n </html>\n `);\n } catch (error) {\n log.error(`Live View \u670D\u52A1\u5668\u9519\u8BEF: ${error.message}`);\n res.status(500).send(`\u65E0\u6CD5\u52A0\u8F7D\u5C4F\u5E55\u622A\u56FE: ${error.message}`);\n }\n });\n\n // \u76D1\u542C Apify \u5BB9\u5668\u7AEF\u53E3 \n const port = process.env.APIFY_CONTAINER_PORT || 4321;\n app.listen(port, () => { log.info(`Live View \u670D\u52A1\u5668\u5DF2\u542F\u52A8\uFF0C\u76D1\u542C\u7AEF\u53E3 ${port}\u3002\u8BF7\u6253\u5F00 \"Live View\" \u9009\u9879\u5361\u67E5\u770B\u3002`); });\n}\n\n/**\n * \u62CD\u6444\u5F53\u524D\u9875\u9762\u7684\u5C4F\u5E55\u622A\u56FE\u5E76\u5C06\u5176\u4FDD\u5B58\u5230 Key-Value Store\u3002\n * @param {import('playwright').Page} page\n * @param {string} [logMessage] - \u53EF\u9009\u7684\u65E5\u5FD7\u6D88\u606F\u3002\n */\nasync function takeLiveScreenshot(liveViewKey, page, logMessage) {\n try {\n const buffer = await page.screenshot({ type: 'png' });\n await Actor.setValue(liveViewKey, buffer, { contentType: 'image/png' });\n if (logMessage) {\n log.info(`(\u622A\u56FE): ${logMessage}`);\n }\n } catch (e) {\n log.warning(`\u65E0\u6CD5\u6355\u83B7 Live View \u5C4F\u5E55\u622A\u56FE: ${e.message}`);\n }\n}\n\nconst useLiveView = (liveViewKey = PresetOfLiveViewKey) => {\n return {\n takeLiveScreenshot: async (page, logMessage) => {\n return await takeLiveScreenshot(liveViewKey, page, logMessage)\n },\n startLiveViewServer: async () => {\n return await startLiveViewServer(liveViewKey);\n }\n }\n}\n\nexport const LiveView = {\n useLiveView,\n};\n", "import { ApifyKit } from './src/apify-kit.js';\nimport { Utils } from './src/utils.js';\nimport { Stealth } from './src/stealth.js';\nimport { Humanize } from './src/humanize.js';\nimport { Launch } from './src/launch.js';\nimport { LiveView } from './src/live-view.js';\nimport * as Constants from './src/constants.js';\n\n// Unified Entry Point\nexport const usePlaywrightToolKit = () => {\n return {\n ApifyKit,\n Stealth,\n Humanize,\n Launch,\n LiveView,\n Constants,\n Utils\n };\n};\n"],
5
+ "mappings": "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",
6
+ "names": ["log", "log", "log", "Actor", "Actor", "log"]
7
+ }
package/package.json CHANGED
@@ -1,12 +1,27 @@
1
1
  {
2
2
  "name": "@skrillex1224/playwright-toolkit",
3
- "version": "2.0.1",
3
+ "version": "2.0.3",
4
4
  "description": "一个在 Apify/Crawlee Actor 中启用实时截图视图的实用工具库。",
5
- "main": "index.js",
5
+ "main": "dist/index.cjs",
6
+ "module": "dist/index.js",
6
7
  "type": "module",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.js",
11
+ "require": "./dist/index.cjs"
12
+ }
13
+ },
7
14
  "scripts": {
15
+ "build": "node build.js",
16
+ "release": "npm run build && npm publish",
17
+ "prepublishOnly": "npm run build",
8
18
  "test": "echo \"Error: no test specified\" && exit 1"
9
19
  },
20
+ "files": [
21
+ "dist/",
22
+ "docs/",
23
+ "README.md"
24
+ ],
10
25
  "keywords": [
11
26
  "apify",
12
27
  "crawlee",
@@ -24,5 +39,8 @@
24
39
  "apify": "*",
25
40
  "crawlee": "*",
26
41
  "playwright": "*"
42
+ },
43
+ "devDependencies": {
44
+ "esbuild": "^0.24.2"
27
45
  }
28
46
  }
package/index.js DELETED
@@ -1,21 +0,0 @@
1
- import { ApifyKit } from './src/apify-kit.js';
2
- import { Utils } from './src/utils.js';
3
- import { Stealth } from './src/stealth.js';
4
- import { Humanize } from './src/humanize.js';
5
- import { Launch } from './src/launch.js';
6
- import { LiveView } from './src/live-view.js';
7
- import * as Constants from './src/constants.js';
8
-
9
- // Unified Entry Point
10
- export const usePlaywrightToolKit = () => {
11
- return {
12
- ApifyKit,
13
-
14
- Stealth,
15
- Humanize,
16
- Launch,
17
- LiveView,
18
- Constants,
19
- Utils
20
- };
21
- };
package/src/apify-kit.js DELETED
@@ -1,108 +0,0 @@
1
- import { log } from 'crawlee';
2
- import { Actor } from 'apify';
3
- import { Status, FAILED_KEY_SEPARATOR, StatusCode } from './constants.js';
4
-
5
- export const ApifyKit = {
6
-
7
- /**
8
- * 包装 Step Name
9
- */
10
- wrapStepNameWithFailedKey(key, stepName) {
11
- return `${key}${FAILED_KEY_SEPARATOR}${stepName}`;
12
- },
13
-
14
- /**
15
- * 解包 Step Name
16
- */
17
- unwrapStepName(stepName) {
18
- const splitIndex = stepName.indexOf(FAILED_KEY_SEPARATOR);
19
- if (splitIndex === -1) {
20
- return ['-', stepName];
21
- }
22
- const key = stepName.substring(0, splitIndex);
23
- const value = stepName.substring(splitIndex + FAILED_KEY_SEPARATOR.length);
24
- return [key, value];
25
- },
26
-
27
- /**
28
- * 核心封装:执行步骤,带自动日志确认和失败截图处理
29
- */
30
- async runStep(pendingStepName, page, actionFn, options = {}) {
31
- const { failActor = true } = options; // 默认调用 Actor.fail
32
- const [failedKey, stepName] = this.unwrapStepName(pendingStepName);
33
-
34
- log.info(`🔄 [正在执行] ${stepName}...`);
35
-
36
- try {
37
- const result = await actionFn();
38
- log.info(`✅ [执行成功] ${stepName}`);
39
- return result;
40
- } catch (error) {
41
- log.error(`❌ [执行失败] ${stepName}: ${error.message}`);
42
-
43
- let screenshotBase64 = '截图失败';
44
- try {
45
- if (page) {
46
- const buffer = await page.screenshot({ fullPage: true, type: 'jpeg', quality: 60 });
47
- screenshotBase64 = `data:image/jpeg;base64,${buffer.toString('base64')}`;
48
- }
49
- } catch (snapErr) {
50
- log.warning(`截图生成失败: ${snapErr.message}`);
51
- }
52
-
53
- // 使用 pushFailed 方法推送失败数据(私有使用)
54
- await this.pushFailed(error, {
55
- failedStep: stepName,
56
- failedKey: failedKey,
57
- errorMessage: error.message,
58
- errorStack: error.stack,
59
- screenshotBase64: screenshotBase64
60
- });
61
-
62
- // 根据 failActor 决定是否调用 Actor.fail
63
- if (failActor) {
64
- await Actor.fail(`Run Step ${stepName} 失败: ${error.message}`);
65
- } else {
66
- // 不调用 Actor.fail,直接抛出错误
67
- throw error;
68
- }
69
- }
70
- },
71
-
72
- /**
73
- * 宽松版runStep:失败时不调用Actor.fail,只抛出异常
74
- */
75
- async runStepLoose(stepName, page, fn) {
76
- return await this.runStep(stepName, page, fn, { failActor: false });
77
- },
78
-
79
- /**
80
- * 推送成功数据的通用方法
81
- * @param {Object} data - 要推送的数据对象
82
- */
83
- async pushSuccess(data) {
84
- await Actor.pushData({
85
- code: StatusCode.Success,
86
- status: Status.Success,
87
- timestamp: new Date().toISOString(),
88
- ...data
89
- });
90
- },
91
-
92
- /**
93
- * 推送失败数据的通用方法(私有方法,仅供runStep内部使用)
94
- * @param {Error|Object} error - 错误对象(可包含其他的错误/或部分处理成功的额外信息)
95
- * @param {Object} [meta] - 额外的数据(如failedStep, screenshotBase64等,仅runStep使用)
96
- * @private
97
- */
98
- async pushFailed(error, meta = {}) {
99
- await Actor.pushData({
100
- code: StatusCode.Failed,
101
- status: Status.Failed,
102
- // 这里可能带其他错误信息
103
- error,
104
- timestamp: new Date().toISOString(),
105
- ...meta
106
- });
107
- }
108
- }
package/src/constants.js DELETED
@@ -1,18 +0,0 @@
1
- export const ErrorKeygen = {
2
- NotLogin: 30000001,
3
- Chaptcha: 30000002,
4
- }
5
-
6
- export const Status = {
7
- Success: 'SUCCESS',
8
- Failed: 'FAILED'
9
- }
10
-
11
- export const StatusCode = {
12
- Success: 0,
13
- Failed: -1
14
- }
15
-
16
- export const FAILED_KEY_SEPARATOR = '::<@>::';
17
-
18
- export const PresetOfLiveViewKey = 'LIVE_VIEW_SCREENSHOT';
package/src/humanize.js DELETED
@@ -1,37 +0,0 @@
1
- import delay from 'delay';
2
- import { log } from 'crawlee';
3
-
4
- export const Humanize = {
5
- /**
6
- * 随机延迟一段毫秒数 (API Wrapper for 'delay' package)
7
- * @param {number} min - 最小毫秒
8
- * @param {number} max - 最大毫秒
9
- */
10
- async randomSleep(min, max) {
11
- const ms = typeof max === 'number'
12
- ? delay.range(min, max)
13
- : delay(min); // 如果只传一个参数,视为固定延迟或最小延迟
14
-
15
- // log.debug(`[Humanize] Sleeping for ${await ms} ms...`); // delay return promise acts like number somewhat but best await it
16
- // The delay package returns a promise that resolves after the delay.
17
- // delay.range() returns a promise too.
18
-
19
- await ms;
20
- },
21
-
22
- /**
23
- * 模拟人类“注视”或“阅读”行为:鼠标在页面上随机微动。
24
- * @param {import('ghost-cursor-playwright').GhostCursor} cursor
25
- * @param {number} durationMs - 持续时间
26
- */
27
- async simulateGaze(cursor, durationMs = 2000) {
28
- const startTime = Date.now();
29
- while (Date.now() - startTime < durationMs) {
30
- // 随机小幅度移动
31
- const x = Math.random() * 800;
32
- const y = Math.random() * 600;
33
- await cursor.moveTo({ x, y });
34
- await delay.range(200, 800);
35
- }
36
- }
37
- }
package/src/launch.js DELETED
@@ -1,26 +0,0 @@
1
- // 集中管理启动配置,暂时主要由 Stealth 模块提供 Args,这里作为扩展点
2
- import { Stealth } from './stealth.js';
3
-
4
- export const Launch = {
5
- getLaunchOptions(customArgs = []) {
6
- return {
7
- args: [
8
- ...Stealth.getStealthLaunchArgs(),
9
- ...customArgs
10
- ],
11
- ignoreDefaultArgs: ['--enable-automation'],
12
- };
13
- },
14
-
15
- /**
16
- * 推荐的 Fingerprint Generator 选项
17
- * 确保生成的是桌面端、较新的 Chrome,以匹配我们的脚本逻辑
18
- */
19
- getFingerprintGeneratorOptions() {
20
- return {
21
- browsers: [{ name: 'chrome', minVersion: 110 }],
22
- devices: ['desktop'],
23
- operatingSystems: ['windows', 'linux'], // 包含 Linux 兼容容器
24
- };
25
- }
26
- }
package/src/live-view.js DELETED
@@ -1,81 +0,0 @@
1
- import express from 'express';
2
- import { log } from 'crawlee';
3
- import { Actor } from 'apify';
4
- import { PresetOfLiveViewKey } from './constants';
5
-
6
- /**
7
- * 启动一个 Web 服务器以在 Live View 选项卡中显示最新的屏幕截图。
8
- */
9
- async function startLiveViewServer(liveViewKey) {
10
- const app = express();
11
-
12
- app.get('/', async (req, res) => {
13
- try {
14
- // 从默认的 Key-Value Store 中读取最新的屏幕截图
15
- const screenshotBuffer = await Actor.getValue(liveViewKey);
16
-
17
- if (!screenshotBuffer) {
18
- // 如果还没有截图,发送一个自动刷新的占位页面
19
- res.send('<html><head><meta http-equiv="refresh" content="2"></head><body>等待第一个屏幕截图...</body></html>');
20
- return;
21
- }
22
-
23
- // 将 Buffer 转换为 Base64 字符串
24
- const screenshotBase64 = screenshotBuffer.toString('base64');
25
-
26
- // 发送一个 HTML 页面,该页面每 1 秒自动刷新一次,并显示截图
27
- res.send(`
28
- <html>
29
- <head>
30
- <title>Live View (截图)</title>
31
- <meta http-equiv="refresh" content="1">
32
- </head>
33
- <body style="margin:0; padding:0;">
34
- <img src="data:image/png;base64,${screenshotBase64}"
35
- alt="Live View Screenshot"
36
- style="width: 100%; height: auto;" />
37
- </body>
38
- </html>
39
- `);
40
- } catch (error) {
41
- log.error(`Live View 服务器错误: ${error.message}`);
42
- res.status(500).send(`无法加载屏幕截图: ${error.message}`);
43
- }
44
- });
45
-
46
- // 监听 Apify 容器端口
47
- const port = process.env.APIFY_CONTAINER_PORT || 4321;
48
- app.listen(port, () => { log.info(`Live View 服务器已启动,监听端口 ${port}。请打开 "Live View" 选项卡查看。`); });
49
- }
50
-
51
- /**
52
- * 拍摄当前页面的屏幕截图并将其保存到 Key-Value Store。
53
- * @param {import('playwright').Page} page
54
- * @param {string} [logMessage] - 可选的日志消息。
55
- */
56
- async function takeLiveScreenshot(liveViewKey, page, logMessage) {
57
- try {
58
- const buffer = await page.screenshot({ type: 'png' });
59
- await Actor.setValue(liveViewKey, buffer, { contentType: 'image/png' });
60
- if (logMessage) {
61
- log.info(`(截图): ${logMessage}`);
62
- }
63
- } catch (e) {
64
- log.warning(`无法捕获 Live View 屏幕截图: ${e.message}`);
65
- }
66
- }
67
-
68
- const useLiveView = (liveViewKey = PresetOfLiveViewKey) => {
69
- return {
70
- takeLiveScreenshot: async (page, logMessage) => {
71
- return await takeLiveScreenshot(liveViewKey, page, logMessage)
72
- },
73
- startLiveViewServer: async () => {
74
- return await startLiveViewServer(liveViewKey);
75
- }
76
- }
77
- }
78
-
79
- export const LiveView = {
80
- useLiveView,
81
- };
package/src/stealth.js DELETED
@@ -1,75 +0,0 @@
1
- import { log } from 'crawlee';
2
-
3
- export const Stealth = {
4
- /**
5
- * 关键修复:将 Page 视口调整为与浏览器指纹 (window.screen) 一致。
6
- * 防止 "Viewport Mismatch" 类型的反爬检测。
7
- * @param {import('playwright').Page} page
8
- */
9
- async syncViewportWithScreen(page) {
10
- try {
11
- // 获取指纹中的屏幕尺寸
12
- const screen = await page.evaluate(() => ({
13
- width: window.screen.width,
14
- height: window.screen.height,
15
- availWidth: window.screen.availWidth,
16
- availHeight: window.screen.availHeight,
17
- }));
18
-
19
- // 调整视口
20
- await page.setViewportSize({
21
- width: screen.width,
22
- height: screen.height
23
- });
24
-
25
- log.info(`[Stealth] Viewport synced to fingerprint: ${screen.width}x${screen.height}`);
26
- } catch (e) {
27
- log.warning(`[Stealth] Failed to sync viewport: ${e.message}. Fallback to 1920x1080.`);
28
- await page.setViewportSize({ width: 1920, height: 1080 });
29
- }
30
- },
31
-
32
- /**
33
- * 确保 navigator.webdriver 隐藏 (通常 Playwright Stealth 插件已处理,但双重保险)
34
- */
35
- async hideWebdriver(page) {
36
- await page.addInitScript(() => {
37
- Object.defineProperty(navigator, 'webdriver', {
38
- get: () => false,
39
- });
40
- });
41
- },
42
-
43
- /**
44
- * 通用的 Playwright 资源拦截器,用于屏蔽不必要的资源以加速加载
45
- * @param {import('playwright').Page} page
46
- * @param {string[]} [resourceTypes] - 要屏蔽的资源类型,默认为 ['font', 'image', 'media']
47
- */
48
- async setupBlockingResources(page, resourceTypes = ['font', 'image', 'media']) {
49
- await page.route('**/*', (route) => {
50
- const request = route.request();
51
- const type = request.resourceType();
52
- if (resourceTypes.includes(type)) {
53
- return route.abort();
54
- }
55
- return route.continue();
56
- });
57
- },
58
-
59
- /**
60
- * 获取推荐的 Stealth 启动参数
61
- */
62
- getStealthLaunchArgs() {
63
- return [
64
- '--disable-blink-features=AutomationControlled',
65
- '--no-sandbox',
66
- '--disable-setuid-sandbox',
67
- '--disable-infobars',
68
- '--window-position=0,0',
69
- '--ignore-certificate-errors',
70
- '--disable-web-security',
71
- // 注意:不建议这里强制指定 window-size,让 syncViewportWithScreen 去动态调整
72
- // '--window-size=1920,1080'
73
- ];
74
- }
75
- }
package/src/utils.js DELETED
@@ -1,22 +0,0 @@
1
- export const Utils = {
2
- /**
3
- * 解析 SSE 流文本
4
- */
5
- parseSseStream(sseStreamText) {
6
- const events = [];
7
- const lines = sseStreamText.split('\n');
8
- for (const line of lines) {
9
- if (line.startsWith('data: ')) {
10
- try {
11
- const jsonContent = line.substring(6).trim();
12
- if (jsonContent && jsonContent !== '[DONE]') {
13
- events.push(JSON.parse(jsonContent));
14
- }
15
- } catch (e) {
16
- // Ignore lines that are not valid JSON
17
- }
18
- }
19
- }
20
- return events;
21
- }
22
- }