Extract public Facebook photos in bulk with this fast, reliable scraper. Capture profile, page, album, and timeline images plus metadata. Ideal for SEO, research, analytics, archiving, and automation workflows. Easy to use and perfect for large-scale visual data collection.
Default proxy flipped to residential — direct connection doesn't work from Apify's infra
Live Apify verification of the 0.2 fix (facebook.com/nike, no proxy):
correctly resolved the photo-viewer doc_id, then every photo request came
back "Maintenance trigger: required page data missing" — Facebook serves
an incomplete/degraded page (HTTP 200, not a block status, so the existing
mark_proxy_fallback_needed() auto-escalation never triggers) to Apify's
direct/datacenter egress IPs. The identical run with
proxyConfiguration: {useApifyProxy: true, apifyProxyGroups: ["RESIDENTIAL"]}
returned real photos immediately (reactions 143/114/... on nike). Changed
.actor/input_schema.json's proxyConfiguration prefill to residential-on
by default so a fresh run works without the user needing to discover this.
0.2 — 2026-08-29
Fixed — actor returned 0 results on every run
Root cause: the photo-viewer GraphQL request (CometPhotoRootContentQuery,
used to fetch each photo's image/caption/engagement data) was sent with the
wrong doc_id.
collect_facebook_photos_ids() / get_initial_photos_data() only ever
found the pagination query's doc_id
(ProfileCometAppCollectionPhotosRendererPaginationQuery, embedded in the
gallery page's JS bundles) — the photo-viewer component's bundle is never
loaded on the /photos gallery page itself (only when a photo is actually
opened), so every attempt to find photo_viewer_doc_id via the gallery
HTML/JS-bundle scan or the base-profile-page scan (extract_doc_id) failed
and returned None.
scrape_facebook_photos_async() in src/main.py then silently papered
over that failure by falling back to pagination_doc_id as the
photo_viewer_doc_id:
ifnot photo_viewer_doc_id and pagination_doc_id:
photo_viewer_doc_id = pagination_doc_id
Sending the pagination query's doc_id to /api/graphql/ with the photo
viewer's nodeID/variables shape makes Facebook's API reject the request
outright ("noncoercible_argument_value",
"There was a problem with this request"
), which parse_photo_data() correctly recognizes as an error and
discards — so every single photo silently failed to parse, and every
run produced 0 dataset rows regardless of target, sample size, or proxy
settings. This was reproduced live against facebook.com/nike,
facebook.com/natgeo, and facebook.com/IamSRK.
Fix (src/utils/node_id_utils.py, src/main.py):
Added extract_photo_viewer_doc_id_from_photo_page(): fetches a single
photo's permalink page (https://www.facebook.com/photo/?fbid=<id>, using
an id already collected from the gallery page) and extracts the real
CometPhotoRootContentQuery doc_id from the Relay preloader entry Facebook
inlines there ("preloaderID":"...CometPhotoRootContentQueryRelayPreloader...","queryID":"<doc_id>").
This is the only page Facebook actually embeds that doc_id on for a
logged-out visitor.
Wired this into collect_facebook_photos_ids() as the primary resolution
path for photo_viewer_doc_id, ahead of the (previously-always-failing)
gallery/JS-bundle scan fallbacks.
Removed the photo_viewer_doc_id = pagination_doc_id fallback in
scrape_facebook_photos_async() — it silently guaranteed an API error on
every photo instead of failing loudly, and is never a valid substitute.
Verified against live Facebook (public pages nike, natgeo, and public
profile IamSRK, no login/cookies): photo IDs, images, captions, owner info,
and reactionCount/totalEngagement now parse correctly on every target
tested, including with minReactions filtering applied. commentCount and
shareCount remain null on the stripped logged-out payload Facebook
currently serves for these targets — this is a Facebook-side data-visibility
limit (documented in the README), not a parsing bug, and is left as-is.
No input schema changes. No login/cookie support was added — the fix
resolves a request-construction bug in the existing logged-out-only design.