# Facebook Reels Scraper — Views, Reactions & MP4 URLs (`scrapersdelight/facebook-reels-scraper`) Actor

Scrape every Reel a public Facebook Page posts: permalink, MP4 video URL, thumbnail, caption, view count, exact reactions, comments, shares, duration, posted-at and author Page. No login, no cookies. Measured on 624 live reels: 100% views/reactions/comments/shares, 98% captions.

- **URL**: https://apify.com/scrapersdelight/facebook-reels-scraper.md
- **Developed by:** [Scrapers Delight](https://apify.com/scrapersdelight) (community)
- **Categories:** Social media, Videos, Marketing
- **Stats:** 6 total users, 3 monthly users, 99.5% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$2.00 / 1,000 per reel returneds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Facebook Reels Scraper — Views, Reactions & MP4 URLs

Pull every Reel a public Facebook Page has published, as clean rows. One row per reel:
**permalink, video\_url\_hd, video\_url\_sd, thumbnail\_url, caption, posted\_at,
duration\_seconds, view\_count\_approx, reaction\_count, comment\_count, share\_count\_approx,
audio\_track\_title** and the author Page (**page\_name, page\_id, page\_url,
page\_is\_verified**). No login, no cookies, no session token, no CAPTCHA solving.

```json
{
  "pages": ["redbull", "gopro"],
  "maxReelsPerPage": 20,
  "proxyConfiguration": { "useApifyProxy": true }
}
```

Click **Try for free** and press **Start** — that block is literally the input this Actor
ships with. It returned **40 reels, 0 duplicates, 100% fill on views, reactions, comments,
shares, thumbnails and MP4 URLs**, and cost **$0.08**.

Calling it from the API, an MCP client or an AI agent with an **empty input** is safe too:
with no Pages supplied the Actor runs that same two-Page sample (20 reels each) and says so
in the run status, instead of failing.

***

### Read this before you buy rows

Six things that would otherwise turn into a refund request.

1. **View counts are ROUNDED. Reactions and comments are EXACT.** This is the single most
   important thing on this page. Facebook itself only publishes a *bucketed* play count on
   the reels surface — the literal string it renders is `"3.2M"`, `"807K"`, `"9.8K"`. On a
   360-reel sample, **93.9% of view counts came back bucketed** (they carry a K/M/B suffix
   and are rounded to two significant figures); only the 6.1% under 1,000 are exact. You
   get both `view_count_text` (`"3.2M"` — what Facebook literally said) and
   `view_count_approx` (`3200000` — that string expanded to a number). **Do not present
   `view_count_approx` as a precise figure.** `reaction_count` and `comment_count`, by
   contrast, are exact integers on 100% of rows. `share_count` sits in between: 85.6% of
   rows carried an exact integer, 14.4% were bucketed.
2. **There is no keyword mode and no hashtag mode.** This Actor reads a Page's public reels
   tab. Facebook puts hashtag pages, keyword search and Watch behind a login — verified
   2026-08-15, all four surfaces (`/hashtag/…`, `/search/videos/`, `/watch/search/`,
   `/reel/?q=`) redirect a logged-out client to a login form. Anything that claims to
   search reels by keyword without a login is doing something this Actor deliberately does
   not do. **You must supply Pages.**
3. **Some Pages have no public reels tab, and that is not a bug.** Facebook answers
   `"This content isn't available right now"` for those — measured identically on
   datacenter and on residential IPs, so it is a content state, not a block. Of 20 varied
   brand Pages tested, 2 answered this way (tiffanyandco, Corona) and 2 more served a reels
   tab with an empty collection (NASA, thedodo). Those Pages are logged, skipped, and
   **never billed**.
4. **`video_url_hd` and `video_url_sd` are signed, expiring CDN links.** They are the real
   MP4s and they play, but Facebook signs them with an expiry (hours, not days) and ties
   them to the fetching context. Download what you need promptly; do not store them as
   permanent links. `permalink` is the stable one.
5. **No follower count, no email, no contact details.** This Actor returns reels, not Page
   profiles. If you need Page-level fields (likes, category, phone, email, website, bio),
   run our **Facebook Page Scraper** and join on `page_id`.
6. **No comments text, no commenter list.** `comment_count` is a number. The comments
   themselves are a different record and are not in this dataset.

***

### What you get

| Field | What it is |
|---|---|
| `reel_id` | Facebook's numeric reel/video id. Stable, and the de-duplication key. |
| `permalink` | `https://www.facebook.com/reel/<id>` — the durable public link. |
| `page_slug` | The handle you supplied. |
| `page_name` | The Page's display name, as Facebook prints it. |
| `page_id` | Numeric Page id — join key to our Facebook Page Scraper. |
| `page_url` | Canonical Page URL. |
| `page_is_verified` | Blue-check state of the author Page. |
| `caption` | The reel's caption text, with line breaks and emoji preserved. |
| `posted_at` | ISO 8601 UTC publish timestamp, to the second. |
| `duration_seconds` | Playable length, fractional. |
| `view_count_text` | **What Facebook literally rendered** — `"3.2M"`, `"9.8K"`, `"415"`. |
| `view_count_approx` | That string expanded to a number. **Rounded — see limit #1.** |
| `reaction_count` | **Exact** total reactions (all types combined). |
| `comment_count` | **Exact** comment count. |
| `share_count_text` | Facebook's share string — exact below ~1,000, bucketed above. |
| `share_count_approx` | That string as a number. |
| `thumbnail_url` | Poster frame, full resolution. |
| `video_url_hd` / `video_url_sd` | Signed MP4 URLs. **Expiring — see limit #4.** |
| `video_width` / `video_height` | Source pixel dimensions. |
| `audio_track_title` | e.g. `"Nike · Original audio"`. **Sparse — see below.** |
| `is_original_audio` | Whether the audio is the creator's own. |
| `is_ai_generated` | Facebook's own AI-content detection flag. |
| `is_reshare` | Whether the reel is a reshare of someone else's. |
| `post_id` | The underlying post id (differs from `reel_id`). |
| `video_permalink_url` | Facebook's `/videos/` form of the same asset. |
| `scraped_at` | When this row was captured. |

***

### Field fill — measured on 360 live reels

360 unique reels across 12 public brand Pages (Cristiano, Red Bull, National Geographic,
BuzzFeed, Sephora, GoPro, Starbucks, Tasty, LADbible, ESPN, LEGO, Airbnb), 30 reels per
Page, pulled through Apify datacenter proxies on 2026-08-15. Counted, not estimated.

| Field | Fill |
|---|---|
| `reel_id` | 100.0% |
| `permalink` | 100.0% |
| `page_slug` · `page_name` · `page_id` · `page_url` · `page_is_verified` | 100.0% |
| `posted_at` | 100.0% |
| `duration_seconds` | 100.0% |
| `view_count_text` · `view_count_approx` | 100.0% |
| `reaction_count` | 100.0% |
| `comment_count` | 100.0% |
| `share_count_text` · `share_count_approx` | 100.0% |
| `thumbnail_url` | 100.0% |
| `video_url_hd` · `video_url_sd` | 100.0% |
| `video_width` · `video_height` | 100.0% |
| `is_original_audio` · `is_ai_generated` · `is_reshare` | 100.0% |
| `post_id` · `video_permalink_url` | 100.0% |
| `caption` | 94.2% |
| **`audio_track_title`** | **43.6%** |

**The sparse ones, plainly:**

- **`audio_track_title` — 43.6%.** Facebook only names the audio when the reel is attached
  to an identified track or the creator's own original audio is registered. On the other
  56.4% Facebook publishes no track name at all, and the field is `null`.
- **`caption` — 94.2%.** 5.8% of reels are published with no caption text.
- **`video_url_hd` — 100% here, 98.9% on a wider 624-reel sample.** A handful of reels are
  only distributed at SD; `video_url_sd` was present on every row in both samples.
- **`is_ai_generated` and `is_reshare` were `false` on all 360 rows** in this sample. The
  fields are populated (not null), the flags simply did not fire on these Pages. Treat them
  as a signal you can filter on, not as a distribution you can trust from this sample.

Value ranges in the same sample, so you know what the columns look like: views 316 →
71,000,000 (median 166,000); reactions 0 → 4,523,560 (median 1,001); comments 0 → 135,670
(median 47); shares 0 → 126,000 (median 56); duration 2.5s → 13,310s (median 36.2s — yes,
Facebook files some multi-hour videos under the reels tab). Publish dates in the sample ran
from 2018-12-14 to 2026-08-15.

***

### How deep it goes

Facebook serves the reels tab in batches of 10, newest first, and this Actor follows the
same pagination the browser does — contiguously, with no gaps.

- **Sephora, `maxReelsPerPage: 100`** → 100 reels in 10 contiguous batches, **0 duplicates**,
  100% engagement fill, covering 2026-06-08 → 2026-08-14.
- **12 Pages × 30 reels** → 360 rows, **0 duplicates**, 36 contiguous batches.
- Across every measurement run on this page, **in-run duplicate rate on a single Page was
  0.0%**. Duplicates only appear if you list the same Page twice under different aliases
  (e.g. `gopro` and `GoPro`) — those are de-duplicated on `reel_id` **before billing**, so
  you never pay twice.

Depth is bounded by what the Page has published, not by the Actor. Set
`maxReelsPerPage` to whatever you need; the log tells you when a Page ran out of reels
versus when it hit your cap.

***

### Reliability — measured through Apify proxies, not from a laptop

A residential home IP tells you nothing about how a scraper behaves from a datacenter, so
every number here was measured **through Apify Proxy**.

| Rung | Calls | Usable | Notes |
|---|---|---|---|
| Apify **DATACENTER**, one pinned session per Page | 20 Page fetches + 50 pagination calls | **68 / 70 = 97.1%** | The 2 misses were Pages with no public reels tab, not blocks |
| Apify **RESIDENTIAL** (spot check) | 9 | 9 / 9 HTTP 200 | Outcomes byte-identical to datacenter on the same Pages, including the empty ones |
| Across all runs on this page | 122 | **118 / 122 = 96.7%** | **0 transport blocks observed.** All 4 misses were Pages with no public reels |

**On Pages that actually publish reels, the through-Apify success rate was 118/118.** That
is why this Actor has **no residential fallback rung**: datacenter is not walled here, and a
reels payload runs about **1.4 MB per reel**, so a residential retry would burn several
times the revenue of the rows it recovered. You can still select RESIDENTIAL in the proxy
input if your account needs it — just budget for the data transfer.

Retries rotate to a fresh datacenter session, because the failure mode on Facebook is a
*served-but-empty* response rather than an HTTP error code. There is one extra safeguard:
the reaction/comment/share counters arrive in a separate deferred chunk of the same
response, and Facebook occasionally omits it — when a Page comes back with zero engagement
fill, the Actor re-fetches it once on a fresh session before accepting the rows, and logs
how many rows still ended up without counters.

***

### How it works

The reels tab at `https://www.facebook.com/<page>/reels/` is **server-rendered**: the first
batch of 10 reels is a plain JSON blob embedded in the HTML that a logged-out client
receives. No headless browser is needed to read it, which is why this Actor runs on 2 GB
instead of the 4–8 GB the browser-based reels scrapers ask for.

Later batches come from the one Relay query the browser fires when you scroll,
`ProfileCometAppCollectionReelsRendererPaginationQuery`. That query **is** answerable
logged out, but only if you send it exactly right: the request tokens Facebook prints into
the same HTML (`lsd`, haste session, revision, `jazoest`), the connection node id and
cursor from `page_info`, and Relay's 19 feature-gate "provided variables" with the precise
values Facebook chose for that response. Guess any of them and the server returns
`noncoercible_variable_value`, so this Actor lifts them verbatim out of the page's own
preloaded-query blob rather than hard-coding them.

The persisted-query id is not in the HTML — it lives in a JS bundle. The Actor tries to
**discover it at run time** from the bundles the page links, and falls back to a pinned id
when it cannot. **Measured 2026-08-19 on 8 platform runs: the bundle that defines this query
is lazily loaded and is not among the ones the reels tab links, so run-time discovery does
not currently fire and every run uses the pinned id.** That pinned id paginated 8/8 runs,
including a 300-reel / 30-batch pass on a single Page, so pagination is working — but the
self-healing path is not carrying it, and the id is re-verified on each release rather than
per run.

The engagement counters are not in the reels connection at all; Facebook streams them later
in the same response as deferred chunks, keyed back to a reel by its video id. The parser
reads both and joins them.

***

### Pricing

**$0.002 per reel returned.** One event, `reel-scraped`. That is the cheapest per-reel price
on the Apify Store for a Facebook reels Actor as of 2026-08-15 (live comparison: $0.0045,
$0.00499, $0.00599, $0.006, $0.007 across the other per-reel actors).

| Reels | Cost |
|---|---|
| 20 (shipped default, 2 Pages) | $0.04 |
| 100 | $0.20 |
| 1,000 | $2.00 |
| 10,000 | $20.00 |

You are billed for **unique reels actually returned**, and only for those:

- Reels are de-duplicated on `reel_id` **before** the charge, across Pages and across
  pagination batches.
- A Page with no public reels tab costs **nothing**.
- A Page that would not serve a payload after retries costs **nothing**.
- A reel removed by the `postedAfter` filter costs **nothing** — the filter runs before
  billing, so it costs you run time, not money.
- If a run ends with zero reels it finishes **SUCCEEDED with a status message naming why**
  (no public reels / no payload / everything removed by your date filter) and bills nothing —
  it does not fail the run on you, and it does not silently pretend it worked.

***

### Input

| Field | Type | Default | What it does |
|---|---|---|---|
| `pages` | array | `["redbull","gopro"]` | Handles, Page URLs, `/profile.php?id=…` URLs or numeric Page ids. |
| `startUrls` | list source | — | Bulk paste, a `.txt`/`.csv` upload, or a linked Google Sheet of Page URLs. |
| `maxReelsPerPage` | integer | `100` (prefill `20`) | Per-Page cap. Facebook pages in 10s. |
| `postedAfter` | string | — | `YYYY-MM-DD`. Keep only reels published on or after this date. |
| `proxyConfiguration` | object | Apify Proxy | Required. Datacenter default is what the numbers above were measured on. |
| `requestDelayMs` | integer | `500` | Pause between Facebook requests. |

Handles, full URLs and numeric ids can be mixed freely, and duplicates across `pages` and
`startUrls` are dropped before anything is fetched.

***

### Who buys this

- **Ad-creative and short-form research.** Every organic reel a competitor ran, with the
  MP4, the caption, the exact reaction and comment counts and the posted date — a swipe
  file with real engagement attached, refreshed on a schedule.
- **Brand and competitor monitoring.** Point it at a set of Pages with `postedAfter` and a
  daily schedule to get posting cadence, format mix and engagement trend per brand.
- **Creator and partnership sourcing.** `reaction_count / view_count_approx` gives a
  per-reel engagement rate you can rank creators by — with the caveat from limit #1 that
  the denominator is rounded.
- **Video and dataset work.** `video_url_hd` plus `caption` and `duration_seconds` is a
  ready ingestion feed for transcription, captioning or vision pipelines.

***

### When a run fails

| Log line | What it means | What to do |
|---|---|---|
| `no public reels ("This content isn't available right now")` | The Page has no public reels tab. | Nothing — not billed. Check the Page in a logged-out browser. |
| `Page has a reels tab but publishes no public reels` | Reels tab exists, collection is empty. | Nothing — not billed. |
| `no reels payload after retries` | Three fetches on three sessions all came back without data. | Re-run; if it persists on one Page only, that Page is likely region-restricted. |
| `reels pagination stopped after N reel(s)` | Facebook ended the connection early. | The N reels already returned are yours; re-run to go deeper. |
| `engagement counters missing from the first response` | The deferred counter chunk was omitted; the Actor is retrying. | Nothing — informational. |
| `No reels scraped — N Page(s) publish no public reels …` | Every Page you listed has no public reels, or your date filter removed all of them. | Run ends SUCCEEDED, nothing billed. Check your handles or widen `postedAfter`. |
| `Stopped early to stay within the run time limit` | The Actor hit its own wall-clock budget and stopped before the platform could kill the run. | The reels already pushed are yours; raise the run timeout or lower `maxReelsPerPage`. |
| `No Facebook Pages supplied — running the built-in sample` | You started the Actor with an empty input (common from the API or an AI agent). | The Actor runs the two-Page demo (`redbull`, `gopro`, 20 reels each) so you get real rows. Fill in `pages` to scrape your own. |

***

### Legal & fair use

`https://www.facebook.com/robots.txt` states, for the generic crawler group:

```
User-agent: *
Disallow: /
```

Facebook's robots.txt disallows generic crawlers site-wide and reserves crawl access to a
named allow-list of bots. This Actor reads only **publicly visible** pages — no login, no
cookies, no session token, nothing behind an authentication wall — and it returns
business/brand Page content, not private individuals' data. It does not download or
re-host video files; it returns the URLs Facebook itself serves. **Robots.txt compliance,
Meta's Terms of Service and the applicable data-protection rules in your jurisdiction are
your responsibility as the operator of the run.** If you are scraping at volume, or for a
commercial product, take your own legal advice first.

***

### Our other Facebook Actors

- **Facebook Page Scraper** — Page name, category, likes, talking-about, phone, email,
  website, address and bio. Join to this dataset on `page_id`.

***

### Feedback

Found a Page this Actor mis-reads, or a field you need that Facebook publishes and this
does not return? Open an issue on the Actor page with the Page handle — measured bug
reports get fixed.

# Actor input Schema

## `pages` (type: `array`):

One per line. A handle (nike), a Page URL (https://www.facebook.com/nike), a /profile.php?id=… URL, or a numeric Page id. Reels are read from that Page's public reels tab. A Page whose reels tab is not public returns Facebook's "This content isn't available right now" — it is logged, skipped and never billed.

## `startUrls` (type: `array`):

For bulk runs: paste many Page URLs at once, upload a .txt/.csv of them, or link a Google Sheet. Every Facebook URL found is added to the list above. Duplicates across both inputs are dropped before anything is fetched.

## `maxReelsPerPage` (type: `integer`):

Stop after this many reels for each Page. Facebook returns the newest reels first, in batches of 10, so 20 = 2 batches. Cost per Page: 20 reels = $0.04, 100 = $0.20, 500 = $1.00. Many Pages have fewer reels than the cap — you are only ever billed for reels actually returned.

## `postedAfter` (type: `string`):

YYYY-MM-DD. Keeps only reels published on or after this date — useful for "what has this brand shipped in the last 30 days". Facebook serves reels newest-first, so this is a post-fetch filter: it costs run time, not money. You are never billed for a reel the filter removed.

## `proxyConfiguration` (type: `object`):

Apify Proxy is required: Facebook rate-limits raw cloud IPs. The default (Apify Proxy, automatic datacenter) is what the numbers above were measured on.

## `requestDelayMs` (type: `integer`):

Pause between Facebook requests. 500 ms is the measured-good default; raise it if you are scraping a very long Page list and start seeing empty responses.

## Actor input object example

```json
{
  "pages": [
    "redbull",
    "gopro"
  ],
  "maxReelsPerPage": 20,
  "postedAfter": "2026-01-01",
  "proxyConfiguration": {
    "useApifyProxy": true
  },
  "requestDelayMs": 500
}
```

# Actor output Schema

## `items` (type: `string`):

One row per reel: permalink, MP4 URLs, thumbnail, caption, posted-at, duration, view count, reactions, comments, shares, audio track and the author Page.

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "pages": [
        "redbull",
        "gopro"
    ],
    "maxReelsPerPage": 20,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("scrapersdelight/facebook-reels-scraper").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "pages": [
        "redbull",
        "gopro",
    ],
    "maxReelsPerPage": 20,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("scrapersdelight/facebook-reels-scraper").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "pages": [
    "redbull",
    "gopro"
  ],
  "maxReelsPerPage": 20,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call scrapersdelight/facebook-reels-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,scrapersdelight/facebook-reels-scraper"
        }
    }
}

```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/nLc68ew3DUvS19SpM/builds/huMROlg9f1YedhCgb/openapi.json
