Facebook Profile Posts & All Photos Scraper avatar

Facebook Profile Posts & All Photos Scraper

Pricing

from $4.49 / 1,000 facebook profile posts

Go to Apify Store
Facebook Profile Posts & All Photos Scraper

Facebook Profile Posts & All Photos Scraper

Scrape latest or historical posts from public Facebook personal profiles as agent-ready JSON with publication dates, stable URLs, nested text, engagement, and all recoverable photos hidden behind +N albums. Built for API, MCP, monitoring, and resumable backfills.

Pricing

from $4.49 / 1,000 facebook profile posts

Rating

5.0

(1)

Developer

Sergei Belostotskii

Sergei Belostotskii

Maintained by Community

Actor stats

0

Bookmarked

4

Total users

3

Monthly active users

21 days ago

Last modified

Share

Run on Apify Source on GitHub AI agents LICENSE

Scrape latest or historical posts from public Facebook personal profiles into clean, agent-ready JSON. Get post text, Facebook publication timestamps, stable post IDs and URLs, authors, engagement counters, best-effort video metadata, and all recoverable photo URLs, including photos hidden behind Facebook +N album grids.

One paid result = one public Facebook profile post. Expanded photo URLs are included in the same dataset row and the same per-post price. No Facebook account, cookies, credentials, or browser session are required from the user.

Best forNot designed for
Public personal profiles, newest-post monitoring, historical backfills, photo-heavy posts, creator/public-figure research, AI agents, MCP, API and scheduled pipelinesPrivate or friends-only profiles, Facebook Pages or Groups, Marketplace, global keyword search, expanded comments, guaranteed video downloads or transcripts

Start here: Run the Actor · Latest-post task · All-photos task · GitHub · Agent guide · Output contract

Table of contents

Why this Actor

The profile-timeline problem

A public Facebook profile is not a clean chronological API. Pinned posts can look recent, shared cards can hide their useful text in nested structures, deleted attachments can consume result slots, and photo albums often expose five previews plus a +N overlay while keeping the rest behind a separate media set.

This Actor treats those details as part of the product:

  1. Resolve the public personal-profile timeline and start from its newest visible posts.
  2. Omit pinned older posts by default.
  3. Recover direct or nested/shared-card text.
  4. Detect media-set tokens and declared album counts.
  5. Expand recoverable photo sets and retry suspicious rows.
  6. Expose coverage and media-quality evidence instead of calling every incomplete response a triumph.

Feature comparison

CapabilityBasic profile-post extractionThis Actor
Public profile post textUsually direct text onlyDirect text plus recoverable nested/shared-card text
Stable post ID and permalinkVariesReturned when Facebook exposes them
Facebook publication timeVariescreated_at with created_at_source provenance
Newest-post monitoringPinned/old rows can interferePinned omission, knownPostIds and sinceDate
Feed preview photosUsuallyYes
Hidden +N photo setOften truncatedMedia-set expansion, permalink fallback and retry
Album-count verificationRareDeclared count and satisfaction flag when exposed
Unavailable cardsCan consume result slotsSkipped by default; optional diagnostic output
Multi-profile failuresOne target can stop the batchPer-profile isolation and separate diagnostics
Historical continuationOften opaqueCursor-backed chunks up to 1,000 posts per profile
AI-agent selectionGeneric input/outputExplicit schemas, MCP prompts, llms.txt and SUMMARY
Result grainCan be unclearExactly one dataset item per accessible post

Measured validation

The public release was tested in Apify cloud against real public profiles, photo-heavy albums, nested posts, rate limits and deep pagination. These are observed results, not a promise that Facebook will remain emotionally stable.

ValidationPostsTimeline pagesFinal photo URLsDeclared albums satisfiedMissing core fields
Deep public-profile history462303Preview-only testn/a0 IDs, dates, URLs or text
Three-profile all-photo pilot60Multiple38640 / 400
Three deeper all-photo runs300Multiple1,428153 / 1530

Individual validated albums expanded to 6, 9, 11, 12 and 19 photos. An unavailable-card regression still returned 100 useful posts after skipping 33 deleted or restricted cards. These observations are validation evidence, not an SLA.

Trust signals

SignalDetails
Open sourceReview the implementation on GitHub.
CI testedParser, media, retry, charging, proxy and settings tests run automatically.
Limited permissionsThe Actor uses Apify's least-privilege execution model.
No user Facebook credentialsInput accepts public profile URLs, not Facebook passwords or cookies.
Structured outputOne post per row with IDs, dates, text, media and quality fields.
Explicit diagnosticsSUMMARY distinguishes complete boundaries, exhausted public history, partial runs and budget limits.
Agent documentationllms.txt, Agent guide and MCP guide.

