tt-help-cli-ycl 1.0.5 → 1.0.6

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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tt-help-cli-ycl",
3
- "version": "1.0.5",
3
+ "version": "1.0.6",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
package/src/lib/io.js CHANGED
@@ -1,5 +1,7 @@
1
1
  import { writeFileSync, readFileSync } from 'fs';
2
2
 
3
+ let lastBarCount = 0;
4
+
3
5
  export function writeOutput(data, outputFile) {
4
6
  const output = JSON.stringify(data, null, 2);
5
7
  const target = outputFile || 'tiktok_data.json';
@@ -11,3 +13,64 @@ export function readUrlFile(filePath) {
11
13
  const content = readFileSync(filePath, 'utf-8');
12
14
  return content.split(/\r?\n/).map(l => l.trim()).filter(l => l.startsWith('http'));
13
15
  }
16
+
17
+ export function createProgressBar(current, total, maxWidth = 30) {
18
+ const filled = Math.round((current / total) * maxWidth);
19
+ return '█'.repeat(filled).padEnd(maxWidth);
20
+ }
21
+
22
+ export function calculateConcurrency(total) {
23
+ return Math.min(5, Math.max(1, Math.floor(total / 10)), total);
24
+ }
25
+
26
+ export function extractUrlDisplay(url) {
27
+ try {
28
+ const pathname = new URL(url).pathname;
29
+ const parts = pathname.split('/').filter(Boolean);
30
+ return parts.slice(-2).join('/');
31
+ } catch {
32
+ return url;
33
+ }
34
+ }
35
+
36
+ export function createMultiProgressBars(count) {
37
+ return Array.from({ length: count }, () => ({
38
+ current: 0,
39
+ total: 0,
40
+ status: 'pending',
41
+ url: '',
42
+ }));
43
+ }
44
+
45
+ export function renderMultiProgressBars(bars, maxWidth = 30) {
46
+ const activeBars = bars.filter(bar => bar.total > 0);
47
+
48
+ if (activeBars.length === 0) return;
49
+
50
+ const lines = activeBars.map((bar) => {
51
+ const prog = createProgressBar(bar.current, bar.total, maxWidth);
52
+ const icon = bar.status === 'done' ? '✓' :
53
+ bar.status === 'error' ? '' : '⟳';
54
+ const urlDisplay = bar.url ? extractUrlDisplay(bar.url) : '';
55
+ return ` [${prog}] ${bar.current}/${bar.total} ${icon} ${urlDisplay}`;
56
+ });
57
+
58
+ const output = lines.join('\n');
59
+
60
+ if (lastBarCount > 0) {
61
+ process.stdout.write(`\x1b[${lastBarCount}A`);
62
+ }
63
+
64
+ process.stdout.write('\x1b[0J');
65
+ process.stdout.write(output + '\n');
66
+
67
+ lastBarCount = activeBars.length;
68
+ }
69
+
70
+ export function clearProgressBars() {
71
+ if (lastBarCount > 0) {
72
+ process.stdout.write(`\x1b[${lastBarCount}A`);
73
+ process.stdout.write('\x1b[0J');
74
+ lastBarCount = 0;
75
+ }
76
+ }
package/src/main.mjs CHANGED
@@ -4,6 +4,7 @@ import { fetchExplore } from './lib/explore.js';
4
4
  import { processUrl } from './lib/scrape.js';
5
5
  import { deduplicate, formatOutput } from './lib/output.js';
6
6
  import { parseFilter, applyFilter, formatFilterDescription } from './lib/filter.js';
7
+ import { createProgressBar, calculateConcurrency, createMultiProgressBars, renderMultiProgressBars, clearProgressBars } from './lib/io.js';
7
8
  import { writeFileSync, readFileSync, existsSync } from 'fs';
8
9
 
9
10
  function showConfig(urls, outputFile) {
@@ -72,6 +73,10 @@ function handleConfig(action, value) {
72
73
  process.exit(1);
73
74
  }
74
75
 
76
+ function randomDelay() {
77
+ return new Promise(r => setTimeout(r, Math.random() * 600 + 200));
78
+ }
79
+
75
80
  function cleanError(msg) {
76
81
  return msg
77
82
  .replace(/\x1b\[[0-9;]*m/g, '')
@@ -112,16 +117,46 @@ async function runExplore(exploreCount, urls, proxyUrl, outputFile, outputFormat
112
117
  if (urls.length > 0) {
113
118
  const errors = [];
114
119
 
115
- for (let i = 0; i < urls.length; i++) {
116
- const bar = '█'.repeat(i + 1).padEnd(urls.length);
117
- process.stdout.write(`\r [${bar}] ${i + 1}/${urls.length}`);
118
- try {
119
- const results = await processUrl(urls[i], proxyUrl);
120
- allResults.push(...results);
121
- } catch (err) {
122
- errors.push({ url: urls[i], message: err.message });
120
+ const concurrency = calculateConcurrency(urls.length);
121
+ const bars = createMultiProgressBars(concurrency);
122
+
123
+ const slots = Array.from({ length: concurrency }, () => []);
124
+ urls.forEach((url, i) => slots[i % concurrency].push(url));
125
+
126
+ bars.forEach((bar, i) => {
127
+ bar.total = slots[i].length;
128
+ bar.status = slots[i].length > 0 ? 'running' : 'done';
129
+ });
130
+
131
+ renderMultiProgressBars(bars);
132
+
133
+ const workers = slots.map(async (slotUrls, slotIndex) => {
134
+ for (const url of slotUrls) {
135
+ bars[slotIndex].url = url;
136
+ renderMultiProgressBars(bars);
137
+
138
+ await randomDelay();
139
+
140
+ try {
141
+ const results = await processUrl(url, proxyUrl);
142
+ allResults.push(...results);
143
+ bars[slotIndex].current++;
144
+ bars[slotIndex].status = 'running';
145
+ } catch (err) {
146
+ errors.push({ url, message: err.message });
147
+ bars[slotIndex].current++;
148
+ bars[slotIndex].status = 'error';
149
+ }
150
+
151
+ renderMultiProgressBars(bars);
123
152
  }
124
- }
153
+ bars[slotIndex].status = bars[slotIndex].current === bars[slotIndex].total ? 'done' : 'error';
154
+ renderMultiProgressBars(bars);
155
+ });
156
+
157
+ await Promise.all(workers);
158
+
159
+ clearProgressBars();
125
160
  console.log();
126
161
 
127
162
  if (errors.length > 0) {
@@ -173,16 +208,52 @@ async function runScrape(urls, proxyUrl, outputFile, outputFormat, filter) {
173
208
  const allResults = [];
174
209
  const errors = [];
175
210
 
176
- for (let i = 0; i < urls.length; i++) {
177
- const bar = ''.repeat(i + 1).padEnd(urls.length);
178
- process.stdout.write(`\r [${bar}] ${i + 1}/${urls.length}`);
179
- try {
180
- const results = await processUrl(urls[i], proxyUrl);
181
- allResults.push(...results);
182
- } catch (err) {
183
- errors.push({ url: urls[i], message: err.message });
211
+ if (urls.length === 0) {
212
+ console.log('\n未获取到数据');
213
+ if (outputFile) {
214
+ writeFileSync(outputFile, '[]', 'utf-8');
184
215
  }
216
+ return;
185
217
  }
218
+
219
+ const concurrency = calculateConcurrency(urls.length);
220
+ const bars = createMultiProgressBars(concurrency);
221
+
222
+ const slots = Array.from({ length: concurrency }, () => []);
223
+ urls.forEach((url, i) => slots[i % concurrency].push(url));
224
+
225
+ bars.forEach((bar, i) => {
226
+ bar.total = slots[i].length;
227
+ bar.status = slots[i].length > 0 ? 'running' : 'done';
228
+ });
229
+
230
+ renderMultiProgressBars(bars);
231
+
232
+ const workers = slots.map(async (slotUrls, slotIndex) => {
233
+ for (const url of slotUrls) {
234
+ bars[slotIndex].url = url;
235
+ renderMultiProgressBars(bars);
236
+
237
+ try {
238
+ const results = await processUrl(url, proxyUrl);
239
+ allResults.push(...results);
240
+ bars[slotIndex].current++;
241
+ bars[slotIndex].status = 'running';
242
+ } catch (err) {
243
+ errors.push({ url, message: err.message });
244
+ bars[slotIndex].current++;
245
+ bars[slotIndex].status = 'error';
246
+ }
247
+
248
+ renderMultiProgressBars(bars);
249
+ }
250
+ bars[slotIndex].status = bars[slotIndex].current === bars[slotIndex].total ? 'done' : 'error';
251
+ renderMultiProgressBars(bars);
252
+ });
253
+
254
+ await Promise.all(workers);
255
+
256
+ clearProgressBars();
186
257
  console.log();
187
258
 
188
259
  const uniqueResults = deduplicate(allResults);