tt-help-cli-ycl 1.4.30 → 1.4.31

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.4.30",
3
+ "version": "1.4.31",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
@@ -736,15 +736,13 @@ async function runAutoMode(options) {
736
736
  try {
737
737
  const modals = await detectModals(session.page);
738
738
  const loginModal = modals.find(
739
- (m) =>
740
- m.type === "login-modal" || m.type === "modal-container",
739
+ (m) => m.type === "login-modal" || m.type === "modal-container",
741
740
  );
742
741
  if (loginModal) {
743
742
  console.error(` 🔔 检测到登录弹窗,正在关闭...`);
744
- const { closed, failed } = await closeAllModals(
745
- session.page,
746
- { waitMs: 300 },
747
- );
743
+ const { closed, failed } = await closeAllModals(session.page, {
744
+ waitMs: 300,
745
+ });
748
746
  console.error(
749
747
  ` 🔔 弹窗关闭: closed=${JSON.stringify(closed)} failed=${JSON.stringify(failed)}`,
750
748
  );
package/src/cli/search.js CHANGED
@@ -71,6 +71,7 @@ export async function handleSearch(parsed) {
71
71
  serverUrl,
72
72
  basePort = 3333,
73
73
  portCount = 10,
74
+ parallel = 1,
74
75
  proxy: cliProxy,
75
76
  } = searchOpts || {};
76
77
 
@@ -196,7 +197,9 @@ export async function handleSearch(parsed) {
196
197
  try {
197
198
  return await p.evaluate(async () => {
198
199
  try {
199
- const res = await fetch("https://www.tiktok.com/passport/web/account/info/");
200
+ const res = await fetch(
201
+ "https://www.tiktok.com/passport/web/account/info/",
202
+ );
200
203
  const data = await res.json();
201
204
  return data?.data?.unique_id || null;
202
205
  } catch {}
@@ -410,6 +413,7 @@ export async function handleSearch(parsed) {
410
413
  let searchError = null;
411
414
  try {
412
415
  ids = await collectSearchAuthors(browser, tag, {
416
+ parallel,
413
417
  log,
414
418
  exclude: myUsername ? [myUsername] : [],
415
419
  });
@@ -432,6 +436,7 @@ export async function handleSearch(parsed) {
432
436
  // 重试搜索
433
437
  try {
434
438
  ids = await collectSearchAuthors(browser, tag, {
439
+ parallel,
435
440
  log,
436
441
  exclude: myUsername ? [myUsername] : [],
437
442
  });
@@ -457,6 +462,7 @@ export async function handleSearch(parsed) {
457
462
  await new Promise((r) => setTimeout(r, 5000));
458
463
  try {
459
464
  ids = await collectSearchAuthors(browser, tag, {
465
+ parallel,
460
466
  log,
461
467
  exclude: myUsername ? [myUsername] : [],
462
468
  });
package/src/lib/args.js CHANGED
@@ -560,6 +560,7 @@ function parseSearchArgs(args) {
560
560
  let serverUrl = null;
561
561
  let basePort = 3333;
562
562
  let portCount = 10;
563
+ let parallel = 1;
563
564
  let proxy = null;
564
565
 
565
566
  for (let i = 0; i < args.length; i++) {
@@ -570,6 +571,8 @@ function parseSearchArgs(args) {
570
571
  basePort = parseInt(args[++i]) || 3333;
571
572
  } else if (arg === "--port-count") {
572
573
  portCount = parseInt(args[++i]) || 10;
574
+ } else if (arg === "-p" || arg === "--parallel") {
575
+ parallel = parseInt(args[++i]) || 1;
573
576
  } else if (arg === "--proxy") {
574
577
  proxy = args[++i];
575
578
  }
@@ -577,7 +580,7 @@ function parseSearchArgs(args) {
577
580
 
578
581
  return {
579
582
  subcommand: "search",
580
- search: { serverUrl, basePort, portCount, proxy },
583
+ search: { serverUrl, basePort, portCount, parallel, proxy },
581
584
  urls: [],
582
585
  outputFormat: "json",
583
586
  exploreCount: 0,
@@ -316,19 +316,21 @@ const HELP_TEXT = [
316
316
  "",
317
317
  " search [选项]",
318
318
  " 基于 productive 标签搜索 TikTok 作者(score>70 的标签作为搜索词)",
319
- " 5 个搜索 Tab 并行采集(综合/视频/用户/直播/照片),去重后提交到 job_base",
319
+ " 4 个搜索 Tab 采集(综合/视频/用户/照片),去重后提交到 job_base",
320
320
  " 选项:",
321
321
  " -s, --server <URL> 服务端地址(默认 http://127.0.0.1:3000)",
322
322
  " --base-port <端口号> 起始 CDP 端口,默认 3333",
323
323
  " --port-count <数量> 端口数量(账户数),默认 10",
324
+ " -p, --parallel <N> 并行搜索数,默认 1(串行)",
324
325
  " --proxy <地址> 浏览器代理",
325
326
  " 行为:",
326
327
  " - 从 tags 表取 score>70 的 productive 标签",
327
328
  " - 去掉 # 号后作为搜索关键词",
328
- " - 并行搜索 5 个 tab,去重后提交到 job_base(来源=search)",
329
+ " - 搜索 4 个 tab,去重后提交到 job_base(来源=search)",
329
330
  " - 猜测国家取标签 countries 数组的第一个",
330
331
  " 示例: tt-help search",
331
332
  " tt-help search -s http://127.0.0.1:3001",
333
+ " tt-help search -p 2 -s http://127.0.0.1:3001",
332
334
  " tt-help search --base-port 3333 -s http://127.0.0.1:3001",
333
335
  " tt-help search --proxy http://127.0.0.1:7890",
334
336
  "",
@@ -14,7 +14,7 @@ const TAB_URLS = {
14
14
  照片: (q) => `https://www.tiktok.com/search/photo?q=${encodeURIComponent(q)}`,
15
15
  };
16
16
 
17
- const DEFAULT_TABS = ["综合", "视频", "用户", "直播", "照片"];
17
+ const DEFAULT_TABS = ["综合", "视频", "用户", "照片"];
18
18
 
19
19
  const SCROLL = {
20
20
  maxRounds: 60,
@@ -60,10 +60,9 @@ async function scrollPage(page) {
60
60
 
61
61
  async function waitForSearchResults(page, timeoutMs = 20000) {
62
62
  try {
63
- await page.waitForSelector(
64
- '[class*="DivItemContainer"]',
65
- { timeout: timeoutMs },
66
- );
63
+ await page.waitForSelector('[class*="DivItemContainer"]', {
64
+ timeout: timeoutMs,
65
+ });
67
66
  return true;
68
67
  } catch {
69
68
  return false;
@@ -160,24 +159,41 @@ async function processOneTab(page, tabName, keyword, allIds, excludeSet, log) {
160
159
  */
161
160
  export async function collectSearchAuthors(browser, keyword, opts = {}) {
162
161
  const tabs = opts.tabs || DEFAULT_TABS;
162
+ const parallel = Math.max(1, opts.parallel || 1);
163
163
  const log = opts.log || ((...args) => console.error(...args));
164
164
  const excludeSet = new Set(opts.exclude || []);
165
165
 
166
- log(`[collect] "${keyword}" Tab: [${tabs.join(", ")}]`);
166
+ log(`[collect] "${keyword}" Tab: [${tabs.join(", ")}] 并行: ${parallel}`);
167
167
  if (excludeSet.size > 0)
168
168
  log(`[collect] 排除: [${[...excludeSet].join(", ")}]`);
169
169
 
170
170
  const context = browser.contexts()[0];
171
- const page = context.pages()[0];
171
+ const mainPage = context.pages()[0];
172
172
  const allIds = new Set();
173
173
 
174
- // 串行:复用同一个页面,逐个 tab 采集
175
- for (const tabName of tabs) {
176
- try {
177
- await processOneTab(page, tabName, keyword, allIds, excludeSet, log);
178
- } catch (e) {
179
- log(`[${tabName}] 异常: ${e.message}`);
180
- }
174
+ // 分批并行:每批 parallel 个 tab,批内并行、批间串行
175
+ for (let i = 0; i < tabs.length; i += parallel) {
176
+ const batch = tabs.slice(i, i + parallel);
177
+ const batchPages = [];
178
+
179
+ const tasks = batch.map(async (tabName, j) => {
180
+ let page = null;
181
+ try {
182
+ page = j === 0 ? mainPage : await context.newPage();
183
+ if (j > 0) batchPages.push(page);
184
+ await processOneTab(page, tabName, keyword, allIds, excludeSet, log);
185
+ } catch (e) {
186
+ log(`[${tabName}] 异常: ${e.message}`);
187
+ } finally {
188
+ if (j > 0 && page) {
189
+ try {
190
+ await page.close();
191
+ } catch {}
192
+ }
193
+ }
194
+ });
195
+
196
+ await Promise.all(tasks);
181
197
  }
182
198
 
183
199
  return [...allIds].sort();