営業チームには、さまざまなチャネルから同じ質問が寄せられます。見込み顧客は、すでにどのような課題の解決を試みているのでしょうか。TikTokを手作業で検索すると動画が大量に見つかりますが、1週間後に同じ検索を再現したり、結果を比較したり、特定の動画がなぜレポートに含まれたのかを説明したりするのは困難です。小規模な調査なら、いくつかの具体的な検索語に対する公開検索結果を再現可能なデータの流れに変換し、元のレスポンスを保存して動画と投稿者の項目を抽出し、重複を除き、購入や製品選択の意図を示す兆候を個別に評価できます。

以下では、jsonscraperのTikTok Scraper APIを使ったPythonの実用的な手順を紹介します。たとえば動画編集サービスが、字幕、テンプレート、書き出しに関する悩みを投稿者がどのように説明しているかを把握したい場合に役立ちます。スクリプトは検索結果として返されたデータを収集し、その後、アナリストが分類を確認して、オーディエンス調査にどの表現を使うべきかを判断します。これは公開コンテンツの観察を整理する方法であり、購入意向のあるユーザー全員を自動的に数えるものではありません。
課題:視聴者の検索語はあるが、調査を再現しにくい
手作業の調査では、文脈が失われがちです。ある担当者は「字幕の追加方法」、別の担当者は「Reels用の自動字幕」を検索し、さらに別の担当者は気に入った動画だけを保存するかもしれません。最終的な表には、異なる検索語、日付、指標、選定基準が混在します。責任者から分析の再実施や2週間分の比較を求められると、結果だけでなく検索の手順そのものも復元しなければなりません。
あらかじめ観察単位を決めておくと便利です。たとえば、説明文、識別子、リンク、投稿者、取得できる公開指標を含む検索対象の動画を単位にします。動画ごとに元の検索語と収集時刻を保存します。そうすれば、どのような表現が登場するか、どの課題が言及されるか、どの動画がより多くの公開反応を得たか、保存したスナップショットの間で何が変化したかを個別に確認できます。
ここでは、字幕と短尺動画の作成ツールを開発する企業を例にします。投稿者が手作業での文字起こし、音声認識の誤り、字幕のタイミング調整、複数プラットフォーム向けのコンテンツ準備について明確に話している公開動画を見つけることが目的です。1本の動画だけを根拠に、投稿者を「リード」や「購入者」と決めつけてはいけません。調査担当者は、観察できる兆候に基づいて内容を順位付けし、その結論を手作業で確認できます。
解決策の流れ:検索、記録、クリーニング、評価
パイプラインは4段階で構成されます。まず、チームが視聴者の具体的な課題に対応する検索語を少数選びます。次に、スクリプトが動画検索を実行し、完全なJSONレスポンスを保存します。3段階目では、異なるレスポンス構造を動画ごとの統一された行に正規化し、識別子を使って重複を除きます。最後に、ルールまたは別のAI分類器が手作業で確認する優先度を付け、その評価理由を記録します。
再現性を確保するには、これらの段階を分けることが重要です。元のレスポンスは、フィールドの変更や正規化エラーの調査に役立ちます。正規化された表はフィルタリングや可視化に便利です。評価用の表を別にすれば、保存データを再処理でき、チームの保存期間ルールにも適合する場合、APIに再度問い合わせずにルールを変更できます。
基本となる検索語として、「自動字幕の誤り」「字幕をすばやく追加する方法」「ビジネス向け短尺動画の編集」などを使えます。これらはあくまで例であり、視聴者の言語や製品に合わせて調整してください。異なる語句では異なるサンプルが形成されるため、検索語は結果と一緒に保存します。
TikTok Scraper APIで取得できる情報
jsonscraperが公開しているドキュメントでは、動画検索用のGETルートとして、ベースアドレスhttps://tiktok.evelode.com上の/searchVideoが示されています。必須パラメーターはkeywordです。文書化された追加パラメーターには、count、cursor、region、date_posted、sort_by、cache_timeoutがあります。たとえばsort_byで指定できる値は、文書に記載されたmost-liked、relevance、date-postedです。date_postedにはthis-weekやall-timeなどの値を指定できます。パラメーターの正確な一覧とルートの用途は、TikTok APIエンドポイントリファレンスに記載されています。
キーはクエリパラメーターlicense_keyとして渡します。以下の例では環境変数JSONSCRAPER_TIKTOK_LICENSE_KEYから読み込み、秘密情報をコードに保存したり、ログに出力したりしないようにしています。認証方法やパラメーターの選択には、現在の利用条件を適用してください。TikTok Scraper APIのページには、製品の説明とデモ、ドキュメントへのリンクがあります。
検索レスポンスは、配列が1つだけだと仮定せず、実際の構造に応じて解析する必要があります。確認済みの例では、APIはトップレベルのステータスとtiktokオブジェクトを返しました。動画はtiktok.aweme_listに含まれる場合があり、検索結果ではtiktok.search_item_list[].aweme_infoの形式も見られます。両方の形式を確認してください。取得したオブジェクトには、フィールドが存在する場合、動画ID、説明、リンク、投稿時刻、投稿者情報、再生数、いいね数、コメント数、シェア数などの統計が含まれます。指標やフィールドは任意とみなしてください。値が欠けている場合に、コードが架空のゼロを補ってはいけません。
検索のページネーションにはcursorを使います。スクリプトで検索結果の続きを取得する場合、返された値は次のリクエストにそのまま渡す不透明なトークンとして扱い、自分で計算してはいけません。初期プロトタイプでは、検索語ごとに1ページだけ収集するのが妥当です。確認が簡単になり、負荷も予測しやすくなります。その後、ページ数の上限とカーソル重複への対策を加えてページネーションを実装できます。
環境の準備
この例ではPython 3.8以降と標準ライブラリを使います。macOSまたはLinuxのシェルで、たとえば次のように環境変数にキーを設定します。
export JSONSCRAPER_TIKTOK_LICENSE_KEY="あなたのキー"
python tiktok_demand.py
Windows PowerShellでは、次のように設定できます。
$env:JSONSCRAPER_TIKTOK_LICENSE_KEY="あなたのキー"
python .\tiktok_demand.py
スクリプトは各キーワードに対して個別にリクエストします。HTTPエラー、JSONレスポンス、外部ステータスを検証した後、確認済みの2種類の配列構造からオブジェクトを抽出します。元のレスポンスはJSONLファイルに1行ずつ記録します。各レコードには検索語、観察時刻、元のレスポンスが含まれます。もう一方では、フィールドを統一したCSVを作成します。キーはコードに含めず、秘密情報の内容も出力しません。
例:検索結果の収集と正規化
import csv
import json
import os
import time
from datetime import datetime, timezone
from pathlib import Path
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen
BASE_URL = "https://tiktok.evelode.com/searchVideo"
QUERIES = [
"自動字幕の誤り",
"字幕をすばやく追加する方法",
"ビジネス向け短尺動画の編集",
]
def extract_videos(payload):
"""観察された2種類の検索レスポンス構造に対応する。"""
tiktok = payload.get("tiktok") or {}
videos = []
aweme_list = tiktok.get("aweme_list") or []
if isinstance(aweme_list, list):
videos.extend(item for item in aweme_list if isinstance(item, dict))
search_items = tiktok.get("search_item_list") or []
if isinstance(search_items, list):
for item in search_items:
if not isinstance(item, dict):
continue
video = item.get("aweme_info")
if isinstance(video, dict):
videos.append(video)
return videos
def fetch(query, license_key):
params = {
"keyword": query,
"count": 20,
"sort_by": "relevance",
"date_posted": "this-month",
"region": "US",
"license_key": license_key,
}
url = BASE_URL + "?" + urlencode(params)
request = Request(url, headers={"Accept": "application/json"})
try:
with urlopen(request, timeout=45) as response:
payload = json.loads(response.read().decode("utf-8"))
except HTTPError as exc:
raise RuntimeError("APIがHTTPステータス{}を返しました".format(exc.code)) from exc
except (URLError, TimeoutError, json.JSONDecodeError) as exc:
raise RuntimeError("APIリクエストを完了または解析できませんでした") from exc
if payload.get("status") != "ok":
raise RuntimeError("APIレスポンスのステータスがokではありません")
return payload
def metric(statistics, key):
value = statistics.get(key)
return value if isinstance(value, (int, float)) else ""
def main():
license_key = os.environ.get("JSONSCRAPER_TIKTOK_LICENSE_KEY")
if not license_key:
raise SystemExit("環境変数JSONSCRAPER_TIKTOK_LICENSE_KEYを設定してください")
raw_path = Path("tiktok_raw.jsonl")
csv_path = Path("tiktok_videos.csv")
seen_ids = set()
rows = []
with raw_path.open("a", encoding="utf-8") as raw_file:
for query in QUERIES:
observed_at = datetime.now(timezone.utc).isoformat()
payload = fetch(query, license_key)
raw_file.write(json.dumps({
"query": query,
"observed_at": observed_at,
"response": payload,
}, ensure_ascii=False) + "\n")
for video in extract_videos(payload):
video_id = video.get("aweme_id")
if not video_id or video_id in seen_ids:
continue
seen_ids.add(video_id)
author = video.get("author") or {}
stats = video.get("statistics") or {}
rows.append({
"query": query,
"observed_at": observed_at,
"video_id": video_id,
"created_at": video.get("create_time", ""),
"description": video.get("desc", ""),
"share_url": video.get("share_url", ""),
"author_username": author.get("unique_id", ""),
"author_nickname": author.get("nickname", ""),
"views": metric(stats, "play_count"),
"likes": metric(stats, "digg_count"),
"comments": metric(stats, "comment_count"),
"shares": metric(stats, "share_count"),
})
time.sleep(1)
columns = [
"query", "observed_at", "video_id", "created_at", "description",
"share_url", "author_username", "author_nickname", "views",
"likes", "comments", "shares",
]
with csv_path.open("w", newline="", encoding="utf-8") as csv_file:
writer = csv.DictWriter(csv_file, fieldnames=columns)
writer.writeheader()
writer.writerows(rows)
print("{}件の重複のない取得動画を{}に保存しました".format(len(rows), csv_path))
if __name__ == "__main__":
main()
ここではcountを、リクエストする検索結果の件数として指定しています。APIが必ずその数を返すとは想定していません。実際の行数はレスポンスによって異なります。リクエスト間の待機時間は実行速度を調整する簡単な方法であり、サービスの制限や要件を示すものではありません。本番運用のスケジュールでは、利用プランの条件を確認し、上限付きの再試行処理、エラー監視、適切なリクエスト頻度のルールを追加してください。
生成された表の読み方
CSVでは、1行が検索語のいずれかで見つかった重複のない取得動画を表します。queryは記録の検索元、observed_atはスナップショットの時刻、video_idは重複排除のキーです。説明とリンクはアナリストが文脈を確認するのに役立ち、公開カウンターは収集時点の反応を評価する手掛かりになります。長期的に比較するには、指標の変化を確認できるよう、履歴を上書きせず別々の観察記録を使ってください。
再生数、いいね数、コメント数、シェア数は、リクエスト時にAPIが返した公開統計のスナップショットです。個々のオブジェクトでは値がない場合もあるため、CSVの空欄はレスポンスに数値フィールドがなかったことを示します。これはゼロとは異なるので、集計では分けて扱う必要があります。また、取得結果は特定の語句、地域、期間フィルター、リクエストパラメーターに基づく検索サンプルです。説明しているのは実際に取得された動画であり、プラットフォーム上のコンテンツや視聴者の全体像ではありません。
潜在的な関連性を初期評価するには、課題を示す表現との一致、使用ツールへの言及、価格や比較に関する質問、具体的な作業シナリオの有無など、透明性のあるルール表を作成できます。不透明な点数だけを記録するより、個別の特徴と評価理由を保存する方が望ましいでしょう。たとえば、自動文字起こしの精度を質問する動画には「要確認:品質について議論」とタグ付けし、一般的なコンテンツのコツを紹介する動画には「テーマとの関連性が低い」と記録できます。
AIを裁定者にせず活用する
サンプルが集まったら、モデルを使って、あらかじめ定めたスキーマに沿って説明文を分類できます。たとえば、課題、言及されたツール、質問のトーン、製品選択を示す兆候の有無などです。モデルには必要なフィールドだけを渡し、たとえば説明文とレコード識別子を入力して、ラベルと短い根拠を含む固定形式のJSONを返すよう求めます。再分類や人による抜き取り確認ができるよう、指示文のバージョン、モデル、結果を元の行と一緒に保存してください。
有用な分類指示では、観察できる特徴に基づいて出力を制限します。字幕に関する明確な問題、ツールの推奨を求める質問、作業工程の議論が含まれているかを判定するよう求めます。情報が少ない場合に備えて「文脈が不十分」というカテゴリも用意してください。モデルは、公開動画1本から投稿者の身元、購入意向、人口統計上の特徴を推測してはいけません。得られるのは分析対象となるテーマの候補であり、自動連絡のための人物リストではありません。
分類の品質を評価するには、ランダムに抽出したサブサンプルに同じカテゴリを手作業で付与します。各ラベルの一致度を比べ、誤りを調べます。たとえば、モデルが皮肉を苦情と誤認したり、教育的なアドバイスを個人の経験として分類したりする可能性があります。モデルをすぐに複雑化するより、検索語の辞書や指示文を修正する方が効果的な場合もあります。レポートでは、採用した保存・アクセス方針に従って、元のテキストとリンクを保持してください。
スケジュール実行と品質管理
手動実行が成功したら、cron、クラウドプラットフォームのタスクスケジューラー、n8nなどのオーケストレーターで定期実行できます。実用上の処理手順は変わりません。検索語一覧を読み込み、APIを呼び出し、ステータスを確認し、元のレスポンスを保存し、動画を正規化し、新しいスナップショットを記録して、レポートを作成します。キーは実行環境のシークレット管理機能から渡し、表、workflowのテキスト、エラーメッセージには含めないでください。
自動タスクでは4点を確認すると便利です。1つ目は、リクエストが正常に完了し、レスポンスがJSONであること。2つ目は、外部ステータスが想定した値であること。3つ目は、処理コードが検索結果の両方の構造に対応し、フィールドがない場合も正しく処理すること。4つ目は、新たに取得した重複のない動画数とエラー数がログに記録されることです。結果が突然なくなった場合、通常の成功レポートに説明なく混ぜるのではなく、実行状態としてサンプルがゼロ件だったことをレポートに示してください。
ページネーションの追加は、最初のページを確認してからにしてください。検索ルートではcursorパラメーターがドキュメントに記載されています。受け取ったトークンを保存し、加工せずに次のリクエストへ渡します。検索語ごとの最大ページ数を設定し、新規項目がない場合、次のカーソルがない場合、または処理済みのカーソルが再登場した場合にはループを止めます。この上限により、実行時間と収集量を管理できます。カーソルが使えることは検索結果の続きを取得できることを示しますが、すべての投稿を網羅する保証にはなりません。
同じ日に再実行すると、動画が重複して返される場合があります。そのため、重複排除には安定した動画識別子を使い、スナップショット履歴は観察日とともに別レコードとして保存します。集約ビューが必要なら、スナップショットを基に作成してください。そうすれば、初めて見つかった動画と、複数の検索語で見つかった動画、後日再取得された動画を区別できます。
指標:適切に比較できるもの
運用監視には、検索語ごとの重複のない動画数、説明文が入力されている動画の割合、取得できた反応数の分布、特定のテーマが登場する頻度などが役立ちます。検索語を比較する際は、パラメーターと実行期間を統一してください。一方の実行でthis-week、もう一方でthis-monthを使っている場合、件数の差は期間の違いによる可能性があります。追加分析なしに、関心の変化として解釈してはいけません。
同じ動画の指標を時系列で比較するには、複数のスナップショットと一貫した保存形式が必要です。各日付の値は取得したまま残し、その後、観察間の差を計算します。実際の実行間隔が異なる場合、その差を「1日の増加」と呼んではいけません。同様に、いいね数による順位付けは、収集サンプル内で目立つ動画を見つけるのに役立ちますが、投稿者や製品の商業的な効果を測定するものではありません。
マーケティングチーム向けの最終レポートでは、クリックできるリンク付きの視聴者の表現例、課題のテーマ別グループ、取得したサンプルの定量的な説明という3つのレベルを組み合わせられます。それぞれの結論には、観察件数、検索語、収集期間を添えてください。責任者に文脈を伝え、アナリストが同じ元データでフィルターを再現できるようになります。
よくある間違いと回避方法
空欄をゼロとして扱う
統計や投稿者情報がない場合、上のコードはセルを空欄のままにします。これにより、不明な値と測定されたゼロを区別できます。分析では欠損値の処理ルールを明示し、特定の計算から除外した行数も示してください。
検索結果が常に1つの配列で返ると思い込む
検索レスポンスにはaweme_listのほか、search_item_list[].aweme_infoのようなネストされた項目もあります。抽出処理は両方を確認し、想定外の型の項目は無視します。スキーマが変わった場合、保存した元レスポンスを使ってデータの新しい配置をすばやく特定できます。テスト用のJSONフィクスチャがあれば、次回の実行前に修正を検証できます。
同じ動画を別々のシグナルとして数える
1本の動画が複数の検索語に該当する場合があります。この例では、最終CSVには1回だけ含め、検索語の欄には現在の順序で最初に一致したものを記録します。「動画—検索語」のすべての対応関係を残す必要がある場合は、関連テーブルを別に保存するか、識別子ごとに検索語の一覧を保持し、記録元を削除しないでください。
比較できないサンプルを比較する
語句、地域、期間フィルター、並べ替え、実行日によってサンプルは変わります。パラメーターをレスポンスと一緒に保存し、実験条件を比較するときは一度に1つの要素だけを変更してください。そうすれば、結果の変化が検索結果の挙動によるのか、実験設定の変更によるのかを把握しやすくなります。
相関関係を購入意向として扱う
動画の説明文と公開カウンターから分かるのは、内容や視聴者の反応を示す兆候です。それだけでは、誰が購入する準備があるか、広告を見たか、コンバージョン率がどうかは分かりません。仮説の立案、テーマの確認、コンテンツ作成に使ってください。販売効果は、この調査と期間や視聴者の条件を合わせた自社のキャンペーンデータで評価します。
パイプラインを拡張する方法
基本版が安定して動いたら、検索語や実行パラメーターをPythonコードを編集せずに変更できるよう、検索語リストと設定を格納したファイルを追加できます。次に、検索語とテーマ別クラスターの対応表を作成します。たとえば「音声認識の品質」「編集の速さ」「書き出し」などです。カテゴリはチームが定義し、実例で検証してください。APIが返すのは検索データであり、意味上の分類体系は調査担当者が決めます。
保存先にはCSVの代わりにリレーショナルデータベースも使えます。実行、動画、指標のスナップショットをそれぞれ別のテーブルにします。動画識別子に一意インデックスを設定すると、カタログの重複を防げます。また、スナップショットを保存すれば、取得可能なカウンターの変化を追跡できます。観察日時はUTCで保存し、正規化処理のバージョンも記録してください。抽出ロジックを変更した場合は、保存済みのJSONLを再処理して結果を比較できます。
n8nではスケジュール実行を設定し、利用環境が対応していれば、正規化コードを別のPythonサービスやノードで実行できます。最初からモデルを組み込む必要はありません。まず収集を安定させ、その後、手作業の分類にかかる時間と、繰り返し登場するカテゴリを測定してください。AIが具体的な反復作業を減らし、そのラベルを検証できる場合に導入する価値があります。
検索語による検索ではなく、特定アカウントを定期的に観察することが目的なら、公式リファレンスにはquery、max_cursor、countをパラメーターとする別ルート/getUserFeedがあります。ハッシュタグ用には、cursorパラメーターを使う/getHashtagFeedが用意されています。これらは異なる収集シナリオです。どのエンドポイントを選ぶかは、別のサンプルで代用するのではなく、調査の問いから決めてください。
結果:ブックマークの寄せ集めではなく、再現可能なサンプル
実行後、チームは2つの有用な成果物を得られます。元のレスポンスのアーカイブと、検索語、観察日、リンク、説明文、取得可能な指標を含む重複のない動画一覧です。これらを使って視聴者の質問を手作業でグループ化したり、制御された分類にテキストを渡したり、週次の概要を作成したり、複数のスナップショットを比較したりできます。コードはリクエストエラーを明示的に処理し、外部ステータスを確認し、検索結果の2種類の構造を区別して、欠損指標を欠損値として保存します。
チームにとって実務上の成果となるのは、インタビュー、ランディングページ、広告メッセージで検証すべき課題を、より透明性の高い根拠に基づいて決められることです。調査に別の種類のデータが必要なら、検索語を変更するか、該当するドキュメント化済みルートを使ってください。パイプラインを調整する前にパラメーターを確認するには、TikTok APIのドキュメントを参照してください。製品情報とデモへの入口は、TikTok Scraper APIのページにあります。
この方法なら、最初の課題であるTikTok検索の再現可能な調査プロセスへの変換に取り組めます。スクリプトは実際に返された動画を収集し、記録元とスナップショットを保存します。その内容から、人または検証可能な分類器が需要に関する仮説を導き出します。チームは次の意思決定に役立つ基盤を得ながら、公開コンテンツに見られる兆候と、確認済みの販売成果との境界を保てます。