Quick start

Run from Apify Console

  1. Open the Actor input page.
  2. Paste one or more public Facebook personal-profile URLs.
  3. Keep Expand all photos and Omit pinned posts enabled.
  4. Set the number of posts per profile and click Start.

Minimal input:

{
"profileUrls": [
"https://www.facebook.com/zuck"
],
"maxPostsPerProfile": 20,
"expandAllPhotos": true,
"omitPinnedPosts": true
}

Each default-dataset item is one profile post. After the run, also read the SUMMARY record from the default key-value store for per-profile coverage, media totals, warnings, charging boundaries and continuation cursors.

Supported profile URL formats

https://www.facebook.com/username
https://www.facebook.com/profile.php?id=100000000000000
https://www.facebook.com/people/Name/100000000000000/
100000000000000

Use a public personal-profile URL, handle or numeric ID. Do not submit a Page, Group, Marketplace listing or direct post URL.

Multiple profiles in one run

{
"profileUrls": [
"https://www.facebook.com/lam.tp.chien.phuong",
"https://www.facebook.com/trang.phan.298890",
"https://www.facebook.com/zuck"
],
"maxPostsPerProfile": 50,
"expandAllPhotos": true,
"omitPinnedPosts": true,
"mediaExpansionConcurrency": 3
}

Up to 20 profiles can be processed in one run. Each profile is isolated: a temporary failure on one target does not erase healthy rows from the others. Inspect every entry in SUMMARY.profiles[].

What data you get

One normalized row per post

DataMain fields
Profile identitysource_profile_id, source_profile_url, source_profile_name, source_profile_type
Post identitysource_post_id, source_url, rank
Timecreated_at, created_at_source, created_at_precision, creation_time
Textraw_text, text_source, text_missing_reason, text_candidates
Authorauthor.source_user_id, author.name, author.url
Engagementstats.reactions, stats.comments, stats.shares when exposed
Photosmedia[].source_media_id, source_url, thumbnail_url, dimensions and source
Video metadatavideo_urls[] when direct public URLs are exposed
Media auditpreview/expanded/final/declared counts, completeness, risk and review severity
Run contextcoverage_status, warnings

Run-level SUMMARY

The default key-value store receives one SUMMARY record. Agents and production pipelines should inspect it before accepting a run as complete.

FieldWhy it matters
profiles[]Per-profile result count, attempts, stop reason, coverage and pointer.
profiles[].coverageStatusExplains whether the target/boundary was reached or collection became partial.
profiles[].pointer.nextCursorCursor for the next older-history chunk.
outputPostsTotal post rows written.
media diagnosticsFinal photos, declared-count satisfaction and review distribution.
chargingPrice event, maximum charge, effective limits and budget state.
skippedProfilesByChargeLimitProfiles not started because the caller's run budget was exhausted.
warningsNon-fatal conditions that deserve inspection.

See the complete output contract.

Export formats

Use Apify Dataset to export results as JSON, JSONL, CSV, Excel, XML or RSS, or send them through a webhook/integration to your own database.

How all-photo recovery works

When expandAllPhotos=true, media is resolved in stages:

Public profile timeline
-> preview photos
-> media-set token and declared-count detection
-> photo-set expansion
-> permalink fallback for suspicious +N rows
-> retry and feed preservation
-> final media list + quality fields
FieldInterpretation
media_preview_countValid photo URLs exposed in the timeline payload.
media_expanded_countPhotos found through media-set expansion.
media_final_countFinal deduplicated photo count returned.
media_declared_countAttachment count declared by Facebook when exposed.
media_declared_count_satisfiedWhether the final count meets that declaration.
media_completenessFinal recovery classification.
media_review_severitynone, low, medium or high; review medium and high.
media_plus_n_risktrue when evidence suggests Facebook hid more photos than were recovered.

The Actor returns source photo URLs, not binary image files. Facebook CDN URLs can expire, so download lawful media promptly when durable storage is required.

Run recipes

GoalRecommended configuration
Small evaluation10-20 posts, all photos and pinned omission enabled
Latest postsStart at the profile head with no startCursor
Daily monitoringStart at the head and stop with knownPostIds or sinceDate
Photo-complete collectionKeep expandAllPhotos=true and concurrency at 3
Faster preview-only testSet expandAllPhotos=false
Historical backfillOne profile, up to 1,000 posts, then continue with pointer.nextCursor
Multi-profile monitoringUp to 20 profiles; validate each entry in SUMMARY.profiles[]
Deleted/restricted-card auditSet includeUnavailablePosts=true

