πŸ“Έ Facebook Profile & Page Photos Scraper avatar

πŸ“Έ Facebook Profile & Page Photos Scraper

Pricing

$19.99/month + usage

Go to Apify Store
πŸ“Έ Facebook Profile & Page Photos Scraper

πŸ“Έ Facebook Profile & Page Photos Scraper

Collect publicly accessible Facebook photos with high accuracy. Extract content image URLs, album photos, post images, and metadata. Ideal for researchers, brands, and engineers who need organized, large-volume visual data without manual downloading.

Pricing

$19.99/month + usage

Rating

5.0

(1)

Developer

SimpleAPI

SimpleAPI

Maintained by Community

Actor stats

0

Bookmarked

23

Total users

2

Monthly active users

18 hours ago

Last modified

Share

Facebook Photos Scraper β€” Captions, Engagement and Owner Data

Facebook Photos Scraper pulls public photos from any Facebook profile or Page and returns each one as a structured record: the real fbid, every image size Facebook serves with its true pixel dimensions, the native accessibilityCaption, the photo's own caption, createdTimeISO, ownerName / ownerUrl, albumTitle, and the reactionCount / commentCount / shareCount engagement figures. It runs without a Facebook login, works from a plain username or a full profile URL, and is built for social media teams, competitive researchers, and archivists who need a photo library's real metadata rather than a folder of images. The sections below cover exactly what it returns, how to target the right photos, and how it differs from scrapers that fabricate fields Facebook never actually sent.

What is Facebook Photos Scraper?

Facebook Photos Scraper is an Apify Actor that collects photos from a public Facebook profile or Page and parses each photo's own GraphQL detail response as real JSON β€” not by scraping visible thumbnails or running OCR over screenshots. No Facebook account, login, or API key is required; every target must be publicly viewable while logged out.

  • Discovers photos by profile/page URL or short handle (startUrls), with a dedicated mode for a specific album URL
  • Returns the real Facebook photo id (fbid), not a synthetic identifier
  • Captures every image variant Facebook serves for a photo, with genuine width/height for each
  • Returns Facebook's own native accessibility_caption alt text plus the photo's caption, never a guessed or AI-hallucinated description
  • Returns reaction, comment, and share counts alongside a per-reaction-type breakdown when Facebook exposes it
  • Supports date filtering on the photo's real upload time and an optional full-resolution image download into the run's key-value store

What data can I extract with Facebook Photos Scraper?

Every field below is copied from the row-building code in the Actor's src/main.py; nothing here is inferred or renamed.

FieldExample valueUse case
fbid"1046701090818345"Stable, reproducible photo identifier across runs
id"1046701090818345"Same value as fbid, kept for compatibility with pipelines expecting id
url"https://www.facebook.com/photo.php?fbid=1046701090818345"Direct link back to the photo on Facebook
inputUrl / facebookUrl"https://www.facebook.com/nasa"The normalized target URL the photo was collected from
mode"own"Which collection mode produced this row
image{"url": "...jpg", "width": 2048, "height": 1365}The single largest image object as a nested value
imageUrl, imageWidth, imageHeight"...jpg", 2048, 1365The largest available image, flattened for easy filtering
imageHd"...jpg"Same URL as the largest image candidate, requested at scale 2
thumbnailUrl"...s130x130.jpg"Smallest image variant found
imageVariants[{"url": "...", "width": 2048, "height": 1365}, ...]Every distinct image size discovered, largest first
accessibilityCaption"May be an image of rocket launch and text"Facebook's own alt-text description, null if Facebook didn't generate one
caption"Liftoff! πŸš€"The photo's own caption/message text, null if none
createdTime / createdTimeISO1706198400 / "2024-01-25T12:00:00+00:00"Upload time as epoch seconds and ISO 8601
ownerId, ownerName, ownerUrl"100064860112060", "NASA", "https://www.facebook.com/100064860112060"The profile/Page that owns the photo
albumId, albumTitle"1046701090818300", "Timeline Photos"The album the photo belongs to, when Facebook exposes one
location{"name": "Kennedy Space Center", "latitude": 28.57, "longitude": -80.65}Geotag, null when the photo isn't geotagged
taggedUsers, commentsnullLogin-gated identity/comment data β€” see below
mediaStorageUrl"https://api.apify.com/v2/key-value-stores/.../records/photo_....jpg"Present only when downloadMedia is on
scrapedAt"2026-07-26T09:00:00+00:00"ISO timestamp of the scrape

