Facebook Video Search Scraper & Creator Video Catalogue avatar

Facebook Video Search Scraper & Creator Video Catalogue

Pricing

Pay per usage

Go to Apify Store
Facebook Video Search Scraper & Creator Video Catalogue

Facebook Video Search Scraper & Creator Video Catalogue

Facebook Video Search Scraper finds and extracts videos matching any keyword on Facebook. Get titles, URLs, views, creators, and metadata fast. Perfect for research, trend analysis, and automation workflows needing accurate Facebook video data.

Pricing

Pay per usage

Rating

0.0

(0)

Developer

API Empire

API Empire

Maintained by Community

Actor stats

0

Bookmarked

10

Total users

0

Monthly active users

3 days ago

Last modified

Share

Facebook Video Search Scraper — Videos, Creators & Catalogues

Facebook video search scraper that takes plain keywords and returns three linked things from Facebook Watch: the matching public videos, the creator behind each one, and that creator's entire public video catalogue — titles, play counts, reaction counts, publish dates, hashtags and derived cadence stats. Everything arrives as typed, normalized JSON keys — no HTML, no selectors, no parsing. No Facebook login, cookie or session value is used anywhere in the code. By the end of this page you will know exactly which keys land in your dataset and what each one means when Facebook withholds it.

What is Facebook Video Search Scraper & Creator Video Catalogue?

It is an Apify Actor that searches Facebook Watch by keyword and expands every result into a creator-level record. The base job — keyword in, public Facebook videos out — is preserved key for key. On top of it, for every distinct creator a keyword surfaces, the Actor fetches that creator's public videos tab once per run and attaches the whole shelf to the row as creatorCatalogue. That is the difference between knowing that one clip about "street food" did well and knowing whether the person who posted it publishes weekly, holds play counts across the shelf, and is worth a partnership email.

It runs entirely logged out. There is no account field, no session cookie parameter and no token in the input schema, and nothing in src/ reads one — the search request and the videos-tab request both go out unauthenticated.

What it handles:

  • Facebook video search — one row per matching public video, with the base Actor's full field contract (videoUrl, title, save_description, thumbnail_image, video_duration_text, video_broadcast_status, relative_time_string, facebookId).
  • Creator identitycreatorId, creatorName, creatorType, creatorIsVerified and creatorLinkedPageId lifted out of the nested owner node so you can group, filter and join on them.
  • Creator video catalogue — the creator's public video shelf as a nested array with playCount, reactionCount, topReactions, hashtags, publishedAt and durationSeconds per video.
  • Derived publishing stats — a computed summary block per creator: median play count, median reaction count, newest and oldest publish date, and median days between posts.
  • Export as JSON, CSV, Excel, XML or HTML from the Apify dataset, or straight over the Apify API.
  • No proxy plumbing and no parsing — proxy tiering, user-agent escalation, cursor handling and the logged-out render fallbacks are all inside the Actor.

📊 What data does this Facebook video scraper collect?

Every run produces one flat row per matching video, and each row carries a nested creator catalogue object with its own nested video array and summary. Four structurally different record types come out of a single dataset.

Data TypeKey FieldsJSON Field Names
Facebook video (search result)Video URL, title, caption, accessibility label, thumbnail, duration, broadcast status, relative post age, video ID, source keyword, render shapevideoUrl, title, save_description, label, thumbnail_image, video_duration_text, video_broadcast_status, relative_time_string, facebookId, video, facebookUrl, inputUrl, query, renderShape
CreatorCreator ID, display name, profile URL, node type, verified badge, backing Page IDcreatorId, creatorName, creatorType, creatorIsVerified, creatorLinkedPageId, video_owner_profile (__typename, id, __isNode, __isActor, name, url, is_verified)
Creator video catalogueVideos-tab URL, fetch status, catalogue size, page-backing flag, per-video play and reaction counts, hashtags, publish time, dimensionscreatorCataloguevideosTabUrl, fetchStatus, catalogueCount, hasMoreOnFacebook, creatorIsPageBacked, videos[]videoId, videoUrl, title, description, hashtags, thumbnailUrl, publishedAt, publishedAtTimestamp, playCount, postPlayCount, reactionCount, topReactions, durationSeconds, width, height, aspectRatio, playableUrl, feedbackId, storyId
Publishing cadence summaryTotal and median plays, total and median reactions, median duration, newest and oldest post, posting rhythmcreatorCatalogue.summaryvideoCount, totalPlayCount, medianPlayCount, totalReactionCount, medianReactionCount, medianDurationSeconds, newestPublishedAt, oldestPublishedAt, medianDaysBetweenPosts
Run accounting recordEmitted only when a keyword returns nothing; never billedrowType, errorReason, catalogueStatus, query, facebookUrl, inputUrl, renderShape

