tt-help-cli-ycl 1.4.25 → 1.4.27

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.25",
3
+ "version": "1.4.27",
4
4
  "description": "TikTok user & video data scraper - extract ttSeller, verified, locationCreated from HTML source",
5
5
  "type": "module",
6
6
  "bin": {
@@ -13,22 +13,6 @@
13
13
 
14
14
  import Database from "better-sqlite3";
15
15
 
16
- // ===== 目标国家 =====
17
- const TARGET_LOCATIONS = [
18
- "CZ",
19
- "GR",
20
- "HU",
21
- "PT",
22
- "ES",
23
- "PL",
24
- "NL",
25
- "BE",
26
- "DE",
27
- "FR",
28
- "IT",
29
- "IE",
30
- "AT",
31
- ];
32
16
 
33
17
  // ===== 命令行参数 =====
34
18
  function parseArgs() {
@@ -37,7 +21,7 @@ function parseArgs() {
37
21
  db: null,
38
22
  archive: null,
39
23
  interval: 30,
40
- threshold: 1000,
24
+ max: 10000,
41
25
  batch: 500,
42
26
  once: false,
43
27
  };
@@ -52,8 +36,8 @@ function parseArgs() {
52
36
  case "--interval":
53
37
  opts.interval = parseInt(args[++i]) || 30;
54
38
  break;
55
- case "--threshold":
56
- opts.threshold = parseInt(args[++i]) || 1000;
39
+ case "--max":
40
+ opts.max = parseInt(args[++i]) || 10000;
57
41
  break;
58
42
  case "--batch":
59
43
  opts.batch = parseInt(args[++i]) || 500;
@@ -77,24 +61,25 @@ if (!opts.db || !opts.archive) {
77
61
  console.error(" --db <path> 主库路径(必填)");
78
62
  console.error(" --archive <path> 归档库路径(必填)");
79
63
  console.error(" --interval <秒> 检测间隔,默认 30");
80
- console.error(" --threshold <条> 每个国家最低保有量,默认 1000");
64
+ console.error(" --max <条> 主库 raw_jobs 保有量上限,默认 10000");
81
65
  console.error(" --batch <条> 每次搬移上限,默认 500");
82
66
  console.error(" --once 只执行一次后退出");
83
67
  process.exit(1);
84
68
  }
85
69
 
86
70
  // ===== 过滤与排序 =====
87
- const FILTER_CONDITIONS = [
88
- "COALESCE(video_count, 0) > 6",
89
- "COALESCE(follower_count, 0) > 0",
90
- "COALESCE(following_count, 0) > 0",
71
+ // 注意: 索引依赖的 WHERE 用原始列名(不用 COALESCE),与 idx_raw_archive_sync 匹配
72
+ const FILTER_INDEXED = [
73
+ "video_count > 6",
74
+ "follower_count > 0",
75
+ "following_count > 0",
91
76
  ];
92
77
 
93
78
  const ORDER_CLAUSE = `
94
79
  ORDER BY
95
80
  CASE WHEN sources LIKE '%tag%' THEN 0 ELSE 1 END,
96
81
  CASE WHEN sources LIKE '%comment%' THEN 0 ELSE 1 END,
97
- COALESCE(video_count, 0) DESC,
82
+ video_count DESC,
98
83
  created_at DESC
99
84
  `;
100
85
 
@@ -136,23 +121,17 @@ const RAW_JOB_COLUMNS = [
136
121
  "user_create_time",
137
122
  ];
138
123
 
139
- // ===== 核心:从归档库搬移一批到主库 =====
140
- function moveBatch(mainDb, archiveDb, country, limit) {
141
- const whereSql = [
142
- ...FILTER_CONDITIONS,
143
- `(UPPER(COALESCE(guessed_location, '')) = ? OR COALESCE(guessed_location, '') = '')`,
144
- ].join(" AND ");
145
-
124
+ // ===== 核心:从归档库搬移一批到主库(全局排序,不区分国家)=====
125
+ function moveBatch(mainDb, archiveDb, limit) {
126
+ const whereSql = FILTER_INDEXED.join(" AND ");
146
127
  const cols = RAW_JOB_COLUMNS.join(", ");
147
128
 
148
- // 1. INSERT OR IGNORE 到主库
149
129
  const insertSql = `
150
130
  INSERT OR IGNORE INTO raw_jobs (${cols})
151
131
  SELECT ${cols}
152
132
  FROM (SELECT * FROM raw_jobs WHERE ${whereSql} ${ORDER_CLAUSE} LIMIT ?)
153
133
  `;
154
134
 
155
- // 2. DELETE 归档库中已搬走的数据(匹配同一批次的 unique_id)
156
135
  const deleteSql = `
157
136
  DELETE FROM raw_jobs
158
137
  WHERE unique_id IN (
@@ -162,10 +141,8 @@ function moveBatch(mainDb, archiveDb, country, limit) {
162
141
  `;
163
142
 
164
143
  const moved = archiveDb.transaction(() => {
165
- // 先删后插?不——先插后删,防止 INSERT OR IGNORE 冲突导致删了没插
166
- // INSERT OR IGNORE: 如果主库已有该 unique_id,跳过但不报错
167
- mainDb.prepare(insertSql).run(country, limit);
168
- const del = archiveDb.prepare(deleteSql).run(country, limit);
144
+ mainDb.prepare(insertSql).run(limit);
145
+ const del = archiveDb.prepare(deleteSql).run(limit);
169
146
  return del.changes;
170
147
  })();
171
148
 
@@ -173,52 +150,35 @@ function moveBatch(mainDb, archiveDb, country, limit) {
173
150
  }
174
151
 
175
152
  // ===== 单次同步 =====
176
- function syncOnce(mainDb, archiveDb) {
153
+ function syncOnce(mainDb, archiveDb, maxTotal, batch) {
177
154
  const start = Date.now();
178
- const results = {};
179
-
180
- for (const country of TARGET_LOCATIONS) {
181
- // 检查主库中该国存量
182
- const countRow = mainDb
183
- .prepare(
184
- `SELECT COUNT(*) as c FROM raw_jobs
185
- WHERE UPPER(COALESCE(guessed_location, '')) = ?`,
186
- )
187
- .get(country);
188
-
189
- const current = countRow?.c || 0;
190
-
191
- if (current >= opts.threshold) {
192
- continue;
193
- }
194
-
195
- const need = opts.threshold - current;
196
- const limit = Math.min(need, opts.batch);
197
155
 
198
- const moved = moveBatch(mainDb, archiveDb, country, limit);
156
+ // 主库 raw_jobs 总数够了就不搬
157
+ const currentTotal =
158
+ mainDb.prepare("SELECT COUNT(*) as c FROM raw_jobs").get()?.c || 0;
199
159
 
200
- results[country] = { before: current, moved, after: current + moved };
201
-
202
- console.error(
203
- `[sync] ${country}: ${current} → ${current + moved} (搬入 ${moved} 条)`,
204
- );
160
+ if (currentTotal >= maxTotal) {
161
+ return { moved: 0, total: currentTotal };
205
162
  }
206
163
 
207
- const totalMoved = Object.values(results).reduce((s, r) => s + r.moved, 0);
164
+ const need = maxTotal - currentTotal;
165
+ const limit = Math.min(need, batch);
166
+
167
+ const moved = moveBatch(mainDb, archiveDb, limit);
208
168
  const elapsed = ((Date.now() - start) / 1000).toFixed(1);
209
169
 
210
- if (totalMoved > 0) {
211
- console.error(`[sync] 本轮共搬入 ${totalMoved} 条,耗时 ${elapsed}s`);
212
- }
170
+ console.error(
171
+ `[sync] ${currentTotal} → ${currentTotal + moved} (搬入 ${moved} 条),耗时 ${elapsed}s`,
172
+ );
213
173
 
214
- return results;
174
+ return { moved, total: currentTotal + moved };
215
175
  }
216
176
 
217
177
  // ===== 主流程 =====
218
178
  console.error("[sync] raw_jobs 同步程序启动");
219
179
  console.error(`[sync] 主库: ${opts.db}`);
220
180
  console.error(`[sync] 归档库: ${opts.archive}`);
221
- console.error(`[sync] 最低保有量: ${opts.threshold} 条/国家`);
181
+ console.error(`[sync] 主库上限: ${opts.max} 条,每次搬 ${opts.batch} 条`);
222
182
  console.error(`[sync] 检测间隔: ${opts.once ? "仅一次" : opts.interval + "s"}`);
223
183
  console.error("");
224
184
 
@@ -255,10 +215,23 @@ const archiveTotal =
255
215
  console.error(
256
216
  `[sync] 归档库 raw_jobs 总量: ${archiveTotal.toLocaleString()} 条`,
257
217
  );
218
+
219
+ // 确保归档库有加速索引(首次运行自动创建)
220
+ const startIdx = Date.now();
221
+ archiveDb.exec(`
222
+ CREATE INDEX IF NOT EXISTS idx_raw_archive_sync
223
+ ON raw_jobs(video_count DESC, created_at DESC)
224
+ WHERE video_count > 6 AND follower_count > 0 AND following_count > 0;
225
+ `);
226
+ const idxElapsed = ((Date.now() - startIdx) / 1000).toFixed(1);
227
+ if (idxElapsed > 0.1) {
228
+ console.error(`[sync] 归档库索引就绪,耗时 ${idxElapsed}s`);
229
+ }
258
230
  console.error("");
259
231
 
260
232
  // 执行同步
261
- syncOnce(mainDb, archiveDb);
233
+ const MAX_TOTAL = opts.max ?? 10000;
234
+ syncOnce(mainDb, archiveDb, MAX_TOTAL, opts.batch);
262
235
 
263
236
  if (opts.once) {
264
237
  mainDb.close();
@@ -269,7 +242,7 @@ if (opts.once) {
269
242
 
270
243
  // 定时循环
271
244
  setInterval(() => {
272
- syncOnce(mainDb, archiveDb);
245
+ syncOnce(mainDb, archiveDb, MAX_TOTAL, opts.batch);
273
246
  }, opts.interval * 1000);
274
247
 
275
248
  // 优雅退出
@@ -16,8 +16,8 @@ export function handleRawSync(parsed) {
16
16
 
17
17
  if (parsed.rawSyncInterval != null)
18
18
  args.push("--interval", String(parsed.rawSyncInterval));
19
- if (parsed.rawSyncThreshold != null)
20
- args.push("--threshold", String(parsed.rawSyncThreshold));
19
+ if (parsed.rawSyncMax != null)
20
+ args.push("--max", String(parsed.rawSyncMax));
21
21
  if (parsed.rawSyncBatch != null)
22
22
  args.push("--batch", String(parsed.rawSyncBatch));
23
23
  if (parsed.rawSyncOnce) args.push("--once");
package/src/lib/args.js CHANGED
@@ -690,7 +690,7 @@ function parseRawSyncArgs(args) {
690
690
  let rawSyncDb = null;
691
691
  let rawSyncArchive = null;
692
692
  let rawSyncInterval = null;
693
- let rawSyncThreshold = null;
693
+ let rawSyncMax = null;
694
694
  let rawSyncBatch = null;
695
695
  let rawSyncOnce = false;
696
696
 
@@ -702,8 +702,8 @@ function parseRawSyncArgs(args) {
702
702
  rawSyncArchive = args[++i];
703
703
  } else if (arg === "--interval" && i + 1 < args.length) {
704
704
  rawSyncInterval = parseInt(args[++i]) || null;
705
- } else if (arg === "--threshold" && i + 1 < args.length) {
706
- rawSyncThreshold = parseInt(args[++i]) || null;
705
+ } else if (arg === "--max" && i + 1 < args.length) {
706
+ rawSyncMax = parseInt(args[++i]) || null;
707
707
  } else if (arg === "--batch" && i + 1 < args.length) {
708
708
  rawSyncBatch = parseInt(args[++i]) || null;
709
709
  } else if (arg === "--once") {
@@ -716,7 +716,7 @@ function parseRawSyncArgs(args) {
716
716
  rawSyncDb,
717
717
  rawSyncArchive,
718
718
  rawSyncInterval,
719
- rawSyncThreshold,
719
+ rawSyncMax,
720
720
  rawSyncBatch,
721
721
  rawSyncOnce,
722
722
  urls: [],
@@ -315,17 +315,17 @@ const HELP_TEXT = [
315
315
  " tt-help tag refresh --port 8222 -s http://127.0.0.1:3001",
316
316
  "",
317
317
  " raw-sync --db <主库路径> --archive <归档库路径> [选项]",
318
- " 从归档库向主库 raw_jobs 同步数据,按国家保持最低保有量",
318
+ " 从归档库向主库 raw_jobs 同步数据,保持主库总量不超上限",
319
319
  " 选项:",
320
320
  " --db <路径> 主库路径(必填)",
321
321
  " --archive <路径> 归档库路径(必填)",
322
322
  " --interval <秒> 检测间隔,默认 30",
323
- " --threshold <条> 每个国家最低保有量,默认 1000",
324
- " --batch <条> 每次搬移上限,默认 500",
323
+ " --max <条> 主库 raw_jobs 保有量上限,默认 10000",
324
+ " --batch <条> 每次搬移条数,默认 500",
325
325
  " --once 只执行一次后退出",
326
326
  " 示例: tt-help raw-sync --db data/result.db --archive data/result-archive.db",
327
327
  " tt-help raw-sync --db data/result.db --archive data/result-archive.db --once",
328
- " tt-help raw-sync --db data/result.db --archive data/result-archive.db --interval 60 --threshold 2000",
328
+ " tt-help raw-sync --db data/result.db --archive data/result-archive.db --interval 60 --max 20000",
329
329
  "",
330
330
  " config [show|set|unset|reset]",
331
331
  " config 查看当前配置",
package/src/npm-main.js CHANGED
@@ -15,6 +15,7 @@ import {
15
15
  handleScoreAll,
16
16
  handleTagRefresh,
17
17
  } from "./cli/tag.js";
18
+ import { handleRawSync } from "./cli/raw-sync.js";
18
19
 
19
20
  function exitUnsupportedCommand(command) {
20
21
  console.error(
@@ -56,6 +57,8 @@ async function main() {
56
57
  return handleScoreAll(parsed);
57
58
  case "tag-refresh":
58
59
  return handleTagRefresh(parsed);
60
+ case "raw-sync":
61
+ return handleRawSync(parsed);
59
62
  }
60
63
 
61
64
  const {