跳到正文
DEV Community· meng xipeng·· 2 小時前AI 評分59

如何取得 Facebook 影片及 Reels 文字稿(即使沒有字幕)

How to Get Facebook Video and Reel Transcripts (Even When There Are No Captions)

AI 導讀

作者示範三種取得 Facebook 影片及 Reels 文字稿的方法,包括免程式碼操作、Python 批量處理,以及免登入、免 API key 擷取 Facebook 字幕或音軌的自製腳本。作者測試的 13 條影片中,6 條有 Facebook 字幕、6 條以 Whisper AI 轉錄,另有 1 條已刪除影片無法取得;整次測試用時 61 秒。

正文

Facebook plays captions on many videos and Reels, but if you don't own the video there's no button to export them, and there's no public API for transcripts of other pages' videos. Most online tools want you to download the file first, and several of the URL-based ones only return captions that already exist or stop at clips of about two minutes.

This tutorial covers three routes: a no-code run, a few lines of Python for bulk jobs, and a do-it-yourself script that reads Facebook's own caption file and audio track with no login and no API key. I'll also show what happens with videos that have no captions, which in my test was half of them.

Disclosure: I built the Apify actor used in options 1 and 2 (Facebook Video & Reel Transcript Scraper). Option 3 doesn't use it.

Captions vs. no captions: what I found

On 27 Oct 2026 I ran a test on Apify's cloud with 7 links I picked (NASA, Gordon Ramsay, MrBeast Gaming, Tasty, BBC News, plus one deleted video) and the 4 latest videos from the CNN and Gordon Ramsay pages. Two of the Gordon Ramsay page videos were already in my link list, so that's 13 different videos:

  • 6 had a Facebook caption track, which came back as is. One of them was a 12-minute MrBeast Gaming video (2,613 words).
  • 6 had no caption track and were transcribed with Whisper AI, including all four CNN videos.
  • 1 (the deleted video) came back as a not_available row.

The whole run took 61 seconds. A captions-only tool would have returned nothing for half of the live videos.

What you get per video

One row per video. Real values from that run:

Field Example
url, videoId, isReel https://www.facebook.com/reel/1863947697952668/
ownerName, ownerId NASA - National Aeronautics and Space Administration
title, description NASA Moon Base: Rovers and Lunar Terrain Vehicles, plus the post text
createdAt 2026-10-20T15:04:42Z
viewCount, reactionCount, commentCount 54203, 2840, 142
durationSeconds 45.82
transcriptSource facebook_captions (or ai_whisper when the video had none)
language, isAutoGenerated, isTranslated en, false, false
hook Think about hundreds of square miles where we have different assets.
transcript [{"start": 0.934, "end": 5.205, "text": "Think about hundreds of square miles where we have different assets."}, …]
srt, vtt ready-to-use subtitle text (optional)
sourcePage cnn when the video came from a page input

Facebook doesn't show view counts for every link style: in my runs /reel/… links often had none, while watch and page-video links had them.

Option 1: No code (Apify Console)

  1. Open the actor page and click Try for free. Apify's free plan includes monthly platform credit; the actor itself is pay-per-event (current price on the Store page), and videos without speech or removed videos aren't charged.
  2. Paste links into Facebook videos and Reels: Reel links (facebook.com/reel/…), watch links (facebook.com/watch/?v=…), page video links or bare numeric video IDs.
  3. Or put a page name such as cnn into Pages (latest videos) to transcribe the videos on that page's Videos tab.
  4. Pick the Transcript source:
    • auto: Facebook's captions when present, Whisper AI otherwise,
    • captions: Facebook's captions only (cheapest; no AI),
    • ai: always transcribe the audio, e.g. to translate everything to English.
  5. Click Start and export the results as CSV, JSON or Excel. Turn on Add SRT and VTT subtitles if you need subtitle files.

Option 2: Python

pip install apify-client, then take your API token from Apify Console → Settings → Integrations.

from apify_client import ApifyClient

client = ApifyClient("<YOUR_APIFY_TOKEN>")

run = client.actor("axlymxp/facebook-video-transcript-scraper").call(run_input={
    "videoUrls": [
        "https://www.facebook.com/watch/?v=1863947697952668",
        "https://www.facebook.com/reel/1083087217889684",
    ],
    "pageUrls": ["cnn"],          # latest videos from a page's Videos tab
    "maxVideosPerPage": 4,
    "transcriptSource": "auto",
    "outputFormat": "both",       # segments + one joined string
})

for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    if item["error"]:             # no_speech, not_available, too_long, ...
        print("skip", item["videoId"], item["error"])
        continue
    print(item["ownerName"], item["viewCount"], item["transcriptSource"])
    print("  hook:", item["hook"])
    print("  text:", item["transcriptText"][:200])

outputFormat accepts segments ([{start, end, text}]), text (one string) or both (segments in transcript, the string in transcriptText).

Track what a competitor page talks about

Schedule the same input daily with only pageUrls, then search the new rows:

keywords = ["election", "tariff", "AI"]
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    text = (item.get("transcriptText") or "").lower()
    hits = [k for k in keywords if k.lower() in text]
    if hits:
        print(item["createdAt"], item["url"], hits)

Example output

One real row from a CNN video without captions (Whisper; trimmed to three segments):