Latest public posts

{
"profileUrls": ["PROFILE_URL"],
"maxPostsPerProfile": 50,
"expandAllPhotos": true,
"omitPinnedPosts": true
}

Do not pass an old startCursor when the goal is newest posts. A backfill cursor points toward older history.

Daily incremental monitoring

Start at the newest profile posts every time and stop before a post already stored by your system:

{
"profileUrls": ["PROFILE_URL"],
"maxPostsPerProfile": 100,
"knownPostIds": ["LAST_STORED_POST_ID"],
"expandAllPhotos": true,
"omitPinnedPosts": true
}

Alternative date boundary:

{
"profileUrls": ["PROFILE_URL"],
"maxPostsPerProfile": 200,
"sinceDate": "2026-07-01T00:00:00Z",
"expandAllPhotos": true,
"omitPinnedPosts": true
}

Healthy incremental runs normally finish with complete_until_known_post or complete_until_since_date. Keep a unique constraint on source_post_id, with source_url as a fallback.

Historical backfill

Request up to 1,000 posts from one profile in one run:

{
"profileUrls": ["PROFILE_URL"],
"maxPostsPerProfile": 1000,
"expandAllPhotos": true,
"omitPinnedPosts": true,
"mediaExpansionConcurrency": 3
}

For older history:

  1. Read SUMMARY.profiles[0].pointer.nextCursor after the first run.
  2. Pass that value as startCursor in the next run.
  3. Store the new cursor only after the preceding batch is accepted.
  4. Repeat until no next cursor remains or the required history is complete.
{
"profileUrls": ["PROFILE_URL"],
"maxPostsPerProfile": 1000,
"startCursor": "PREVIOUS_SUMMARY_NEXT_CURSOR",
"expandAllPhotos": true,
"omitPinnedPosts": true,
"mediaExpansionConcurrency": 3
}

The repository also includes npm run backfill:profile for checkpointed JSONL, CSV and Markdown exports. See operations.

Unavailable timeline cards

Deleted or access-restricted attachment cards are skipped by default so they do not consume useful result slots. Set includeUnavailablePosts=true only when an audit intentionally needs diagnostic rows with text_missing_reason=content_unavailable.

AI agents and MCP

This Actor is designed to be discovered, configured and validated by agents as well as people.

Agent selection contract

Choose this Actor when the user asks for:

  • posts from public Facebook personal profiles;
  • latest posts or recurring profile monitoring;
  • Facebook publication dates, stable post IDs and permalinks;
  • direct and nested/shared post text;
  • every recoverable public photo, including hidden +N album grids;
  • structured JSON for translation, classification, research, archiving or database import;
  • resumable historical profile backfills;
  • Apify API, MCP, schedules, webhooks or no-code automation.

Choose another tool for Facebook Pages, Groups, Marketplace, global search, private/friends-only profiles, profile-metadata enrichment, full comment threads or guaranteed video extraction.

Connect through Apify MCP

The hosted Apify MCP server exposes Store Actors to ChatGPT, Claude, Codex, Cursor and other MCP clients.

Minimal configuration:

{
"mcpServers": {
"apify": {
"url": "https://mcp.apify.com?tools=spbotdel/facebook-profile-posts-all-photos-scraper"
}
}
}

Authenticate through OAuth when prompted, or configure the Apify MCP server with an Apify token.

Prompts that work well

  • Get the latest 20 public posts from this Facebook personal profile. Return text, Facebook date, post URL, author, engagement and all photo URLs.
  • Monitor these public profiles. Omit pinned posts, stop at these known post IDs and report each profile's coverage separately.
  • Backfill 1,000 older posts from this public profile and return SUMMARY.profiles[0].pointer.nextCursor.
  • Return posts where media_final_count is greater than 5. Flag rows where media_declared_count_satisfied is false.

Required agent behavior after a run

  1. Read default-dataset rows.
  2. Read SUMMARY from the default key-value store.
  3. Check every SUMMARY.profiles[].coverageStatus before claiming complete coverage.
  4. Keep healthy profile rows when another target is partial; retry only the affected profile.
  5. Review rows with media_review_severity=medium or high or an unsatisfied declared count.
  6. Store known post IDs for monitoring or pointer.nextCursor for older backfills.
  7. Never use an old backfill cursor to look for tomorrow's new posts.

More context: Agent guide · MCP/API guide · llms.txt

API and integrations

Actor ID:

spbotdel/facebook-profile-posts-all-photos-scraper

REST API

