@skrillex1224/playwright-toolkit 2.1.8 → 2.1.10

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
@@ -1281,6 +1281,14 @@ var DEFAULT_BLOCKING_CONFIG = {
1281
1281
  /** 额外自定义扩展名列表 */
1282
1282
  customExtensions: []
1283
1283
  };
1284
+ var SHARED_GOT_OPTIONS = {
1285
+ http2: false,
1286
+ // 禁用 HTTP2 避免在拦截场景下的握手兼容性问题
1287
+ retry: { limit: 0 },
1288
+ // 让 Playwright 或外层逻辑处理重试
1289
+ throwHttpErrors: false
1290
+ // 404/500 等错误不抛出异常,直接透传给浏览器
1291
+ };
1284
1292
  var Interception = {
1285
1293
  /**
1286
1294
  * 根据配置生成需要屏蔽的扩展名列表
@@ -1324,16 +1332,6 @@ var Interception = {
1324
1332
  /**
1325
1333
  * 设置网络拦截规则(资源屏蔽 + CDN 直连)
1326
1334
  *
1327
- * 工作流程:
1328
- * 1. 检查请求是否在屏蔽列表中 → 如果是,直接 abort
1329
- * 2. 检查是否匹配直连域名 → 如果是,使用 Node.js fetch 直连
1330
- * 3. 其他请求正常走代理
1331
- *
1332
- * 适用场景:
1333
- * - 代理 IP 无法访问某些 CDN 域名
1334
- * - 需要加速静态资源加载
1335
- * - 屏蔽不必要的资源请求
1336
- *
1337
1335
  * @param {import('playwright').Page} page - Playwright Page 对象
1338
1336
  * @param {Object} [options] - 配置选项
1339
1337
  * @param {string[]} [options.directDomains] - 需要直连的域名列表
@@ -1346,10 +1344,10 @@ var Interception = {
1346
1344
  directDomains = [],
1347
1345
  blockingConfig = {},
1348
1346
  fallbackToProxy = true
1349
- // 默认回退到代理,保证可用性
1350
1347
  } = options;
1351
1348
  const mergedBlockingConfig = { ...DEFAULT_BLOCKING_CONFIG, ...blockingConfig };
1352
1349
  const blockedExtensions = this.getBlockedExtensions(mergedBlockingConfig);
1350
+ const hasDirectDomains = directDomains.length > 0;
1353
1351
  const enabledCategories = [];
1354
1352
  if (mergedBlockingConfig.blockArchive) enabledCategories.push("\u538B\u7F29\u5305");
1355
1353
  if (mergedBlockingConfig.blockExecutable) enabledCategories.push("\u53EF\u6267\u884C\u6587\u4EF6");
@@ -1359,21 +1357,19 @@ var Interception = {
1359
1357
  if (mergedBlockingConfig.blockFont) enabledCategories.push("\u5B57\u4F53");
1360
1358
  if (mergedBlockingConfig.blockCss) enabledCategories.push("CSS");
1361
1359
  if (mergedBlockingConfig.blockOther) enabledCategories.push("\u5176\u4ED6");
1362
- const hasDirectDomains = directDomains.length > 0;
1363
- logger9.start("setup", hasDirectDomains ? `\u76F4\u8FDE\u57DF\u540D: [${directDomains.join(", ")}]` : "\u4EC5\u8D44\u6E90\u5C4F\u853D\u6A21\u5F0F");
1360
+ 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(", ")}]`);
1364
1361
  await page.route("**/*", async (route) => {
1365
- const request = route.request();
1366
- const url = request.url();
1367
- const urlLower = url.toLowerCase();
1368
- const urlPath = urlLower.split("?")[0];
1369
- const shouldBlock = blockedExtensions.some((ext) => urlPath.endsWith(ext));
1370
- if (shouldBlock) {
1371
- logger9.debug(`\u5DF2\u5C4F\u853D: ${url.substring(0, 100)}`);
1372
- return route.abort();
1373
- }
1374
- if (hasDirectDomains) {
1375
- const matchesDomain = directDomains.some((domain) => url.includes(domain));
1376
- if (matchesDomain) {
1362
+ try {
1363
+ const request = route.request();
1364
+ const url = request.url();
1365
+ const urlLower = url.toLowerCase();
1366
+ const urlPath = urlLower.split("?")[0];
1367
+ const shouldBlock = blockedExtensions.some((ext) => urlPath.endsWith(ext));
1368
+ if (shouldBlock) {
1369
+ await route.abort();
1370
+ return;
1371
+ }
1372
+ if (hasDirectDomains && directDomains.some((domain) => url.includes(domain))) {
1377
1373
  try {
1378
1374
  const reqHeaders = await request.allHeaders();
1379
1375
  delete reqHeaders["host"];
@@ -1381,30 +1377,29 @@ var Interception = {
1381
1377
  const method = request.method();
1382
1378
  const postData = method !== "GET" && method !== "HEAD" ? request.postDataBuffer() : void 0;
1383
1379
  const response = await (0, import_got_scraping.gotScraping)({
1380
+ ...SHARED_GOT_OPTIONS,
1381
+ // 应用通用配置
1384
1382
  url,
1385
1383
  method,
1386
1384
  headers: reqHeaders,
1387
1385
  body: postData,
1388
1386
  responseType: "buffer",
1389
- // 直接获取 Buffer
1390
- // 【修复 1】禁用 HTTP/2,防止握手挂起
1391
- http2: false,
1392
- // 模拟浏览器指纹 (Header 部分)
1387
+ // 强制获取 Buffer
1388
+ // 模拟浏览器 TLS 指纹
1393
1389
  headerGeneratorOptions: Stealth.getTlsFingerprintOptions(userAgent),
1394
- retry: { limit: 0 },
1395
- // 【修复 2】显式使用新的 Agent 实例,彻底无视 Apify 的环境变量代理
1396
- // 注意:这会牺牲一部分 TLS 指纹模拟能力,但能保证网络通畅
1390
+ // 【核心修复 1】: keepAlive 设置为 false
1391
+ // 每一个拦截请求都是独立的,使用 keepAlive 会导致 Agent 池耗尽,
1392
+ // 从而导致后续请求一直 Pending。
1397
1393
  agent: {
1398
- http: new import_http.Agent({ keepAlive: true }),
1399
- https: new import_https2.Agent({ keepAlive: true, rejectUnauthorized: false })
1394
+ http: new import_http.Agent({ keepAlive: false }),
1395
+ https: new import_https2.Agent({ keepAlive: false, rejectUnauthorized: false })
1400
1396
  },
1401
- // 设置超时,防止永久 Pending
1397
+ // 设置较短的超时时间,给回退代理留出机会
1402
1398
  timeout: {
1403
- request: 15 * 1e3
1404
- // 建议缩短到 15s
1399
+ request: 12 * 1e3
1400
+ // 12秒超时
1405
1401
  }
1406
1402
  });
1407
- const body = response.body;
1408
1403
  const resHeaders = {};
1409
1404
  for (const [key, value] of Object.entries(response.headers)) {
1410
1405
  if (Array.isArray(value)) {
@@ -1415,29 +1410,61 @@ var Interception = {
1415
1410
  }
1416
1411
  delete resHeaders["content-encoding"];
1417
1412
  delete resHeaders["content-length"];
1418
- logger9.debug(`\u76F4\u8FDE\u6210\u529F: ${url.substring(0, 100)}`);
1419
- await route.fulfill({
1413
+ delete resHeaders["transfer-encoding"];
1414
+ delete resHeaders["connection"];
1415
+ delete resHeaders["keep-alive"];
1416
+ logger9.info(`\u76F4\u8FDE\u6210\u529F: ${url}`);
1417
+ await safeFulfill(route, {
1420
1418
  status: response.statusCode,
1421
1419
  headers: resHeaders,
1422
- body
1420
+ body: response.body
1423
1421
  });
1424
1422
  return;
1425
1423
  } catch (e) {
1426
1424
  if (fallbackToProxy) {
1427
- logger9.warn(`\u76F4\u8FDE\u5931\u8D25\uFF0C\u56DE\u9000\u4EE3\u7406: ${url.substring(0, 80)} | \u539F\u56E0: ${e.message}`);
1428
- return route.continue();
1425
+ logger9.warn(`\u76F4\u8FDE\u5F02\u5E38\uFF0C\u56DE\u9000\u4EE3\u7406: ${url} | Err: ${e.message}`);
1426
+ await safeContinue(route);
1427
+ return;
1429
1428
  } else {
1430
- logger9.warn(`\u76F4\u8FDE\u5931\u8D25: ${url.substring(0, 80)} | \u539F\u56E0: ${e.message}`);
1431
- return route.abort();
1429
+ logger9.warn(`\u76F4\u8FDE\u5931\u8D25: ${url} | Err: ${e.message}`);
1430
+ await route.abort();
1431
+ return;
1432
1432
  }
1433
1433
  }
1434
1434
  }
1435
+ await safeContinue(route);
1436
+ } catch (err) {
1437
+ try {
1438
+ if (!route.request().failure()) {
1439
+ await route.continue();
1440
+ }
1441
+ } catch (_) {
1442
+ }
1435
1443
  }
1436
- return route.continue();
1437
1444
  });
1438
- logger9.success("setup", `\u5C4F\u853D\u5206\u7C7B: [${enabledCategories.join(", ")}]`);
1439
1445
  }
1440
1446
  };
1447
+ async function safeFulfill(route, options) {
1448
+ try {
1449
+ await route.fulfill(options);
1450
+ } catch (error) {
1451
+ if (!isIgnorableError(error)) {
1452
+ console.error(`[Interception] Fulfill Error: ${error.message}`);
1453
+ }
1454
+ }
1455
+ }
1456
+ async function safeContinue(route) {
1457
+ try {
1458
+ await route.continue();
1459
+ } catch (error) {
1460
+ if (!isIgnorableError(error)) {
1461
+ }
1462
+ }
1463
+ }
1464
+ function isIgnorableError(error) {
1465
+ const msg = error.message;
1466
+ return msg.includes("already handled") || msg.includes("Target closed") || msg.includes("closed");
1467
+ }
1441
1468
 
1442
1469
  // index.js
1443
1470
  var usePlaywrightToolKit = () => {