@skrillex1224/playwright-toolkit 2.1.22 → 2.1.23

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 CHANGED
@@ -352,133 +352,243 @@ var Utils = {
352
352
  };
353
353
 
354
354
  // src/stealth.js
355
- var logger3 = createLogger("Stealth");
356
- var Stealth = {
355
+ var logger3 = createLogger("AntiDetect");
356
+ var BASE_CONFIG = Object.freeze({
357
+ locale: "zh-CN",
358
+ acceptLanguage: "zh-CN,zh;q=0.9",
359
+ timezoneId: "Asia/Shanghai",
360
+ timezoneOffset: -480,
361
+ geolocation: null
362
+ });
363
+ var DEFAULT_LAUNCH_ARGS = [
364
+ "--disable-blink-features=AutomationControlled",
365
+ "--no-sandbox",
366
+ "--disable-setuid-sandbox",
367
+ "--window-position=0,0",
368
+ `--lang=${BASE_CONFIG.locale}`
369
+ ];
370
+ var ADVANCED_LAUNCH_ARGS = [
371
+ ...DEFAULT_LAUNCH_ARGS,
372
+ "--disable-dev-shm-usage",
373
+ "--disable-background-networking",
374
+ "--disable-default-apps",
375
+ "--disable-extensions",
376
+ "--disable-sync",
377
+ "--disable-translate",
378
+ "--metrics-recording-only",
379
+ "--mute-audio",
380
+ "--no-first-run"
381
+ ];
382
+ var CONTEXT_CONFIG_CACHE = /* @__PURE__ */ new WeakMap();
383
+ function buildFingerprintOptions(locale) {
384
+ return {
385
+ browsers: [{ name: "chrome", minVersion: 110 }],
386
+ devices: ["desktop"],
387
+ operatingSystems: ["windows", "macos", "linux"],
388
+ locales: [locale]
389
+ };
390
+ }
391
+ function parseAcceptLanguage(acceptLanguage) {
392
+ if (!acceptLanguage) return [];
393
+ return acceptLanguage.split(",").map((part) => part.trim().split(";")[0]).filter(Boolean);
394
+ }
395
+ function normalizeLanguages(acceptLanguage, fallbackLocale) {
396
+ const languages = parseAcceptLanguage(acceptLanguage);
397
+ if (languages.length === 0) return [fallbackLocale];
398
+ if (!languages.includes(fallbackLocale)) {
399
+ return [fallbackLocale, ...languages];
400
+ }
401
+ return languages;
402
+ }
403
+ function getOperatingSystemsFromUserAgent(userAgent) {
404
+ const lowerUA = userAgent.toLowerCase();
405
+ if (lowerUA.includes("windows")) return ["windows"];
406
+ if (lowerUA.includes("mac os") || lowerUA.includes("macintosh")) return ["macos"];
407
+ if (lowerUA.includes("linux")) return ["linux"];
408
+ return [];
409
+ }
410
+ function buildContextConfigKey(config) {
411
+ return JSON.stringify({
412
+ locale: config.locale,
413
+ acceptLanguage: config.acceptLanguage,
414
+ timezoneId: config.timezoneId,
415
+ timezoneOffset: config.timezoneOffset
416
+ });
417
+ }
418
+ async function applyContextSettings(context, config, languages, permissions, injectLocaleTimezone) {
419
+ const contextKey = buildContextConfigKey(config);
420
+ const cached = CONTEXT_CONFIG_CACHE.get(context);
421
+ const isFirstInit = !cached;
422
+ const effectiveConfig = cached?.config || config;
423
+ const effectiveLanguages = cached?.languages || languages;
424
+ if (isFirstInit) {
425
+ CONTEXT_CONFIG_CACHE.set(context, {
426
+ key: contextKey,
427
+ config,
428
+ languages
429
+ });
430
+ } else if (cached.key !== contextKey) {
431
+ logger3.warn("applyContext", "Context already initialized; ignore conflicting locale/timezone.");
432
+ }
433
+ await context.setExtraHTTPHeaders({
434
+ "accept-language": effectiveConfig.acceptLanguage
435
+ });
436
+ if (isFirstInit) {
437
+ await context.addInitScript(({ locale, timezoneId, timezoneOffset, languages: languages2, applyLocaleTimezone }) => {
438
+ const originalDateTimeFormat = Intl.DateTimeFormat;
439
+ if (applyLocaleTimezone) {
440
+ Intl.DateTimeFormat = function(locales, initOptions) {
441
+ const nextLocales = locales || locale;
442
+ const nextOptions = initOptions ? { ...initOptions } : {};
443
+ nextOptions.timeZone = nextOptions.timeZone || timezoneId;
444
+ return new originalDateTimeFormat(nextLocales, nextOptions);
445
+ };
446
+ Intl.DateTimeFormat.prototype = originalDateTimeFormat.prototype;
447
+ Date.prototype.getTimezoneOffset = function() {
448
+ return timezoneOffset;
449
+ };
450
+ Object.defineProperty(navigator, "language", { get: () => languages2[0] });
451
+ Object.defineProperty(navigator, "languages", { get: () => languages2 });
452
+ }
453
+ Object.defineProperty(navigator, "webdriver", { get: () => void 0 });
454
+ }, {
455
+ locale: effectiveConfig.locale,
456
+ timezoneId: effectiveConfig.timezoneId,
457
+ timezoneOffset: effectiveConfig.timezoneOffset,
458
+ languages: effectiveLanguages,
459
+ applyLocaleTimezone: injectLocaleTimezone
460
+ });
461
+ }
462
+ if (effectiveConfig.geolocation) {
463
+ await context.setGeolocation(effectiveConfig.geolocation);
464
+ await context.grantPermissions(["geolocation"]);
465
+ }
466
+ if (permissions?.length) {
467
+ await context.grantPermissions(permissions);
468
+ }
469
+ }
470
+ function resolveConfig(overrides = {}) {
471
+ return {
472
+ ...BASE_CONFIG,
473
+ ...overrides,
474
+ geolocation: overrides.geolocation === null ? null : overrides.geolocation || BASE_CONFIG.geolocation
475
+ };
476
+ }
477
+ var AntiDetect = {
478
+ /**
479
+ * 获取统一的基础配置(中国、桌面端、中文语言)。
480
+ */
481
+ getBaseConfig() {
482
+ return { ...BASE_CONFIG };
483
+ },
484
+ /**
485
+ * 用于 Crawlee fingerprint generator 的统一配置(桌面端)。
486
+ */
487
+ getFingerprintGeneratorOptions() {
488
+ return buildFingerprintOptions(BASE_CONFIG.locale);
489
+ },
490
+ /**
491
+ * 获取基础启动参数。
492
+ */
493
+ getLaunchArgs() {
494
+ return [...DEFAULT_LAUNCH_ARGS];
495
+ },
496
+ /**
497
+ * 获取增强启动参数(高风险场景)。
498
+ */
499
+ getAdvancedLaunchArgs() {
500
+ return [...ADVANCED_LAUNCH_ARGS];
501
+ },
502
+ /**
503
+ * 统一应用到 BrowserContext(时区/语言/权限/地理位置)。
504
+ *
505
+ * @param {import('playwright').BrowserContext} context
506
+ * @param {Object} [options]
507
+ * @param {string} [options.locale]
508
+ * @param {string} [options.acceptLanguage]
509
+ * @param {string} [options.timezoneId]
510
+ * @param {number} [options.timezoneOffset]
511
+ * @param {import('playwright').Geolocation|null} [options.geolocation]
512
+ * @param {string[]} [options.permissions]
513
+ */
514
+ async applyContext(context, options = {}) {
515
+ const config = resolveConfig(options);
516
+ const languages = normalizeLanguages(config.acceptLanguage, config.locale);
517
+ const permissions = Array.isArray(options.permissions) ? options.permissions : [];
518
+ await applyContextSettings(context, config, languages, permissions, true);
519
+ logger3.success("applyContext", `${config.locale} | ${config.timezoneId}`);
520
+ },
521
+ /**
522
+ * 统一应用到 Page(Context + 视口同步)。
523
+ *
524
+ * @param {import('playwright').Page} page
525
+ * @param {Object} [options] - 传递给 applyContext 的选项
526
+ */
527
+ async applyPage(page, options = {}) {
528
+ const config = resolveConfig(options);
529
+ const languages = normalizeLanguages(config.acceptLanguage, config.locale);
530
+ const permissions = Array.isArray(options.permissions) ? options.permissions : [];
531
+ let injectLocaleTimezone = true;
532
+ try {
533
+ const env = await page.evaluate(() => ({
534
+ language: navigator.language,
535
+ languages: Array.isArray(navigator.languages) ? navigator.languages : [],
536
+ timeZone: Intl.DateTimeFormat().resolvedOptions().timeZone,
537
+ tzOffset: (/* @__PURE__ */ new Date()).getTimezoneOffset()
538
+ }));
539
+ const languageMatch = env.language === languages[0];
540
+ const timeZoneMatch = env.timeZone === config.timezoneId && env.tzOffset === config.timezoneOffset;
541
+ injectLocaleTimezone = !(languageMatch && timeZoneMatch);
542
+ } catch (e) {
543
+ injectLocaleTimezone = true;
544
+ }
545
+ await applyContextSettings(page.context(), config, languages, permissions, injectLocaleTimezone);
546
+ await this.syncViewportWithScreen(page);
547
+ },
357
548
  /**
358
- * 关键修复:将 Page 视口调整为与浏览器指纹 (window.screen) 一致。
359
- * 防止 "Viewport Mismatch" 类型的反爬检测。
360
- * @param {import('playwright').Page} page
549
+ * 同步 Page 视口到 window.screen,避免视口/屏幕不一致检测。
361
550
  */
362
551
  async syncViewportWithScreen(page) {
363
552
  try {
364
553
  const screen = await page.evaluate(() => ({
365
554
  width: window.screen.width,
366
- height: window.screen.height,
367
- availWidth: window.screen.availWidth,
368
- availHeight: window.screen.availHeight
555
+ height: window.screen.height
369
556
  }));
370
557
  await page.setViewportSize({
371
558
  width: screen.width,
372
559
  height: screen.height
373
560
  });
374
- logger3.success("syncViewportWithScreen", `size=${screen.width}x${screen.height}`);
561
+ logger3.success("syncViewport", `size=${screen.width}x${screen.height}`);
375
562
  } catch (e) {
376
- logger3.warn(`syncViewportWithScreen Failed: ${e.message}. Fallback to 1920x1080.`);
563
+ logger3.warn(`syncViewport \u5931\u8D25: ${e.message}\uFF0C\u56DE\u9000\u5230 1920x1080`);
377
564
  await page.setViewportSize({ width: 1920, height: 1080 });
378
565
  }
379
566
  },
380
567
  /**
381
- * 确保 navigator.webdriver 隐藏 (通常 Playwright Stealth 插件已处理,但双重保险)
382
- */
383
- async hideWebdriver(page) {
384
- await page.addInitScript(() => {
385
- Object.defineProperty(navigator, "webdriver", {
386
- get: () => false
387
- });
388
- });
389
- logger3.success("hideWebdriver");
390
- },
391
- /**
392
- * 获取推荐的 Stealth 启动参数
393
- */
394
- getStealthLaunchArgs() {
395
- return [
396
- "--disable-blink-features=AutomationControlled",
397
- "--no-sandbox",
398
- "--disable-setuid-sandbox",
399
- "--disable-infobars",
400
- "--window-position=0,0",
401
- "--ignore-certificate-errors",
402
- "--disable-web-security"
403
- // 注意:不建议这里强制指定 window-size,让 syncViewportWithScreen 去动态调整
404
- // '--window-size=1920,1080'
405
- ];
406
- },
407
- /**
408
- * 获取增强版 Stealth 启动参数 (推荐用于高风险反爬场景)
409
- * 包含更多针对自动化检测的防护
410
- */
411
- getAdvancedStealthArgs() {
412
- return [
413
- ...this.getStealthLaunchArgs(),
414
- // 禁用各种可能暴露自动化的特征
415
- "--disable-dev-shm-usage",
416
- "--disable-accelerated-2d-canvas",
417
- "--disable-gpu-sandbox",
418
- "--disable-background-networking",
419
- "--disable-default-apps",
420
- "--disable-extensions",
421
- "--disable-sync",
422
- "--disable-translate",
423
- "--metrics-recording-only",
424
- "--mute-audio",
425
- "--no-first-run",
426
- // 模拟真实用户配置
427
- "--lang=zh-CN,zh"
428
- ];
429
- },
430
- /**
431
- * 设置中国时区 (Asia/Shanghai, UTC+8)
432
- *
433
- * 防止时区不一致的检测。对于中国境内爬取强烈推荐使用。
434
- * 应在 preNavigationHooks 或 BrowserContext 创建后调用。
435
- *
436
- * @param {import('playwright').BrowserContext} context
568
+ * got-scraping 生成与浏览器一致的 TLS 指纹配置(桌面端)。
569
+ *
570
+ * @param {string} [userAgent]
437
571
  */
438
- async setChinaTimezone(context) {
439
- await context.addInitScript(() => {
440
- const originalDateTimeFormat = Intl.DateTimeFormat;
441
- Intl.DateTimeFormat = function(locales, options) {
442
- options = options || {};
443
- options.timeZone = options.timeZone || "Asia/Shanghai";
444
- return new originalDateTimeFormat(locales, options);
445
- };
446
- Intl.DateTimeFormat.prototype = originalDateTimeFormat.prototype;
447
- Date.prototype.getTimezoneOffset = function() {
448
- return -480;
449
- };
450
- });
451
- logger3.success("setChinaTimezone", "Asia/Shanghai (UTC+8)");
572
+ getTlsFingerprintOptions(userAgent = "", acceptLanguage = "") {
573
+ const primaryLocale = parseAcceptLanguage(acceptLanguage || BASE_CONFIG.acceptLanguage)[0] || BASE_CONFIG.locale;
574
+ const fingerprint = buildFingerprintOptions(primaryLocale);
575
+ const os = getOperatingSystemsFromUserAgent(userAgent);
576
+ if (os.length > 0) fingerprint.operatingSystems = os;
577
+ return fingerprint;
452
578
  },
453
579
  /**
454
- * 获取 TLS 指纹配置 (用于 got-scraping)
455
- * 根据传入的 User-Agent 动态匹配操作系统和设备类型,确保 UA 与 TLS 指纹一致
456
- *
457
- * @param {string} [userAgent] - 当前请求的 User-Agent
580
+ * 规范化请求头,确保语言与浏览器一致。
581
+ *
582
+ * @param {Record<string, string>} headers
583
+ * @returns {Record<string, string>}
458
584
  */
459
- getTlsFingerprintOptions(userAgent = "") {
460
- const lowerUA = userAgent.toLowerCase();
461
- const os = [];
462
- if (lowerUA.includes("windows")) os.push("windows");
463
- else if (lowerUA.includes("mac os") || lowerUA.includes("macintosh")) os.push("macos");
464
- else if (lowerUA.includes("linux")) os.push("linux");
465
- else if (lowerUA.includes("android")) os.push("android");
466
- else if (lowerUA.includes("iphone") || lowerUA.includes("ipad")) os.push("ios");
467
- if (os.length === 0) os.push("windows", "macos");
468
- const devices = [];
469
- if (lowerUA.includes("mobile") || lowerUA.includes("android") || lowerUA.includes("iphone")) {
470
- devices.push("mobile");
471
- } else {
472
- devices.push("desktop");
585
+ applyLocaleHeaders(headers, acceptLanguage = "") {
586
+ if (acceptLanguage) {
587
+ headers["accept-language"] = acceptLanguage;
588
+ } else if (!headers["accept-language"]) {
589
+ headers["accept-language"] = BASE_CONFIG.acceptLanguage;
473
590
  }
474
- return {
475
- browsers: [{ name: "chrome", minVersion: 110 }],
476
- // 保持较新的 Chrome 版本
477
- devices,
478
- locales: ["zh-CN", "en-US"],
479
- // 保持与 setChinaTimezone 一致的中文优先
480
- operatingSystems: os
481
- };
591
+ return headers;
482
592
  }
483
593
  };
484
594
 
@@ -957,12 +1067,11 @@ var Humanize = {
957
1067
  };
958
1068
 
959
1069
  // src/launch.js
960
- var logger5 = createLogger("Launch");
961
1070
  var Launch = {
962
1071
  getLaunchOptions(customArgs = []) {
963
1072
  return {
964
1073
  args: [
965
- ...Stealth.getStealthLaunchArgs(),
1074
+ ...AntiDetect.getLaunchArgs(),
966
1075
  ...customArgs
967
1076
  ],
968
1077
  ignoreDefaultArgs: ["--enable-automation"]
@@ -974,7 +1083,7 @@ var Launch = {
974
1083
  getAdvancedLaunchOptions(customArgs = []) {
975
1084
  return {
976
1085
  args: [
977
- ...Stealth.getAdvancedStealthArgs(),
1086
+ ...AntiDetect.getAdvancedLaunchArgs(),
978
1087
  ...customArgs
979
1088
  ],
980
1089
  ignoreDefaultArgs: ["--enable-automation"]
@@ -985,42 +1094,14 @@ var Launch = {
985
1094
  * 确保生成的是桌面端、较新的 Chrome,以匹配我们的脚本逻辑
986
1095
  */
987
1096
  getFingerprintGeneratorOptions() {
988
- return {
989
- browsers: [{ name: "chrome", minVersion: 110 }],
990
- devices: ["desktop"],
991
- operatingSystems: ["windows", "linux"]
992
- // 包含 Linux 兼容容器
993
- };
994
- },
995
- /**
996
- * 创建已注册 Stealth 插件的 Chromium 实例
997
- *
998
- * 封装了 `chromium.use(stealthPlugin())` 的常用模式
999
- *
1000
- * @example
1001
- * ```javascript
1002
- * import { chromium } from 'playwright-extra';
1003
- * import stealthPlugin from 'puppeteer-extra-plugin-stealth';
1004
- *
1005
- * const stealthChromium = Launch.createStealthChromium(chromium, stealthPlugin);
1006
- * // 现在 stealthChromium 已注册 stealth 插件,可用于 launchContext.launcher
1007
- * ```
1008
- *
1009
- * @param {import('playwright-extra').ChromiumExtra} chromium - playwright-extra 的 chromium
1010
- * @param {Function} stealthPlugin - puppeteer-extra-plugin-stealth 的默认导出
1011
- * @returns {import('playwright-extra').ChromiumExtra} 已注册 stealth 的 chromium
1012
- */
1013
- createStealthChromium(chromium, stealthPlugin) {
1014
- chromium.use(stealthPlugin());
1015
- logger5.success("createStealthChromium", "Stealth plugin registered");
1016
- return chromium;
1097
+ return AntiDetect.getFingerprintGeneratorOptions();
1017
1098
  }
1018
1099
  };
1019
1100
 
1020
1101
  // src/live-view.js
1021
1102
  var import_express = __toESM(require("express"), 1);
1022
1103
  var import_apify = require("apify");
1023
- var logger6 = createLogger("LiveView");
1104
+ var logger5 = createLogger("LiveView");
1024
1105
  async function startLiveViewServer(liveViewKey) {
1025
1106
  const app = (0, import_express.default)();
1026
1107
  app.get("/", async (req, res) => {
@@ -1045,13 +1126,13 @@ async function startLiveViewServer(liveViewKey) {
1045
1126
  </html>
1046
1127
  `);
1047
1128
  } catch (error) {
1048
- logger6.fail("Live View Server", error);
1129
+ logger5.fail("Live View Server", error);
1049
1130
  res.status(500).send(`\u65E0\u6CD5\u52A0\u8F7D\u5C4F\u5E55\u622A\u56FE: ${error.message}`);
1050
1131
  }
1051
1132
  });
1052
1133
  const port = process.env.APIFY_CONTAINER_PORT || 4321;
1053
1134
  app.listen(port, () => {
1054
- logger6.success("startLiveViewServer", `\u76D1\u542C\u7AEF\u53E3 ${port}`);
1135
+ logger5.success("startLiveViewServer", `\u76D1\u542C\u7AEF\u53E3 ${port}`);
1055
1136
  });
1056
1137
  }
1057
1138
  async function takeLiveScreenshot(liveViewKey, page, logMessage) {
@@ -1059,10 +1140,10 @@ async function takeLiveScreenshot(liveViewKey, page, logMessage) {
1059
1140
  const buffer = await page.screenshot({ type: "png" });
1060
1141
  await import_apify.Actor.setValue(liveViewKey, buffer, { contentType: "image/png" });
1061
1142
  if (logMessage) {
1062
- logger6.info(`(\u622A\u56FE): ${logMessage}`);
1143
+ logger5.info(`(\u622A\u56FE): ${logMessage}`);
1063
1144
  }
1064
1145
  } catch (e) {
1065
- logger6.warn(`\u65E0\u6CD5\u6355\u83B7 Live View \u5C4F\u5E55\u622A\u56FE: ${e.message}`);
1146
+ logger5.warn(`\u65E0\u6CD5\u6355\u83B7 Live View \u5C4F\u5E55\u622A\u56FE: ${e.message}`);
1066
1147
  }
1067
1148
  }
1068
1149
  var useLiveView = (liveViewKey = PresetOfLiveViewKey) => {
@@ -1081,7 +1162,7 @@ var LiveView = {
1081
1162
 
1082
1163
  // src/captcha-monitor.js
1083
1164
  var import_uuid = require("uuid");
1084
- var logger7 = createLogger("Captcha");
1165
+ var logger6 = createLogger("Captcha");
1085
1166
  function useCaptchaMonitor(page, options) {
1086
1167
  const { domSelector, urlPattern, onDetected } = options;
1087
1168
  if (!domSelector && !urlPattern) {
@@ -1153,7 +1234,7 @@ function useCaptchaMonitor(page, options) {
1153
1234
  };
1154
1235
  })();
1155
1236
  }, { selector: domSelector, callbackName: exposedFunctionName, cleanerName });
1156
- logger7.success("useCaptchaMonitor", `DOM \u76D1\u63A7\u5DF2\u542F\u7528: ${domSelector}`);
1237
+ logger6.success("useCaptchaMonitor", `DOM \u76D1\u63A7\u5DF2\u542F\u7528: ${domSelector}`);
1157
1238
  cleanupFns.push(async () => {
1158
1239
  try {
1159
1240
  await page.evaluate((name) => {
@@ -1176,14 +1257,14 @@ function useCaptchaMonitor(page, options) {
1176
1257
  }
1177
1258
  };
1178
1259
  page.on("framenavigated", frameHandler);
1179
- logger7.success("useCaptchaMonitor", `URL \u76D1\u63A7\u5DF2\u542F\u7528: ${urlPattern}`);
1260
+ logger6.success("useCaptchaMonitor", `URL \u76D1\u63A7\u5DF2\u542F\u7528: ${urlPattern}`);
1180
1261
  cleanupFns.push(async () => {
1181
1262
  page.off("framenavigated", frameHandler);
1182
1263
  });
1183
1264
  }
1184
1265
  return {
1185
1266
  stop: async () => {
1186
- logger7.info("useCaptchaMonitor", "\u6B63\u5728\u505C\u6B62\u76D1\u63A7...");
1267
+ logger6.info("useCaptchaMonitor", "\u6B63\u5728\u505C\u6B62\u76D1\u63A7...");
1187
1268
  for (const fn of cleanupFns) {
1188
1269
  await fn();
1189
1270
  }
@@ -1198,7 +1279,7 @@ var Captcha = {
1198
1279
  // src/sse.js
1199
1280
  var import_https = __toESM(require("https"), 1);
1200
1281
  var import_url = require("url");
1201
- var logger8 = createLogger("Sse");
1282
+ var logger7 = createLogger("Sse");
1202
1283
  var Sse = {
1203
1284
  /**
1204
1285
  * 解析 SSE 流文本
@@ -1217,11 +1298,11 @@ var Sse = {
1217
1298
  events.push(JSON.parse(jsonContent));
1218
1299
  }
1219
1300
  } catch (e) {
1220
- logger8.debug("parseSseStream", `JSON \u89E3\u6790\u5931\u8D25: ${e.message}, line: ${line.substring(0, 100)}...`);
1301
+ logger7.debug("parseSseStream", `JSON \u89E3\u6790\u5931\u8D25: ${e.message}, line: ${line.substring(0, 100)}...`);
1221
1302
  }
1222
1303
  }
1223
1304
  }
1224
- logger8.success("parseSseStream", `\u89E3\u6790\u5B8C\u6210, events \u6570\u91CF: ${events.length}`);
1305
+ logger7.success("parseSseStream", `\u89E3\u6790\u5B8C\u6210, events \u6570\u91CF: ${events.length}`);
1225
1306
  return events;
1226
1307
  },
1227
1308
  /**
@@ -1256,7 +1337,7 @@ var Sse = {
1256
1337
  const workPromise = new Promise((resolve, reject) => {
1257
1338
  page.route(urlPattern, async (route) => {
1258
1339
  const request = route.request();
1259
- logger8.info(`[MITM] \u5DF2\u62E6\u622A\u8BF7\u6C42: ${request.url()}`);
1340
+ logger7.info(`[MITM] \u5DF2\u62E6\u622A\u8BF7\u6C42: ${request.url()}`);
1260
1341
  try {
1261
1342
  const headers = await request.allHeaders();
1262
1343
  const postData = request.postData();
@@ -1281,7 +1362,7 @@ var Sse = {
1281
1362
  clearTimeout(initialTimer);
1282
1363
  initialTimer = null;
1283
1364
  }
1284
- logger8.debug("[Intercept] \u5DF2\u63A5\u6536\u521D\u59CB\u6570\u636E");
1365
+ logger7.debug("[Intercept] \u5DF2\u63A5\u6536\u521D\u59CB\u6570\u636E");
1285
1366
  }
1286
1367
  chunks.push(chunk);
1287
1368
  const textChunk = chunk.toString("utf-8");
@@ -1290,18 +1371,18 @@ var Sse = {
1290
1371
  try {
1291
1372
  onData(textChunk, resolve, accumulatedText);
1292
1373
  } catch (e) {
1293
- logger8.fail(`onData \u9519\u8BEF`, e);
1374
+ logger7.fail(`onData \u9519\u8BEF`, e);
1294
1375
  }
1295
1376
  }
1296
1377
  });
1297
1378
  res.on("end", () => {
1298
- logger8.info("[MITM] \u4E0A\u6E38\u54CD\u5E94\u7ED3\u675F");
1379
+ logger7.info("[MITM] \u4E0A\u6E38\u54CD\u5E94\u7ED3\u675F");
1299
1380
  clearAllTimers();
1300
1381
  if (onEnd) {
1301
1382
  try {
1302
1383
  onEnd(accumulatedText, resolve);
1303
1384
  } catch (e) {
1304
- logger8.fail(`onEnd \u9519\u8BEF`, e);
1385
+ logger7.fail(`onEnd \u9519\u8BEF`, e);
1305
1386
  }
1306
1387
  } else if (!onData) {
1307
1388
  resolve(accumulatedText);
@@ -1383,7 +1464,7 @@ var Sse = {
1383
1464
  var import_got_scraping = require("got-scraping");
1384
1465
  var import_http = require("http");
1385
1466
  var import_https2 = require("https");
1386
- var logger9 = createLogger("Interception");
1467
+ var logger8 = createLogger("Interception");
1387
1468
  var SHARED_HTTP_AGENT = new import_http.Agent({ keepAlive: false });
1388
1469
  var SHARED_HTTPS_AGENT = new import_https2.Agent({ keepAlive: false, rejectUnauthorized: false });
1389
1470
  var DirectConfig = {
@@ -1514,7 +1595,7 @@ var Interception = {
1514
1595
  if (mergedBlockingConfig.blockFont) enabledCategories.push("\u5B57\u4F53");
1515
1596
  if (mergedBlockingConfig.blockCss) enabledCategories.push("CSS");
1516
1597
  if (mergedBlockingConfig.blockOther) enabledCategories.push("\u5176\u4ED6");
1517
- logger9.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(", ")}]`);
1598
+ 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(", ")}]`);
1518
1599
  await page.route("**/*", async (route) => {
1519
1600
  let handled = false;
1520
1601
  try {
@@ -1541,6 +1622,9 @@ var Interception = {
1541
1622
  try {
1542
1623
  const reqHeaders = await request.allHeaders();
1543
1624
  delete reqHeaders["host"];
1625
+ const currentAcceptLanguage = reqHeaders["accept-language"] || "";
1626
+ AntiDetect.applyLocaleHeaders(reqHeaders, currentAcceptLanguage);
1627
+ const resolvedAcceptLanguage = reqHeaders["accept-language"] || "";
1544
1628
  const userAgent = reqHeaders["user-agent"] || "";
1545
1629
  const method = request.method();
1546
1630
  const postData = method !== "GET" && method !== "HEAD" ? request.postDataBuffer() : void 0;
@@ -1554,7 +1638,7 @@ var Interception = {
1554
1638
  responseType: "buffer",
1555
1639
  // 强制获取 Buffer
1556
1640
  // 模拟浏览器 TLS 指纹
1557
- headerGeneratorOptions: Stealth.getTlsFingerprintOptions(userAgent),
1641
+ headerGeneratorOptions: AntiDetect.getTlsFingerprintOptions(userAgent, resolvedAcceptLanguage),
1558
1642
  // 使用共享的 Agent 单例(keepAlive: false,不会池化连接)
1559
1643
  agent: {
1560
1644
  http: SHARED_HTTP_AGENT,
@@ -1576,7 +1660,7 @@ var Interception = {
1576
1660
  delete resHeaders["transfer-encoding"];
1577
1661
  delete resHeaders["connection"];
1578
1662
  delete resHeaders["keep-alive"];
1579
- isSilent ? logger9.debug(`\u76F4\u8FDE\u6210\u529F: ${urlPath}`) : logger9.info(`\u76F4\u8FDE\u6210\u529F: ${urlPath}`);
1663
+ isSilent ? logger8.debug(`\u76F4\u8FDE\u6210\u529F: ${urlPath}`) : logger8.info(`\u76F4\u8FDE\u6210\u529F: ${urlPath}`);
1580
1664
  await safeFulfill(route, {
1581
1665
  status: response.statusCode,
1582
1666
  headers: resHeaders,
@@ -1588,7 +1672,7 @@ var Interception = {
1588
1672
  const isTimeout = e.code === "ETIMEDOUT" || e.message.toLowerCase().includes("timeout");
1589
1673
  const action = fallbackToProxy ? "\u56DE\u9000\u4EE3\u7406" : "\u5DF2\u653E\u5F03";
1590
1674
  const reason = isTimeout ? `\u8D85\u65F6(${DirectConfig.directTimeout}s)` : `\u5F02\u5E38: ${e.message}`;
1591
- logger9.warn(`\u76F4\u8FDE${reason}\uFF0C${action}: ${urlPath}`);
1675
+ logger8.warn(`\u76F4\u8FDE${reason}\uFF0C${action}: ${urlPath}`);
1592
1676
  if (fallbackToProxy) {
1593
1677
  await safeContinue(route);
1594
1678
  } else {
@@ -1601,7 +1685,7 @@ var Interception = {
1601
1685
  await safeContinue(route);
1602
1686
  handled = true;
1603
1687
  } catch (err) {
1604
- logger9.warn(`\u8DEF\u7531\u5904\u7406\u5F02\u5E38: ${err.message}`);
1688
+ logger8.warn(`\u8DEF\u7531\u5904\u7406\u5F02\u5E38: ${err.message}`);
1605
1689
  if (!handled) {
1606
1690
  try {
1607
1691
  await route.continue();
@@ -1638,7 +1722,7 @@ function isIgnorableError(error) {
1638
1722
  var usePlaywrightToolKit = () => {
1639
1723
  return {
1640
1724
  ApifyKit,
1641
- Stealth,
1725
+ AntiDetect,
1642
1726
  Humanize,
1643
1727
  Launch,
1644
1728
  LiveView,