curl -X POST "https://api.apify.com/v2/acts/spbotdel~facebook-profile-posts-all-photos-scraper/runs?token=$APIFY_TOKEN" \
-H "Content-Type: application/json" \
-d '{
"profileUrls": ["https://www.facebook.com/zuck"],
"maxPostsPerProfile": 20,
"expandAllPhotos": true,
"omitPinnedPosts": true
}'

Python

import os
from apify_client import ApifyClient
client = ApifyClient(os.environ['APIFY_TOKEN'])
run = client.actor('spbotdel/facebook-profile-posts-all-photos-scraper').call(run_input={
'profileUrls': ['https://www.facebook.com/zuck'],
'maxPostsPerProfile': 20,
'expandAllPhotos': True,
'omitPinnedPosts': True,
})
posts = client.dataset(run['defaultDatasetId']).list_items(clean=True).items
summary_record = client.key_value_store(run['defaultKeyValueStoreId']).get_record('SUMMARY')
summary = summary_record.get('value') if summary_record else None
print(len(posts), summary['profiles'][0]['coverageStatus'])

Node.js

import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client
.actor('spbotdel/facebook-profile-posts-all-photos-scraper')
.call({
profileUrls: ['https://www.facebook.com/zuck'],
maxPostsPerProfile: 20,
expandAllPhotos: true,
omitPinnedPosts: true,
});
const { items: posts } = await client.dataset(run.defaultDatasetId).listItems();
const summaryRecord = await client
.keyValueStore(run.defaultKeyValueStoreId)
.getRecord('SUMMARY');
console.log(posts.length, summaryRecord.value.profiles[0].coverageStatus);

Apify CLI

$apify call spbotdel/facebook-profile-posts-all-photos-scraper -p '{"profileUrls":["https://www.facebook.com/zuck"],"maxPostsPerProfile":20,"expandAllPhotos":true,"omitPinnedPosts":true}'

Schedules, webhooks and no-code tools

  • Use Apify Schedules for daily or hourly profile monitoring.
  • Add a webhook on ACTOR.RUN.SUCCEEDED to import the dataset into your application.
  • Connect through Apify integrations in Make, n8n or Zapier.
  • Export datasets directly to JSON, CSV or Excel for manual workflows.

Reliability and troubleshooting

SituationRecommendation
First run on a new profileStart with 10-20 posts and verify public visibility.
Normal all-photo runKeep mediaExpansionConcurrency=3 and photo expansion enabled.
Latest-post automationKeep omitPinnedPosts=true and do not pass startCursor.
Deep historyUse one profile per backfill and persist a cursor only after successful import.
Several profilesInspect each profile in SUMMARY.profiles[]; retry only failed targets.
Durable image storageDownload CDN URLs soon after collection.
Forensic card auditEnable includeUnavailablePosts only when those rows are useful.

Coverage statuses

SUMMARY.profiles[].coverageStatusMeaningAction
complete_target_reachedRequested post count was returned.Use the rows normally.
complete_until_known_postMonitoring reached a stored post ID.Healthy incremental run.
complete_until_since_dateMonitoring reached the date boundary.Healthy incremental run.
complete_feed_exhaustedFacebook exposed no older public page.Backfill reached the visible end.
no_public_postsNo public post rows were visible.Verify the profile in a logged-out browser.
partial_no_cursorRows exist but no continuation cursor was exposed.Keep rows; retry if more history is required.
partial_stalled_cursorFacebook repeated a cursor.Keep rows and retry from a fresh run.
partial_page_limitAn internal safety page limit was reached.Continue from the returned cursor when available.
partial_errorEarlier rows survived a later page failure.Retry only this profile.
partial_charge_limitThe caller's maximum run charge reduced coverage.Increase the budget or accept the bounded result.

Why did I receive fewer posts than requested?

Common reasons:

  • the profile exposes fewer posts to logged-out visitors;
  • a known-post or date boundary was reached;
  • Facebook stopped returning an older cursor;
  • the caller's maximum run charge bounded the result;
  • the profile became private, restricted or temporarily rate-limited;
  • deleted/restricted cards were skipped because includeUnavailablePosts=false.

Read the profile entry in SUMMARY; do not infer the reason from row count alone.

A public profile returned zero posts

Open the exact profile in a logged-out browser and confirm that its Posts surface is public. Then retry the profile alone. Public visibility can differ from what a signed-in account sees.

Photo URLs stopped working later

Facebook CDN links can be signed or temporary. Download lawful media soon after the run when durable storage is required.

Pricing

The Store price is $4.99 per 1,000 returned Facebook profile posts. Platform usage is included under the current Store pricing configuration.

Apify also displays a tiny synthetic Actor-start event. The main billable event is one dataset post result.

