probe-dy-search-v2.js 3.6 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. #!/usr/bin/env node
  2. /**
  3. * 用 POST + JSON 测试 Douyin-Search-API V2 接口
  4. * 关键线索:不需要 cookie,POST body 参数
  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. function post(apiPath, body) {
  17. return new Promise((resolve) => {
  18. const payload = JSON.stringify(body);
  19. const opts = {
  20. hostname: 'api.tikhub.io',
  21. path: apiPath,
  22. method: 'POST',
  23. headers: {
  24. Authorization: `Bearer ${TIKHUB_TOKEN}`,
  25. 'Content-Type': 'application/json',
  26. 'Content-Length': Buffer.byteLength(payload),
  27. Accept: 'application/json',
  28. },
  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 text = Buffer.concat(chunks).toString('utf-8');
  35. try {
  36. resolve({ status: res.statusCode, json: JSON.parse(text) });
  37. } catch (e) {
  38. resolve({ status: res.statusCode, json: { _raw: text.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.write(payload);
  45. req.end();
  46. });
  47. }
  48. const candidatePaths = [
  49. // more granular attempts
  50. '/api/v1/douyin/fetch_video_search_v2',
  51. '/api/v1/douyin/fetch_user_search_v2',
  52. '/api/v1/douyin/app/v2/fetch_video_search_v2',
  53. '/api/v1/douyin/app/v2/fetch_user_search_v2',
  54. '/api/v1/douyin/app/v1/fetch_video_search_v2',
  55. '/api/v1/douyin/app/v1/fetch_user_search_v2',
  56. // 按图中 apifox 路径 id 尝试完整路径
  57. '/api/v1/douyin_search/v2/api_v1_douyin_search_fetch_video_search_v2_post',
  58. '/api/v1/douyin_search/v2/api_v1_douyin_search_fetch_user_search_v2_post',
  59. // 重试已知的 fetch_user_search_result_v2(曾 200),看 POST 是否不同
  60. '/api/v1/douyin/web/fetch_user_search_result_v2',
  61. // 官方 API v3 带 _v2 后缀
  62. '/api/v1/douyin/app/v3/fetch_video_search_result_v2',
  63. '/api/v1/douyin/app/v3/fetch_user_search_result_v2',
  64. // Web V3 / V2
  65. '/api/v3/douyin/search/fetch_video_search_v2',
  66. '/api/v3/douyin/search/fetch_user_search_v2',
  67. ];
  68. (async () => {
  69. const keyword = '康恩贝';
  70. console.log(`Testing search for "${keyword}"\n`);
  71. const results = [];
  72. for (const p of candidatePaths) {
  73. const body = p.includes('user')
  74. ? { keyword, cursor: 0, sort_type: 0, publish_time: 0, search_id: '' }
  75. : { keyword, cursor: 0, sort_type: '0', publish_time: '0', filter_duration: '0', content_type: '', search_id: '', backtrace: '' };
  76. const r = await post(p, body);
  77. const code = r.json?.code;
  78. const msg = (r.json?.message_zh || r.json?.message || r.json?.detail?.message_zh || r.json?.detail?.message || '').slice(0, 80);
  79. const data = r.json?.data;
  80. const dataKeys = data && typeof data === 'object' && !Array.isArray(data) ? Object.keys(data).slice(0, 8).join(',') : (Array.isArray(data) ? `arr[${data.length}]` : typeof data);
  81. const row = { path: p, status: r.status, code, msg, dataKeys };
  82. results.push(row);
  83. console.log(` [${r.status}] ${p}`);
  84. console.log(` code=${code} msg="${msg}" data=${dataKeys}`);
  85. await new Promise((res) => setTimeout(res, 600));
  86. }
  87. fs.writeFileSync('data/dy-v2-probe.json', JSON.stringify(results, null, 2), 'utf-8');
  88. })();