Engagement and caption data

reactionCount, commentCount, and shareCount are read from Facebook's @defer-streamed feedback fragments and returned as integers (or null when Facebook doesn't expose a count for that photo). engagementTotal is the sum of whichever of those three are present. topReactions is a per-reaction-type breakdown β€” e.g. [{"type": "Like", "count": 12300}, {"type": "Love", "count": 5100}] β€” built from the reaction edges Facebook returns; it is null when Facebook doesn't stream the breakdown for that photo. None of these values are estimated: a count Facebook doesn't send is null, never a fabricated zero.

Targeting and identification data

ownerName / ownerUrl identify who a photo belongs to, albumTitle groups photos by album, and createdTimeISO is what the newerThan / olderThan filters key on. mode on every row tells you which collection (own uploads, tagged, or album) produced it, which matters once you start mixing modes across a batch of startUrls.

Why not build this yourself?

Facebook doesn't offer a public API for reading an arbitrary profile or Page's photos anonymously β€” the Meta Graph API's photo endpoints require an access token tied to a Page or asset you already manage, which doesn't help if you need photos from accounts you don't own. Building this scraper in-house means solving problems this Actor already handles in src/main.py and src/utils/: locating the current GraphQL doc_id for the photo viewer (Facebook rotates these; the Actor scans the page's own embedded values first, then falls back to scanning up to 10 JS bundles for the operation name), parsing a response that streams multiple @defer/@stream JSON documents rather than one flat object, detecting a login wall or checkpoint before silently returning an empty result, and retrying blocked or failed requests (up to 3 attempts per request) with proxy rotation. Facebook also serves a login wall to logged-out datacenter and generic-guest IPs, which is why this Actor defaults to Apify's residential proxy rather than a cheaper pool. If you only need a handful of photos from one profile you already have access to, doing it by hand is reasonable. For repeated or multi-profile collection where you need genuine metadata back as structured JSON, this Actor removes the doc_id maintenance and anti-bot handling.

How to use data extracted from Facebook photos?

Social media and brand teams

Set startUrls to your own Page and any competitor or partner Pages, leave mode at its default own, and use newerThan to pull only photos posted since your last check. The output's imageUrl, caption, and reactionCount/commentCount/shareCount fields let you track a competitor's visual output and which posts are actually landing, without opening Facebook manually.

Agencies auditing client or competitor pages

Point startUrls at a list of client and competitor Pages in one run, and use albumTitle and createdTimeISO to group results by campaign period across accounts. Running the same input on a schedule (via an Apify schedule) turns this into a recurring content audit rather than a one-off pull.

Market and content research

Collect photos across a set of Pages in the same category and use engagementTotal and topReactions to compare which visual content types draw the most reaction volume, or use accessibilityCaption text as a lightweight content-classification signal across a large sample without running your own vision model.

AI agents and automated pipelines

Because the Actor is a standard Apify Actor invoked over the Apify API, an agent framework can call it as a tool: pass a profile handle and maxPhotos, get back typed JSON with imageUrl, caption, and engagement fields ready to feed into a RAG index or an automated content-monitoring pipeline, with no scraping logic inside the agent itself.

πŸ”Ό Input sample