Included in one post resultAdditional per-photo charge
Post text and text-source diagnostics$0
Facebook publication timestamp$0
Stable post/profile identifiers and URLs$0
Author and engagement fields$0
Feed preview photos$0
Expanded +N album photo URLs$0
Media completeness and coverage diagnostics$0

You pay per returned post, not per photo URL. A post with 19 recovered photos is still one result.

When a caller sets maximum cost per run, the Actor reduces collection before expensive feed and album work. A budget-bounded run reports partial_charge_limit instead of silently claiming complete coverage.

FAQ

Does this Actor require Facebook login or cookies?

No. It uses logged-out public Facebook routes and Apify proxy sessions. Users are not asked for Facebook credentials, cookies or browser profiles.

Does it work with private or friends-only profiles?

No. Only posts Facebook exposes publicly to logged-out sessions are in scope.

Does it scrape Facebook Pages or Groups?

No. This Actor is specialized for personal profiles. Use a Page or Group Actor for those surfaces.

Does it collect profile bio, employment, email or phone?

No. The product is focused on profile posts, not profile-metadata or contact enrichment.

Does it return every photo?

It returns all recoverable public photo URLs and specifically expands +N album grids. Facebook can still hide, remove or expire media, so use declared counts and media-review fields for audit.

How can I audit possible missing photos?

Check media_declared_count_satisfied, media_completeness, media_review_severity, media_plus_n_risk, media_preview_count and media_final_count.

Is created_at the Facebook date or scrape time?

created_at is Facebook's publication time from GraphQL creation metadata. Run timestamps live separately in SUMMARY.

Does it recover text from shared or attached posts?

Yes. The parser ranks direct and nested text candidates and records the selected source in text_source. Deleted or inaccessible attachment cards can still have no recoverable text.

Can it monitor only new posts?

Yes. Start every run at the profile head, omit pinned posts and pass knownPostIds or sinceDate as the stop boundary.

Can it backfill years of profile history?

Yes, when that history remains public. Request up to 1,000 posts in a run and continue toward older history with SUMMARY.profiles[].pointer.nextCursor.

What about video?

video_urls contains best-effort direct public media URLs when Facebook exposes them. Video download, long-term availability and transcripts are not guaranteed.

Does it download images into Apify storage?

No. It returns source photo URLs. Downloading and durable storage belong in the caller's downstream pipeline.

Can an AI agent use this Actor?

Yes. Inputs are explicit, output is one post per row, pricing is deterministic, and SUMMARY provides coverage evidence for MCP/API workflows.

Limitations and responsible use

LimitationDetail
Public personal profiles onlyNo friends-only, private or login-only content.
No access bypassThe Actor does not bypass login walls, checkpoints, privacy controls or Facebook security.
Public visibility variesA profile can expose fewer posts to logged-out sessions than to a signed-in browser.
Pages and GroupsThese use different timeline surfaces and are intentionally out of scope.
CommentsCounters may be returned; full threads are not expanded.
VideosBest-effort URLs only; download and transcript completeness are not guaranteed.
ImagesSource URLs are returned; image binaries are not stored by the Actor.
Expiring CDN URLsMirror lawful media promptly when durable storage is needed.
Facebook changesPublic HTML and internal GraphQL documents can change; maintenance is part of this surface.

Use the Actor only for lawful collection of content Facebook exposes publicly. Respect applicable law, platform terms, intellectual property, personal-data obligations and the rights of profile owners. Do not use it to evade access controls or harass individuals.

Ready-made task pages

TaskBest for
Latest public Facebook profile postsA small newest-post run from one profile.
Facebook profile posts with all photosPhoto-heavy posts and hidden +N albums.
Daily Facebook profile monitoringScheduled monitoring with a known-ID/date boundary.
Historical Facebook profile backfillOlder public posts with cursor continuation.
Monitor multiple public Facebook profilesIsolated collection from several profiles.

Need posts from public Facebook groups instead? Use Facebook Group Posts & All Photos Scraper.

Documentation

DocumentUse it for
Agent guideTool selection and safe run patterns for agents.
MCP/API usageMCP prompts, API clients and monitoring state.
Output contractField semantics and media/coverage statuses.
OperationsMonitoring, backfill and incident handling.
ArchitectureCollection and media-expansion pipeline.

Get help

Open a GitHub issue or use the Actor's Issues tab. Include:

  • public profile URL;
  • Apify run ID;
  • affected post URL when available;
  • whether the issue concerns text, timestamps, pagination, photos or coverage;
  • relevant SUMMARY status and warnings.

Email: spbotdel@gmail.com