tt-help-cli-ycl 1.3.39 → 1.3.40
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
CHANGED
package/src/cli/explore.js
CHANGED
|
@@ -393,38 +393,67 @@ export async function handleExplore(options) {
|
|
|
393
393
|
if (result.error) {
|
|
394
394
|
consecutiveNetworkErrors++;
|
|
395
395
|
errorCount++;
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
: "other";
|
|
404
|
-
await withRetry("report error", () =>
|
|
405
|
-
apiPost(`${serverUrl}/api/error-report`, {
|
|
406
|
-
userId,
|
|
396
|
+
|
|
397
|
+
// 临时性错误:自动重试一次
|
|
398
|
+
if (result.retryable) {
|
|
399
|
+
console.error(` [临时错误] 等待 5 秒后重试 @${username}...`);
|
|
400
|
+
await new Promise((r) => setTimeout(r, 5000));
|
|
401
|
+
result = await processExplore(
|
|
402
|
+
page,
|
|
407
403
|
username,
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
404
|
+
{
|
|
405
|
+
maxVideos: exploreMaxVideos,
|
|
406
|
+
enableFollow: exploreEnableFollow,
|
|
407
|
+
loggedIn,
|
|
408
|
+
maxFollowing: exploreMaxFollowing,
|
|
409
|
+
maxFollowers: exploreMaxFollowers,
|
|
410
|
+
location: exploreLocation,
|
|
411
|
+
browser,
|
|
412
|
+
},
|
|
413
|
+
console.error,
|
|
414
|
+
);
|
|
415
|
+
// 重试成功后继续正常流程
|
|
416
|
+
if (!result.error) {
|
|
417
|
+
consecutiveNetworkErrors = 0;
|
|
418
|
+
errorCount--;
|
|
420
419
|
}
|
|
421
|
-
continue;
|
|
422
420
|
}
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
421
|
+
|
|
422
|
+
if (result.error) {
|
|
423
|
+
// 上报错误(重试后仍有错误才上报)
|
|
424
|
+
await apiPost(`${serverUrl}/api/job/${username}`, {
|
|
425
|
+
error: result.error,
|
|
426
|
+
});
|
|
427
|
+
const errorType = result.error.startsWith("被封:")
|
|
428
|
+
? "被封"
|
|
429
|
+
: consecutiveNetworkErrors > 1
|
|
430
|
+
? "network"
|
|
431
|
+
: "other";
|
|
432
|
+
await withRetry("report error", () =>
|
|
433
|
+
apiPost(`${serverUrl}/api/error-report`, {
|
|
434
|
+
userId,
|
|
435
|
+
username,
|
|
436
|
+
errorType,
|
|
437
|
+
errorMessage: result.error,
|
|
438
|
+
stage: "process",
|
|
439
|
+
errorStack: result.errorStack || "",
|
|
440
|
+
}),
|
|
441
|
+
).catch(() => {});
|
|
442
|
+
if (errorType === "被封") {
|
|
443
|
+
blockedCount++;
|
|
444
|
+
console.error(` [被封] 累计 ${blockedCount} 次`);
|
|
445
|
+
if (blockedCount >= 3) {
|
|
446
|
+
await handleAccountSwitch(`账号被封累计 ${blockedCount} 次`);
|
|
447
|
+
blockedCount = 0;
|
|
448
|
+
}
|
|
449
|
+
continue;
|
|
450
|
+
}
|
|
451
|
+
if (exploreMaxUsers > 0 && processedCount >= exploreMaxUsers) {
|
|
452
|
+
console.error(`\n已达上限 ${exploreMaxUsers} 个用户,停止处理`);
|
|
453
|
+
break;
|
|
454
|
+
}
|
|
455
|
+
continue;
|
|
426
456
|
}
|
|
427
|
-
continue;
|
|
428
457
|
}
|
|
429
458
|
|
|
430
459
|
if (result.captchaDetected) {
|
|
@@ -42,7 +42,7 @@ async function processAPIResponse(
|
|
|
42
42
|
}, newUrl);
|
|
43
43
|
} catch (e) {
|
|
44
44
|
if (
|
|
45
|
-
e.message.includes(
|
|
45
|
+
e.message.includes("Execution context was destroyed") &&
|
|
46
46
|
i < retries - 1
|
|
47
47
|
) {
|
|
48
48
|
await delay(500 * (i + 1), 500 * (i + 1));
|
|
@@ -102,10 +102,10 @@ async function fetchUserVideosAPI(page, username, maxVideos, log) {
|
|
|
102
102
|
let interceptionError = null;
|
|
103
103
|
|
|
104
104
|
try {
|
|
105
|
-
await page.goto(
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
);
|
|
105
|
+
await page.goto(`https://www.tiktok.com/@${username}`, {
|
|
106
|
+
waitUntil: "domcontentloaded",
|
|
107
|
+
timeout: 30000,
|
|
108
|
+
});
|
|
109
109
|
await assertPageUrl(page, `@${username}`);
|
|
110
110
|
|
|
111
111
|
const response = await page.waitForResponse(
|
|
@@ -158,4 +158,4 @@ async function fetchUserVideosAPI(page, username, maxVideos, log) {
|
|
|
158
158
|
return new Map();
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
export { fetchUserVideosAPI };
|
|
161
|
+
export { fetchUserVideosAPI };
|
|
@@ -56,7 +56,9 @@ const PATTERNS = {
|
|
|
56
56
|
|
|
57
57
|
export async function detectPageError(page) {
|
|
58
58
|
return page.evaluate((patterns) => {
|
|
59
|
-
const
|
|
59
|
+
const body = document.body;
|
|
60
|
+
if (!body) return null;
|
|
61
|
+
const bodyText = body.innerText;
|
|
60
62
|
const lower = bodyText.toLowerCase();
|
|
61
63
|
|
|
62
64
|
for (const [type, phrases] of Object.entries(patterns)) {
|
|
@@ -81,7 +83,9 @@ export async function detectPageErrorWithWait(page, timeout = 8000) {
|
|
|
81
83
|
try {
|
|
82
84
|
const handle = await page.waitForFunction(
|
|
83
85
|
(patterns) => {
|
|
84
|
-
const
|
|
86
|
+
const body = document.body;
|
|
87
|
+
if (!body) return null;
|
|
88
|
+
const bodyText = body.innerText;
|
|
85
89
|
const lower = bodyText.toLowerCase();
|
|
86
90
|
|
|
87
91
|
for (const [type, phrases] of Object.entries(patterns)) {
|
|
@@ -174,7 +174,7 @@ export class TikTokScraper {
|
|
|
174
174
|
timeout: 15000,
|
|
175
175
|
});
|
|
176
176
|
|
|
177
|
-
|
|
177
|
+
const content = await page.evaluate(() => {
|
|
178
178
|
const rows = document.querySelectorAll("tr");
|
|
179
179
|
let content = "";
|
|
180
180
|
rows.forEach((r) => {
|
|
@@ -183,6 +183,16 @@ export class TikTokScraper {
|
|
|
183
183
|
});
|
|
184
184
|
return content;
|
|
185
185
|
});
|
|
186
|
+
|
|
187
|
+
// 导航到 about:blank 释放当前页面的 DOM 和 JS 堆
|
|
188
|
+
await page
|
|
189
|
+
.goto("about:blank", {
|
|
190
|
+
waitUntil: "domcontentloaded",
|
|
191
|
+
timeout: 5000,
|
|
192
|
+
})
|
|
193
|
+
.catch(() => {});
|
|
194
|
+
|
|
195
|
+
return content;
|
|
186
196
|
}
|
|
187
197
|
|
|
188
198
|
async getUserInfo(uniqueId) {
|
|
@@ -173,6 +173,15 @@ async function processExplore(page, username, options, log) {
|
|
|
173
173
|
result.noVideo = false;
|
|
174
174
|
log(` @${username} 认定为被封`);
|
|
175
175
|
}
|
|
176
|
+
|
|
177
|
+
// 临时性错误:超时、执行上下文销毁、网络异常 — 标记为可重试
|
|
178
|
+
const retryablePatterns = [
|
|
179
|
+
"Timeout",
|
|
180
|
+
"Execution context was destroyed",
|
|
181
|
+
"ERR_CONNECTION",
|
|
182
|
+
"net::",
|
|
183
|
+
];
|
|
184
|
+
result.retryable = retryablePatterns.some((p) => e.message.includes(p));
|
|
176
185
|
}
|
|
177
186
|
|
|
178
187
|
return result;
|
package/src/videos/core.js
CHANGED
|
@@ -14,7 +14,7 @@ async function getUserInfo(page) {
|
|
|
14
14
|
return await page.evaluate(fn);
|
|
15
15
|
} catch (e) {
|
|
16
16
|
if (
|
|
17
|
-
e.message.includes(
|
|
17
|
+
e.message.includes("Execution context was destroyed") &&
|
|
18
18
|
i < retries - 1
|
|
19
19
|
) {
|
|
20
20
|
await new Promise((r) => setTimeout(r, 500 * (i + 1)));
|