ParameterRequiredTypeDescriptionExample value
startUrlsYesarray (stringList)Public profile or Page references, one per line. Full URLs and short handles both work. For a specific album, paste its /media/set/?set=a.<id> URL and set mode to album.["nasa", "https://www.facebook.com/nasa"]
modeNostring (enum, default "own")Which photo collection to scrape. own = the page/profile's own uploaded/timeline photos (default, fully verified). tagged = photos the target is tagged in (public pages only; falls back honestly if the tab is unavailable). album = a specific album (paste its /media/set/ URL). albums = treats the own collection."own"
maxPhotosNointeger (min 1, max 10000, default 20)Maximum photos to collect per target.20
newerThanNostring (datepicker)Keep only photos on/after this date. Absolute (2024-01-15) or relative ("2 weeks", "30 days", "1 month"). Leave empty for no lower bound."30 days"
olderThanNostring (datepicker)Keep only photos on/before this date. Absolute or relative. Combine with newerThan for a date range."2024-01-15"
downloadMediaNoboolean (default false)When on, the largest image of each photo is fetched and stored in the run's key-value store; the row gets a mediaStorageUrl. Each stored image is a distinct media_download billable event.false
minPhotosNointeger (min 1, default 1)Lower bound to aim for per target, capped by maxPhotos.1
concurrentPhotosNointeger (min 1, max 8, default 4)How many photo-detail requests run in parallel. Lower is gentler on Facebook's anti-bot and less block-prone.4
debugDumpNoboolean (default false)Saves the first raw stage-2 GraphQL response to the key-value store (key DEBUG_RAW_<id>) for troubleshooting field coverage.false
proxyConfigurationNoobject (proxy editor)Proxy used for all requests. If left unset, Apify residential proxy is used by default β€” Facebook blocks datacenter IPs for logged-out photo scraping.{"useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"]}
{
"startUrls": ["nasa", "https://www.facebook.com/SpaceX"],
"mode": "own",
"maxPhotos": 50,
"newerThan": "60 days",
"downloadMedia": false,
"concurrentPhotos": 4,
"proxyConfiguration": { "useApifyProxy": true, "apifyProxyGroups": ["RESIDENTIAL"] }
}

Common pitfall: setting mode to "album" without pasting the actual /media/set/?set=a.<id> URL β€” a plain username or profile URL will not resolve to an album. Also note that mode: "albums" currently collects the same own collection as the default mode rather than returning one row per album; it does not yet provide a distinct album-level listing.

πŸ”½ Output sample

Every row is typed, normalized JSON pushed straight to the run's dataset β€” one photo per row, no export-format wrapper required. Download it from the Apify Console as JSON, CSV, Excel, XML, or HTML using the standard dataset export options.

{
"facebookUrl": "https://www.facebook.com/nasa",
"inputUrl": "https://www.facebook.com/nasa",
"mode": "own",
"fbid": "1046701090818345",
"id": "1046701090818345",
"url": "https://www.facebook.com/photo.php?fbid=1046701090818345",
"image": { "url": "https://scontent.xx.fbcdn.net/v/t39.30808-6/...n.jpg", "width": 2048, "height": 1365 },
"imageUrl": "https://scontent.xx.fbcdn.net/v/t39.30808-6/...n.jpg",
"imageWidth": 2048,
"imageHeight": 1365,
"imageHd": "https://scontent.xx.fbcdn.net/v/t39.30808-6/...n.jpg",
"thumbnailUrl": "https://scontent.xx.fbcdn.net/v/t39.30808-6/...s130x130.jpg",
"imageVariants": [
{ "url": "https://scontent.xx.fbcdn.net/v/t39.30808-6/...n.jpg", "width": 2048, "height": 1365 },
{ "url": "https://scontent.xx.fbcdn.net/v/t39.30808-6/...s130x130.jpg", "width": 130, "height": 87 }
],
"accessibilityCaption": "May be an image of rocket launch and text",
"caption": "Liftoff! πŸš€",
"createdTime": 1706198400,
"createdTimeISO": "2024-01-25T12:00:00+00:00",
"ownerId": "100064860112060",
"ownerName": "NASA",
"ownerUrl": "https://www.facebook.com/100064860112060",
"albumId": "1046701090818300",
"albumTitle": "Timeline Photos",
"reactionCount": 18452,
"commentCount": 621,
"shareCount": 3104,
"engagementTotal": 22177,
"topReactions": [
{ "type": "Like", "count": 12300 },
{ "type": "Love", "count": 5100 }
],
"aspectRatio": 1.4996,
"location": null,
"taggedUsers": null,
"comments": null,
"mediaStorageUrl": null,
"scrapedAt": "2026-07-26T09:00:00+00:00"
}