Because the catalogue is nested, a CSV export flattens creatorCatalogue.videos into indexed columns. If you want the catalogue as its own table, take the JSON export and unnest creatorCatalogue.videos on creatorId.

Need more Facebook data?

The catalogue here stops at a creator's video shelf. If you need the conversation around those videos, the Facebook Videos Scraper with Commenters, Reactors & Sharers returns the people who engaged rather than the clips themselves. For Page-level rather than creator-level analysis, the Facebook Page Engagement Analytics Scraper covers posting output and engagement on a Page, and Facebook Pages Scraper returns the public Page profile. For discovery beyond Watch, the Facebook Hashtag Search Scraper + Post Comments and the Facebook Groups Search Scraper with Group Details & Rules cover the hashtag and group surfaces. They all write to Apify datasets, so they join on creatorId or Page ID without a transformation layer in between.

How does this differ from the official Meta Graph API?

Meta's Graph API is an API for content you own or administer; this Actor reads the public Watch search surface that any logged-out visitor already sees. That distinction decides the tool for you before any feature comparison starts.

FeatureMeta Graph APIFacebook Video Search Scraper & Creator Video Catalogue
Keyword search across public videosNot offered — Graph API endpoints are scoped to objects you hold a token and permission for, not to a global public video indexNative: startUrls takes plain search phrases, one per line
Access prerequisiteA Meta developer app, App Review for most permissions, and Business Verification, per Meta's App Review documentationAn Apify account. No Facebook account, no app, no review
An arbitrary creator's video shelfRequires that creator to grant your app a Page access tokenRead logged out from the creator's public /videos tab
Research access to broad public contentRouted through Meta Content Library, which Meta restricts to approved academic and research institutionsPublic Watch search results and public videos tabs, no institutional approval
Rate limitingApp-level and Page-level ceilings calculated from your app's user base, per Meta's Graph API Rate Limiting documentationBounded by the Actor's own settings — maxVideos per keyword and maxCreatorsToFetch per run — plus Facebook's own IP-level throttling
Output shapeVaries by endpoint, API version and the fields you requestOne stable row contract per video, plus a fixed creatorCatalogue object, unchanged between runs
Setup timeApp creation, permission scoping, review submission, token refresh handlingPaste keywords, press Start

Use the Graph API when you administer the Pages you are reading and need write access, ad objects or private insights — it is the correct and supported tool for that. Use this Actor when you need to discover public videos and creators you have no relationship with, which the Graph API is not designed to do.

Why do developers and teams scrape Facebook Watch?

Facebook Watch is one of the few large video surfaces where discovery is keyword-driven and the creator behind a clip is a first-class object. That combination attracts four distinct groups.

For AI engineers and agent builders

The output is already agent-shaped: a video row plus a creator dossier in one JSON object, so there is no join step inside your loop. A creator-discovery agent can call the Actor with a topic, filter rows on creatorCatalogue.summary.medianPlayCount and medianDaysBetweenPosts, and pass the shortlist into an LLM with creatorCatalogue.videos[].title, description and hashtags as context for a fit judgement. Because fetchStatus is explicit — ok, empty, blocked, skipped_creator_budget — the agent can branch on genuinely missing data instead of hallucinating around a silent null.

For partnership and influencer marketing teams

Keyword search alone tells you a clip performed. It does not tell you whether the creator publishes twice a year. Run your niche terms, then sort on creatorCatalogue.summary.medianPlayCount to find consistency rather than one outlier, and on medianDaysBetweenPosts to find creators who actually ship. creatorLinkedPageId shows whether a profile publishes on behalf of a Page — usually the difference between a hobbyist and a media operation. The hashtags array across a whole shelf reveals which tags a creator genuinely lives in versus which one they borrowed for a single viral attempt.

For researchers and analysts

Everything returned is publicly visible to a logged-out visitor; nothing behind a login or a friends-only setting is reachable by this Actor. That makes it usable for content-ecosystem work: sample a topic across keywords, then answer distributional questions straight from the catalogue block — how play counts spread within one creator's shelf (playCount per video against medianPlayCount), how long the median clip runs (medianDurationSeconds), how far back a public shelf reaches (oldestPublishedAt to newestPublishedAt). Publish times come back as both ISO 8601 strings and Unix integers, so time-series work needs no date parsing.

For developers building data products

