🎯 目的
TikTokのAPI:
https://ads.tiktok.com/creative_radar_api/v1/popular_trend/list
からトレンド動画リストを取得したい。
しかし、このAPIを直接呼び出すには以下のヘッダーが必要です:
anonymous-user-id
user-sign
timestamp
これらはブラウザ内で動的に生成されるため、通常のHTTPクライアントでは再現できません。
そこで、本コードでは仮想ブラウザ環境(jsdom)を使ってヘッダーを抽出するというアプローチをとっています。
🔎 なぜ anonymous-user-id
や user-sign
が必要なのか?
TikTokは非公開APIへのアクセスに対して、リクエストの正当性を確認するための署名ヘッダーを要求します。
-
anonymous-user-id
は匿名ユーザーの識別用 -
user-sign
はユーザーIDやタイムスタンプを元に生成される署名トークン
これらがないと、APIはエラーや空データを返します。
❗ 課題
- 必要なヘッダーはブラウザ上でJavaScriptにより生成される
- 単純なHTTPリクエストではヘッダーを再現できず、APIアクセスが拒否される
✅ 解決方法
本コードは、以下のステップでヘッダーを取得します:
-
got-scraping
でページHTMLを取得 -
jsdom
で仮想DOMを生成し、JavaScriptを実行 -
XMLHttpRequest
のsetRequestHeader
をフックしてヘッダーを傍受 - 全ヘッダーが揃うまで待機して値を取得
🧠 コードの概要(補足)
// TikTok API用の認証ヘッダーを生成する
import { JSDOM, VirtualConsole } from 'jsdom'; // Layer jsdomから
import { gotScraping } from 'got-scraping'; // Layer got-scrapingから
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
async function getApiUrlWithVerificationToken(body, url) {
// JSDOMを使用してAPIセッションヘッダーを取得
console.log('JSDOMでAPIセッションヘッダーを取得開始...');
console.log('ページURL: ', url);
console.log('ページ内容の長さ: ', body.length);
const virtualConsole = new VirtualConsole();
const { window } = new JSDOM(body, {
url,
contentType: 'text/html',
runScripts: 'dangerously',
resources: 'usable',
pretendToBeVisual: false,
virtualConsole,
});
virtualConsole.on('error', (err) => {
// JSDOMエラーをログ
console.error('JSDOMエラー: ', err);
});
const apiHeaderKeys = ['anonymous-user-id', 'timestamp', 'user-sign'];
const apiValues = {};
let retries = 10;
let headersFound = false;
window.XMLHttpRequest.prototype.setRequestHeader = (name, value) => {
if (apiHeaderKeys.includes(name)) {
apiValues[name] = value;
console.log(`取得したヘッダー: ${name}=${value}`);
if (Object.keys(apiValues).length === apiHeaderKeys.length) {
headersFound = true;
console.log('すべてのヘッダーを取得: ', apiValues);
}
}
};
window.XMLHttpRequest.prototype.open = (method, urlToOpen) => {
console.log(`XHRリクエスト開始: ${method} ${urlToOpen}`);
};
// ヘッダー取得ロジック
console.log('ヘッダー取得ループ開始...');
while (retries > 0 && !headersFound) {
console.log(`試行残り: ${retries}, 取得済みヘッダー: `, Object.keys(apiValues));
await sleep(1000);
retries--;
}
console.log('ヘッダー取得ループ終了、headersFound: ', headersFound);
window.close();
if (!headersFound) {
console.error('最終的に取得できたヘッダー: ', apiValues);
throw new Error('トークン生成に失敗: 必要なヘッダーをすべて抽出できませんでした。');
}
console.log('API認証ヘッダーの取得に成功: ', apiValues);
return apiValues;
}
export async function getHeaders() {
// 初期ページをフェッチしてヘッダーを生成
console.log('ヘッダー生成のために初期ページをフェッチ中...');
const url = 'https://ads.tiktok.com/business/creativecenter/inspiration/popular/hashtag/pad/en';
try {
console.log('gotScrapingリクエスト開始: ', url);
const response = await gotScraping({
url,
timeout: { request: 30000 },
});
console.log('ページフェッチ成功、ステータスコード: ', response.statusCode);
return await getApiUrlWithVerificationToken(response.body.toString(), url);
} catch (error) {
console.error(`${url} からページフェッチ失敗: `, error.message);
throw error;
}
}
-
getHeaders()
- 初期ページのHTMLを取得し、
getApiUrlWithVerificationToken()
に渡す
- 初期ページのHTMLを取得し、
-
getApiUrlWithVerificationToken(body, url)
- ヘッダーを検出するため、仮想DOM内でスクリプトを実行
-
XMLHttpRequest
を上書きし、必要なヘッダー値を取得
💡 工夫したこと
- jsdomの virtualConsole を使って、仮想ブラウザ内のエラーもログに出力
- ヘッダーが揃うまで最大10回リトライし、安定して値を取得できるように設計
- 本来セキュリティ対策として隠されている内部通信を、副作用(XHR)を利用して自然に取得
📌 まとめ
TikTokの内部APIを扱う際に必要な認証ヘッダーを、仮想ブラウザで抽出する手法です。
- 公開されていないAPIにアクセスしたい
- JavaScript内で生成される認証ヘッダーが必要
- 通常のクローリング手法では不十分
といった場面で、このような仮想実行+傍受のアプローチが有効です。