tt-help-cli-ycl 1.3.1 → 1.3.3
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 +17 -17
- package/cli.js +9 -9
- package/package.json +45 -44
- package/src/cli/auto.js +1 -1
- package/src/cli/explore.js +2 -3
- package/src/cli/progress.js +111 -111
- package/src/cli/scrape.js +47 -47
- package/src/cli/utils.js +18 -18
- package/src/cli/videos.js +41 -41
- package/src/cli/watch.js +28 -28
- package/src/lib/args.js +390 -377
- package/src/lib/browser/anti-detect.js +23 -23
- package/src/lib/browser/cdp.js +142 -142
- package/src/lib/browser/launch.js +43 -43
- package/src/lib/browser/page.js +80 -62
- package/src/lib/constants.js +94 -85
- package/src/lib/delay.js +54 -54
- package/src/lib/{explore.js → explore-fetch.js} +118 -118
- package/src/lib/fetcher.js +45 -45
- package/src/lib/filter.js +66 -66
- package/src/lib/io.js +54 -54
- package/src/lib/output.js +80 -80
- package/src/lib/parser.js +47 -47
- package/src/lib/retry.js +44 -44
- package/src/lib/scrape.js +40 -40
- package/src/lib/url.js +52 -52
- package/src/main.mjs +221 -200
- package/src/results/user-videos-bar.lar.lar.moeta.json +37 -0
- package/src/{auto-core.mjs → scraper/auto-core.mjs} +183 -174
- package/src/scraper/core.mjs +188 -182
- package/src/{explore-core.mjs → scraper/explore-core.mjs} +159 -148
- package/src/scraper/modules/captcha-handler.mjs +114 -0
- package/src/scraper/modules/comment-extractor.mjs +69 -57
- package/src/scraper/modules/follow-extractor.mjs +121 -121
- package/src/scraper/modules/guess-extractor.mjs +51 -51
- package/src/scraper/modules/page-error-detector.mjs +70 -68
- package/src/scraper/modules/page-helpers.mjs +46 -44
- package/src/scraper/modules/scroll-collector.mjs +189 -189
- package/src/{get-user-videos-core.mjs → videos/core.mjs} +126 -126
- package/src/{data-store.mjs → watch/data-store.mjs} +29 -3
- package/src/watch/public/index.html +444 -344
- package/src/watch/server.mjs +24 -1
- package/src/lib/auto-browser.mjs +0 -6
- package/src/lib/get-user-videos-browser.mjs +0 -1
- package/src/lib/scrape-browser.mjs +0 -1
- package/src/test-auto-follow.cjs +0 -109
- package/src/test-extractors.cjs +0 -75
- package/src/test-follow.cjs +0 -41
package/src/lib/url.js
CHANGED
|
@@ -1,52 +1,52 @@
|
|
|
1
|
-
const BASE_URL = 'https://www.tiktok.com';
|
|
2
|
-
|
|
3
|
-
export function extractUniqueId(url) {
|
|
4
|
-
const m = url.match(/\/@([^/]+)/);
|
|
5
|
-
return m ? m[1] : null;
|
|
6
|
-
}
|
|
7
|
-
|
|
8
|
-
export function extractVideoId(url) {
|
|
9
|
-
const m = url.match(/\/video\/(\d+)/);
|
|
10
|
-
return m ? m[1] : null;
|
|
11
|
-
}
|
|
12
|
-
|
|
13
|
-
export function normalizeUsername(input) {
|
|
14
|
-
return (input || '').replace(/^@/, '');
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
export function toProfileUrl(handle) {
|
|
18
|
-
const clean = normalizeUsername(handle);
|
|
19
|
-
return `${BASE_URL}/@${clean}`;
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
export function toVideoUrl(handle, videoId) {
|
|
23
|
-
const clean = normalizeUsername(handle);
|
|
24
|
-
return `${BASE_URL}/@${clean}/video/${videoId}`;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export function ensureAbsoluteUrl(href) {
|
|
28
|
-
if (href.startsWith('http')) return href;
|
|
29
|
-
return `${BASE_URL}${href}`;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export function isProfileUrl(url) {
|
|
33
|
-
return /\/@[\w-]+(?:$|[?#])/.test(url);
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export function isVideoUrl(url) {
|
|
37
|
-
return /\/video\/\d+/.test(url);
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function extractDisplayPath(url) {
|
|
41
|
-
try {
|
|
42
|
-
const parts = new URL(url).pathname.split('/').filter(Boolean);
|
|
43
|
-
return parts.slice(-2).join('/');
|
|
44
|
-
} catch {
|
|
45
|
-
return url;
|
|
46
|
-
}
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
export function extractAuthorFromVideoUrl(url) {
|
|
50
|
-
const m = url.match(/@([^/]+)\/video/);
|
|
51
|
-
return m ? '@' + m[1] : null;
|
|
52
|
-
}
|
|
1
|
+
const BASE_URL = 'https://www.tiktok.com';
|
|
2
|
+
|
|
3
|
+
export function extractUniqueId(url) {
|
|
4
|
+
const m = url.match(/\/@([^/]+)/);
|
|
5
|
+
return m ? m[1] : null;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export function extractVideoId(url) {
|
|
9
|
+
const m = url.match(/\/video\/(\d+)/);
|
|
10
|
+
return m ? m[1] : null;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function normalizeUsername(input) {
|
|
14
|
+
return (input || '').replace(/^@/, '');
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export function toProfileUrl(handle) {
|
|
18
|
+
const clean = normalizeUsername(handle);
|
|
19
|
+
return `${BASE_URL}/@${clean}`;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function toVideoUrl(handle, videoId) {
|
|
23
|
+
const clean = normalizeUsername(handle);
|
|
24
|
+
return `${BASE_URL}/@${clean}/video/${videoId}`;
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
export function ensureAbsoluteUrl(href) {
|
|
28
|
+
if (href.startsWith('http')) return href;
|
|
29
|
+
return `${BASE_URL}${href}`;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function isProfileUrl(url) {
|
|
33
|
+
return /\/@[\w-]+(?:$|[?#])/.test(url);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export function isVideoUrl(url) {
|
|
37
|
+
return /\/video\/\d+/.test(url);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export function extractDisplayPath(url) {
|
|
41
|
+
try {
|
|
42
|
+
const parts = new URL(url).pathname.split('/').filter(Boolean);
|
|
43
|
+
return parts.slice(-2).join('/');
|
|
44
|
+
} catch {
|
|
45
|
+
return url;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export function extractAuthorFromVideoUrl(url) {
|
|
50
|
+
const m = url.match(/@([^/]+)\/video/);
|
|
51
|
+
return m ? '@' + m[1] : null;
|
|
52
|
+
}
|
package/src/main.mjs
CHANGED
|
@@ -1,200 +1,221 @@
|
|
|
1
|
-
import { parseArgs } from './lib/args.js';
|
|
2
|
-
import { HELP_TEXT, CONFIG_TEXT, proxy, configFile, configPath, DEFAULT_PROXY, saveBrowser } from './lib/constants.js';
|
|
3
|
-
import { parseFilter, applyFilter, formatFilterDescription } from './lib/filter.js';
|
|
4
|
-
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
5
|
-
import { handleScrape } from './cli/scrape.js';
|
|
6
|
-
import { handleVideos } from './cli/videos.js';
|
|
7
|
-
import { handleAuto } from './cli/auto.js';
|
|
8
|
-
import { handleExplore } from './cli/explore.js';
|
|
9
|
-
import { handleWatch } from './cli/watch.js';
|
|
10
|
-
import { processUrlsWithProgress } from './cli/progress.js';
|
|
11
|
-
import { cleanError } from './cli/utils.js';
|
|
12
|
-
import { fileURLToPath } from 'url';
|
|
13
|
-
import { dirname, join } from 'path';
|
|
14
|
-
|
|
15
|
-
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
-
const pkgPath = join(__dirname, '..', 'package.json');
|
|
17
|
-
const { version } = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
18
|
-
|
|
19
|
-
function showConfig(urls, outputFile) {
|
|
20
|
-
const lines = [...CONFIG_TEXT];
|
|
21
|
-
if (outputFile) lines.push(` 输出文件: ${outputFile}`);
|
|
22
|
-
if (urls.length > 0) lines.push(` 待处理URL: ${urls.length}`);
|
|
23
|
-
lines.push('', '参数:', ' -c, --config 显示当前配置', ' -h, --help 显示帮助');
|
|
24
|
-
console.log(lines.join('\n'));
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
function showUsage() {
|
|
28
|
-
console.log(HELP_TEXT.join('\n'));
|
|
29
|
-
process.exit(0);
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
function handleConfig(action, value) {
|
|
33
|
-
if (action === 'show' || action === null) {
|
|
34
|
-
showConfig([], null);
|
|
35
|
-
return;
|
|
36
|
-
}
|
|
37
|
-
if (action === 'set' || action === 'set-proxy') {
|
|
38
|
-
if (!
|
|
39
|
-
console.error('用法: tt-help config set
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
console.log(
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
}
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
}
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
1
|
+
import { parseArgs } from './lib/args.js';
|
|
2
|
+
import { HELP_TEXT, CONFIG_TEXT, proxy, configFile, configPath, DEFAULT_PROXY, saveBrowser } from './lib/constants.js';
|
|
3
|
+
import { parseFilter, applyFilter, formatFilterDescription } from './lib/filter.js';
|
|
4
|
+
import { writeFileSync, readFileSync, existsSync } from 'fs';
|
|
5
|
+
import { handleScrape } from './cli/scrape.js';
|
|
6
|
+
import { handleVideos } from './cli/videos.js';
|
|
7
|
+
import { handleAuto } from './cli/auto.js';
|
|
8
|
+
import { handleExplore } from './cli/explore.js';
|
|
9
|
+
import { handleWatch } from './cli/watch.js';
|
|
10
|
+
import { processUrlsWithProgress } from './cli/progress.js';
|
|
11
|
+
import { cleanError } from './cli/utils.js';
|
|
12
|
+
import { fileURLToPath } from 'url';
|
|
13
|
+
import { dirname, join } from 'path';
|
|
14
|
+
|
|
15
|
+
const __dirname = dirname(fileURLToPath(import.meta.url));
|
|
16
|
+
const pkgPath = join(__dirname, '..', 'package.json');
|
|
17
|
+
const { version } = JSON.parse(readFileSync(pkgPath, 'utf-8'));
|
|
18
|
+
|
|
19
|
+
function showConfig(urls, outputFile) {
|
|
20
|
+
const lines = [...CONFIG_TEXT];
|
|
21
|
+
if (outputFile) lines.push(` 输出文件: ${outputFile}`);
|
|
22
|
+
if (urls.length > 0) lines.push(` 待处理URL: ${urls.length}`);
|
|
23
|
+
lines.push('', '参数:', ' -c, --config 显示当前配置', ' -h, --help 显示帮助');
|
|
24
|
+
console.log(lines.join('\n'));
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function showUsage() {
|
|
28
|
+
console.log(HELP_TEXT.join('\n'));
|
|
29
|
+
process.exit(0);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function handleConfig(action, key, value) {
|
|
33
|
+
if (action === 'show' || action === null) {
|
|
34
|
+
showConfig([], null);
|
|
35
|
+
return;
|
|
36
|
+
}
|
|
37
|
+
if (action === 'set' || action === 'set-proxy') {
|
|
38
|
+
if (!key) {
|
|
39
|
+
console.error('用法: tt-help config set <key> <value>');
|
|
40
|
+
console.error(' 可用 key: proxy, server, browser');
|
|
41
|
+
process.exit(1);
|
|
42
|
+
}
|
|
43
|
+
if (!value && key.startsWith('http://')) {
|
|
44
|
+
// 兼容旧用法: config set <代理地址>(key 实际是 value)
|
|
45
|
+
const cfg = existsSync(configPath) ? JSON.parse(readFileSync(configPath, 'utf-8')) : {};
|
|
46
|
+
cfg.proxy = key;
|
|
47
|
+
writeFileSync(configPath, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
48
|
+
console.log(`代理已设置为: ${key}`);
|
|
49
|
+
console.log(`配置文件: ${configPath}`);
|
|
50
|
+
return;
|
|
51
|
+
}
|
|
52
|
+
if (!value) {
|
|
53
|
+
console.error(`请提供 ${key} 的值`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
const cfg = existsSync(configPath) ? JSON.parse(readFileSync(configPath, 'utf-8')) : {};
|
|
57
|
+
if (key === 'proxy') cfg.proxy = value;
|
|
58
|
+
else if (key === 'server') cfg.server = value;
|
|
59
|
+
else if (key === 'browser') cfg.browser = value;
|
|
60
|
+
else {
|
|
61
|
+
console.error(`未知配置项: ${key}`);
|
|
62
|
+
console.error(' 可用 key: proxy, server, browser');
|
|
63
|
+
process.exit(1);
|
|
64
|
+
}
|
|
65
|
+
writeFileSync(configPath, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
66
|
+
console.log(`${key} 已设置为: ${value}`);
|
|
67
|
+
console.log(`配置文件: ${configPath}`);
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
if (action === 'set-browser') {
|
|
71
|
+
if (!key) {
|
|
72
|
+
console.error('用法: tt-help config set-browser <浏览器路径 或 auto>');
|
|
73
|
+
process.exit(1);
|
|
74
|
+
}
|
|
75
|
+
if (key === 'auto') {
|
|
76
|
+
if (existsSync(configPath)) {
|
|
77
|
+
const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
78
|
+
delete cfg.browser;
|
|
79
|
+
writeFileSync(configPath, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
80
|
+
}
|
|
81
|
+
console.log('已切换为自动探测浏览器模式');
|
|
82
|
+
} else {
|
|
83
|
+
saveBrowser(key);
|
|
84
|
+
console.log(`浏览器已设置为: ${key}`);
|
|
85
|
+
}
|
|
86
|
+
console.log(`配置文件: ${configPath}`);
|
|
87
|
+
return;
|
|
88
|
+
}
|
|
89
|
+
if (action === 'reset') {
|
|
90
|
+
if (existsSync(configPath)) {
|
|
91
|
+
const cfg = JSON.parse(readFileSync(configPath, 'utf-8'));
|
|
92
|
+
cfg.proxy = DEFAULT_PROXY;
|
|
93
|
+
writeFileSync(configPath, JSON.stringify(cfg, null, 2), 'utf-8');
|
|
94
|
+
console.log(`已恢复默认代理: ${DEFAULT_PROXY}`);
|
|
95
|
+
console.log(`配置文件: ${configPath}`);
|
|
96
|
+
} else {
|
|
97
|
+
console.log('当前使用默认代理,无需重置');
|
|
98
|
+
}
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
console.error(`未知配置命令: ${action}`);
|
|
102
|
+
console.error('用法: tt-help config [show|set|set-browser|reset]');
|
|
103
|
+
process.exit(1);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
async function runExploreDefault(exploreCount, urls, proxyUrl, outputFile, outputFormat, pipeMode, filter) {
|
|
107
|
+
console.log(`\n代理: ${proxyUrl}`);
|
|
108
|
+
console.log(`Explore 数量: ${exploreCount}`);
|
|
109
|
+
if (urls.length > 0) console.log(`额外 URL: ${urls.length}\n`);
|
|
110
|
+
else console.log('');
|
|
111
|
+
|
|
112
|
+
const allResults = [];
|
|
113
|
+
|
|
114
|
+
if (exploreCount > 0) {
|
|
115
|
+
try {
|
|
116
|
+
const { fetchExplore } = await import('./lib/explore-fetch.js');
|
|
117
|
+
const exploreResults = await fetchExplore(exploreCount);
|
|
118
|
+
console.log(` 获取到 ${exploreResults.length} 个视频\n`);
|
|
119
|
+
if (pipeMode) {
|
|
120
|
+
const videoUrls = exploreResults.map(r => r.url).filter(Boolean);
|
|
121
|
+
if (videoUrls.length > 0) {
|
|
122
|
+
await runScrapeDefault(videoUrls, proxyUrl, outputFile, outputFormat, filter);
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
allResults.push(...exploreResults);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
console.error(` Explore 获取失败: ${cleanError(err.message)}\n`);
|
|
129
|
+
console.error(` 请确保代理 ${proxyUrl} 正常运行\n`);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (urls.length > 0) {
|
|
134
|
+
const { processUrl } = await import('./lib/scrape.js');
|
|
135
|
+
await processUrlsWithProgress({
|
|
136
|
+
urls,
|
|
137
|
+
proxyUrl,
|
|
138
|
+
outputFile,
|
|
139
|
+
outputFormat,
|
|
140
|
+
filter,
|
|
141
|
+
processFn: (url, px) => processUrl(url, px),
|
|
142
|
+
label: '数据',
|
|
143
|
+
log: console.log,
|
|
144
|
+
});
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const { deduplicate, formatOutput } = await import('./lib/output.js');
|
|
149
|
+
const uniqueResults = deduplicate(allResults);
|
|
150
|
+
const filteredResults = applyFilter(uniqueResults, filter);
|
|
151
|
+
|
|
152
|
+
if (filteredResults.length === 0) {
|
|
153
|
+
console.log('\n未获取到数据');
|
|
154
|
+
if (outputFile) writeFileSync(outputFile, '[]', 'utf-8');
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const output = formatOutput(filteredResults, outputFormat);
|
|
159
|
+
if (outputFile) {
|
|
160
|
+
writeFileSync(outputFile, output, 'utf-8');
|
|
161
|
+
console.log(`\n结果已写入: ${outputFile}`);
|
|
162
|
+
} else {
|
|
163
|
+
console.log(output);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (filter) {
|
|
167
|
+
console.log(`\n共 ${uniqueResults.length} 个数据,过滤后 ${filteredResults.length} 个(过滤条件: ${formatFilterDescription(filter)})`);
|
|
168
|
+
} else {
|
|
169
|
+
console.log(`\n共 ${filteredResults.length} 个数据`);
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
async function runScrapeDefault(urls, proxyUrl, outputFile, outputFormat, filter) {
|
|
174
|
+
const { processUrl } = await import('./lib/scrape.js');
|
|
175
|
+
await processUrlsWithProgress({
|
|
176
|
+
urls,
|
|
177
|
+
proxyUrl,
|
|
178
|
+
outputFile,
|
|
179
|
+
outputFormat,
|
|
180
|
+
filter,
|
|
181
|
+
processFn: (url, px) => processUrl(url, px),
|
|
182
|
+
label: '用户的数据',
|
|
183
|
+
log: console.log,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function main() {
|
|
188
|
+
const parsed = parseArgs();
|
|
189
|
+
|
|
190
|
+
switch (parsed.subcommand) {
|
|
191
|
+
case 'scrape': return handleScrape(parsed);
|
|
192
|
+
case 'videos': return handleVideos(parsed);
|
|
193
|
+
case 'auto': return handleAuto(parsed);
|
|
194
|
+
case 'explore':return handleExplore(parsed);
|
|
195
|
+
case 'watch': return handleWatch(parsed);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const { urls, outputFile, outputFormat, exploreCount, showConfig: showCfg, showHelp, showVersion, customProxy, configAction, configKey, configValue, pipeMode, filterStr } = parsed;
|
|
199
|
+
const proxyUrl = customProxy || proxy;
|
|
200
|
+
const filter = parseFilter(filterStr);
|
|
201
|
+
|
|
202
|
+
if (showVersion) {
|
|
203
|
+
console.log(version);
|
|
204
|
+
process.exit(0);
|
|
205
|
+
}
|
|
206
|
+
if (showHelp) return showUsage();
|
|
207
|
+
if (configAction) return handleConfig(configAction, configKey, configValue);
|
|
208
|
+
if (showCfg) return showConfig(urls, outputFile);
|
|
209
|
+
if (urls.length === 0 && exploreCount === 0) return showUsage();
|
|
210
|
+
|
|
211
|
+
if (exploreCount > 0) {
|
|
212
|
+
await runExploreDefault(exploreCount, urls, proxyUrl, outputFile, outputFormat, pipeMode, filter);
|
|
213
|
+
} else {
|
|
214
|
+
await runScrapeDefault(urls, proxyUrl, outputFile, outputFormat, filter);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
main().catch(err => {
|
|
219
|
+
console.error(`错误: ${err.message}`);
|
|
220
|
+
process.exit(1);
|
|
221
|
+
});
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"user": {
|
|
3
|
+
"uniqueId": "bar.lar.lar.moeta",
|
|
4
|
+
"secUid": "MS4wLjABAAAA3cgKTWvKfga0JAWeakAzx3zQ-aFAC8RuQvxD4HQFraKKsc_TbOIyMo3_ofVlXofV",
|
|
5
|
+
"nickname": "Bar Lar Lar Moetain",
|
|
6
|
+
"ttSeller": false,
|
|
7
|
+
"verified": false,
|
|
8
|
+
"followerCount": 24000,
|
|
9
|
+
"videoCount": 749,
|
|
10
|
+
"followingCount": 4293,
|
|
11
|
+
"heartCount": 254300,
|
|
12
|
+
"signature": ""
|
|
13
|
+
},
|
|
14
|
+
"totalVideos": 5,
|
|
15
|
+
"videos": [
|
|
16
|
+
{
|
|
17
|
+
"id": "7638231799084158228",
|
|
18
|
+
"url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7638231799084158228"
|
|
19
|
+
},
|
|
20
|
+
{
|
|
21
|
+
"id": "7638162444698914068",
|
|
22
|
+
"url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7638162444698914068"
|
|
23
|
+
},
|
|
24
|
+
{
|
|
25
|
+
"id": "7638116251767819541",
|
|
26
|
+
"url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7638116251767819541"
|
|
27
|
+
},
|
|
28
|
+
{
|
|
29
|
+
"id": "7638069637321690388",
|
|
30
|
+
"url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7638069637321690388"
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"id": "7637927171025112341",
|
|
34
|
+
"url": "https://www.tiktok.com/@bar.lar.lar.moeta/video/7637927171025112341"
|
|
35
|
+
}
|
|
36
|
+
]
|
|
37
|
+
}
|