Schedule the Actor on a keyword list and every run writes to a dataset you can pull with the Apify API or push through a webhook. The row contract is fixed, so a downstream table needs no migration between runs, and the nested catalogue unnests cleanly on creatorId. Typical builds: a niche creator leaderboard refreshed weekly, a competitor cadence alert that fires when newestPublishedAt moves, or an internal search over creator captions and hashtags indexed from creatorCatalogue.videos[].

How to scrape Facebook videos by keyword (step by step)

The Actor runs on the Apify platform. You start it from the Apify Console or through the Apify API — there is no separate signup, no Actor-specific API key, and no external service to connect.

  1. Open the Actor on its Apify Store listing and click Try for free, or open it in the Apify Console if it is already in your account.
  2. Enter your keywords in Keywords & topics (one per line) — this is the startUrls field and it is the only required input. One search phrase per line: cricket highlights, street food, indie games. Each line is searched separately and every row records which keyword produced it in query.
  3. Set the depth and the filtersmaxVideos controls how many matching videos come back per keyword (default 10). Leave Fetch each creator's video catalogue on to get the creator shelves, use maxCreatorsToFetch to cap how many distinct creators get a catalogue fetch, and leave onlyVerifiedCreators off unless you have read the caveat below.
  4. Start the run. Leave the proxy on Apify Residential unless you have your own proxy URLs — the search GraphQL endpoint is IP-throttled, and an unproxied datacentre address gets rate-limited rather than blocked outright.
  5. Download the results from the Storage tab as JSON, CSV, Excel, XML or HTML, or fetch the dataset over the Apify API.

What to do when Facebook changes its structure

Facebook rotates its GraphQL operation IDs, its render shapes and its logged-out walls on its own schedule. The Actor resolves the search operation ID at runtime instead of hard-coding it, understands both of Facebook's current search view-models, and falls back to the crawler-rendered page when the API path is refused. The Actor is maintained against those changes and the output keys stay put, so your integrations, spreadsheets and downstream tables keep working without edits.

What changed in Facebook video scraping recently?

The largest shift was Meta's shutdown of CrowdTangle on 14 August 2024, which retired the long-standing public-content research tool and pushed that work onto the Meta Content Library, whose access Meta restricts to approved academic and research institutions. Before that, Graph API v3.0 in April 2018 removed broad public-content search from the developer API — which is why no supported Meta endpoint answers "find public videos about X" today.

On the anti-bot side, this build's own transport notes record two live signatures worth knowing about. Facebook's search GraphQL endpoint does not fail with a 4xx when it throttles; it answers HTTP 200 carrying errors[0].code = 1675004 with zero result edges, which naive scrapers read as "no results". The genuine interstitial wall is HTTP 400 with a body of roughly 1,542 bytes and no result container. A scraper that only watches for 403/429/503 catches neither.

For DIY scrapers that means silent zero-yield runs rather than loud failures, plus a hard-coded operation ID that dies on the next JavaScript bundle rotation. For users of this Actor it means nothing: both signatures are detected explicitly, throttling triggers a proxy-exit rotation on the same cursor, and the walled path drops to the crawler render. Maintenance against Facebook's next change is ongoing.

⬇️ Input

The Actor takes seven parameters. Only startUrls is required; everything else has a working default, so a run with just a keyword list is valid.