taggedUsers and comments (individual comment authors/bodies) require a logged-in Facebook session and are honestly returned as null on every anonymous run β€” never a faked value. mediaStorageUrl is only added to the row when downloadMedia is true.

How do you filter and target specific photos?

Facebook Photos Scraper's targeting works on three axes, and each one changes what you get back rather than just how it's labeled. First, mode decides which collection is read: own for the profile/Page's own uploads (the fully verified default), tagged for photos the target appears in, and album for one specific album via its /media/set/ URL. Second, newerThan and olderThan narrow by the photo's genuine createdTime β€” use them together for a bounded window, or one alone for an open-ended cutoff; a photo with no readable timestamp is dropped whenever either filter is active, so it's never silently included out of range. Third, maxPhotos, minPhotos, and concurrentPhotos control volume and pacing per target: maxPhotos caps how many are collected (1–10000), minPhotos sets the floor the Actor aims for, and concurrentPhotos (1–8) trades speed against how aggressively you hit Facebook's anti-bot.

{ "startUrls": ["nike"], "mode": "own", "maxPhotos": 30 }
{ "startUrls": ["https://www.facebook.com/nike/media/set/?set=a.10159384712345678"], "mode": "album", "maxPhotos": 500 }
{ "startUrls": ["nike", "adidas"], "mode": "tagged", "newerThan": "2025-01-01", "olderThan": "2025-12-31", "maxPhotos": 100 }

▢️ Want to try other Facebook scrapers?

ScraperWhat it extracts
Facebook Group Insights ScraperFacebook Group metadata and activity insights
Facebook Group Posts & Comments ScraperPosts and comment threads from public Facebook Groups
Facebook Groups Posts & Comments Scraper (pay-per-events)Pay-per-event variant of Group post and comment extraction
Instagram Followers Enrichment ScraperEnriched profile data for an Instagram account's followers
Instagram Profile Phone & Contact ScraperContact details surfaced from public Instagram profiles
LinkedIn Profile Contact LookupContact information lookup from LinkedIn profiles

How to extract Facebook photo data programmatically

Facebook Photos Scraper runs like any Apify Actor: one API call starts the run, and results land in a dataset you fetch back as structured JSON β€” no bespoke integration to maintain.

Python example

