Instagram doesn't give you the words spoken in a reel. Public reel data has the caption, likes and comments, but no transcript and no auto-captions you can download. Most guides about this get you one reel at a time: download the video, upload it to a transcription site, copy the text.
That's fine for one reel. It breaks down when you want the last 50 reels of five creators, with timestamps, in a spreadsheet or a Python script. This tutorial shows two ways to do that: a no-code run in the browser and a few lines of Python. At the end there's a do-it-yourself section on running Whisper on audio you already have.
Disclosure: I built the Apify actor used below (Instagram Reels Transcript Scraper). The DIY section at the end works without it.
What you get per reel
Every reel comes back as one row. These are real field names and values from a run on 26 Oct 2026:
| Field | Example |
|---|---|
url |
https://www.instagram.com/reel/DS46KJNjva9/ |
ownerUsername |
jayshetty |
takenAt |
2025-12-30T13:25:00Z |
likeCount / commentCount
|
50887 / 342
|
playCount |
999070 (filled for reels collected from a creator's profile) |
durationSeconds |
8.81 |
language / languageProbability
|
en / 0.984
|
hook |
One of my favorite quotes from Zig Ziglar is, |
transcript |
timed segments: [{"start": 0, "end": 2.8, "text": "…"}]
|
hasSpeech |
true (music-only reels come back false) |
caption, hashtags, mentions, musicTitle
|
the post text and audio info |
The hook field holds the opening line (segments that start in the first 3 seconds). If you study why some reels outperform others, that's often the part you care about most.
Option 1: No code (Apify Console)
- Open the actor page and click Try for free. Apify's free plan includes monthly platform credit; the actor itself is pay-per-event, and the current price is on the Store page.
- Fill in one or both inputs:
-
Reel or post URLs: paste links like
https://www.instagram.com/reel/DS46KJNjva9/(bare shortcodes work too). -
Creators:
natgeo,@natgeoorhttps://www.instagram.com/natgeo/. The actor lists the creator's reels newest first and works back through their history.
-
Reel or post URLs: paste links like
- Optional: set Max reels per creator, Only reels posted after (a date), Spoken language (leave empty to auto-detect), or Translate to English.
- Click Start. When the run finishes, export the dataset as CSV, JSON or Excel, or send it to Google Sheets.
No Instagram account or cookies are needed; the actor only reads public reels.
Option 2: Python
Install the client: pip install apify-client, then get your API token from Apify Console → Settings → Integrations.
from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("axlymxp/instagram-reel-transcript-scraper").call(run_input={
"reelUrls": ["https://www.instagram.com/reel/DS46KJNjva9/"],
"usernames": ["hubermanlab"],
"maxReelsPerProfile": 15,
"newerThan": "2026-09-01",
"outputFormat": "both", # timed segments + one joined string
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item["error"]: # no_speech, not_available, ...
print("skip", item["url"], item["error"])
continue
print(item["ownerUsername"], item["playCount"], item["url"])
print(" hook:", item["hook"])
print(" text:", item["transcriptText"][:200])
outputFormat accepts segments (a list of {start, end, text}), text (one string) or both (segments in transcript, the string in transcriptText). For LLM input, text is the most compact.
Turn it into a CSV of hooks
A common next step is ranking a creator's reels by plays and reading their opening lines side by side:
import csv
rows = [i for i in client.dataset(run["defaultDatasetId"]).iterate_items() if i["hasSpeech"]]
rows.sort(key=lambda i: i["playCount"] or 0, reverse=True)
with open("hooks.csv", "w", newline="") as f:
w = csv.writer(f)
w.writerow(["plays", "likes", "posted", "hook", "url"])
for i in rows:
w.writerow([i["playCount"], i["likeCount"], i["takenAt"], i["hook"], i["url"]])
Example output
One real row (trimmed: caption, thumbnailUrl and videoUrl removed):
{
"shortcode": "DS46KJNjva9",
"url": "https://www.instagram.com/reel/DS46KJNjva9/",
"sourceType": "url",
"ownerUsername": "jayshetty",
"ownerFullName": "Jay Shetty",
"ownerIsVerified": true,
"takenAt": "2025-12-30T13:25:00Z",
"likeCount": 50887,
"commentCount": 342,
"durationSeconds": 8.81,
"language": "en",
"languageProbability": 0.984,
"hasSpeech": true,
"hook": "One of my favorite quotes from Zig Ziglar is,",
"transcript": [
{"start": 0, "end": 2.8, "text": "One of my favorite quotes from Zig Ziglar is,"},
{"start": 3.9, "end": 6.1, "text": "you don't have to be great to start,"},
{"start": 6.7, "end": 8.7, "text": "but you have to start to be great."}
],
"error": null
}
Reels that can't be transcribed still produce a row, with an error code so you can see what happened: no_speech (music-only or silent), not_available (photo post, removed, private or age-restricted), too_long (over your Skip videos longer than limit). These rows aren't charged.
DIY: transcribe audio you already have with faster-whisper
If you already have the video files (your own reels, for example), you can transcribe them locally with faster-whisper, the same open-source Whisper implementation the actor runs:
import av, numpy as np
from faster_whisper import WhisperModel
def load_audio(path, rate=16000):
"""16 kHz mono float32. Decoding with PyAV yourself avoids faster-whisper's
built-in decoder, which passes an argument that newer PyAV releases removed."""
chunks = []
with av.open(path) as c:
rs = av.AudioResampler(format="s16", layout="mono", rate=rate)
for frame in c.decode(audio=0):
chunks += [f.to_ndarray().reshape(-1) for f in rs.resample(frame)]
chunks += [f.to_ndarray().reshape(-1) for f in rs.resample(None)]
return np.concatenate(chunks).astype(np.float32) / 32768.0
model = WhisperModel("base", device="cpu", compute_type="int8", cpu_threads=1)
segments, info = model.transcribe(load_audio("my_reel.mp4"), beam_size=1, vad_filter=True)
print(info.language)
for s in segments:
print(f"[{s.start:6.2f} → {s.end:6.2f}] {s.text.strip()}")
Three things I learned while building the actor:
-
cpu_threads=1can be faster. On a small cloud container (about one vCPU), more threads made transcription slower. If you have more cores, run several files in parallel instead (num_workersinWhisperModel). -
Turn on
vad_filter. Without voice-activity detection, Whisper tends to invent words over background music. Many reels are music with no speech, so this matters. - Instagram serves more than one audio codec. Some of Instagram's own video streams use xHE-AAC audio, which FFmpeg (and so PyAV) can't decode yet. If decoding fails with "Not yet implemented in FFmpeg", the audio is the problem, not your code.
Scheduling and integrations
To follow a set of creators, save the input as an Apify task, set Only reels posted after to a recent date, and schedule it daily or weekly. Each run picks up the new reels; send the dataset to Google Sheets, Slack, Make, Zapier or a webhook. If a run is interrupted, starting it again with the same input continues where it stopped, without duplicate rows.
FAQ
Do I need an Instagram login? No. Only public reels are read, logged out. Private accounts and stories aren't accessible.
How accurate is it? It's Whisper's base model. Clear speech in English and other major languages comes out well; heavy music, several people talking at once, or slang lower accuracy. Setting the spoken language helps on short clips, where auto-detection has little to go on.
Which languages? Whisper covers about 100. With Translate to English on, you get English text for any of them; in my test, Spanish reels from a Spanish-language creator came back translated.
How far back can it go for a creator? In testing it paged through 300 reels per creator, with more still available. Use Max reels per creator and Only reels posted after to set the window.
Is it legal? This reads publicly available posts without logging in. What you do with the data is on you: respect Instagram's terms and privacy law (GDPR/CCPA) when the content includes personal data.
If you try it and something breaks, open an issue on the actor page and I'll take a look.
Top comments (0)