ParameterRequiredTypeDescriptionExample Value
startUrlsYesarray (editor: stringList)One search phrase per line. Each line is searched separately and every row records the keyword it came from in query. Despite the field name these are keywords, not URLs — the base Actor named it this way and the name is kept so existing runs and integrations keep working. Prefilled with ["cricket", "football"]. Duplicate lines are collapsed before the run starts.["cricket highlights", "street food"]
maxVideosNointeger, default 10, minimum 1, maximum 1000How many matching videos to collect per keyword. Facebook's search feed returns roughly 7–8 results per page, so larger numbers simply mean more pages and a longer run. Start at 10 while you shape the output.50
includeCreatorCatalogueNoboolean, default trueON: for every creator surfaced by your keywords, also fetch their public videos tab and attach it as creatorCatalogue. Each creator is fetched once per run — ten matching clips by the same page cost one request. OFF: search rows only, no extra requests, and every row's catalogueStatus reads skipped_disabled.true
catalogueVideosPerCreatorNointeger, default 9, minimum 1, maximum 50Upper bound on the catalogue videos attached to each creator. Honest ceiling: Facebook's videos tab renders 9 full video nodes per creator and the cursor it hands out cannot be replayed logged out, so any value above 9 still returns 9. Lower it to keep rows compact.9
maxCreatorsToFetchNointeger, default 25, minimum 1, maximum 500Budget cap on how many distinct creators get a catalogue fetch in one run. Creators beyond the cap still produce full search rows; their catalogue block reports fetchStatus: "skipped_creator_budget" instead of pretending to be empty.25
onlyVerifiedCreatorsNoboolean, default falseKeep only rows whose creator carries Facebook's verified badge. Read the caveat below before switching this on — a missing badge and an absent badge are the same value here.false
proxyConfigurationNoobject (editor: proxy)Your proxy selection is honoured as supplied — custom proxyUrls are tried first, then your chosen Apify proxy groups, with Apify Residential as the fallback tier. Prefilled with {"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}. Residential is the recommended default: from an unproxied datacentre address Facebook's search API answers HTTP 200 with a rate-limit error and zero results. The creator videos tab, by contrast, was measured working on every tier including no proxy.{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}

The onlyVerifiedCreators caveat, in full. Facebook omits the verification flag entirely on story-shaped search results, and a missing flag is normalised to false by the parser. So creatorIsVerified: false means "no verified badge present in this payload" — which is not the same as "confirmed not verified". Turning the filter on can therefore drop genuinely verified creators along with the unverified ones. The build notes record 3 of 20 creators carrying the badge on a live 20-row run measured 2026-07-23.

Why there is no page-versus-person filter. Facebook's "new Pages experience" backs Pages with User GraphQL nodes. The build notes record __typename reading User on 35 of 35 live Watch-search rows and 13 of 13 creator video tabs — NASA, BBC News, National Geographic, Red Bull and 9GAG included. A page/person input filter would therefore return nothing, forever, so it was dropped rather than shipped as a permanently empty toggle. Use the creatorLinkedPageId output field instead: it is the Page a creator publishes on behalf of, and the notes record it present on 13 of 13 creators measured. If you pass a legacy creatorTypeFilter key from older input JSON, the run does not fail — the value is accepted, a warning is logged explaining it has no effect, and the run continues.

Note on out-of-range values. The three integer parameters are clamped in code as well as in the schema. A maxVideos of 5000 becomes 1000, a catalogueVideosPerCreator of 0 becomes 1, and a non-numeric value falls back to the default. The clamping is silent in the run log, so check the values you send from the API rather than assuming a rejection.

Example JSON input

{
"startUrls": [
"cricket highlights",
"street food",
"indie game devlog"
],
"maxVideos": 40,
"includeCreatorCatalogue": true,
"catalogueVideosPerCreator": 9,
"maxCreatorsToFetch": 25,
"onlyVerifiedCreators": false,
"proxyConfiguration": {
"useApifyProxy": true,
"apifyProxyGroups": ["RESIDENTIAL"]
}
}

The legacy [{"url": "cricket"}] object shape is also accepted for startUrls — the Actor reads the url property off dict entries — so historical input JSON from the base Actor runs unchanged.

The most common input mistake: treating startUrls as a URL field and pasting https://www.facebook.com/watch/search?q=cricket. The Actor URL-encodes whatever you give it into a search phrase, so a pasted URL becomes a literal search for that string and returns nothing useful. Type cricket. The second most common mistake is raising catalogueVideosPerCreator to 30 or 50 expecting deeper shelves — that ceiling is a Facebook render limit, not an Actor setting, and 9 is what comes back.

⬆️ Output

Results land in an Apify dataset as typed, normalized JSON with a stable key set — no HTML fragments, no CSS selectors, nothing left for you to parse. Export from the Storage tab as JSON, JSONL, CSV, Excel, XML, HTML or RSS, or read the dataset over the Apify API.

Every successful row carries the base Actor's 13-key video contract plus renderShape, then nine creator and catalogue keys merged on top — 23 keys per row. One of them, creatorType, is written to every row by the Actor but is not part of the default dataset table view, so pull it from the JSON export or add it to a custom view if you need it.

Scraped Facebook video row

renderShape records which of Facebook's payload shapes the row came from: meta (the video_metadata_model view-model), story (the story-shaped view-model), or ssr (recovered from the crawler-rendered search page when the GraphQL path was refused).

{
"facebookUrl": "https://www.facebook.com/watch/search?q=street+food",
"videoUrl": "https://www.facebook.com/streetfoodjourney/videos/1748262476113780/",
"label": "Midnight noodle stalls in Bangkok\nStreet Food Journey\nVerified\nVideo length:4minutes; 12seconds\nJun 18 \n 210K views",
"thumbnail_image": {
"uri": "https://scontent-ord5-3.xx.fbcdn.net/v/t15.5256-10/499568895_1227151215606296.jpg"
},
"video_duration_text": "4:12",
"video": {
"id": "1748262476113780"
},
"video_owner_profile": {
"__typename": "User",
"id": "100064843384938",
"__isNode": "User",
"__isActor": "User",
"name": "Street Food Journey",
"url": "https://www.facebook.com/streetfoodjourney",
"is_verified": true
},
"video_broadcast_status": null,
"save_description": "Midnight noodle stalls in Bangkok - the ones locals actually queue for.",
"title": "Midnight noodle stalls in Bangkok",
"relative_time_string": "Jun 18 · 210K views",
"facebookId": "1748262476113780",
"inputUrl": "https://www.facebook.com/watch/search?q=street+food",
"query": "street food",
"renderShape": "meta",
"creatorId": "100064843384938",
"creatorName": "Street Food Journey",
"creatorType": "person",
"creatorIsVerified": true,
"creatorLinkedPageId": "156296631069194",
"catalogueStatus": "ok",
"catalogueVideoCount": 9,
"creatorCatalogue": { "...": "expanded in the next section" }
}

Two notes on the video fields. facebookId is the real numeric video ID, parsed out of the click URI (?v=, /videos/<id>/ or /reel/<id>/) with the node ID as a fallback — it is not a copy of the search URL. And relative_time_string differs by shape: on meta rows it is Facebook's own string, which usually appends a view count ("Jun 18 · 210K views"); on story rows it is computed from the exact creation_time Unix timestamp and therefore carries no view clause ("3 days ago"). creatorType passes Facebook's __typename through as person, page or null; see the note in the Input section on why it reads person for almost every creator.

Creator video catalogue block

This is the creatorCatalogue object referenced above, expanded. It is present on every row — when a fetch does not happen, the same 13 keys are still returned with videos: [] and a fetchStatus that says why.

{
"creatorId": "100064843384938",
"creatorName": "Street Food Journey",
"creatorUrl": "https://www.facebook.com/streetfoodjourney",
"creatorType": "person",
"isVerified": true,
"videosTabUrl": "https://www.facebook.com/streetfoodjourney/videos",
"fetchStatus": "ok",
"catalogueCount": 9,
"hasMoreOnFacebook": true,
"creatorLinkedPageId": "156296631069194",
"creatorIsPageBacked": true,
"videos": [
{
"videoId": "1748262476113780",
"videoUrl": "https://www.facebook.com/streetfoodjourney/videos/1748262476113780/",
"title": "Midnight noodle stalls in Bangkok",
"description": "Midnight noodle stalls in Bangkok - the ones locals actually queue for. #streetfood #bangkok #noodles",
"hashtags": ["#streetfood", "#bangkok", "#noodles"],
"thumbnailUrl": "https://scontent-ord5-3.xx.fbcdn.net/v/t15.5256-10/499568895_1227151215606296.jpg",
"publishedAt": "2026-06-18T14:02:11Z",
"publishedAtTimestamp": 1781272931,
"playCount": 214803,
"postPlayCount": 209117,
"reactionCount": 8412,
"topReactions": [
{ "type": "Like", "count": 5120 },
{ "type": "Love", "count": 2611 },
{ "type": "Wow", "count": 681 }
],
"durationSeconds": 252.6,
"width": 1080,
"height": 1350,
"aspectRatio": 0.8,
"playableUrl": "https://video-ord5-3.xx.fbcdn.net/v/t42.1790-2/499568895.mp4",
"feedbackId": "ZmVlZGJhY2s6MTc0ODI2MjQ3NjExMzc4MA==",
"storyId": "UzpfSTEwMDA2NDg0MzM4NDkzODoxNzQ4MjYyNDc2MTEzNzgw"
}
],
"summary": {
"videoCount": 9,
"totalPlayCount": 1382940,
"medianPlayCount": 128411,
"totalReactionCount": 51228,
"medianReactionCount": 4903,
"medianDurationSeconds": 188.4,
"newestPublishedAt": "2026-06-18T14:02:11Z",
"oldestPublishedAt": "2026-01-09T09:41:02Z",
"medianDaysBetweenPosts": 17.5
}
}

Every value in summary is computed from the integers in videos[] — nothing is estimated. medianDaysBetweenPosts is the median gap in days between consecutive publish timestamps on the shelf; with fewer than two dated videos it returns null rather than a guess, and the same is true of every other roll-up when its source field is missing. hashtags are sliced out of the caption by Unicode code-point offset, which is why they survive emoji and non-Latin captions that break naive byte- or UTF-16-based slicing. The catalogue itself is assembled by merging Facebook's latest_videos and all_videos collections out of the server-rendered JSON blobs and de-duplicating on video ID, which is how a shelf that renders as six thumbnails still yields nine full nodes.

fetchStatus — the value also copied to the top-level catalogueStatus — takes one of eight values:

fetchStatusMeaning
okThe videos-tab render contained rich video nodes; videos[] is populated
emptyHTTP 200 rendered, but no public video nodes were found for this creator
blockedFacebook served the wall — HTTP 400 with a tiny body and no result container — or another 4xx
not_foundHTTP 404, or no usable videos-tab URL could be built from the creator's identity
errorHTTP 5xx, or the request failed at the transport layer
skipped_disabledincludeCreatorCatalogue was off for this run
skipped_creator_budgetThe run had already fetched maxCreatorsToFetch distinct creators
no_creator_identityThe search payload carried neither a creator ID nor a creator URL to fetch from

hasMoreOnFacebook reports whether Facebook's own latest_videos.page_info.has_next_page was true — it is informational only. The pagination operation ID for the videos tab is not present in the logged-out render, and re-requesting the tab with the cursor returns page 1 verbatim, so deeper paging is not shipped. The field tells you a shelf continues; it does not imply the Actor can reach the rest of it.

Run accounting record

When a keyword produces no rows at all, the Actor writes one diagnostic record instead of leaving a silent gap. It is pushed without a charged event, so an empty keyword is never billed as a result.

{
"facebookUrl": "https://www.facebook.com/watch/search?q=zxqv+nonsense+term",
"inputUrl": "https://www.facebook.com/watch/search?q=zxqv+nonsense+term",
"query": "zxqv nonsense term",
"rowType": "run_accounting",
"errorReason": "no_results",
"renderShape": null,
"catalogueStatus": "no_row"
}

errorReason reads no_results when the search genuinely matched nothing, or carries the failure that stopped the keyword: rate_limited, wall, http_error, unparseable, network_error or search_page_unavailable. Filter these out with rowType != "run_accounting" if you only want video rows. Like creatorType, rowType and errorReason are not in the default dataset table view — read them from the JSON export.

How does it compare to other Facebook video scrapers?

FeatureThis ActorGeneric Facebook video search scraper
Entity coverageVideo + creator identity + the creator's full video catalogue + computed publishing stats, in one rowVideo rows only; creator data limited to the name and ID embedded in the search result
Result payload shapes handledTwo Facebook view-models (meta and story) plus a crawler-rendered fallback, tagged per row in renderShapeTypically the single video_metadata_model shape; story-shaped edges are dropped without notice
Engagement metricsplayCount, postPlayCount, reactionCount and topReactions per catalogue video, as integersUsually a single raw string such as "2 Mar 2022 · 24K views", requiring downstream parsing
Empty-result behaviourExplicit fetchStatus and errorReason values; a keyword that fails writes an uncharged accounting recordAn empty dataset with no indication whether the keyword matched nothing or the run was throttled
Proxy handlingCustom proxyUrls honoured first, then your Apify groups, with Residential fallback and per-exit rotation on throttlingFixed proxy behaviour; the user's proxy selection is often ignored entirely

For comparison against the closest listings: apify/facebook-video-search-scraper documents 14 output fields and its published example rows return facebookId set to the search URL rather than a video ID (checked on the Apify Store 2026-07-25 — not measured here). powerai/facebook-video-search-scraper advertises date-range and location filters that this Actor does not offer, and returns time and views as a single time_and_views_raw string (checked on the Apify Store 2026-07-25 — not measured here). igview-owner/facebook-search-scraper takes a single query string plus a page count rather than a keyword list (checked on the Apify Store 2026-07-25 — not measured here).

If you are building an AI agent or a RAG pipeline, the output-format row is the decision-maker — parsing HTML or splitting a "2 Mar 2022 · 24K views" string inside an agent loop is a reliability failure mode, not a feature. And if you are shortlisting creators rather than clips, the entity-coverage row is the only one that matters: a video-only scraper cannot tell you whether the account behind a hit posts weekly or once a year.

How many results can you scrape?

The hard limits are maxVideos at 1,000 videos per keyword and maxCreatorsToFetch at 500 distinct creator catalogues per run, both enforced by the input schema and clamped again in code. There is no cap on how many keywords you can put in startUrls, so total run size is keywords × maxVideos.

Search pagination is cursor-driven. The Actor bootstraps the first cursor from the search page's embedded page_info.end_cursor, then posts SearchCometResultsPaginatedResultsQuery requests asking for up to 20 results at a time until maxVideos is reached. Paging follows Facebook's has_next_page and end_cursor — never the number of rows a given page happened to parse — so a page that yields nothing parseable does not end the crawl early. Facebook's search feed returns roughly 7–8 results per page in practice, so higher maxVideos values mean proportionally more round trips.

Two platform-imposed ceilings you cannot raise from the input. First, the creator videos tab renders 9 full video nodes logged out; the cursor it exposes cannot be replayed without a session, so catalogueVideosPerCreator above 9 still returns 9. Second, if the Actor cannot resolve the search operation ID from Facebook's JavaScript bundles on a given run, it drops to the crawler-rendered search page, which contains page 1 only — roughly one page of results per keyword for that run. Both cases are visible in the run log rather than silent.

🔌 Integrate and automate your workflow

The Actor works with any language or tool that can send an HTTP request. It runs on the Apify platform, so you start it from the Apify Console, from the Apify API, or from any integration that can call the Apify API on your behalf. There are no Actor-specific credentials to manage — your Apify API token is the only secret involved.

REST API integration

Run the Actor and read the dataset with the official apify-client package. Replace <YOUR_USERNAME> with the account the Actor is published under.

from apify_client import ApifyClient
client = ApifyClient("<YOUR_APIFY_TOKEN>")
run = client.actor("<YOUR_USERNAME>/facebook-video-search-scraper-creator-video-catalogue").call(
run_input={
"startUrls": ["street food", "cricket highlights"],
"maxVideos": 40,
"includeCreatorCatalogue": True,
"catalogueVideosPerCreator": 9,
"maxCreatorsToFetch": 25,
"proxyConfiguration": {"useApifyProxy": True, "apifyProxyGroups": ["RESIDENTIAL"]},
}
)
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
if item.get("rowType") == "run_accounting":
continue
summary = item["creatorCatalogue"]["summary"]
print(item["creatorName"], item["title"], summary["medianPlayCount"], summary["medianDaysBetweenPosts"])

Works in Python, Node.js, Go, Ruby, cURL — any client that can POST to the Apify API and GET the dataset.

Automation platforms (n8n, Make, Zapier, LangChain)

In n8n, use the Apify node with the Run Actor operation, point it at this Actor and pass the same input JSON; chain a Get Dataset Items operation after it to pull rows into the workflow. In Make, the Apify app's Run an Actor module plus Get Dataset Items does the equivalent, and the nested creatorCatalogue arrives as a structured collection you can walk with an Iterator. In Zapier, the Apify integration's Run Actor action fires the run and its dataset trigger delivers rows into Sheets, Airtable or Slack. For LangChain, ApifyDatasetLoader reads the finished dataset into documents — map creatorCatalogue.videos[].description and hashtags into page_content and keep creatorId, creatorName and videoUrl as metadata for retrieval. Apify webhooks cover the push direction: fire on ACTOR.RUN.SUCCEEDED and post the dataset straight to your own endpoint.

Scraping publicly available data is generally lawful in the US and the EU, and this Actor returns only content a logged-out visitor can already see — public Watch search results and public creator videos tabs. It uses no Facebook account, no session cookie and no login, and it cannot reach friends-only posts, private profiles or anything behind an age or region gate.

That said, creator names, profile URLs and profile IDs are personal data under GDPR and CCPA. If you store or process those fields at scale you need a lawful basis, a retention position and a way to honour deletion requests — the same obligations that apply to any personal dataset, however it was obtained. Aggregate metrics such as medianPlayCount or medianDaysBetweenPosts carry materially less risk than a retained list of named individuals.

Consult legal counsel for commercial use cases involving bulk personal data.

❓ Frequently asked questions

Does this Facebook video scraper work without a Facebook account?

Yes. There is no account, login, cookie or token parameter anywhere in the input schema, and nothing in the source reads one. Both requests it makes — the Watch search GraphQL POST and the creator videos-tab GET — go out logged out. The only credential involved in the whole workflow is your Apify API token, and that is only needed if you start runs over the API rather than from the Console.

How often is the scraped data updated?

Every run fetches live. Nothing is cached between runs and no stored snapshot is served. Within a single run there is one deliberate cache: each distinct creator's videos tab is fetched once, so if ten matching clips belong to the same page, all ten rows share that one fetch and carry an identical creatorCatalogue object. Schedule the Actor in the Apify Console if you want a repeating refresh.

What happens when a keyword returns nothing, or a creator has no public videos?

Neither case fails the run. A keyword that produces no video rows writes a single accounting record with rowType: "run_accounting" and an errorReason telling you whether it was no_results or a transport failure such as rate_limited — and that record is pushed without a charged event, so an empty keyword is not billed. A creator whose videos tab renders but contains no public video nodes comes back with catalogueStatus: "empty", catalogueVideoCount: 0 and videos: []. Deleted or unreachable creators return not_found. In every case the row keeps its full key set, so your downstream schema never shifts.

Can I scrape private Facebook videos or login-gated content?

No. Only publicly accessible content is returned. Videos on private profiles, inside closed groups, restricted by age or region, or otherwise hidden from a logged-out visitor are not reachable by this Actor, and no setting changes that. The same applies to a creator who has hidden their videos tab — the catalogue comes back blocked or empty rather than filled by some other route.

Why does creatorIsVerified say false for a creator I know is verified?

Because Facebook omits the verification flag entirely on story-shaped search results, and a missing flag is normalised to false. So false means "no verified badge in this payload", not "confirmed unverified". That is exactly why onlyVerifiedCreators defaults to false — switching it on can drop genuinely verified creators along with the unverified ones. If verification matters to your workflow, filter after the fact rather than during the run, and cross-check against creatorLinkedPageId, which the build notes record as present on 13 of 13 creators measured on 2026-07-23.

Does it work for AI agent workflows and LLM pipelines?

Yes. The Actor is callable as an HTTP endpoint through the Apify API, so any agent framework that can make a request can run it as a tool — LangChain, LlamaIndex, CrewAI, or a raw function-calling loop. Every response is typed JSON with stable field names, so there is no parsing step before the data reaches a model's context window, and the explicit fetchStatus values give an agent something concrete to branch on when data is genuinely missing rather than an ambiguous null.

How does it handle Facebook's anti-bot system?

With four measures, all implemented in the source. Proxy tiering: your custom proxyUrls are tried first, then your selected Apify proxy groups, with Apify Residential as the fallback tier and a bounded budget of six exits per tier. Rotation on throttling: Facebook's rate limit arrives as HTTP 200 with errors[0].code = 1675004, and its wall as HTTP 400 with a body under 4 KB and no result container — both are detected explicitly, and either triggers a new proxy exit that retries the same cursor with backoff, so no results are skipped. User-agent escalation: the search page is tried with a Chrome UA, then Googlebot, then bingbot; the creator videos tab uses the crawler-UA class directly because it returns a leaner, more stable render. Transport retries: three attempts on the search POST and the search page GET, two on JavaScript bundle fetches, with explicit timeouts on all of them. If a keyword still fails outright, the run logs it and moves to the next keyword rather than aborting.

How does it compare to the other Facebook video scrapers on the Apify Store?

The difference is entity coverage. The three closest listings — apify/facebook-video-search-scraper, powerai/facebook-video-search-scraper and igview-owner/facebook-search-scraper — all return video rows from a keyword search, and their published output examples stop at the video plus a short author object (checked on the Apify Store 2026-07-25 — not measured here). This Actor returns the same video row and then attaches the creator's whole public video shelf with per-video play and reaction counts and a computed publishing-cadence summary. powerai/facebook-video-search-scraper does advertise date-range and location filters that this Actor does not offer (checked on the Apify Store 2026-07-25 — not measured here); if a date window is your primary need, that is a genuine difference in its favour.

Does it return data in a format LLMs can use directly?

Yes. Typed, normalized JSON with stable field names — no HTML, no selectors, no parsing. Numbers come back as numbers (playCount, reactionCount, durationSeconds), dates come back both as ISO 8601 strings (publishedAt) and Unix integers (publishedAtTimestamp), and hashtags come back as a clean array rather than buried in a caption blob. Pass a row straight into a context window, index creatorCatalogue.videos[].description into a vector store, or route the whole object through an agent tool.

Can I use it without managing proxies?

Yes. Leave proxyConfiguration at its prefilled Apify Residential setting and the Actor handles exit selection, rotation on throttling and tier fallback for you. If a custom or group tier becomes unavailable mid-run, it falls back to Residential and logs the switch. Supply your own proxyUrls only if you have infrastructure you specifically want used — those are tried first, with Residential still available as the safety net.

What happens when Facebook changes its structure or blocks the scraper?

The Actor is maintained against Facebook's changes, and the output schema stays stable — field names and types do not change on your end when the internals are updated. The design already anticipates the common breakages: the GraphQL operation ID is resolved at runtime from Facebook's own JavaScript bundles rather than hard-coded, two search view-models are parsed instead of one, and a crawler-rendered fallback recovers page 1 when the API path is walled. If something does break, it surfaces as a labelled errorReason in the dataset rather than a silent empty run.

Your feedback

Found a bug, hit a fetchStatus value that does not match what you see on Facebook, or need a field the catalogue does not return yet? We want to know. Open an issue on the Actor's Issues tab in the Apify Console — that is the fastest route to the maintainers, and reported parsing gaps are exactly what keeps this Actor accurate as Facebook's payloads shift.