@skrillex1224/playwright-toolkit 2.1.25 → 2.1.27

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/README.md CHANGED
@@ -23,7 +23,7 @@ import { usePlaywrightToolKit } from '@skrillex1224/playwright-toolkit';
23
23
  await Actor.init();
24
24
 
25
25
  // 初始化工具箱
26
- const { ApifyKit: KitHook, Launch, AntiDetect, Humanize, Captcha, LiveView, Constants } = usePlaywrightToolKit();
26
+ const { ApifyKit: KitHook, Launch, AntiCheat, Humanize, Captcha, LiveView, Constants } = usePlaywrightToolKit();
27
27
 
28
28
  // ⚠️ ApifyKit 需要异步初始化
29
29
  const ApifyKit = await KitHook.useApifyKit();
@@ -39,7 +39,7 @@ const crawler = new PlaywrightCrawler({
39
39
  preNavigationHooks: [
40
40
  async ({ page }) => {
41
41
  // 统一反爬:时区/语言/权限/视口
42
- await AntiDetect.applyPage(page);
42
+ await AntiCheat.applyPage(page);
43
43
 
44
44
  // 验证码监控
45
45
  Captcha.useCaptchaMonitor(page, {
@@ -77,32 +77,32 @@ await Actor.exit();
77
77
 
78
78
  ### 架构
79
79
 
80
- | 层次 | 问题 | 解决方案 |
81
- |------|------|----------|
82
- | **指纹层** | UA/屏幕/语言/时区一致性 | Crawlee `useFingerprints` + `AntiDetect` |
83
- | **行为层** | 机械输入/点击/滚动 | `ghost-cursor-playwright` + Humanize |
84
- | **页面层** | 验证码/风控检测 | Captcha 监控器 |
80
+ | 层次 | 问题 | 解决方案 |
81
+ | ---------- | ----------------------- | --------------------------------------- |
82
+ | **指纹层** | UA/屏幕/语言/时区一致性 | Crawlee `useFingerprints` + `AntiCheat` |
83
+ | **行为层** | 机械输入/点击/滚动 | `ghost-cursor-playwright` + Humanize |
84
+ | **页面层** | 验证码/风控检测 | Captcha 监控器 |
85
85
 
86
86
  ### API 一览
87
87
 
88
- | 模块 | 方法 | 说明 |
89
- |------|------|------|
90
- | `Launch` | `getAdvancedLaunchOptions()` | 增强版启动参数 |
91
- | `Launch` | `getLaunchOptions()` | 基础启动参数 |
92
- | `Launch` | `getFingerprintGeneratorOptions()` | 指纹生成器选项 |
93
- | `AntiDetect` | `applyPage(page, options?)` | 应用时区/语言/权限/视口 |
94
- | `AntiDetect` | `applyContext(context, options?)` | 仅应用 Context 设置 |
95
- | `AntiDetect` | `syncViewportWithScreen(page)` | 同步视口与屏幕 |
96
- | `AntiDetect` | `getTlsFingerprintOptions(userAgent?)` | got-scraping TLS 指纹 |
97
- | `Humanize` | `initializeCursor(page)` | 初始化 Cursor (必须先调用) |
98
- | `Humanize` | `jitterMs(base, jitterPercent?)` | 生成带抖动的毫秒数 (同步,返回 number) |
99
- | `Humanize` | `humanType(page, selector, text, options?)` | 人类化输入 (baseDelay=180ms ±40%) |
100
- | `Humanize` | `humanClick(page, selector, options?)` | 人类化点击 (reactionDelay=250ms ±40%) |
101
- | `Humanize` | `warmUpBrowsing(page, baseDuration?)` | 页面预热 (3500ms ±40%) |
102
- | `Humanize` | `naturalScroll(page, direction?, distance?, steps?)` | 自然滚动 (带惯性+抖动) |
103
- | `Humanize` | `simulateGaze(page, baseDurationMs?)` | 模拟注视 (2500ms ±40%) |
104
- | `Humanize` | `randomSleep(baseMs, jitterPercent?)` | 随机延迟 (±30% 抖动) |
105
- | `Captcha` | `useCaptchaMonitor(page, options)` | 验证码监控 |
88
+ | 模块 | 方法 | 说明 |
89
+ | ----------- | ---------------------------------------------------- | -------------------------------------- |
90
+ | `Launch` | `getAdvancedLaunchOptions()` | 增强版启动参数 |
91
+ | `Launch` | `getLaunchOptions()` | 基础启动参数 |
92
+ | `Launch` | `getFingerprintGeneratorOptions()` | 指纹生成器选项 |
93
+ | `AntiCheat` | `applyPage(page, options?)` | 应用时区/语言/权限/视口 |
94
+ | `AntiCheat` | `applyContext(context, options?)` | 仅应用 Context 设置 |
95
+ | `AntiCheat` | `syncViewportWithScreen(page)` | 同步视口与屏幕 |
96
+ | `AntiCheat` | `getTlsFingerprintOptions(userAgent?)` | got-scraping TLS 指纹 |
97
+ | `Humanize` | `initializeCursor(page)` | 初始化 Cursor (必须先调用) |
98
+ | `Humanize` | `jitterMs(base, jitterPercent?)` | 生成带抖动的毫秒数 (同步,返回 number) |
99
+ | `Humanize` | `humanType(page, selector, text, options?)` | 人类化输入 (baseDelay=180ms ±40%) |
100
+ | `Humanize` | `humanClick(page, selector, options?)` | 人类化点击 (reactionDelay=250ms ±40%) |
101
+ | `Humanize` | `warmUpBrowsing(page, baseDuration?)` | 页面预热 (3500ms ±40%) |
102
+ | `Humanize` | `naturalScroll(page, direction?, distance?, steps?)` | 自然滚动 (带惯性+抖动) |
103
+ | `Humanize` | `simulateGaze(page, baseDurationMs?)` | 模拟注视 (2500ms ±40%) |
104
+ | `Humanize` | `randomSleep(baseMs, jitterPercent?)` | 随机延迟 (±30% 抖动) |
105
+ | `Captcha` | `useCaptchaMonitor(page, options)` | 验证码监控 |
106
106
 
107
107
  ---
108
108
 
package/dist/index.cjs CHANGED
@@ -351,8 +351,8 @@ var Utils = {
351
351
  }
352
352
  };
353
353
 
354
- // src/stealth.js
355
- var logger3 = createLogger("AntiDetect");
354
+ // src/anti-cheat.js
355
+ var logger3 = createLogger("AntiCheat");
356
356
  var BASE_CONFIG = Object.freeze({
357
357
  locale: "zh-CN",
358
358
  acceptLanguage: "zh-CN,zh;q=0.9",
@@ -474,7 +474,7 @@ function resolveConfig(overrides = {}) {
474
474
  geolocation: overrides.geolocation === null ? null : overrides.geolocation || BASE_CONFIG.geolocation
475
475
  };
476
476
  }
477
- var AntiDetect = {
477
+ var AntiCheat = {
478
478
  /**
479
479
  * 获取统一的基础配置(中国、桌面端、中文语言)。
480
480
  */
@@ -1076,7 +1076,7 @@ var Launch = {
1076
1076
  getLaunchOptions(customArgs = []) {
1077
1077
  return {
1078
1078
  args: [
1079
- ...AntiDetect.getLaunchArgs(),
1079
+ ...AntiCheat.getLaunchArgs(),
1080
1080
  ...customArgs
1081
1081
  ],
1082
1082
  ignoreDefaultArgs: ["--enable-automation"]
@@ -1088,7 +1088,7 @@ var Launch = {
1088
1088
  getAdvancedLaunchOptions(customArgs = []) {
1089
1089
  return {
1090
1090
  args: [
1091
- ...AntiDetect.getAdvancedLaunchArgs(),
1091
+ ...AntiCheat.getAdvancedLaunchArgs(),
1092
1092
  ...customArgs
1093
1093
  ],
1094
1094
  ignoreDefaultArgs: ["--enable-automation"]
@@ -1099,7 +1099,7 @@ var Launch = {
1099
1099
  * 确保生成的是桌面端、较新的 Chrome,以匹配我们的脚本逻辑
1100
1100
  */
1101
1101
  getFingerprintGeneratorOptions() {
1102
- return AntiDetect.getFingerprintGeneratorOptions();
1102
+ return AntiCheat.getFingerprintGeneratorOptions();
1103
1103
  }
1104
1104
  };
1105
1105
 
@@ -1282,9 +1282,18 @@ var Captcha = {
1282
1282
  };
1283
1283
 
1284
1284
  // src/sse.js
1285
- var import_https = __toESM(require("https"), 1);
1286
- var import_url = require("url");
1285
+ var import_got_scraping = require("got-scraping");
1286
+ var import_http = require("http");
1287
+ var import_https = require("https");
1287
1288
  var logger7 = createLogger("Sse");
1289
+ var SHARED_HTTP_AGENT = new import_http.Agent({ keepAlive: false });
1290
+ var SHARED_HTTPS_AGENT = new import_https.Agent({ keepAlive: false, rejectUnauthorized: false });
1291
+ var SHARED_GOT_OPTIONS = {
1292
+ http2: false,
1293
+ retry: { limit: 0 },
1294
+ throwHttpErrors: false,
1295
+ decompress: false
1296
+ };
1288
1297
  var Sse = {
1289
1298
  /**
1290
1299
  * 解析 SSE 流文本
@@ -1311,7 +1320,7 @@ var Sse = {
1311
1320
  return events;
1312
1321
  },
1313
1322
  /**
1314
- * 拦截网络请求并使用 Node.js 原生 https 模块转发,以解决流式数据捕获问题。
1323
+ * 拦截网络请求并使用 got-scraping 转发,以解决流式数据捕获问题。
1315
1324
  * @param {import('playwright').Page} page
1316
1325
  * @param {string|RegExp} urlPattern - 拦截的 URL 模式
1317
1326
  * @param {object} options
@@ -1319,7 +1328,7 @@ var Sse = {
1319
1328
  * @param {function(string, function): void} [options.onEnd] - (fullText, resolve) => void
1320
1329
  * @param {function(Error, function): void} [options.onTimeout] - (error, reject) => void
1321
1330
  * @param {number} [options.initialTimeout=90000] - 初始数据接收超时 (ms),默认 90s
1322
- * @param {number} [options.timeout=180000] - 整体请求超时时间 (ms),默认 180s
1331
+ * @param {number} [options.overallTimeout=180000] - 整体请求超时时间 (ms),默认 180s
1323
1332
  * @returns {Promise<any>} - 返回 Promise,当流满足条件时 resolve
1324
1333
  */
1325
1334
  async intercept(page, urlPattern, options = {}) {
@@ -1345,19 +1354,39 @@ var Sse = {
1345
1354
  logger7.info(`[MITM] \u5DF2\u62E6\u622A\u8BF7\u6C42: ${request.url()}`);
1346
1355
  try {
1347
1356
  const headers = await request.allHeaders();
1348
- const postData = request.postData();
1349
- const urlObj = new import_url.URL(request.url());
1357
+ delete headers["host"];
1350
1358
  delete headers["accept-encoding"];
1351
1359
  delete headers["content-length"];
1360
+ const currentAcceptLanguage = headers["accept-language"] || "";
1361
+ AntiCheat.applyLocaleHeaders(headers, currentAcceptLanguage);
1362
+ const resolvedAcceptLanguage = headers["accept-language"] || "";
1363
+ const userAgent = headers["user-agent"] || "";
1364
+ const method = request.method();
1365
+ const postData = method !== "GET" && method !== "HEAD" ? request.postDataBuffer() : void 0;
1352
1366
  const reqOptions = {
1353
- hostname: urlObj.hostname,
1354
- port: 443,
1355
- path: urlObj.pathname + urlObj.search,
1356
- method: request.method(),
1367
+ ...SHARED_GOT_OPTIONS,
1368
+ url: request.url(),
1369
+ method,
1357
1370
  headers,
1358
- timeout: overallTimeout
1371
+ headerGeneratorOptions: AntiCheat.getTlsFingerprintOptions(userAgent, resolvedAcceptLanguage),
1372
+ agent: {
1373
+ http: SHARED_HTTP_AGENT,
1374
+ https: SHARED_HTTPS_AGENT
1375
+ },
1376
+ timeout: { request: overallTimeout }
1377
+ };
1378
+ if (postData) {
1379
+ reqOptions.body = postData;
1380
+ }
1381
+ const stream = import_got_scraping.gotScraping.stream(reqOptions);
1382
+ const handleStreamError = (error) => {
1383
+ clearAllTimers();
1384
+ stream.destroy();
1385
+ route.abort().catch(() => {
1386
+ });
1387
+ reject(error);
1359
1388
  };
1360
- const req = import_https.default.request(reqOptions, (res) => {
1389
+ stream.on("response", (res) => {
1361
1390
  const chunks = [];
1362
1391
  let accumulatedText = "";
1363
1392
  res.on("data", (chunk) => {
@@ -1380,6 +1409,7 @@ var Sse = {
1380
1409
  }
1381
1410
  }
1382
1411
  });
1412
+ res.on("error", handleStreamError);
1383
1413
  res.on("end", () => {
1384
1414
  logger7.info("[MITM] \u4E0A\u6E38\u54CD\u5E94\u7ED3\u675F");
1385
1415
  clearAllTimers();
@@ -1392,22 +1422,28 @@ var Sse = {
1392
1422
  } else if (!onData) {
1393
1423
  resolve(accumulatedText);
1394
1424
  }
1425
+ const resHeaders = {};
1426
+ for (const [key, value] of Object.entries(res.headers || {})) {
1427
+ if (Array.isArray(value)) {
1428
+ resHeaders[key] = value.join(", ");
1429
+ } else if (value) {
1430
+ resHeaders[key] = String(value);
1431
+ }
1432
+ }
1433
+ delete resHeaders["content-encoding"];
1434
+ delete resHeaders["content-length"];
1435
+ delete resHeaders["transfer-encoding"];
1436
+ delete resHeaders["connection"];
1437
+ delete resHeaders["keep-alive"];
1395
1438
  route.fulfill({
1396
- status: res.statusCode,
1397
- headers: res.headers,
1439
+ status: res.statusCode || 200,
1440
+ headers: resHeaders,
1398
1441
  body: Buffer.concat(chunks)
1399
1442
  }).catch(() => {
1400
1443
  });
1401
1444
  });
1402
1445
  });
1403
- req.on("error", (e) => {
1404
- clearAllTimers();
1405
- route.abort().catch(() => {
1406
- });
1407
- reject(e);
1408
- });
1409
- if (postData) req.write(postData);
1410
- req.end();
1446
+ stream.on("error", handleStreamError);
1411
1447
  } catch (e) {
1412
1448
  clearAllTimers();
1413
1449
  route.continue().catch(() => {
@@ -1466,12 +1502,12 @@ var Sse = {
1466
1502
  };
1467
1503
 
1468
1504
  // src/interception.js
1469
- var import_got_scraping = require("got-scraping");
1470
- var import_http = require("http");
1505
+ var import_got_scraping2 = require("got-scraping");
1506
+ var import_http2 = require("http");
1471
1507
  var import_https2 = require("https");
1472
1508
  var logger8 = createLogger("Interception");
1473
- var SHARED_HTTP_AGENT = new import_http.Agent({ keepAlive: false });
1474
- var SHARED_HTTPS_AGENT = new import_https2.Agent({ keepAlive: false, rejectUnauthorized: false });
1509
+ var SHARED_HTTP_AGENT2 = new import_http2.Agent({ keepAlive: false });
1510
+ var SHARED_HTTPS_AGENT2 = new import_https2.Agent({ keepAlive: false, rejectUnauthorized: false });
1475
1511
  var DirectConfig = {
1476
1512
  /** 直连请求超时时间(秒) */
1477
1513
  directTimeout: 12,
@@ -1524,7 +1560,7 @@ var DEFAULT_BLOCKING_CONFIG = {
1524
1560
  /** 额外自定义扩展名列表 */
1525
1561
  customExtensions: []
1526
1562
  };
1527
- var SHARED_GOT_OPTIONS = {
1563
+ var SHARED_GOT_OPTIONS2 = {
1528
1564
  http2: false,
1529
1565
  // 禁用 HTTP2 避免在拦截场景下的握手兼容性问题
1530
1566
  retry: { limit: 0 },
@@ -1628,13 +1664,13 @@ var Interception = {
1628
1664
  const reqHeaders = await request.allHeaders();
1629
1665
  delete reqHeaders["host"];
1630
1666
  const currentAcceptLanguage = reqHeaders["accept-language"] || "";
1631
- AntiDetect.applyLocaleHeaders(reqHeaders, currentAcceptLanguage);
1667
+ AntiCheat.applyLocaleHeaders(reqHeaders, currentAcceptLanguage);
1632
1668
  const resolvedAcceptLanguage = reqHeaders["accept-language"] || "";
1633
1669
  const userAgent = reqHeaders["user-agent"] || "";
1634
1670
  const method = request.method();
1635
1671
  const postData = method !== "GET" && method !== "HEAD" ? request.postDataBuffer() : void 0;
1636
- const response = await (0, import_got_scraping.gotScraping)({
1637
- ...SHARED_GOT_OPTIONS,
1672
+ const response = await (0, import_got_scraping2.gotScraping)({
1673
+ ...SHARED_GOT_OPTIONS2,
1638
1674
  // 应用通用配置
1639
1675
  url,
1640
1676
  method,
@@ -1643,11 +1679,11 @@ var Interception = {
1643
1679
  responseType: "buffer",
1644
1680
  // 强制获取 Buffer
1645
1681
  // 模拟浏览器 TLS 指纹
1646
- headerGeneratorOptions: AntiDetect.getTlsFingerprintOptions(userAgent, resolvedAcceptLanguage),
1682
+ headerGeneratorOptions: AntiCheat.getTlsFingerprintOptions(userAgent, resolvedAcceptLanguage),
1647
1683
  // 使用共享的 Agent 单例(keepAlive: false,不会池化连接)
1648
1684
  agent: {
1649
- http: SHARED_HTTP_AGENT,
1650
- https: SHARED_HTTPS_AGENT
1685
+ http: SHARED_HTTP_AGENT2,
1686
+ https: SHARED_HTTPS_AGENT2
1651
1687
  },
1652
1688
  // 超时时间
1653
1689
  timeout: { request: DirectConfig.directTimeout * 1e3 }
@@ -1727,7 +1763,7 @@ function isIgnorableError(error) {
1727
1763
  var usePlaywrightToolKit = () => {
1728
1764
  return {
1729
1765
  ApifyKit,
1730
- AntiDetect,
1766
+ AntiCheat,
1731
1767
  Humanize,
1732
1768
  Launch,
1733
1769
  LiveView,