tt-help-cli-ycl 1.3.24 → 1.3.25
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/explore.js +50 -7
- package/src/cli/open.js +102 -0
- package/src/lib/args.js +34 -0
- package/src/lib/browser/cdp.js +45 -2
- package/src/lib/browser/health-checker.js +54 -0
- package/src/lib/constants.js +7 -0
- package/src/main.js +2 -0
- package/src/scraper/explore-core.js +32 -23
package/package.json
CHANGED
package/src/cli/explore.js
CHANGED
|
@@ -2,8 +2,9 @@ import { getOrCreatePage, isBrowserClosedError, relaunchBrowser } from '../lib/b
|
|
|
2
2
|
import { delay, getDelayConfig, setDelayConfig } from '../scraper/modules/page-helpers.js';
|
|
3
3
|
import { userId as configuredUserId, saveUserId } from '../lib/constants.js';
|
|
4
4
|
import { getMacOrUuid } from '../lib/mac-or-uuid.js';
|
|
5
|
-
import { ensureBrowserReady as ensureBrowserReadyCDP } from '../lib/browser/cdp.js';
|
|
5
|
+
import { ensureBrowserReady as ensureBrowserReadyCDP, switchAccount } from '../lib/browser/cdp.js';
|
|
6
6
|
import { showResourceUsage } from '../utils/index.js';
|
|
7
|
+
import { HealthChecker } from '../lib/browser/health-checker.js';
|
|
7
8
|
import path from 'path';
|
|
8
9
|
import os from 'os';
|
|
9
10
|
|
|
@@ -70,15 +71,22 @@ export async function handleExplore(options) {
|
|
|
70
71
|
console.error(`关注/粉丝: ${exploreEnableFollow ? '启用' : '禁用'}`);
|
|
71
72
|
console.error(`服务器: ${serverUrl}(断开会自动重连)`);
|
|
72
73
|
if (exploreMaxUsers > 0) console.error(`上限: ${exploreMaxUsers} 个用户`);
|
|
73
|
-
|
|
74
|
-
|
|
74
|
+
|
|
75
|
+
const healthChecker = new HealthChecker();
|
|
76
|
+
let currentAccount = healthChecker.getCurrentAccount();
|
|
75
77
|
|
|
76
78
|
const cdpOptions = {};
|
|
77
79
|
if (explorePort) cdpOptions.port = explorePort;
|
|
78
80
|
if (exploreProfile) {
|
|
79
81
|
cdpOptions.userDataDir = path.join(os.homedir(), 'Library', 'Application Support', `Microsoft Edge For Testing_${exploreProfile}`);
|
|
82
|
+
} else {
|
|
83
|
+
cdpOptions.port = currentAccount.port;
|
|
84
|
+
cdpOptions.userDataDir = currentAccount.userDataDir;
|
|
80
85
|
}
|
|
81
86
|
|
|
87
|
+
console.error(`CDP 端口: ${cdpOptions.port || 9222}, 用户编号: ${userId}`);
|
|
88
|
+
if (exploreProfile || cdpOptions.userDataDir) console.error(`浏览器配置: ${exploreProfile || path.basename(cdpOptions.userDataDir)}`);
|
|
89
|
+
|
|
82
90
|
let browser = await ensureBrowserReadyCDP(cdpOptions);
|
|
83
91
|
const { processExplore } = await import('../scraper/explore-core.js');
|
|
84
92
|
|
|
@@ -94,6 +102,36 @@ export async function handleExplore(options) {
|
|
|
94
102
|
}
|
|
95
103
|
});
|
|
96
104
|
|
|
105
|
+
// 账户切换后,重新初始化 page
|
|
106
|
+
async function setupNewPage(newBrowser) {
|
|
107
|
+
const newPage = await getOrCreatePage(newBrowser);
|
|
108
|
+
await newPage.route("**/*", (route) => {
|
|
109
|
+
const resourceType = route.request().resourceType();
|
|
110
|
+
if (resourceType === 'image' || resourceType === 'stylesheet') {
|
|
111
|
+
route.abort();
|
|
112
|
+
} else {
|
|
113
|
+
route.continue();
|
|
114
|
+
}
|
|
115
|
+
});
|
|
116
|
+
return newPage;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function handleAccountSwitch(reason) {
|
|
120
|
+
console.error(`\n[健康检查] ${reason},切换到下一个账户...`);
|
|
121
|
+
const oldAccount = currentAccount;
|
|
122
|
+
const nextAccount = healthChecker.getNextAccount();
|
|
123
|
+
currentAccount = nextAccount;
|
|
124
|
+
const newBrowser = await switchAccount(
|
|
125
|
+
{ port: oldAccount.port, userDataDir: oldAccount.userDataDir },
|
|
126
|
+
{ port: nextAccount.port, userDataDir: nextAccount.userDataDir }
|
|
127
|
+
);
|
|
128
|
+
browser = newBrowser;
|
|
129
|
+
const newPage = await setupNewPage(browser);
|
|
130
|
+
Object.assign(page, newPage);
|
|
131
|
+
Object.assign(cdpOptions, { port: nextAccount.port, userDataDir: nextAccount.userDataDir });
|
|
132
|
+
console.error(`[健康检查] 已切换到端口 ${nextAccount.port}`);
|
|
133
|
+
}
|
|
134
|
+
|
|
97
135
|
let processedCount = 0;
|
|
98
136
|
let errorCount = 0;
|
|
99
137
|
let consecutiveNetworkErrors = 0;
|
|
@@ -109,6 +147,11 @@ export async function handleExplore(options) {
|
|
|
109
147
|
showResourceUsage();
|
|
110
148
|
}
|
|
111
149
|
|
|
150
|
+
const checkResult = healthChecker.check();
|
|
151
|
+
if (checkResult.shouldSwitch) {
|
|
152
|
+
await handleAccountSwitch(checkResult.reason);
|
|
153
|
+
}
|
|
154
|
+
|
|
112
155
|
if (consecutiveNetworkErrors > 0) {
|
|
113
156
|
const waitTime = consecutiveNetworkErrors <= 2
|
|
114
157
|
? 0
|
|
@@ -137,9 +180,9 @@ export async function handleExplore(options) {
|
|
|
137
180
|
|
|
138
181
|
// 浏览器关闭检测:processExplore 内部 catch 了异常,需要从 result.error 判断
|
|
139
182
|
if (result.error && isBrowserClosedError(new Error(result.error))) {
|
|
140
|
-
const newBrowser = await relaunchBrowser(cdpOptions,
|
|
183
|
+
const newBrowser = await relaunchBrowser(cdpOptions, cdpOptions.port || 9222);
|
|
141
184
|
browser = newBrowser;
|
|
142
|
-
const newPage = await
|
|
185
|
+
const newPage = await setupNewPage(browser);
|
|
143
186
|
Object.assign(page, newPage);
|
|
144
187
|
// 重试当前用户
|
|
145
188
|
result = await processExplore(page, username, {
|
|
@@ -178,8 +221,8 @@ export async function handleExplore(options) {
|
|
|
178
221
|
})
|
|
179
222
|
).catch(() => {});
|
|
180
223
|
if (errorType === '被封') {
|
|
181
|
-
|
|
182
|
-
|
|
224
|
+
await handleAccountSwitch('账号被封');
|
|
225
|
+
continue;
|
|
183
226
|
}
|
|
184
227
|
if (exploreMaxUsers > 0 && processedCount >= exploreMaxUsers) {
|
|
185
228
|
console.error(`\n已达上限 ${exploreMaxUsers} 个用户,停止处理`);
|
package/src/cli/open.js
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
import { ensureBrowserReady, killEdgeProcesses } from '../lib/browser/cdp.js';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
import os from 'os';
|
|
4
|
+
|
|
5
|
+
const BASE_PORT = 9222;
|
|
6
|
+
const TOTAL_ACCOUNTS = 10;
|
|
7
|
+
|
|
8
|
+
export async function handleOpen(parsed) {
|
|
9
|
+
const { openPort, openList } = parsed;
|
|
10
|
+
|
|
11
|
+
if (openList) {
|
|
12
|
+
console.error('内置浏览器配置:');
|
|
13
|
+
for (let i = 0; i < TOTAL_ACCOUNTS; i++) {
|
|
14
|
+
const port = BASE_PORT + i;
|
|
15
|
+
const profile = `p${port}`;
|
|
16
|
+
const userDataDir = path.join(os.homedir(), 'Library', 'Application Support', `Microsoft Edge For Testing_${profile}`);
|
|
17
|
+
console.error(` open ${port} → profile: ${profile}, userDataDir: ${userDataDir}`);
|
|
18
|
+
}
|
|
19
|
+
console.error('');
|
|
20
|
+
console.error('用法: tt-help open <端口>');
|
|
21
|
+
console.error('示例: tt-help open 9222');
|
|
22
|
+
process.exit(0);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
if (!openPort) {
|
|
26
|
+
console.error('用法: tt-help open <端口>');
|
|
27
|
+
console.error('示例: tt-help open 9222');
|
|
28
|
+
console.error('');
|
|
29
|
+
console.error('可用端口: 9222 - 9231 (共 10 个)');
|
|
30
|
+
console.error('运行 "tt-help open --list" 查看所有配置');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const port = parseInt(openPort);
|
|
35
|
+
if (isNaN(port) || port < BASE_PORT || port >= BASE_PORT + TOTAL_ACCOUNTS) {
|
|
36
|
+
console.error(`端口 ${openPort} 不在有效范围内 (9222 - 9231)`);
|
|
37
|
+
process.exit(1);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
const profile = `p${port}`;
|
|
41
|
+
const userDataDir = path.join(os.homedir(), 'Library', 'Application Support', `Microsoft Edge For Testing_${profile}`);
|
|
42
|
+
|
|
43
|
+
console.error(`正在启动浏览器... 端口: ${port}, profile: ${profile}`);
|
|
44
|
+
console.error(`userDataDir: ${userDataDir}`);
|
|
45
|
+
console.error('');
|
|
46
|
+
console.error('启动后请在浏览器中登录 TikTok 账户');
|
|
47
|
+
console.error('登录完成后关闭浏览器即可,下次 open 会保留登录状态');
|
|
48
|
+
console.error('');
|
|
49
|
+
console.error('按 Ctrl+C 退出并关闭浏览器');
|
|
50
|
+
|
|
51
|
+
let browser = null;
|
|
52
|
+
|
|
53
|
+
try {
|
|
54
|
+
const cdpOptions = {};
|
|
55
|
+
cdpOptions.port = port;
|
|
56
|
+
cdpOptions.userDataDir = userDataDir;
|
|
57
|
+
|
|
58
|
+
browser = await ensureBrowserReady(cdpOptions);
|
|
59
|
+
|
|
60
|
+
// Ctrl+C 关闭浏览器
|
|
61
|
+
process.on('SIGINT', async () => {
|
|
62
|
+
console.error('\n正在关闭浏览器...');
|
|
63
|
+
try {
|
|
64
|
+
await killEdgeProcesses(userDataDir);
|
|
65
|
+
await browser.close();
|
|
66
|
+
} catch { /* ignore */ }
|
|
67
|
+
console.error('浏览器已关闭');
|
|
68
|
+
process.exit(0);
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
console.error('');
|
|
72
|
+
console.error('浏览器已启动并连接成功');
|
|
73
|
+
console.error('TikTok 页面将在浏览器中自动打开...');
|
|
74
|
+
|
|
75
|
+
// 打开 TikTok 首页
|
|
76
|
+
try {
|
|
77
|
+
const pages = await browser.pages();
|
|
78
|
+
let page = pages.find(p => !p.url().startsWith('devtools'));
|
|
79
|
+
if (!page) {
|
|
80
|
+
const context = await browser.newBrowserCDPSession();
|
|
81
|
+
await context.send('Target.createTarget', { url: '' });
|
|
82
|
+
await new Promise(r => setTimeout(r, 1000));
|
|
83
|
+
const newPages = await browser.pages();
|
|
84
|
+
page = newPages.find(p => !p.url().startsWith('devtools'));
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (page) {
|
|
88
|
+
await page.goto('https://www.tiktok.com', { waitUntil: 'domcontentloaded', timeout: 30000 });
|
|
89
|
+
console.error('已打开 https://www.tiktok.com');
|
|
90
|
+
}
|
|
91
|
+
} catch (e) {
|
|
92
|
+
console.error(`打开页面时出错: ${e.message}`);
|
|
93
|
+
console.error('浏览器已启动,可以手动在浏览器中打开 TikTok');
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
// 保持进程运行
|
|
97
|
+
await new Promise(() => {});
|
|
98
|
+
} catch (err) {
|
|
99
|
+
console.error(`启动失败: ${err.message}`);
|
|
100
|
+
process.exit(1);
|
|
101
|
+
}
|
|
102
|
+
}
|
package/src/lib/args.js
CHANGED
|
@@ -426,6 +426,36 @@ function parseAttachArgs(args) {
|
|
|
426
426
|
};
|
|
427
427
|
}
|
|
428
428
|
|
|
429
|
+
function parseOpenArgs(args) {
|
|
430
|
+
let openPort = null;
|
|
431
|
+
let openList = false;
|
|
432
|
+
|
|
433
|
+
for (let i = 0; i < args.length; i++) {
|
|
434
|
+
const arg = args[i];
|
|
435
|
+
if (arg === '--list') {
|
|
436
|
+
openList = true;
|
|
437
|
+
} else if (!arg.startsWith('-')) {
|
|
438
|
+
openPort = arg;
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
return {
|
|
443
|
+
subcommand: 'open',
|
|
444
|
+
openPort,
|
|
445
|
+
openList,
|
|
446
|
+
urls: [],
|
|
447
|
+
outputFormat: 'json',
|
|
448
|
+
exploreCount: 0,
|
|
449
|
+
showConfig: false,
|
|
450
|
+
showHelp: false,
|
|
451
|
+
customProxy: null,
|
|
452
|
+
configAction: null,
|
|
453
|
+
configValue: null,
|
|
454
|
+
pipeMode: false,
|
|
455
|
+
filterStr: null,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
429
459
|
export function parseArgs() {
|
|
430
460
|
const args = process.argv.slice(2);
|
|
431
461
|
|
|
@@ -452,6 +482,10 @@ export function parseArgs() {
|
|
|
452
482
|
return parseAttachArgs(args.slice(1));
|
|
453
483
|
}
|
|
454
484
|
|
|
485
|
+
if (args.length > 0 && args[0] === 'open') {
|
|
486
|
+
return parseOpenArgs(args.slice(1));
|
|
487
|
+
}
|
|
488
|
+
|
|
455
489
|
const urls = [];
|
|
456
490
|
let inputFile = null;
|
|
457
491
|
let outputFile = null;
|
package/src/lib/browser/cdp.js
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { exec } from 'child_process';
|
|
2
2
|
import { execSync } from 'child_process';
|
|
3
|
+
import fs from 'fs';
|
|
3
4
|
import http from 'http';
|
|
4
5
|
import os from 'os';
|
|
5
6
|
import path from 'path';
|
|
@@ -82,8 +83,33 @@ function killEdgeProcesses(targetDir) {
|
|
|
82
83
|
command = 'killall -9 "Microsoft Edge" 2>/dev/null; rm -f ~/Library/Caches/Microsoft\\ Edge/Singleton*; true';
|
|
83
84
|
}
|
|
84
85
|
} else if (platform === 'win32') {
|
|
85
|
-
|
|
86
|
-
|
|
86
|
+
if (targetDir) {
|
|
87
|
+
try {
|
|
88
|
+
const ps1Path = path.join(os.tmpdir(), `kill-edge-${Date.now()}.ps1`);
|
|
89
|
+
const escapedDir = targetDir.replace(/'/g, "''");
|
|
90
|
+
const ps1Content = `$procs = Get-CimInstance Win32_Process -Filter "Name='msedge.exe'" 2>$null; ` +
|
|
91
|
+
`$count = 0; ` +
|
|
92
|
+
`foreach ($p in $procs) { ` +
|
|
93
|
+
` if ($p.CommandLine -and $p.CommandLine -like "*${escapedDir}*") { ` +
|
|
94
|
+
` Stop-Process -Id $p.ProcessId -Force -ErrorAction SilentlyContinue; ` +
|
|
95
|
+
` $count++ ` +
|
|
96
|
+
` } ` +
|
|
97
|
+
`}; ` +
|
|
98
|
+
`Get-ChildItem "$env:LOCALAPPDATA\\Microsoft\\Edge\\Singleton*" -ErrorAction SilentlyContinue | Remove-Item -Force; ` +
|
|
99
|
+
`Write-Output $count`;
|
|
100
|
+
fs.writeFileSync(ps1Path, ps1Content);
|
|
101
|
+
const result = execSync(
|
|
102
|
+
`powershell.exe -ExecutionPolicy Bypass -File "${ps1Path}"`
|
|
103
|
+
).toString().trim();
|
|
104
|
+
fs.unlinkSync(ps1Path);
|
|
105
|
+
command = 'exit 0';
|
|
106
|
+
} catch {
|
|
107
|
+
command = 'taskkill /F /IM msedge.exe 2>nul || exit 0';
|
|
108
|
+
}
|
|
109
|
+
} else {
|
|
110
|
+
command = 'taskkill /F /IM msedge.exe 2>nul || exit 0';
|
|
111
|
+
}
|
|
112
|
+
} else {
|
|
87
113
|
if (targetDir) {
|
|
88
114
|
const escapedDir = targetDir.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&');
|
|
89
115
|
command = `ps aux | grep msedge | grep -v Helper | grep -E -- '--user-data-dir=${escapedDir}($|[^A-Za-z0-9_])' | awk '{print $2}' | xargs -r kill -9 2>/dev/null; true`;
|
|
@@ -192,3 +218,20 @@ export async function ensureBrowserReady(options = {}) {
|
|
|
192
218
|
const browser = await chromium.connectOverCDP(`http://127.0.0.1:${port}`);
|
|
193
219
|
return browser;
|
|
194
220
|
}
|
|
221
|
+
|
|
222
|
+
export async function switchAccount(oldAccount, newAccount) {
|
|
223
|
+
console.error(`\n[账户切换] 从端口 ${oldAccount.port} -> ${newAccount.port}`);
|
|
224
|
+
|
|
225
|
+
await killEdgeProcesses(oldAccount.userDataDir);
|
|
226
|
+
await new Promise(r => setTimeout(r, 3000));
|
|
227
|
+
|
|
228
|
+
const newCdpOptions = {};
|
|
229
|
+
newCdpOptions.port = newAccount.port;
|
|
230
|
+
newCdpOptions.userDataDir = newAccount.userDataDir;
|
|
231
|
+
|
|
232
|
+
const browser = await ensureBrowserReady(newCdpOptions);
|
|
233
|
+
|
|
234
|
+
await new Promise(r => setTimeout(r, 10000));
|
|
235
|
+
|
|
236
|
+
return browser;
|
|
237
|
+
}
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import path from 'path';
|
|
3
|
+
|
|
4
|
+
const TOTAL_ACCOUNTS = 10;
|
|
5
|
+
const BASE_PORT = 9222;
|
|
6
|
+
const MEMORY_THRESHOLD = 0.9;
|
|
7
|
+
const ROTATE_INTERVAL_MS = 2 * 60 * 60 * 1000;
|
|
8
|
+
|
|
9
|
+
class HealthChecker {
|
|
10
|
+
constructor() {
|
|
11
|
+
this.accounts = [];
|
|
12
|
+
for (let i = 0; i < TOTAL_ACCOUNTS; i++) {
|
|
13
|
+
const port = BASE_PORT + i;
|
|
14
|
+
const profile = `p${port}`;
|
|
15
|
+
this.accounts.push({
|
|
16
|
+
port,
|
|
17
|
+
profile,
|
|
18
|
+
userDataDir: path.join(
|
|
19
|
+
os.homedir(), 'Library', 'Application Support', `Microsoft Edge For Testing_${profile}`
|
|
20
|
+
),
|
|
21
|
+
});
|
|
22
|
+
}
|
|
23
|
+
this.currentIndex = 0;
|
|
24
|
+
this.startTime = Date.now();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
getCurrentAccount() {
|
|
28
|
+
return this.accounts[this.currentIndex];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
getNextAccount() {
|
|
32
|
+
this.currentIndex = (this.currentIndex + 1) % TOTAL_ACCOUNTS;
|
|
33
|
+
return this.accounts[this.currentIndex];
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
check() {
|
|
37
|
+
const memUsage = (1 - os.freemem() / os.totalmem());
|
|
38
|
+
const memPercent = (memUsage * 100).toFixed(1);
|
|
39
|
+
|
|
40
|
+
if (memUsage >= MEMORY_THRESHOLD) {
|
|
41
|
+
return { shouldSwitch: true, reason: `系统内存 ${memPercent}%` };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const elapsed = Date.now() - this.startTime;
|
|
45
|
+
if (elapsed >= ROTATE_INTERVAL_MS) {
|
|
46
|
+
const mins = Math.round(elapsed / 60000);
|
|
47
|
+
return { shouldSwitch: true, reason: `运行 ${mins} 分钟` };
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
return { shouldSwitch: false };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export { HealthChecker };
|
package/src/lib/constants.js
CHANGED
|
@@ -136,6 +136,13 @@ const HELP_TEXT = [
|
|
|
136
136
|
' -s, --server <URL> 服务端地址(默认: http://127.0.0.1:3001)',
|
|
137
137
|
' 示例: tt-help attach -p 5 -i 10',
|
|
138
138
|
'',
|
|
139
|
+
' tt-help open <端口号>',
|
|
140
|
+
' 打开指定端口的浏览器,用于配置 TikTok 登录账户',
|
|
141
|
+
' 端口范围: 9222 - 9231(对应 explore 的 10 个内置账户)',
|
|
142
|
+
' --list 列出所有端口和配置',
|
|
143
|
+
' 示例: tt-help open 9222',
|
|
144
|
+
' tt-help open --list',
|
|
145
|
+
'',
|
|
139
146
|
' config [show|set|reset]',
|
|
140
147
|
' config 查看当前配置',
|
|
141
148
|
' config set <key> <value> 设置配置(key: proxy, server, browser, userId, maxFollowing, maxFollowers, maxVideos, maxComments)',
|
package/src/main.js
CHANGED
|
@@ -5,6 +5,7 @@ import { handleExplore } from './cli/explore.js';
|
|
|
5
5
|
import { handleAttach } from './cli/attach.js';
|
|
6
6
|
import { handleWatch } from './cli/watch.js';
|
|
7
7
|
import { handleConfig, showConfig, showUsage, version } from './cli/config.js';
|
|
8
|
+
import { handleOpen } from './cli/open.js';
|
|
8
9
|
|
|
9
10
|
async function main() {
|
|
10
11
|
const parsed = parseArgs();
|
|
@@ -14,6 +15,7 @@ async function main() {
|
|
|
14
15
|
case 'info': return handleInfo(parsed);
|
|
15
16
|
case 'attach': return handleAttach(parsed);
|
|
16
17
|
case 'watch': return handleWatch(parsed);
|
|
18
|
+
case 'open': return handleOpen(parsed);
|
|
17
19
|
}
|
|
18
20
|
|
|
19
21
|
const { urls, outputFile, outputFormat, exploreCount, showConfig: showCfg, showHelp, showVersion, customProxy, configAction, configKey, configValue } = parsed;
|
|
@@ -65,29 +65,18 @@ async function processExplore(page, username, options, log) {
|
|
|
65
65
|
if (videoArray.length <= 0) {
|
|
66
66
|
result.processed = true;
|
|
67
67
|
result.noVideo = true;
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
{
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
result.error = `被封: ${username}`;
|
|
81
|
-
result.errorStack = '';
|
|
82
|
-
result.processed = false;
|
|
83
|
-
result.noVideo = false;
|
|
84
|
-
log(` @${username} 服务异常(service_error),标记错误`);
|
|
85
|
-
} else if (pageError) {
|
|
86
|
-
result.restricted = true;
|
|
87
|
-
log(` @${username} 页面受限(${pageError}),标记跳过`);
|
|
88
|
-
} else {
|
|
89
|
-
log(` @${username} 没有视频,标记已处理`);
|
|
90
|
-
}
|
|
68
|
+
const pageError = await detectPageError(page);
|
|
69
|
+
if (pageError === 'service_error') {
|
|
70
|
+
result.error = `被封: ${username}`;
|
|
71
|
+
result.errorStack = '';
|
|
72
|
+
result.processed = false;
|
|
73
|
+
result.noVideo = false;
|
|
74
|
+
log(` @${username} 服务异常(service_error),标记被封`);
|
|
75
|
+
} else if (pageError) {
|
|
76
|
+
result.restricted = true;
|
|
77
|
+
result.processed = true;
|
|
78
|
+
result.noVideo = true;
|
|
79
|
+
log(` @${username} 页面受限(${pageError}),标记跳过`);
|
|
91
80
|
} else {
|
|
92
81
|
log(` @${username} 没有视频,标记已处理`);
|
|
93
82
|
}
|
|
@@ -181,6 +170,26 @@ async function processExplore(page, username, options, log) {
|
|
|
181
170
|
result.error = e.message;
|
|
182
171
|
result.errorStack = e.stack || "";
|
|
183
172
|
log(` [错误] ${e.message}`);
|
|
173
|
+
|
|
174
|
+
// API 拦截失败时,复用 videoArray.length <= 0 的被封检测逻辑
|
|
175
|
+
if (e.message.includes('API拦截')) {
|
|
176
|
+
log(` [被封检测] 检查页面状态...`);
|
|
177
|
+
const hasErrorPage = await page.waitForFunction(
|
|
178
|
+
(keywords) => {
|
|
179
|
+
const text = document.body.innerText;
|
|
180
|
+
return keywords.some(k => text.includes(k));
|
|
181
|
+
},
|
|
182
|
+
["出错了", "很抱歉"],
|
|
183
|
+
{ timeout: 5000 }
|
|
184
|
+
).then(() => true).catch(() => false);
|
|
185
|
+
|
|
186
|
+
if (hasErrorPage) {
|
|
187
|
+
result.error = `被封: ${username}`;
|
|
188
|
+
result.processed = false;
|
|
189
|
+
result.noVideo = false;
|
|
190
|
+
log(` @${username} API 拦截失败且页面异常,认定为被封`);
|
|
191
|
+
}
|
|
192
|
+
}
|
|
184
193
|
}
|
|
185
194
|
|
|
186
195
|
return result;
|