

# 内容分析研究のためにTikTokの文字起こしデータを準備する

はい。公開されているTikTokの字幕は、自分で検証したうえで、量的内容分析のために収集できます。量的内容分析とは、自分で範囲を定めた動画群について、明確に定めた1つの内容上の基準に当てはまる件数を数えることです。字幕トラックは、動画にすでに付いている公開テキストです。この記事では、その字幕テキストを文字起こしと呼びます。発話のすべてを新たに書き起こすことではありません。検証とは、自分で字幕と動画を照合することです。返されるテキストは字幕データです。発話や映像の完全な記録でも、すべてのTikTok投稿の記録でもありません。

公開の研究相談では、研究用にTikTokの文字起こしを収集する方法が尋ねられています。正確さの確認方法と、Rでテキストを分析する方法についても質問されています。Rは、表の処理や集計に使うプログラムです。この質問から、そうした必要性があることは分かります。ただし、この方法の妥当性を証明するものではありません。この方法を必要とする研究がどれほどあるかも分かりません。[TikTokの文字起こしに関する公開の質問](https://www.reddit.com/r/CompSocial/comments/1h1zijh/help_needed_scraping_tiktok_video_transcripts_for/)をご覧ください。

データの行はActorで収集します。Actorは、サービスとして提供されるデータ収集プログラムです。自分のコンピューター上のファイルとしてではなく、サービス上で動作します。ここで使うActorは、[TikTok Transcript Scraper](https://apify.com/chronometrica/tiktok-transcript-scraper)です。Taskは、保存済みの入力プリセットです。プリセットとは、入力項目の選択内容を保存したものです。現在のTaskは、[TikTok Transcript Dataset for Content Analysis](https://apify.com/chronometrica/tiktok-transcript-scraper/examples/tiktok-transcript-content-analysis-dataset)です。Actorはソースデータの行を収集します。コーディングと集計は自分で行います。公開Taskは、入力の出発点にすぎません。分析が自動で進むという意味ではありません。

## 現在の収集プログラムが返すデータ

以下の公開情報は、2026-10-13に確認しました。現在のビルドは0.1.36です。1回の実行で、既知の公開対象を1〜100件、`startUrls`に指定できます。対象には、動画URL、短縮リンク、数値形式の動画IDを使えます。このActorは、プロフィール、キーワード、楽曲ページを検索しません。対象の動画を事前に把握しておく必要があります。

`preferredLanguages`は、公開字幕の言語を優先順に並べたリストです。Actorは、最初に一致した公開字幕トラックを選びます。リストが空の場合は、最初の公開字幕トラックを使います。テキストの翻訳は行いません。字幕のない音声からテキストを作成することもありません。コメントは収集しません。内容コードも付与しません。

保存済みTaskの入力は次のとおりです。

```json
{"startUrls": ["https://www.tiktok.com/@hankgreen1/video/7047596209028074758"], "preferredLanguages": []}
```

現在の収集プログラムが返すデータ

このサンプルを、自分が把握している公開動画に置き換えてください。動画1本で予備試験を行う場合は、`startUrls`にURLを1つ入れます。研究対象の言語が英語なら、`preferredLanguages`を`["en"]`に設定します。別の言語を対象にする場合は、その言語の選定基準を明記し、該当する言語コードを使ってください。英語以外の研究に`["en"]`をそのまま使わないでください。リストを空にすると、別の公開字幕トラックが返される場合があります。

送信した入力1件につき、データセットに1行が作られます。入力の処理に失敗した場合も、行が作られます。その行を残してください。収集を試みた証拠になります。フィールド名は正確には、`inputIndex`、`inputUrl`、`videoId`、`videoUrl`、`authorHandle`、`durationSeconds`、`languageCode`、`text`、`segments`、`status`、`statusReason`、`isBillable`、`scrapedAt`です。各セグメントには、`startSeconds`、`endSeconds`、`text`があります。TikTokの時刻情報には、この2つのキーを使います。`startSeconds`と`endSeconds`は、返された値のまま使ってください。すべての`videoId`は、数値ではなく文字列として保存してください。長いIDを数値として保存すると、プログラムによって値が変わる場合があります。

`status`には、`succeeded`、`no_public_captions`、`language_unavailable`、`duplicate_input`、`not_tiktok`、`invalid_input`、`unsupported_url`、`video_unavailable`、`not_public`、`blocked_or_challenged`があります。抽出の失敗や字幕処理の失敗が起きる場合もあります。成功とするには、空でないテキストが必要です。失敗を示すステータスが付き、文字起こしがnullまたは空だったとしても、発話がないことを意味しません。動画には発話がある可能性があります。分かるのは、使える字幕が返されなかったということだけです。

`isBillable`がtrueになるのは、動画に元から付いている字幕による文字起こしの取得に成功した場合だけです。フィールド名のbは小文字です。それ以外の行も、証拠としてデータに残ります。`isBillable`は、実行全体について示すものではありません。実行には、trueの行とfalseの行が混在する場合があります。実行全体を無料と呼ばないでください。

実行の集計には、正確に次の名前が使われます。`inputsReceived`、`resultRows`、`billableTranscriptRows`、`nonBillableResultRows`、`duplicateVideosSkipped`、`statusCounts`です。この集計は、実行結果を確認するためのものです。選んだ動画が、すべてのクリエイターを代表していることを示すものではありません。

2026-10-13に、保存済みサンプルを使って動画1本の確認を行いました。その行の処理は成功しました。動画IDは、指定したIDの`7047596209028074758`でした。言語コードは`eng-US`でした。`eng-US`のようなコードは、地域を含む字幕のラベルです。米国の地域タグが付いた英語を意味します。翻訳ではありません。テキストは空ではありませんでした。行には12個のセグメントがありました。セグメントの時刻情報は、`startSeconds`と`endSeconds`という形式でした。その動画の字幕の文言は、ここには転載していません。この確認によって、その1件のソースから公開字幕を取得できる経路があることは示されました。文言が完全に正しいことは証明していません。すべての動画で成功することも証明していません。自分の内容コードが妥当であることも証明していません。自分の研究用の予備試験は、引き続き必要です。

## 8つの手順

1. 研究課題、コード、サンプルを定める

研究課題を1つ書きます。集計できる内容コードを1つ書きます。内容コードとは、存在するかどうかを記録する特徴のことです。説明できる基準に従って、既知の動画URLを選びます。収集する前に、対象期間、言語、クリエイターの範囲を明記してください。

サンプリング記録表は、選んだすべての動画の一覧です。字幕を見る前に、この記録表を埋めてください。字幕があるという理由だけで動画を選ばないでください。字幕がある動画だけを残すと、サンプルを変更したことになります。字幕がない動画が、欠損として記録される代わりにサンプルから外れてしまいます。

1. 集計単位を明記する

何を1単位として数えるかを決めます。単位には、動画1本、字幕セグメント1つ、自分で定義した一節などを使えます。この手順では、動画を集計単位とします。セグメントとは、時刻情報を持つ字幕テキストの一部分です。セグメントは、その動画について判断する根拠です。別の動画として数えるものではありません。

同じコードが付いたセグメントが3つあっても、動画は1本と数えます。実行前に、このルールを書いておいてください。入力URL、返された`videoId`、自分で付けた`selected_video_key`を保存します。研究用のキーは自分で管理します。URLの形式が変わっても、このキーは変えません。

1. 選んだ動画1本で試す

複数の動画をまとめて実行する前に、選んだ動画1本で実行します。これが予備試験です。予備試験とは、手順全体を小規模に試すことです。`status`、`videoId`、`languageCode`、`text`を確認してください。成功とするには、テキストが空でない必要があります。研究対象の言語が英語なら、この予備試験で`preferredLanguages`を`["en"]`に設定してください。

次に、[短い一節を音声と照合します](/ja/articles/tiktok-%E5%AD%97%E5%B9%95%E3%81%AE%E5%8D%98%E8%AA%9E%E7%B2%BE%E5%BA%A6%E7%A2%BA%E8%AA%8D/)。画面の映像も確認してください。抜けた語、余分な語、誤った語を記録します。行の処理が成功しても、文言の正確さが証明されたわけではありません。前述の12セグメントの確認は、公開字幕の取得経路から字幕が返され得ることを示しただけです。自分の研究のための検証にはなりません。

1. 範囲を定めたリストを実行し、結果を照合する

把握済みのURLだけを送信します。1回の実行では最大100件です。選定リストがそれより長い場合は、別の実行が必要です。送信した入力のすべてを、それぞれ1行の結果と対応付けます。`inputIndex`、`inputUrl`、`videoId`を使ってください。それらの行を、実行の集計と照合します。

`inputsReceived`、`resultRows`、`billableTranscriptRows`、`nonBillableResultRows`、`duplicateVideosSkipped`、`statusCounts`を確認してください。重複はメモに残し、その動画は1回だけ数えます。取得できなかった字幕も記録表に残しますが、成功したデータの集合からは除外します。ソースへのアクセスに失敗した場合も同じように扱います。該当するステータスには、`video_unavailable`、`not_public`、`blocked_or_challenged`、`unsupported_url`、`not_tiktok`、`invalid_input`があります。

取得に失敗して空になったテキストを、決して「コードに該当する内容なし」と分類しないでください。空のテキストは、コードに該当する内容を観測できなかったことを意味します。その内容が存在しないという意味ではありません。

1. 動画表とセグメント表を作る

選んだ動画1本につき1行を作ります。列は、`selected_video_key`、`videoId`、`videoUrl`、`languageCode`、`caption_status`、`caption_validated`、`study_eligible`、`exclusion_reason`を使います。`study_eligible`には、その動画を集計に含めてよいかどうかを記録します。内容コードではありません。`caption_validated`には、字幕を動画と照合したかどうかを記録します。

字幕を確認できなかった場合は、`unknown`を使ってください。実際には不明なのに、`caption_validated`をnoにしないでください。失敗した行は検証済みではありません。確認が途中の行も、検証済みではありません。

セグメントは2つ目の表に保存します。動画キー、`startSeconds`、`endSeconds`、`text`、ソースについての観察記録を使います。セグメントの配列を表計算の1つのセルに入れるのは、適切なエクスポート方法ではありません。CSVは、入れ子のないテキストの表です。入れ子のリストを、その形式に安全に収めることはできません。まずJSONでエクスポートしてください。JSONは、1つの項目の中にリストを保持できるテキスト形式です。その後、意図的に平坦化します。平坦化とは、セグメントごとに通常の1行を作ることです。各セグメント行に動画キーを繰り返し記入します。テキストをカンマで分割しないでください。文の中にもカンマがあります。元の字幕テキストは変更せずに残します。コードはすべて新しい列に入れてください。

1. 簡単なコーディングルールを書き、架空の例で試す

この手順以降の例は架空のものです。Actorの出力ではありません。これらのIDを実際の入力に貼り付けないでください。対応する動画URLは不明です。

短いルールを書き、バージョンを付けます。単語の完全一致を使う簡単なルールなら、対象の単語、大文字と小文字の扱い、単語境界を定めます。単語境界は、短い単語が長い単語の一部として一致するのを防ぐものです。曖昧な用法は自分で確認してください。キーワードのルールで見つかるのは、その文字のパターンだけです。話し手の意図を証明するものではありません。映像だけで示される主張が、言葉と食い違う場合もあります。皮肉も、言葉どおりの意味と食い違う場合があります。そのような場合は、人がコーディングする必要があります。

ここで使う架空のコードは、水を量るよう明示的に指示しているかどうかです。ルールのバージョンv1では、検証済みの字幕テキストにその指示があれば、コード1を付けます。単語全体が「measure」と一致すれば、大文字と小文字を問わず数えます。「measurement」は別の単語です。人が確認してください。キーワードのルールだけでコードを付けないでください。コード0を付けてよいのは、検証済みの字幕にその指示がない場合だけです。それ以外の場合は、コードを不明のままにします。

1. 予備試験用の小さなデータ群を確認してから、Rに読み込む

大規模な研究の前に、少数の予備試験用データをコーディングします。2人が互いに独立してコーディングしてもかまいません。あるいは、自分で後日もう一度コーディングし、比較することもできます。`code`、`code_version`、`coder`、`decision_note`、タイムスタンプを保存します。タイムスタンプには、判断した日時を記録します。元のテキストは専用の列に残してください。

Rは、入れ子のないCSVを読み込めます。ID列は文字列として扱うよう、Rに指定してください。数値のIDだとRに推測させないでください。以下のコードは、Rの標準機能であるbase Rだけを使います。架空の行を使っています。パッケージは追加しません。検証済みの研究標準ではありません。ここで、`denom`は分母です。分母とは、割り算で割る数となる、集計対象の動画数です。`NA`は、値が得られないことを表します。この例では、不明なコードに`NA`を使います。

```r
videos <- data.frame(
  videoId = c("video-v1", "video-v2", "video-v3", "video-v4", "video-v5"),
  study_eligible = c("yes", "yes", "no", "no", "no"),
  code = c(1, 0, NA, NA, NA),
  stringsAsFactors = FALSE
)
stopifnot(is.character(videos[["videoId"]]))
eligible <- videos[["study_eligible"]] == "yes"
stopifnot(!any(is.na(videos[["code"]][eligible])))
code_count <- sum(videos[["code"]][eligible])
denom <- sum(eligible)
share <- code_count / denom
```

8つの手順

`code_count`は1です。`denom`は2です。`share`は0.5です。`NA`は、不明なコードを表します。ゼロではありません。`stopifnot`の行は、集計対象の動画に不明なコードがある場合、スクリプトを停止します。

実際のCSVをエクスポートする場合も、同じ文字列の確認を行ってください。

```r
videos_from_csv <- read.csv(
  "video_table.csv",
  stringsAsFactors = FALSE,
  colClasses = c(videoId = "character")
)
stopifnot(is.character(videos_from_csv[["videoId"]]))
```

8つの手順

架空の例の単語ルールも、base Rで試せます。

```r
raw_text <- "I measure the water before I pour it"
hit <- grepl("\\bmeasure\\b", raw_text, ignore.case = TRUE)
ambiguous <- grepl("\\bmeasure\\b", "measurement", ignore.case = TRUE)
```

8つの手順

`hit`はTRUEです。`ambiguous`はFALSEです。2つ目の結果が示すとおり、長い単語を自動的に一致扱いするのではなく、人が確認するためのルールが必要です。

1. すべての処理結果の記録表とともに集計値を報告する

コードに該当する動画の割合は、集計対象の動画だけを分母にして計算します。集計対象とは、`succeeded`で、テキストが空でなく、`caption_validated`がyesの動画です。その割合と併せて、選んだすべての動画の処理結果を報告してください。読者が、失敗した行や確認が途中の行も確認できる必要があります。結果の適用範囲は、自分で選び、観測した動画に限定してください。すべてのクリエイターについての結果として述べないでください。政治的な発話すべてについての結果としても述べないでください。

字幕の欠損によって、結論が変わる可能性があります。この記事で扱うのは、記録表の作成までです。[欠損した行によって結果がどれほど変わり得るか](/ja/articles/tiktok-%E5%AD%97%E5%B9%95%E6%AC%A0%E6%90%8D-%E7%A0%94%E7%A9%B6%E3%82%B5%E3%83%B3%E3%83%97%E3%83%AB/)は説明しません。

## 架空の動画5本の集計例

ここでも、同じ架空の例を続けます。V1からV5は、架空の研究用ラベルです。`videoId`の値も、架空の文字列ラベルです。実在するTikTokのIDではありません。どの行も`videoUrl`は不明です。これらのラベルをActorに送信しないでください。実際の`languageCode`は、`eng-US`のような地域を含む値になる場合があります。この架空の例では、研究対象の言語を英語と定めているため、`en`を使います。表では、研究上の不明値をunknownで表します。Rの例では、同じ不明値を`NA`で保存します。不明値を0として保存しないでください。

定義したコードは、引き続き、水を量るよう明示的に指示しているかどうかです。

V1は英語です。取得は成功しました。字幕は検証済みです。テキストは「I measure the water before I pour it」です。このテキストは、水を量るよう明示的に指示しています。コードは1です。

V2は英語です。取得は成功しました。字幕は検証済みです。テキストは「I pour water into the tray」です。水を注ぐよう伝えています。量るようには伝えていません。コードは0です。

V3のステータスは`no_public_captions`です。テキストは空です。`languageCode`は不明です。値を作って埋めないでください。`caption_validated`はnoではなくunknownです。コードは不明です。空のテキストはコード0ではありません。無言である証拠でもありません。

V4のステータスは`language_unavailable`です。テキストは空です。`languageCode`は不明です。`caption_validated`はunknownです。コードは不明です。この失敗は、V3と同じ失敗ではありません。2つの理由を分けて記録してください。

V5は英語です。取得は成功しました。人が字幕と音声を照合しました。字幕には、水を量ることに関する表現が抜けていました。検証は終わっていません。`caption_validated`はnoではなくunknownです。`study_eligible`はnoです。コードは不明のままにします。取得の成功と、字幕を信頼できることは同じではありません。この行をコード0にしないでください。

割合の計算に動画を含めるのは、`study_eligible`がyesの場合だけです。その判断は、コードとは別です。V1とV2は、字幕の取得に成功し、テキストが空でなく、人が検証したため、集計対象です。V3とV4は、収集に失敗したため、集計対象ではありません。V5は、検証が終わっていないため、集計対象ではありません。V1、V2、V5は文字起こしの取得に成功しているため、`isBillable`はtrueになります。V3とV4の`isBillable`はfalseになります。V5は課金対象ですが、集計対象ではありません。課金対象フラグによって、研究の集計対象が決まるわけではありません。

動画表：

| selected_video_key | videoId | videoUrl | languageCode | caption_status | caption_validated | study_eligible | exclusion_reason |
| --- | --- | --- | --- | --- | --- | --- | --- |
| V1 | video-v1 | unknown | en | succeeded | yes | yes | none |
| V2 | video-v2 | unknown | en | succeeded | yes | yes | none |
| V3 | video-v3 | unknown | unknown | no_public_captions | unknown | no | 公開字幕なし。空のテキストにはコードを付けない |
| V4 | video-v4 | unknown | unknown | language_unavailable | unknown | no | 指定言語の字幕なし。空のテキストにはコードを付けない |
| V5 | video-v5 | unknown | en | succeeded | unknown | no | 音声との照合が未完了。量ることに関する表現が字幕から抜けている |

コード表：

| selected_video_key | code | code_version | coder | decision_note | timestamp |
| --- | --- | --- | --- | --- | --- |
| V1 | 1 | v1 | coder-a | 水を量るよう明示的に指示している | example-only |
| V2 | 0 | v1 | coder-a | 注ぐ指示のみ。量る指示はない | example-only |
| V3 | unknown | v1 | coder-a | 字幕の取得に失敗。コードに該当する内容は観測できていない | example-only |
| V4 | unknown | v1 | coder-a | 字幕の取得に失敗。コードに該当する内容は観測できていない | example-only |
| V5 | unknown | v1 | coder-a | 字幕は未検証。コードに該当する内容は観測できていない | example-only |

セグメント表：

| selected_video_key | startSeconds | endSeconds | text | source_observation |
| --- | --- | --- | --- | --- |
| V1 | 0.5 | 2.5 | I measure the water before I pour it | コード1の根拠となる検証済み字幕 |
| V1 | unknown | unknown | unknown | 量ることに関する2つ目のセグメント。追加の根拠としてのみ使用。ここでは時刻を設定していない |
| V2 | unknown | unknown | I pour water into the tray | コード0の根拠となる検証済み字幕 |
| V5 | unknown | unknown | unknown | 音声にある、量ることに関する表現が字幕から抜けている。ここでは文言を設定していない |

V3とV4には、セグメント行がありません。結果のテキストが空だからです。0秒のセグメントを追加しないでください。0を入れると、実際の時刻のように見えてしまいます。欠損した時刻は不明のままにします。

V1の最初のセグメントは、2.5秒で終わり、0.5秒から始まります。2.5から0.5を引くと、2.0秒です。この長さは、`endSeconds`と`startSeconds`を使って求めます。V1の2つ目のセグメントも、量ることに関するコードの根拠になります。ただし、動画が2本になるわけではありません。V1は、コードを付けた動画1本のままです。集計単位は動画です。

検証済みで集計対象となる動画は2本です。コード1の動画は1本です。1 / 2 = 0.5で、50パーセントです。選定記録表には、引き続き5本の動画があります。1 / 5 = 0.2で、20パーセントです。20パーセントと報告しないでください。V3、V4、V5は、コード0であると確認されていません。不明は0ではありません。

選んだ動画5本の処理結果は、検証済みで集計対象が2本、失敗が2本、検証未完了が1本です。失敗した2本のステータスは異なります。1本は`no_public_captions`です。もう1本は`language_unavailable`です。

この5件の入力を1回送信したとします。重複はなかったとします。以下の想定集計は、この架空の例から計算したものです。実際の実行記録ではありません。`inputsReceived`は5になります。`resultRows`は5になります。`billableTranscriptRows`は3になります。`nonBillableResultRows`は2になります。`duplicateVideosSkipped`は0になります。`statusCounts`には、`succeeded`が3、`no_public_captions`が1、`language_unavailable`が1と表示されます。実際の実行では、実際に受け取った集計と照合する必要があります。

この例は、セグメントが増えても動画の本数は増えないことを示しています。集計対象だけを使う分母も示しています。実際の動画群を示すものではありません。字幕の正確さの割合を示すものでもありません。この架空の行以外に及ぶ効果も示していません。

値が分かっているV1のセグメントを、入れ子のないJSONで表すと次のようになります。

```json
{
  "videoId": "video-v1",
  "segments": [
    {
      "startSeconds": 0.5,
      "endSeconds": 2.5,
      "text": "I measure the water before I pour it"
    }
  ]
}
```

架空の動画5本の集計例

このオブジェクトは、平坦化の手順を示すための架空のものです。平坦化した後は、上のセグメント表をコーディング用に使います。動画表は、引き続き集計用に使います。

## 次に行うこと

リスト全体を収集する前に、選んだ公開動画1本を検証してください。入力、ステータスを含む結果行、コーディング記録表を確認します。Actorの[TikTok Transcript Scraper](https://apify.com/chronometrica/tiktok-transcript-scraper)と、Taskの[TikTok Transcript Dataset for Content Analysis](https://apify.com/chronometrica/tiktok-transcript-scraper/examples/tiktok-transcript-content-analysis-dataset)を使ってください。サンプル動画を、自分で把握している公開動画のURL1つに置き換えてください。

## 出典と参考資料

- [TikTok Transcript Scraper](https://apify.com/chronometrica/tiktok-transcript-scraper)
- [TikTok Transcript Dataset for Content Analysis](https://apify.com/chronometrica/tiktok-transcript-scraper/examples/tiktok-transcript-content-analysis-dataset)
- [TikTok動画の文字起こしに関する公開の質問](https://www.reddit.com/r/CompSocial/comments/1h1zijh/help_needed_scraping_tiktok_video_transcripts_for/)