from apify_client import ApifyClient
client = ApifyClient("<APIFY_API_TOKEN>")
run = client.actor("facebook-photos-scraper").call(run_input={
"startUrls": ["nasa"],
"mode": "own",
"maxPhotos": 30,
"newerThan": "30 days",
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
print(item["imageUrl"], item["caption"], item["reactionCount"])

Export to spreadsheets or CRM

Every dataset can be downloaded directly as CSV or Excel from the Apify Console, or pulled via the API for a scheduled import. Map ownerName to an "Owner" column, imageUrl / imageHd to an "Image URL" column, caption and accessibilityCaption to content-description columns, and reactionCount / commentCount / shareCount to engagement columns β€” the JSON keys carry over as-is, so no relabeling is needed on the destination side.

Yes β€” scraping publicly accessible Facebook photos and their metadata is legal; this Actor only ever reads what Facebook already serves to a logged-out visitor. These photos and captions are public precisely because their owner chose to make them visible without a login. Because the output includes personal data β€” ownerName, ownerUrl, and photo captions tied to an identifiable individual or Page β€” GDPR and CCPA obligations apply if you store or process records tied to people in the EU/UK or California: you need a lawful basis for that processing, and you should honor deletion/access requests. Consult legal counsel for commercial applications involving bulk storage of personal data.

❓ FAQ

What happens if a photo is deleted or an album is removed mid-run?

The row for that photo simply won't appear β€” there's no separate "deleted" flag. If Facebook's detail response for a given photo id no longer contains a recognizable media node, parse_photo_data returns nothing and the Actor moves on to the next photo rather than emitting a placeholder row.

Can I get engagement data along with the photos?

Yes, and it's included automatically β€” no extra parameter or surcharge. Every row carries reactionCount, commentCount, shareCount, a derived engagementTotal, and a topReactions per-type breakdown when Facebook streams one. A count Facebook doesn't expose for a given photo comes back as null, not a guessed number.

How accurate is the caption and engagement data?

The Actor returns exactly what Facebook's own GraphQL response contains at request time, parsed as real JSON rather than screen-scraped or OCR'd. Accuracy therefore tracks Facebook's own data β€” a photo's reaction count can keep climbing after your run finishes, so treat counts as a snapshot at scrapedAt.

How many photos can I get per run?

Up to maxPhotos per target, which accepts 1 to 10,000. In practice the real ceiling is however many photo ids Facebook's own pagination exposes for that profile β€” the Actor keeps requesting the next page until Facebook stops returning a cursor, so a profile with fewer public photos than your maxPhotos setting simply returns fewer rows, never a padded result.

How does the mode parameter change what's returned?

mode: "own" is the fully verified default and reads the profile/Page's own uploads. mode: "tagged" switches to the "photos of" collection, but only when Facebook exposes that tab publicly for the target β€” if it doesn't, the Actor reports honestly rather than substituting the wrong collection. mode: "album" reads one specific album when you supply its /media/set/ URL. mode: "albums" currently reads the same own collection rather than returning a distinct list of albums.

Does Facebook Photos Scraper work with Claude, ChatGPT, and AI agent frameworks?

It isn't exposed through a dedicated MCP server, but any agent framework that can call an HTTP endpoint can run it through the standard Apify API β€” start a run, poll for completion, and read the dataset back as JSON, the same call pattern shown in the Python example above.

How does Facebook Photos Scraper compare to other Facebook photo scrapers?

As observed on the Apify Store on 2026-07-26, apify/facebook-photos-scraper's sample output returns an ocrText field (OCR text run over the image) rather than Facebook's own accessibility caption, and its README states results only appear at the very end of the run rather than incrementally. igview-owner/facebook-page-photos-downloader's output is four flat fields per photo (page_id, photo_id, photo_type, photo_url) with no caption or engagement data, and it requires numeric Facebook page IDs rather than URLs or handles. premiumscraper/facebook-photos-scraper, per its listing, charges an additional per-item surcharge for enabling tagged photos or albums beyond its base price. This Actor returns captions, engagement counts, and album/owner data for every photo at no extra parameter cost, sourced from parsed GraphQL JSON rather than OCR.

Can I use Facebook Photos Scraper without a Facebook API key or developer account?

Yes. You only need the public profile/Page handles or URLs you want to scrape β€” no Facebook login, API key, developer account, or app review is required. The Actor runs entirely against Facebook's public, logged-out surface via Apify's residential proxy.

Conclusion

Facebook Photos Scraper turns a public profile or Page's photo library into structured JSON β€” real photo ids, every image size with true dimensions, native captions, and full engagement counts β€” without a Facebook login or a hand-built GraphQL client. It's built for teams who need that data repeatedly across many profiles rather than one photo at a time, and its parsed-JSON approach means the fields you get are the fields Facebook actually sent, never a fabricated stand-in. Start a run from the Apify Console with a handful of profile handles, or call it from the API using the Python example above to fold it into an existing pipeline.