merge_replies.js 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106
  1. /**
  2. * 把 /replies/*.json 的子回复合并进顶级评论 JSON
  3. * 用法: node merge_replies.js <comments.json> <repliesDir> <outDir> [awemeId]
  4. */
  5. const fs = require('fs');
  6. const path = require('path');
  7. const inFile = process.argv[2];
  8. const repliesDir = process.argv[3];
  9. const outDir = process.argv[4] || path.dirname(inFile);
  10. const awemeId = process.argv[5] || '7552202593888603410';
  11. const comments = JSON.parse(fs.readFileSync(inFile, 'utf8'));
  12. const byId = {};
  13. comments.forEach(c => { byId[c.cid] = c; c.replies = c.replies || []; });
  14. const stats = { files: 0, badFiles: 0, rawReplies: 0, uniqueReplies: 0, commentsMatched: 0, orphan: 0 };
  15. let files = [];
  16. if (fs.existsSync(repliesDir)) {
  17. files = fs.readdirSync(repliesDir).filter(f => f.endsWith('.json'));
  18. }
  19. console.log('回复页面文件:', files.length, '个');
  20. // 按 cid 分组,保证按 cursor 顺序处理
  21. const byComment = {};
  22. files.forEach(f => {
  23. const m = /^(\d+)_(\d+)\.json$/.exec(f);
  24. if (!m) return;
  25. const cid = m[1], cursor = parseInt(m[2], 10);
  26. (byComment[cid] = byComment[cid] || []).push({ f, cursor });
  27. });
  28. console.log('涉及评论数:', Object.keys(byComment).length);
  29. const seenReply = {};
  30. for (const cid of Object.keys(byComment)) {
  31. byComment[cid].sort((a, b) => a.cursor - b.cursor);
  32. const parent = byId[cid];
  33. if (!parent) { stats.orphan++; continue; }
  34. let n = 0;
  35. for (const { f } of byComment[cid]) {
  36. stats.files++;
  37. let j;
  38. try { j = JSON.parse(fs.readFileSync(path.join(repliesDir, f), 'utf8')); }
  39. catch (e) { stats.badFiles++; continue; }
  40. if (j.code !== 0 || !j.data) continue;
  41. for (const r of (j.data.comments || [])) {
  42. stats.rawReplies++;
  43. if (!r || !r.id || seenReply[r.id]) continue;
  44. seenReply[r.id] = 1;
  45. stats.uniqueReplies++;
  46. const u = r.user || {};
  47. parent.replies.push({
  48. cid: r.id,
  49. text: r.text || '',
  50. user: u.unique_id || u.nickname || '匿名',
  51. nickname: u.nickname || '',
  52. userId: u.id || '',
  53. region: u.region || '',
  54. likes: parseInt(r.digg_count, 10) || 0,
  55. createTime: r.create_time || 0,
  56. replyCount: parseInt(r.reply_total, 10) || 0
  57. });
  58. n++;
  59. }
  60. }
  61. if (n > 0) stats.commentsMatched++;
  62. }
  63. console.log('合并统计:', JSON.stringify(stats, null, 2));
  64. // 排序:每个父评论的回复按点赞降序
  65. comments.forEach(c => c.replies.sort((a, b) => b.likes - a.likes));
  66. const withR = comments.filter(c => (c.replies || []).length > 0);
  67. const totalReplies = comments.reduce((s, c) => s + (c.replies || []).length, 0);
  68. fs.mkdirSync(outDir, { recursive: true });
  69. const base = path.join(outDir, 'tiktok_' + awemeId);
  70. fs.writeFileSync(base + '_comments.json', JSON.stringify(comments, null, 2), 'utf8');
  71. const L = [];
  72. L.push('视频ID: ' + awemeId);
  73. L.push('来源: tikwm镜像(www.tikwm.com) 顶级评论 ' + comments.length + ' 条');
  74. L.push('子回复: ' + totalReplies + ' 条(覆盖 ' + withR.length + ' 条评论)');
  75. L.push('抓取时间: ' + new Date().toISOString());
  76. L.push('');
  77. comments.forEach((c, i) => {
  78. L.push((i + 1) + '. [' + c.user + ' | 赞' + c.likes + ' | 回复' + c.replyCount + ' | 地区' + c.region + '] ' + c.text);
  79. (c.replies || []).forEach(r => {
  80. L.push(' └─ [' + r.user + ' | 赞' + r.likes + '] ' + r.text);
  81. });
  82. });
  83. fs.writeFileSync(base + '_comments.txt', L.join('\n'), 'utf8');
  84. const summary = JSON.parse(fs.readFileSync(base + '_stats.json', 'utf8'));
  85. summary.repliesFetched = totalReplies;
  86. summary.commentsWithRepliesFetched = withR.length;
  87. summary.replyFiles = stats.files;
  88. summary.mergedAt = new Date().toISOString();
  89. summary.note = 'tikwm 顶级评论上限约 10369;子回复按热度抓取,可在云机断点续传';
  90. fs.writeFileSync(base + '_stats.json', JSON.stringify(summary, null, 2), 'utf8');
  91. console.log('\n已写出:');
  92. ['_comments.json', '_comments.txt', '_stats.json'].forEach(s => {
  93. const p = base + s;
  94. console.log(' ' + p + ' (' + fs.statSync(p).size + ' bytes)');
  95. });