{
  "videoId": "1806375253903444",
  "url": "https://www.facebook.com/reel/1806375253903444/",
  "sourcePage": "cnn",
  "description": "On Sunday, during stormy weather, a TUI Cruises passenger ship clashed with two container ships while departing Germany on its way to the Canary Islands.",
  "ownerName": "CNN",
  "createdAt": "2026-10-27T00:25:01Z",
  "durationSeconds": 51.67,
  "viewCount": 12591,
  "reactionCount": 150,
  "availableCaptionLanguages": [],
  "transcriptSource": "ai_whisper",
  "language": "en",
  "hook": "This is the moment a cruise liner bound for the Canary Islands collides with two container ships",
  "transcript": [
    {"start": 0, "end": 6, "text": "This is the moment a cruise liner bound for the Canary Islands collides with two container ships"},
    {"start": 6, "end": 13.28, "text": "while departing Bremerhaven Germany. During Stormy Weather a T.Y. cruise's ship was caught by a gust of wind"},
    {"start": 13.28, "end": 19.68, "text": "and local police said that it caused it to collide with two more container ships. The impact"}
  ],
  "wordCount": 130,
  "audioSeconds": 51.69,
  "error": null
}

Note "T.Y. cruise's" in the second segment: the post text says "TUI Cruises". Whisper (base model) handles clear speech well but can mishear brand names, so check names against the description when they matter.

Option 3: DIY, Facebook's own captions with no login

A public Facebook video page embeds its data as JSON inside <script type="application/json"> tags. The video's caption tracks and an audio-only stream are in there, readable without cookies or login. You need curl_cffi (pip install curl_cffi) so the request looks like a normal browser:

import json, re
from curl_cffi import requests

video_id = "1863947697952668"
s = requests.Session(impersonate="chrome124")
html = s.get(f"https://www.facebook.com/watch/?v={video_id}",
             headers={"Accept-Language": "en-US,en;q=0.9", "Sec-Fetch-Mode": "navigate"}).text

def walk(o):
    if isinstance(o, dict):
        yield o
        for v in o.values(): yield from walk(v)
    elif isinstance(o, list):
        for v in o: yield from walk(v)

dicts = [d for m in re.finditer(r'<script type="application/json"[^>]*>(.*?)</script>', html, re.S)
         for d in walk(json.loads(m.group(1)))]

# caption tracks sit on the video node; the original track is "Auto-generated" or uploaded (null)
tracks = next((d["video_available_captions_locales"] for d in dicts
               if d.get("id") == video_id and d.get("video_available_captions_locales")), [])
for t in tracks:
    print(t["locale"], t["localized_creation_method"])
if tracks:
    print(s.get(tracks[0]["captions_url"]).text[:200])

# audio-only track for speech-to-text when there are no captions
audio = next((r["base_url"] for d in dicts if str(d.get("video_id")) == video_id
              for r in d.get("representations") or [] if r.get("mime_type", "").startswith("audio")), None)
print("audio:", audio[:80] if audio else None)

For this NASA video it prints en_US None (a caption file the page uploaded), the first SRT cues, and an audio URL. Things I ran into while building this:

  • The video is split across many JSON fragments. Duration, captions, owner and the post text live in different objects that share the video's id, and the audio stream sits in a separate all_video_dash_prefetch_representations block keyed by video_id. Collect everything first, then merge.
  • Don't trust the first counter you find. The page also embeds comments and "related videos", each with its own reaction_count. My first version gave three different MrBeast videos the same reaction count. Read counters only from the post's own feedback object and skip the comment and related-video branches.
  • Many caption tracks are machine translations. One Gordon Ramsay Reel had 10 tracks. localized_creation_method is written in the track's own language: Auto-generated marks the original speech-recognition track, null an uploaded file, and strings like Traducido automáticamente a translation. Pick the original unless you want a translation.
  • The audio-only track is small. It's an AAC stream of about 70 kbit/s that a plain GET downloads in full, so you don't need the video file for speech-to-text. Transcribe it with faster-whisper.
  • A deleted video doesn't return 404. A dead Reel loads a normal page whose embedded query returns "video": null, and a dead watch link shows the logged-out "Discover" feed instead. Check for that before reporting a block.
  • Page listings stop at the first screen. A page's Videos tab embeds 9 to 41 videos logged out; loading more needs a login. For a page's full history, collect the video links another way.

Scheduling and integrations

Run the actor from a schedule (for example, daily on a list of pages), Make, Zapier or the API, and send the dataset to Google Sheets, Slack or a webhook. An interrupted run resumes where it stopped when started again with the same input.

FAQ

Do I need a Facebook account? No. Only public videos are read, logged out. Group videos, private videos and Stories aren't supported.

Long videos? Caption tracks work at any length (the 12-minute MrBeast Gaming video above came from captions). AI transcription skips videos longer than Skip AI transcription above (minutes), 30 by default, such as multi-hour live-stream replays; they come back as free too_long rows.

Other languages? Set your preferred caption order with Preferred caption languages; isTranslated: true marks Facebook's machine translation. Whisper detects ~100 spoken languages and can translate them to English.

Is it legal? These are public videos read without logging in. How you use the data is on you: respect Meta's terms and privacy law (GDPR/CCPA).

Questions or a video that won't work? Open an issue on the actor page and I'll take a look.

來源:DEV Community · dev.to