tt-help-cli-ycl 1.3.86 → 1.3.88

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.3.86",
3
+ "version": "1.3.88",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
package/src/cli/tag.js CHANGED
@@ -419,7 +419,7 @@ async function reportToServer(baseUrl, result) {
419
419
 
420
420
  export async function handleScoreAll(parsed) {
421
421
  const { tagScoreAll } = parsed;
422
- let { countries, serverUrl } = tagScoreAll || {};
422
+ let { countries, serverUrl, autoDiscover } = tagScoreAll || {};
423
423
 
424
424
  const baseUrl = serverUrl || DEFAULT_SERVER;
425
425
  const targetCountries = countries || [
@@ -445,13 +445,15 @@ export async function handleScoreAll(parsed) {
445
445
  log(" 自动循环打分模式(客户端本地执行)");
446
446
  log(` 目标国家: ${targetCountries.join(", ")}`);
447
447
  log(` 服务端: ${baseUrl}`);
448
+ if (autoDiscover) log(` 自动发现: 开启(无任务时自动生成标签)`);
448
449
  log(" 流程: 从服务端拉 tag → 本地 Playwright 抓取 → enrich → 算分 → 上报");
449
450
  log(" 每个标签约 1-2 分钟");
450
451
  log("========================================");
451
452
  log("");
452
453
 
453
454
  let totalScored = 0;
454
- let totalNew = null;
455
+ let lastDiscoverTime = 0;
456
+ const DISCOVER_COOLDOWN = 5 * 60 * 1000; // 5 分钟冷却
455
457
 
456
458
  // 复用 TikTokScraper 实例,避免每次 enrich 都启动/关闭 headless 浏览器
457
459
  const enrichScraper = new TikTokScraper({ poolSize: 3 });
@@ -461,28 +463,34 @@ export async function handleScoreAll(parsed) {
461
463
 
462
464
  try {
463
465
  while (true) {
464
- // 查剩余数量
465
- if (totalNew === null) {
466
- try {
467
- const statsRes = await fetch(
468
- `${baseUrl}/api/tags?status=new&limit=1000`,
469
- );
470
- const statsData = await statsRes.json();
471
- totalNew = statsData.total || 0;
472
- log(`📋 待打分标签: ${totalNew} 个`);
473
- log("");
474
- } catch (e) {
475
- log(`⚠️ 无法连接服务端: ${e.message}`);
476
- break;
477
- }
478
- }
479
-
480
466
  // 从服务端取下一个 new 标签
481
467
  const tagsRes = await fetch(`${baseUrl}/api/tags?status=new&limit=1`);
482
468
  const tagsData = await tagsRes.json();
483
469
  if (!tagsData.tags || tagsData.tags.length === 0) {
484
- log(` ⏳ 暂无待打分标签,10 秒后重试...`);
485
- totalNew = null; // 重置计数,下次新标签到达时重新查询
470
+ // 自动发现:无任务时自动生成标签
471
+ if (autoDiscover && Date.now() - lastDiscoverTime > DISCOVER_COOLDOWN) {
472
+ log(
473
+ `🔍 无待打分标签,自动为 ${targetCountries.length} 个国家生成标签...`,
474
+ );
475
+ for (const country of targetCountries) {
476
+ try {
477
+ const discRes = await fetch(
478
+ `${baseUrl}/api/tags/discover?country=${country}&count=5`,
479
+ );
480
+ const discData = await discRes.json();
481
+ if (discData.inserted) {
482
+ log(` ${country}: 新增 ${discData.inserted} 个`);
483
+ }
484
+ } catch (e) {
485
+ log(` ${country}: 请求失败 (${e.message})`);
486
+ }
487
+ }
488
+ lastDiscoverTime = Date.now();
489
+ // 等 3 秒让服务端处理完
490
+ await new Promise((r) => setTimeout(r, 3000));
491
+ continue;
492
+ }
493
+ log(`⏳ 暂无待打分标签,10 秒后重试...`);
486
494
  await new Promise((r) => setTimeout(r, 10000));
487
495
  continue;
488
496
  }
@@ -490,7 +498,7 @@ export async function handleScoreAll(parsed) {
490
498
  const tag = tagsData.tags[0].tag.replace(/^#+/, "").trim().toLowerCase();
491
499
  const startTime = Date.now();
492
500
 
493
- log(`[${totalScored + 1}/${totalNew || "?"}] 正在打分 #${tag} ...`);
501
+ log(`[${totalScored + 1}] 正在打分 #${tag} ...`);
494
502
 
495
503
  const result = {
496
504
  tag,
@@ -551,7 +559,6 @@ export async function handleScoreAll(parsed) {
551
559
  }
552
560
 
553
561
  // enrich: 逐个视频查 view-source 获取国家
554
- log(` 补充国家信息...`);
555
562
  const enriched = await enrichVideosWithLocation(videos, {
556
563
  mode: "videos",
557
564
  existingScraper: enrichScraper,
@@ -564,8 +571,6 @@ export async function handleScoreAll(parsed) {
564
571
  },
565
572
  });
566
573
  videos = enriched.videos;
567
- const withLoc = videos.filter((v) => v.locationCreated).length;
568
- log(` 完成: ${withLoc}/${videos.length} 个视频有国家信息`);
569
574
 
570
575
  // 过滤 + 算分 (共用函数)
571
576
  const { matchedAuthorSet } = applyFilterAndScore(
@@ -601,10 +606,8 @@ export async function handleScoreAll(parsed) {
601
606
  .map((c) => `${c.c}:${c.n}`)
602
607
  .join(" ");
603
608
  log(
604
- ` ${icon} ${result.status} score=${result.score} authors=${result.authorCount} matched=${result.matchedAuthors} (${elapsed}s)`,
609
+ ` ${icon} ${result.status} score=${result.score} authors=${result.authorCount} matched=${result.matchedAuthors} (${elapsed}s)${mc ? " " + mc : ""}`,
605
610
  );
606
- if (mc) log(` 国家: ${mc}`);
607
- log(` 剩余: ~${Math.max(0, (totalNew || 0) - totalScored)} 个`);
608
611
  log("");
609
612
  } catch (e) {
610
613
  log(` ❌ 失败: ${e.message}`);
package/src/lib/args.js CHANGED
@@ -727,6 +727,7 @@ function parseTagArgs(args) {
727
727
  let discoverCountries = [];
728
728
  let discoverCount = 4;
729
729
  let discoverPrompt = null;
730
+ let autoDiscover = false;
730
731
  let isDiscover = false;
731
732
  let isScore = false;
732
733
  let isScoreAll = false;
@@ -767,6 +768,8 @@ function parseTagArgs(args) {
767
768
  }
768
769
  } else if (arg === "--count") {
769
770
  discoverCount = parseInt(args[++i]) || 4;
771
+ } else if (arg === "--auto-discover") {
772
+ autoDiscover = true;
770
773
  } else if (arg === "--countries") {
771
774
  scoreCountries = args[++i]
772
775
  .split(",")
@@ -854,6 +857,7 @@ function parseTagArgs(args) {
854
857
  tagScoreAll: {
855
858
  countries: scoreCountries,
856
859
  serverUrl,
860
+ autoDiscover,
857
861
  },
858
862
  urls: [],
859
863
  outputFormat: "json",
@@ -94,7 +94,13 @@ export async function isLoggedIn(page) {
94
94
  console.error(
95
95
  ` [登录检测] DOM 无法判断,刷新页面后重试 (${attempt}/${DOM_CHECK_RETRIES})...`,
96
96
  );
97
- await page.reload({ waitUntil: "domcontentloaded" });
97
+ try {
98
+ await page.reload({ waitUntil: "domcontentloaded", timeout: 30000 });
99
+ } catch (reloadErr) {
100
+ console.error(
101
+ ` [登录检测] 页面刷新失败: ${reloadErr.message},跳过重试继续检测...`,
102
+ );
103
+ }
98
104
  }
99
105
  }
100
106
  // 重试后仍无法判断,信任 Cookie
@@ -125,9 +131,16 @@ export async function safeCheckLogin(page) {
125
131
  for (let round = 2; round <= SAFE_CHECK_ROUNDS; round++) {
126
132
  await new Promise((r) => setTimeout(r, SAFE_CHECK_INTERVAL));
127
133
  // 重新导航到 TikTok 页面,确保页面状态刷新
128
- await page.goto("https://www.tiktok.com", {
129
- waitUntil: "domcontentloaded",
130
- });
134
+ try {
135
+ await page.goto("https://www.tiktok.com", {
136
+ waitUntil: "domcontentloaded",
137
+ timeout: 30000,
138
+ });
139
+ } catch (gotoErr) {
140
+ console.error(
141
+ `[安全登录检测] 第 ${round} 轮: 页面导航失败: ${gotoErr.message},直接在当前页面检测...`,
142
+ );
143
+ }
131
144
  loggedIn = await isLoggedIn(page);
132
145
  if (loggedIn) {
133
146
  console.error(`[安全登录检测] 第 ${round} 轮: 已登录 ✓`);