次のキャンペーンのテーマを探すとき、数十のプロフィールを手作業で確認していると、統一された方法のないメモの寄せ集めになりがちです。ある分析担当者は再生数を記録し、別の担当者は動画のキャプションを書き留め、似た表現の投稿が別々の表に入ってしまいます。1週間後には、繰り返し登場するのがテーマなのか、見せ方なのか、それとも単に好調だった動画なのかを判断するのが難しくなります。

実用的な方法は、公開アカウントの小さなリストを作り、動画に関する情報を定期的に構造化データとして取得し、取得ごとに個別のスナップショットとして保存することです。その後、投稿や利用可能な指標を時系列で比較し、言語モデルをキャプションの予備的なグループ分けに利用できます。以下では、jsonscraperのTikTok Scraper APIを使ったPythonの調査パイプラインを紹介します。
データを集める前に問いを定める
小規模なスポーツ栄養ブランドのマーケティングチームを想定してみましょう。選定した公開TikTokクリエイターの間でどのテーマが定期的に登場するかを把握し、どの動画を手作業で確認すべきかを知りたいとします。目的は、事前に選んだアカウントの投稿を観察期間にわたって比較することであり、TikTok全体の人気を示す汎用ランキングを作ることではありません。
この目的によって、分析の範囲が定まります。選んだプロフィールのフィードを取得すれば、それらのアカウントの内容や投稿頻度を調べられます。利用可能な公開カウンターは、リクエスト時点の値として記録できます。自社広告の成果を評価するには、広告システムの費用やウェブ解析のコンバージョンなど、別のデータが必要です。
APIを使う前に、調査プロトコルを記録してください。アカウント一覧、開始日、取得頻度、分析項目、投稿を含める基準を定めます。各リクエストの時刻も記録しましょう。そうすれば、同じ動画の指標の変化と、2つの観測セットの違いを区別できます。
収集するデータと指標
小規模なプロジェクトに必要なデータは3層です。1つ目はアカウントIDと収集開始時刻、2つ目は後から確認できるよう保存したAPIの元レスポンス、3つ目はクエリやレポートに使う正規化済み項目、つまり動画ID、キャプション、投稿時刻、利用可能なエンゲージメントカウンターです。
フィールド名やレスポンスの階層構造は、実際に使うエンドポイントのサンプルを見て確認してください。リストの名前は常にvideos、再生数のカウンターはviewsだと、あらかじめ決めつけないでください。まずドキュメントに記載されたリクエストを実行して実際のJSONを確認し、その後で独自スキーマへの変換を設定します。
役立つ指標には、期間内の新規投稿数、テーマ別の投稿分布、同じ動画のスナップショット間における公開カウンターの変化があります。最後の指標を計算するときは、動画IDが一致するレコード同士を比較します。これにより、すでに公開された動画の推移を新規投稿の指標と分けて扱えます。
TikTok Scraper APIへの接続
TikTok Scraper APIの説明にはベースURLとしてhttps://tiktok.evelode.comが示されており、エンドポイントリファレンスではGET /getUserFeedと、パラメーターquery、max_cursor、count、region、with_pinned_posts、sort_type、cache_timeoutが確認できます。最初のリクエストでは、queryにプロフィールを指定し、たとえばcountで取得件数を指定すれば十分です。キーはクエリ文字列でlicense_keyとして渡します。設定方法はAPIリファレンスにも記載されています。統合前にエンドポイントを照合してレスポンスを確認するには、Postmanコレクションを利用できます。
キーは環境変数に保存してください。ソースコード、リポジトリ、表計算ファイル、クライアントアプリケーションには含めないでください。リクエストを実行したら、成功したJSONのサンプルを日付とエンドポイント名とともに保存し、正規化アダプターとテストの基礎として使います。
Pythonでの最小限のリクエスト
リファレンスによると、ユーザーフィードのレスポンスには外側のステータスとtiktokオブジェクトが含まれ、投稿リストはaweme_listに格納されています。レコードにはID、キャプション、投稿時刻、作成者、統計情報が含まれる場合があります。具体的な項目や値は実際のレスポンスで確認してください。データによっては存在しない場合もあります。
import json
import os
from datetime import datetime, timezone
from pathlib import Path
import requests
API_KEY = os.environ["JSONSCRAPER_TIKTOK_LICENSE_KEY"]
ENDPOINT_URL = "https://tiktok.evelode.com/getUserFeed"
def fetch_user_feed(handle: str, count: int = 20) -> dict:
response = requests.get(
ENDPOINT_URL,
params={
"license_key": API_KEY,
"query": handle,
"count": count,
"region": "US",
},
timeout=45,
)
response.raise_for_status()
payload = response.json()
if not isinstance(payload, dict):
raise ValueError("JSONオブジェクトが必要です")
if payload.get("status") != "ok":
raise RuntimeError(f"APIのステータス: {payload.get('status')!r}")
tiktok = payload.get("tiktok")
if not isinstance(tiktok, dict):
raise ValueError("レスポンスにtiktokオブジェクトがありません")
return payload
def save_raw(payload: dict, account: str, output_dir: str = "data/raw") -> Path:
collected_at = datetime.now(timezone.utc).isoformat()
safe_account = "".join(c for c in account if c.isalnum() or c in "-_@")
path = Path(output_dir) / f"{safe_account}_{collected_at.replace(':', '-')}.json"
path.parent.mkdir(parents=True, exist_ok=True)
path.write_text(
json.dumps(
{"collected_at": collected_at, "account": account, "payload": payload},
ensure_ascii=False,
indent=2,
),
encoding="utf-8",
)
return path
def normalize_items(payload: dict, account: str, collected_at: str) -> list:
items = payload["tiktok"].get("aweme_list", [])
if not isinstance(items, list):
raise ValueError("tiktok.aweme_listはリストではありません")
observations = []
for item in items:
if not isinstance(item, dict):
continue
stats = item.get("statistics")
stats = stats if isinstance(stats, dict) else {}
author = item.get("author")
author = author if isinstance(author, dict) else {}
observations.append({
"account_handle": account,
"collected_at": collected_at,
"video_id": item.get("aweme_id"),
"caption": item.get("desc"),
"published_at_epoch": item.get("create_time"),
"author_handle": author.get("unique_id"),
"views": stats.get("play_count"),
"likes": stats.get("digg_count"),
"comments": stats.get("comment_count"),
"shares": stats.get("share_count"),
})
return observations
if __name__ == "__main__":
account = "creator_handle"
payload = fetch_user_feed(account, count=20)
collected_at = datetime.now(timezone.utc).isoformat()
raw_path = save_raw(payload, account)
observations = normalize_items(payload, account, collected_at)
print(f"保存先: {raw_path}; レコード数: {len(observations)}")
実行前に環境変数JSONSCRAPER_TIKTOK_LICENSE_KEYを設定し、ライブラリrequestsをインストールしてください。このコードはHTTPレスポンス、外側のステータス、tiktokオブジェクトの有無を確認し、その後aweme_listを読み込みます。元のJSONを保存し、リファレンスで確認された項目を抽出して、存在しない値は空のままにします。取得したフィードはAPIが返した結果の集合です。そのため、レポートにはリクエスト時刻と対象プロフィールを明記してください。
スナップショットを保存し、投稿を比較する
小規模なプロジェクトならSQLiteまたはPostgreSQLが適しています。元のレスポンスは正規化済みレコードとは別に保存します。観測テーブルには、次のようなスキーマを使えます。
CREATE TABLE IF NOT EXISTS video_observations (
account_handle TEXT NOT NULL,
video_id TEXT NOT NULL,
collected_at TEXT NOT NULL,
published_at_epoch INTEGER,
caption TEXT,
views INTEGER,
likes INTEGER,
comments INTEGER,
shares INTEGER,
raw_file TEXT,
PRIMARY KEY (account_handle, video_id, collected_at)
);
実行のたびに過去のデータを上書きするのではなく、新しいスナップショットを追加します。カウンターを比較するときは、同じ動画について両方の値がある場合にのみ差分を計算してください。リクエスト間隔と観測時刻をレポートに含めると、変化がどの期間に生じたかを説明できます。
トピックのグループ分けは、まず少数のカテゴリからなる辞書を用意し、必要に応じて分類器や言語モデルを追加します。モデルには、クリーニングしたキャプション、投稿ID、あらかじめ定義したカテゴリを渡します。カテゴリと短い根拠を含む構造化された結果なら、自由形式の要約よりも実行間の比較が容易です。
検証可能なラベル付けで言語モデルを活用する
モデルを使って、「レシピ」「トレーニング」「商品レビュー」「個人的な体験談」といったテーマにキャプションを予備分類できます。まず数十件の投稿に手作業でラベルを付けます。自動ラベルとこれらのサンプルを比較し、カテゴリの定義を見直してから、プロンプトやモデルを変更した後に評価を繰り返します。
新規投稿数、カウンターの中央値、スナップショット間の差分は、通常のコードで計算するほうが確実です。モデルはテキスト分類と簡潔な説明の作成に使います。元のキャプションと数値の観測値は、検証できるよう残してください。
ラベルとともに、投稿ID、プロンプトのバージョン、処理日、使用モデルを保存します。分類ミスがあった場合、分析担当者はキャプションを確認してルールを修正できます。初回のラベルと人手による修正は、別々に保存すると便利です。
実行を自動化する
手動実行で動作を確認したら、スクリプトをcron、GitHub Actions、n8n、またはMakeに移します。スケジューラーが予定に沿ってデータを取得し、アカウントリストを渡し、結果を記録して、選択した保存先にレポートを送ります。キーは実行環境のシークレットに保存してください。まず1つのアカウントでプロセスを確認し、その後リストを拡大します。
自動化は、アカウントのキュー、API呼び出し、元レスポンスの保存、正規化、スナップショットの記録、レポート作成という段階に分けます。1つのアカウントでエラーが起きても、他の結果が消えないようにしてください。実行IDとデータベースの一意制約を使えば、再実行を冪等にできます。
時刻、アカウント、エンドポイント、HTTPステータス、処理時間、処理した要素数、エラーの説明をログに記録します。APIキーはログに書き込まないでください。cache_timeoutは/getUserFeedのパラメーターとして記載されています。指定したキャッシュ期間が必要な場合は、リクエストに追加できます。APIリファレンスでは、既定値は3600秒と説明されています。
レポートを公開する前に品質を確認する
正規化したレコードをいくつか、元のJSONと照合してください。再取得時に同じ動画をIDで認識できること、日付が想定どおりに解釈されること、数値項目の型が適切であること、欠損値の割合が把握できていることを確認します。その後、もう一度実行し、データベースに過去の観測が残り、新しい観測が追加されることを確かめます。
アカウントや収集期間を混同しないでください。テーマを比較するときは、期間の長さをそろえるか、レポートに明記します。カウンターの推移を扱う場合は、比較対象の期間の長さが読者に伝わるよう、スナップショット間隔を記録してください。
キャプションの原文は保存し、クリーニングしたバージョンは別に作成してください。絵文字、ハッシュタグ、リンク、言語ごとの特徴は分類に影響する可能性があります。別々の項目としておけば、クリーニングの影響を確かめたり、APIを再呼び出しせずにカテゴリを再計算したりできます。
このパイプラインで得られる成果
数回の実行を経ると、チームは投稿とスナップショットのテーブル、キャプションのテーマ別の予備グループ分け、選択した期間における新規動画のレポートを作成できます。専門家による確認対象の動画を選び、指定したアカウントで繰り返し登場するテーマを見つけ、観測日の間で公開カウンターがどう変化したかを確認するのに役立ちます。
このデータをもとに、編集カレンダーの仮説を立てられます。同じ問いに対する見せ方を比べたり、自社コンテンツで試すテーマを選んだりできます。商業的な効果を評価するには、観測結果を社内のキャンペーンデータと照合してください。
始めるには、公開アカウントを少数選び、PostmanのリクエストコレクションでGET /getUserFeedを確認してください。エンドポイントのドキュメントと実際のレスポンスで確認できたパラメーターやフィールドパスだけを、コードに取り入れます。
公開データを責任を持って収集する
アカウントリストは調査目的に沿って限定し、必要な項目だけを収集して、元のレスポンスの保存期間を定めてください。データを商用利用する前に、適用されるプラットフォーム利用規約と、管轄地域の個人データ関連要件を確認します。アクセス管理と古いスナップショットの削除により、プロセスを目的の範囲内に保ちやすくなります。
データをAIサービスに送る前に、モデルが必要としない項目は削除してください。レポートには投稿IDと観測日を記載し、担当者が該当レコードに移動して、結論の根拠を確認できるようにします。
実行前の簡易チェックリスト
- 分野、アカウント一覧、期間、分析上の問いを定める。
- リファレンスで
GET /getUserFeedのパラメーターを確認し、Postmanで実際のレスポンスを確認する。 - JSONサンプルを保存し、確認済みの項目に合わせて正規化処理を設定する。
- 元のレスポンスと、取得時刻を記録した個別のスナップショットを保存する。
- 数値指標はコードで計算し、AIはテキストの予備的なグループ分けに使う。
- スケジュール、ログ記録、再試行処理、保存期間を設定する。
こうすることで、モニタリングが再現可能な調査になります。それぞれの結論を投稿と観測時刻に結び付けられ、コードは選んだエンドポイントの実際のスキーマに基づいて動作します。