tt-help-cli-ycl 1.4.38 → 1.4.39
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/package.json +1 -1
- package/src/cli/precheck.js +81 -51
- package/src/lib/args.js +4 -0
- package/src/lib/constants.js +2 -0
package/package.json
CHANGED
package/src/cli/precheck.js
CHANGED
|
@@ -59,8 +59,10 @@ export async function handlePrecheck(options) {
|
|
|
59
59
|
precheckCountries,
|
|
60
60
|
serverUrl,
|
|
61
61
|
customProxy,
|
|
62
|
+
precheckWebserver,
|
|
62
63
|
showHelp,
|
|
63
64
|
} = options;
|
|
65
|
+
const webserverUrl = precheckWebserver || null;
|
|
64
66
|
const effectiveProxy = customProxy || configuredProxy;
|
|
65
67
|
|
|
66
68
|
if (showHelp) {
|
|
@@ -148,12 +150,32 @@ export async function handlePrecheck(options) {
|
|
|
148
150
|
try {
|
|
149
151
|
const { apiGet, apiPost } = createApiClient({ log: false });
|
|
150
152
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
153
|
+
// ── webserver 模式:不启动本地浏览器,通过 HTTP 调用集中式 webserver ──
|
|
154
|
+
let scraper = null;
|
|
155
|
+
let getUserInfo;
|
|
156
|
+
|
|
157
|
+
if (webserverUrl) {
|
|
158
|
+
precheckLog(`[Precheck] webserver 模式: ${webserverUrl}`);
|
|
159
|
+
getUserInfo = async (uniqueId) => {
|
|
160
|
+
const res = await fetch(`${webserverUrl}/api/tiktok/user`, {
|
|
161
|
+
method: "POST",
|
|
162
|
+
headers: { "Content-Type": "application/json" },
|
|
163
|
+
body: JSON.stringify({ uniqueId }),
|
|
164
|
+
signal: AbortSignal.timeout(30000),
|
|
165
|
+
});
|
|
166
|
+
const data = await res.json();
|
|
167
|
+
if (!res.ok || data.error) return null;
|
|
168
|
+
return data;
|
|
169
|
+
};
|
|
170
|
+
} else {
|
|
171
|
+
scraper = new TikTokScraper({
|
|
172
|
+
poolSize: precheckSize,
|
|
173
|
+
proxyServer: effectiveProxy || null,
|
|
174
|
+
});
|
|
175
|
+
await scraper.init();
|
|
176
|
+
precheckLog("[Precheck] 浏览器初始化完成,开始循环预检...");
|
|
177
|
+
getUserInfo = (uniqueId) => scraper.getUserInfo(uniqueId);
|
|
178
|
+
}
|
|
157
179
|
|
|
158
180
|
let loopCount = 0;
|
|
159
181
|
let browserRestartCount = 0;
|
|
@@ -162,8 +184,8 @@ export async function handlePrecheck(options) {
|
|
|
162
184
|
while (!shuttingDown) {
|
|
163
185
|
loopCount++;
|
|
164
186
|
|
|
165
|
-
//
|
|
166
|
-
if (!scraper.isAlive()) {
|
|
187
|
+
// 检查浏览器是否存活(仅本地模式)
|
|
188
|
+
if (scraper && !scraper.isAlive()) {
|
|
167
189
|
precheckLog(
|
|
168
190
|
`[Precheck] 浏览器已关闭,正在重启 (${++browserRestartCount})...`,
|
|
169
191
|
);
|
|
@@ -173,8 +195,10 @@ export async function handlePrecheck(options) {
|
|
|
173
195
|
|
|
174
196
|
// 收到退出信号,完成清理
|
|
175
197
|
if (shuttingDown) {
|
|
176
|
-
|
|
177
|
-
|
|
198
|
+
if (scraper) {
|
|
199
|
+
precheckLog("[Precheck] 正在关闭浏览器...");
|
|
200
|
+
await scraper.close().catch(() => {});
|
|
201
|
+
}
|
|
178
202
|
if (forceExitTimer) {
|
|
179
203
|
clearTimeout(forceExitTimer);
|
|
180
204
|
forceExitTimer = null;
|
|
@@ -192,7 +216,7 @@ export async function handlePrecheck(options) {
|
|
|
192
216
|
// 收到退出信号,不再处理新任务
|
|
193
217
|
if (shuttingDown) {
|
|
194
218
|
precheckLog("[Precheck] 已收到退出信号,跳过本轮...");
|
|
195
|
-
await scraper.close().catch(() => {});
|
|
219
|
+
if (scraper) await scraper.close().catch(() => {});
|
|
196
220
|
if (forceExitTimer) {
|
|
197
221
|
clearTimeout(forceExitTimer);
|
|
198
222
|
forceExitTimer = null;
|
|
@@ -202,12 +226,14 @@ export async function handlePrecheck(options) {
|
|
|
202
226
|
}
|
|
203
227
|
|
|
204
228
|
if (!candidates || candidates.length === 0) {
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
229
|
+
if (scraper) {
|
|
230
|
+
const heap = getHeapUsage();
|
|
231
|
+
if (heap.ratio >= HEAP_RESTART_RATIO) {
|
|
232
|
+
await recycleScraper(
|
|
233
|
+
scraper,
|
|
234
|
+
`空闲时堆占用 ${(heap.ratio * 100).toFixed(1)}%`,
|
|
235
|
+
);
|
|
236
|
+
}
|
|
211
237
|
}
|
|
212
238
|
if (loopCount === 1) {
|
|
213
239
|
precheckLog(
|
|
@@ -242,7 +268,7 @@ export async function handlePrecheck(options) {
|
|
|
242
268
|
candidates.map(async (candidate) => {
|
|
243
269
|
const uniqueId = candidate.uniqueId;
|
|
244
270
|
try {
|
|
245
|
-
const info = await
|
|
271
|
+
const info = await getUserInfo(uniqueId);
|
|
246
272
|
return {
|
|
247
273
|
uniqueId,
|
|
248
274
|
info,
|
|
@@ -262,8 +288,10 @@ export async function handlePrecheck(options) {
|
|
|
262
288
|
|
|
263
289
|
// 批次完成后检查退出信号
|
|
264
290
|
if (shuttingDown) {
|
|
265
|
-
|
|
266
|
-
|
|
291
|
+
if (scraper) {
|
|
292
|
+
precheckLog("[Precheck] 正在关闭浏览器...");
|
|
293
|
+
await scraper.close().catch(() => {});
|
|
294
|
+
}
|
|
267
295
|
if (forceExitTimer) {
|
|
268
296
|
clearTimeout(forceExitTimer);
|
|
269
297
|
forceExitTimer = null;
|
|
@@ -369,39 +397,41 @@ export async function handlePrecheck(options) {
|
|
|
369
397
|
` 本轮结果: ${needRefreshCount} 需刷新, ${skipCount} 跳过, ${failCount} 失败`,
|
|
370
398
|
);
|
|
371
399
|
|
|
372
|
-
// CDN
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
400
|
+
// CDN 限流/堆内存/批次重启(仅本地浏览器模式)
|
|
401
|
+
if (scraper) {
|
|
402
|
+
const cdnRatio =
|
|
403
|
+
candidates.length > 0 ? cdnBlockedCount / candidates.length : 0;
|
|
404
|
+
if (cdnRatio > 0.3) {
|
|
405
|
+
const coolDownSeconds = cdnRatio > 0.8 ? 120 : 60;
|
|
406
|
+
precheckLog(
|
|
407
|
+
` [Precheck] CDN限流比例 ${(cdnRatio * 100).toFixed(0)}% (${cdnBlockedCount}/${candidates.length}),冷却 ${coolDownSeconds} 秒后重启浏览器...`,
|
|
408
|
+
);
|
|
409
|
+
await new Promise((r) => setTimeout(r, coolDownSeconds * 1000));
|
|
410
|
+
await recycleScraper(
|
|
411
|
+
scraper,
|
|
412
|
+
`CDN限流比例过高 (${cdnBlockedCount}/${candidates.length})`,
|
|
413
|
+
);
|
|
414
|
+
browserRestartCount++;
|
|
415
|
+
taskBatchCount = 0;
|
|
416
|
+
}
|
|
388
417
|
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
418
|
+
const heap = getHeapUsage();
|
|
419
|
+
if (heap.ratio >= HEAP_RESTART_RATIO) {
|
|
420
|
+
await recycleScraper(
|
|
421
|
+
scraper,
|
|
422
|
+
`本批后堆占用 ${(heap.ratio * 100).toFixed(1)}%`,
|
|
423
|
+
);
|
|
424
|
+
browserRestartCount++;
|
|
425
|
+
taskBatchCount = 0;
|
|
426
|
+
} else if (taskBatchCount >= MAX_TASK_BATCHES_BEFORE_RESTART) {
|
|
427
|
+
await recycleScraper(scraper, `已连续处理 ${taskBatchCount} 批任务`);
|
|
428
|
+
browserRestartCount++;
|
|
429
|
+
taskBatchCount = 0;
|
|
430
|
+
}
|
|
402
431
|
|
|
403
|
-
|
|
404
|
-
|
|
432
|
+
if (needRestart) {
|
|
433
|
+
precheckLog("[Precheck] 检测到浏览器异常,将在下一轮重启...");
|
|
434
|
+
}
|
|
405
435
|
}
|
|
406
436
|
|
|
407
437
|
// 批次间短暂休息
|
package/src/lib/args.js
CHANGED
|
@@ -710,6 +710,7 @@ function parsePrecheckArgs(args) {
|
|
|
710
710
|
let precheckSize = 5;
|
|
711
711
|
let precheckCountries = null;
|
|
712
712
|
let customProxy = null;
|
|
713
|
+
let precheckWebserver = null;
|
|
713
714
|
let showHelp = false;
|
|
714
715
|
|
|
715
716
|
for (let i = 0; i < args.length; i++) {
|
|
@@ -731,6 +732,8 @@ function parsePrecheckArgs(args) {
|
|
|
731
732
|
.filter(Boolean);
|
|
732
733
|
} else if (arg === "--proxy") {
|
|
733
734
|
customProxy = args[++i];
|
|
735
|
+
} else if (arg === "--webserver") {
|
|
736
|
+
precheckWebserver = args[++i];
|
|
734
737
|
} else if (arg === "--help" || arg === "-h") {
|
|
735
738
|
showHelp = true;
|
|
736
739
|
}
|
|
@@ -745,6 +748,7 @@ function parsePrecheckArgs(args) {
|
|
|
745
748
|
precheckSize,
|
|
746
749
|
precheckCountries,
|
|
747
750
|
customProxy,
|
|
751
|
+
precheckWebserver,
|
|
748
752
|
showHelp,
|
|
749
753
|
urls: [],
|
|
750
754
|
outputFormat: "json",
|
package/src/lib/constants.js
CHANGED
|
@@ -174,9 +174,11 @@ const HELP_TEXT = [
|
|
|
174
174
|
" --size <数量> 浏览器页面池大小,默认 5",
|
|
175
175
|
` -c, --countries <国家> 目标国家列表,逗号分隔,默认 ${DEFAULT_TARGET_LOCATIONS_CSV}`,
|
|
176
176
|
" --proxy <地址> HTTP 代理(如 http://127.0.0.1:7890)",
|
|
177
|
+
" --webserver <URL> 集中式 webserver(如 http://127.0.0.1:3000),省去本地 Playwright 浏览器",
|
|
177
178
|
" 示例: tt-help precheck -s http://127.0.0.1:3001",
|
|
178
179
|
" tt-help precheck -i 60 --max-age 86400 -p 20 -c DE,ES,FR",
|
|
179
180
|
" tt-help precheck --proxy http://127.0.0.1:7890",
|
|
181
|
+
" tt-help precheck --webserver http://127.0.0.1:3000 -p 30",
|
|
180
182
|
"",
|
|
181
183
|
" tt-help info <URL> [URL2 ...] [--onlyvideo]",
|
|
182
184
|
" 获取用户/视频信息,支持多个 URL",
|