@skrillex1224/playwright-toolkit 2.1.224 → 2.1.225
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 +10 -6
- package/dist/browser.js +43 -3
- package/dist/browser.js.map +2 -2
- package/dist/index.cjs +628 -141
- package/dist/index.cjs.map +4 -4
- package/dist/index.js +628 -141
- package/dist/index.js.map +4 -4
- package/index.d.ts +25 -5
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -76,12 +76,18 @@ await Actor.exit();
|
|
|
76
76
|
|
|
77
77
|
## 🛡️ 反检测功能
|
|
78
78
|
|
|
79
|
+
### Desktop / Mobile Device
|
|
80
|
+
|
|
81
|
+
`Constants.ActorInfo[actor].device` 是 visitor 的设备真源,只允许 `desktop` 和 `mobile`,未配置时默认 `desktop`。visitor 继续通过 `RuntimeEnv.parseInput(input, actor.key)` 和 `Launch.getPlaywrightCrawlerOptions({ runtimeState })` 传递状态;把某个 actor 的 `device` 改成 `mobile` 后,toolkit 会生成 Android Chrome 移动端指纹、移动端 viewport/touch context,并切到移动端 Humanize 行为。
|
|
82
|
+
|
|
83
|
+
已有 `browser_profile.core` 会记录 `device`。当旧 core 的 `device` 与当前 ActorInfo 不一致时,Launch 会重建 core,避免配置看起来是移动端但实际仍复用桌面端指纹。
|
|
84
|
+
|
|
79
85
|
### 架构
|
|
80
86
|
|
|
81
87
|
| 层次 | 问题 | 解决方案 |
|
|
82
88
|
| ---------- | ----------------------- | --------------------------------------- |
|
|
83
89
|
| **指纹层** | UA/屏幕/语言/时区一致性 | Crawlee `useFingerprints` + `AntiCheat` |
|
|
84
|
-
| **行为层** | 机械输入/点击/滚动 | `ghost-cursor-playwright
|
|
90
|
+
| **行为层** | 机械输入/点击/滚动 | 桌面端 `ghost-cursor-playwright`,移动端 touch Humanize |
|
|
85
91
|
| **页面层** | 验证码/风控检测 | Captcha 监控器 |
|
|
86
92
|
|
|
87
93
|
### API 一览
|
|
@@ -213,11 +219,9 @@ await page.context().addCookies(cookies);
|
|
|
213
219
|
// 默认会将返回的 base64 压缩到 5MiB 以内,避免 Apify/Crawlee dataset 单条 item 超限
|
|
214
220
|
const base64Image = await Share.captureScreen(page);
|
|
215
221
|
|
|
216
|
-
//
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
// 仅恢复宽度(restore 支持 true / false / 'width-only' / 'height-only')
|
|
220
|
-
const image2 = await Share.captureScreen(page, { restore: 'width-only' });
|
|
222
|
+
// 截图只使用当前页面运行时 viewport;移动端请通过 ActorInfo.device 切换,不再通过截图参数覆盖
|
|
223
|
+
// 仅在完成后恢复页面高度和展开过的滚动容器
|
|
224
|
+
const image2 = await Share.captureScreen(page, { restore: true });
|
|
221
225
|
|
|
222
226
|
// 显式配置 watermarkify:全页淡水印 + 底部一行细条
|
|
223
227
|
const image3 = await Share.captureScreen(page, {
|
package/dist/browser.js
CHANGED
|
@@ -2511,8 +2511,10 @@ var constants_exports = {};
|
|
|
2511
2511
|
__export(constants_exports, {
|
|
2512
2512
|
ActorInfo: () => ActorInfo,
|
|
2513
2513
|
Code: () => Code,
|
|
2514
|
+
Device: () => Device,
|
|
2514
2515
|
PresetOfLiveViewKey: () => PresetOfLiveViewKey,
|
|
2515
|
-
Status: () => Status
|
|
2516
|
+
Status: () => Status,
|
|
2517
|
+
normalizeDevice: () => normalizeDevice
|
|
2516
2518
|
});
|
|
2517
2519
|
var Code = {
|
|
2518
2520
|
Success: 0,
|
|
@@ -2528,6 +2530,17 @@ var Status = {
|
|
|
2528
2530
|
Failed: "FAILED"
|
|
2529
2531
|
};
|
|
2530
2532
|
var PresetOfLiveViewKey = "LIVE_VIEW_SCREENSHOT";
|
|
2533
|
+
var Device = Object.freeze({
|
|
2534
|
+
Desktop: "desktop",
|
|
2535
|
+
Mobile: "mobile"
|
|
2536
|
+
});
|
|
2537
|
+
var normalizeDevice = (value, fallback = Device.Desktop) => {
|
|
2538
|
+
const normalizedFallback = String(fallback || "").trim().toLowerCase() === Device.Mobile ? Device.Mobile : Device.Desktop;
|
|
2539
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
2540
|
+
if (raw === Device.Mobile) return Device.Mobile;
|
|
2541
|
+
if (raw === Device.Desktop) return Device.Desktop;
|
|
2542
|
+
return normalizedFallback;
|
|
2543
|
+
};
|
|
2531
2544
|
var createActorInfo = (info) => {
|
|
2532
2545
|
const normalizeDomain = (value) => {
|
|
2533
2546
|
if (!value) return "";
|
|
@@ -2605,12 +2618,14 @@ var createActorInfo = (info) => {
|
|
|
2605
2618
|
const domain = normalizeDomain(info.domain);
|
|
2606
2619
|
const path = normalizePath(info.path);
|
|
2607
2620
|
const share = normalizeShare(info.share);
|
|
2621
|
+
const device = normalizeDevice(info.device);
|
|
2608
2622
|
return {
|
|
2609
2623
|
...info,
|
|
2610
2624
|
protocol,
|
|
2611
2625
|
domain,
|
|
2612
2626
|
path,
|
|
2613
2627
|
share,
|
|
2628
|
+
device,
|
|
2614
2629
|
get icon() {
|
|
2615
2630
|
if (info.icon) return info.icon;
|
|
2616
2631
|
return buildIcon(this);
|
|
@@ -2662,6 +2677,7 @@ var ActorInfo = {
|
|
|
2662
2677
|
name: "\u8C46\u5305",
|
|
2663
2678
|
domain: "www.doubao.com",
|
|
2664
2679
|
path: "/",
|
|
2680
|
+
device: Device.Mobile,
|
|
2665
2681
|
share: {
|
|
2666
2682
|
mode: "response",
|
|
2667
2683
|
prefix: "https://www.doubao.com/thread/",
|
|
@@ -2677,6 +2693,7 @@ var ActorInfo = {
|
|
|
2677
2693
|
name: "DeepSeek",
|
|
2678
2694
|
domain: "chat.deepseek.com",
|
|
2679
2695
|
path: "/",
|
|
2696
|
+
device: Device.Mobile,
|
|
2680
2697
|
share: {
|
|
2681
2698
|
mode: "response",
|
|
2682
2699
|
prefix: "https://chat.deepseek.com/share/",
|
|
@@ -2892,6 +2909,12 @@ var PageRuntimeStateKey = "__playwright_toolkit_runtime_state__";
|
|
|
2892
2909
|
var BROWSER_PROFILE_SCHEMA_VERSION = 1;
|
|
2893
2910
|
var rememberedRuntimeState = null;
|
|
2894
2911
|
var isPlainObject = (value) => value && typeof value === "object" && !Array.isArray(value);
|
|
2912
|
+
var normalizeKnownDevice = (value) => {
|
|
2913
|
+
const raw = String(value || "").trim().toLowerCase();
|
|
2914
|
+
if (raw === Device.Mobile) return Device.Mobile;
|
|
2915
|
+
if (raw === Device.Desktop) return Device.Desktop;
|
|
2916
|
+
return "";
|
|
2917
|
+
};
|
|
2895
2918
|
var deepClone = (value) => {
|
|
2896
2919
|
if (value == null) return value;
|
|
2897
2920
|
try {
|
|
@@ -3246,6 +3269,10 @@ var normalizeObservedBrowserProfile = (value) => {
|
|
|
3246
3269
|
var normalizeBrowserProfileCore = (value) => {
|
|
3247
3270
|
const source = isPlainObject(value) ? value : {};
|
|
3248
3271
|
const profile = {};
|
|
3272
|
+
const device = normalizeKnownDevice(source.device);
|
|
3273
|
+
if (device) {
|
|
3274
|
+
profile.device = device;
|
|
3275
|
+
}
|
|
3249
3276
|
if (isPlainObject(source.fingerprint) && Object.keys(source.fingerprint).length > 0) {
|
|
3250
3277
|
profile.fingerprint = deepClone(source.fingerprint);
|
|
3251
3278
|
}
|
|
@@ -3303,9 +3330,11 @@ var mergeBrowserProfilePayload = (target = {}, source = {}) => {
|
|
|
3303
3330
|
if (Object.keys(mergedCore).length === 0 && Object.keys(incomingCore).length > 0) {
|
|
3304
3331
|
mergedCore = incomingCore;
|
|
3305
3332
|
} else if (Object.keys(incomingCore).length > 0) {
|
|
3333
|
+
const currentDevice = normalizeKnownDevice(currentCore.device);
|
|
3334
|
+
const incomingDevice = normalizeKnownDevice(incomingCore.device);
|
|
3306
3335
|
const currentVersion = Number(currentCore.browser_major_version || 0);
|
|
3307
3336
|
const incomingVersion = Number(incomingCore.browser_major_version || 0);
|
|
3308
|
-
if (currentVersion > 0 && incomingVersion > 0 && currentVersion !== incomingVersion) {
|
|
3337
|
+
if (incomingDevice && currentDevice !== incomingDevice || currentVersion > 0 && incomingVersion > 0 && currentVersion !== incomingVersion) {
|
|
3309
3338
|
mergedCore = incomingCore;
|
|
3310
3339
|
}
|
|
3311
3340
|
}
|
|
@@ -3356,8 +3385,14 @@ var normalizeRuntimeState = (source = {}, actor = "") => {
|
|
|
3356
3385
|
}
|
|
3357
3386
|
return RuntimeEnv.parseInput(source, actor);
|
|
3358
3387
|
};
|
|
3388
|
+
var resolveInputDevice = (input = {}, runtime = {}, actor = "") => {
|
|
3389
|
+
var _a, _b, _c;
|
|
3390
|
+
const actorDevice = (_b = (_a = ActorInfo) == null ? void 0 : _a[actor]) == null ? void 0 : _b.device;
|
|
3391
|
+
return normalizeDevice((_c = actorDevice != null ? actorDevice : input == null ? void 0 : input.device) != null ? _c : runtime == null ? void 0 : runtime.device);
|
|
3392
|
+
};
|
|
3359
3393
|
var RuntimeEnv = {
|
|
3360
3394
|
tryParseJSON,
|
|
3395
|
+
normalizeDevice,
|
|
3361
3396
|
normalizeCookies,
|
|
3362
3397
|
normalizeLocalStorage,
|
|
3363
3398
|
normalizeSessionStorage,
|
|
@@ -3383,6 +3418,7 @@ var RuntimeEnv = {
|
|
|
3383
3418
|
parseInput(input = {}, actor = "") {
|
|
3384
3419
|
const runtime = tryParseJSON(input == null ? void 0 : input.runtime) || {};
|
|
3385
3420
|
const resolvedActor = String(actor || (input == null ? void 0 : input.actor) || "").trim();
|
|
3421
|
+
const device = resolveInputDevice(input, runtime, resolvedActor);
|
|
3386
3422
|
const query = String((input == null ? void 0 : input.query) || "").trim();
|
|
3387
3423
|
const cookies = normalizeCookies(runtime == null ? void 0 : runtime.cookies);
|
|
3388
3424
|
const cookieMap = buildCookieMap(cookies);
|
|
@@ -3402,6 +3438,7 @@ var RuntimeEnv = {
|
|
|
3402
3438
|
}
|
|
3403
3439
|
const state = {
|
|
3404
3440
|
actor: resolvedActor,
|
|
3441
|
+
device,
|
|
3405
3442
|
runtime: normalizedRuntime,
|
|
3406
3443
|
envId,
|
|
3407
3444
|
query,
|
|
@@ -3447,7 +3484,10 @@ var RuntimeEnv = {
|
|
|
3447
3484
|
},
|
|
3448
3485
|
setBrowserProfileCore(source = {}, core = {}, actor = "") {
|
|
3449
3486
|
const state = normalizeRuntimeState(source, actor);
|
|
3450
|
-
const normalizedCore = normalizeBrowserProfileCore(
|
|
3487
|
+
const normalizedCore = normalizeBrowserProfileCore({
|
|
3488
|
+
...core,
|
|
3489
|
+
device: normalizeKnownDevice(core == null ? void 0 : core.device) || state.device
|
|
3490
|
+
});
|
|
3451
3491
|
state.browserProfileCore = normalizedCore;
|
|
3452
3492
|
state.browserProfile = buildBrowserProfilePayload(normalizedCore, state.browserProfileObserved);
|
|
3453
3493
|
if (Object.keys(state.browserProfile).length > 0) {
|