probe-dy-user-search.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119
  1. #!/usr/bin/env node
  2. /**
  3. * 抖音账号搜索探测:用 TikHub /fetch_user_search_result 搜用户
  4. * 对 6 个候选关键词搜索,返回 Top 3 用户(昵称 / sec_uid / 粉丝)
  5. */
  6. const fs = require('fs');
  7. const path = require('path');
  8. const os = require('os');
  9. const https = require('https');
  10. const TIKHUB_TOKEN = JSON.parse(
  11. fs.readFileSync(
  12. path.join(os.homedir(), '.openclaw', 'skills', 'xiaohongshu-search-notes', 'api-config.json'),
  13. 'utf-8'
  14. )
  15. ).endpoint.headers.Authorization.replace(/^Bearer\s+/, '');
  16. const COOKIE = fs.readFileSync(path.resolve('data/douyin-cookie.txt'), 'utf-8').trim();
  17. function get(apiPath, params) {
  18. params.cookie = COOKIE;
  19. const qs = Object.entries(params)
  20. .filter(([, v]) => v !== undefined && v !== null && v !== '')
  21. .map(([k, v]) => `${encodeURIComponent(k)}=${encodeURIComponent(v)}`)
  22. .join('&');
  23. return new Promise((resolve) => {
  24. const opts = {
  25. hostname: 'api.tikhub.io',
  26. path: `${apiPath}?${qs}`,
  27. method: 'GET',
  28. headers: { Authorization: `Bearer ${TIKHUB_TOKEN}`, Accept: 'application/json' },
  29. };
  30. const req = https.request(opts, (res) => {
  31. const chunks = [];
  32. res.on('data', (c) => chunks.push(c));
  33. res.on('end', () => {
  34. const body = Buffer.concat(chunks).toString('utf-8');
  35. try {
  36. resolve({ status: res.statusCode, json: JSON.parse(body) });
  37. } catch (e) {
  38. resolve({ status: res.statusCode, json: { _raw: body.slice(0, 300) } });
  39. }
  40. });
  41. });
  42. req.on('error', (e) => resolve({ status: 0, json: { _error: e.message } }));
  43. req.setTimeout(30000, () => { req.destroy(); resolve({ status: 0, json: { _error: 'timeout' } }); });
  44. req.end();
  45. });
  46. }
  47. const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
  48. // 关键词 × 产品 候选列表
  49. const candidates = [
  50. // liver 肝纯片
  51. { product: 'liver', role: '品牌竞品号', keyword: '葵花护肝片' },
  52. { product: 'liver', role: '品牌竞品号', keyword: '葵花药业' },
  53. { product: 'liver', role: '垂类博主', keyword: '肝病科' },
  54. { product: 'liver', role: '垂类博主', keyword: '护肝科普' },
  55. // probiotic 儿童乳酸菌
  56. { product: 'probiotic', role: '品牌竞品号', keyword: '妈咪爱' },
  57. { product: 'probiotic', role: '品牌竞品号', keyword: '亿活益生菌' },
  58. { product: 'probiotic', role: '品牌竞品号', keyword: '合生元' },
  59. { product: 'probiotic', role: '垂类博主', keyword: '儿科医生' },
  60. { product: 'probiotic', role: '垂类博主', keyword: '崔玉涛' },
  61. // monkey 猴菇饮
  62. { product: 'monkey', role: '品牌竞品号', keyword: '康恩贝' },
  63. { product: 'monkey', role: '品牌竞品号', keyword: '太阳神' },
  64. { product: 'monkey', role: '品牌竞品号', keyword: '三九胃泰' },
  65. { product: 'monkey', role: '垂类博主', keyword: '胃肠科医生' },
  66. { product: 'monkey', role: '垂类博主', keyword: '养胃' },
  67. ];
  68. (async () => {
  69. const out = [];
  70. for (const c of candidates) {
  71. await sleep(1200);
  72. console.log(`\n[${c.product} · ${c.role}] 搜 "${c.keyword}"`);
  73. // TikHub 的用户搜索接口(v2 可用)
  74. const r = await get('/api/v1/douyin/web/fetch_user_search_result_v2', {
  75. keyword: c.keyword,
  76. search_channel: 'aweme_user_web',
  77. sort_type: 0,
  78. publish_time: 0,
  79. page: 1,
  80. count: 10,
  81. });
  82. const data = r.json?.data;
  83. const users = Array.isArray(data) ? data : (data?.user_list || data?.users || data?.data || []);
  84. if (!Array.isArray(users) || users.length === 0) {
  85. console.log(' ❌ no users. raw:', JSON.stringify(r.json).slice(0, 250));
  86. out.push({ ...c, users: [], error: r.json?.message_zh || r.json?.message || 'empty' });
  87. continue;
  88. }
  89. const top = users.slice(0, 3).map((u) => {
  90. const uinfo = u.user_info || u;
  91. return {
  92. nickname: uinfo.nickname,
  93. sec_uid: uinfo.sec_uid,
  94. uid: uinfo.uid,
  95. follower_count: uinfo.follower_count,
  96. custom_verify: uinfo.custom_verify,
  97. signature: (uinfo.signature || '').replace(/\n/g, ' ').slice(0, 60),
  98. share_url: uinfo.sec_uid ? `https://www.douyin.com/user/${uinfo.sec_uid}` : '',
  99. };
  100. });
  101. console.log(' top candidates:');
  102. top.forEach((t, i) => {
  103. console.log(` #${i + 1} ${t.nickname} · 粉丝 ${(t.follower_count || 0).toLocaleString()} · ${t.custom_verify || '-'}`);
  104. console.log(` sec_uid: ${t.sec_uid}`);
  105. });
  106. out.push({ ...c, users: top });
  107. }
  108. const outPath = path.resolve('data', 'dy-user-candidates.json');
  109. fs.writeFileSync(outPath, JSON.stringify(out, null, 2), 'utf-8');
  110. console.log('\n\n✓ Wrote', outPath);
  111. console.log('共搜 ' + out.length + ' 个关键词');
  112. })();