# Facebook Page Photo Gallery & Engagement Scraper (`vexaloom-labs/facebook-page-photo-gallery-and-engagement-scraper`) Actor

Collect photos from public Facebook pages, profiles, and albums with full-resolution image URLs, alt text, captions, reaction breakdowns, share and comment counts, and tagged people.

- **URL**: https://apify.com/vexaloom-labs/facebook-page-photo-gallery-and-engagement-scraper.md
- **Developed by:** [vexaloom-labs](https://apify.com/vexaloom-labs) (community)
- **Categories:** Social media, Automation, Lead generation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.49 / 1,000 results

This Actor is paid per event and usage. You are charged both the fixed price for specific events and for Apify platform usage.
Since this Actor supports Apify Store discounts, the price gets lower the higher subscription plan you have.

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

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
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.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## 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.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Facebook Page Photo Gallery & Engagement Scraper

Collect public Facebook photos from pages, profiles, albums, tagged collections, and single-photo links. Each row carries the full-size image and thumbnail, caption, upload time, reaction breakdown, comment and share counts, tagged people, owner/page context, album, and post link. No login, cookies, browser, or proxy setup.

### What this actor does

- Walks a page or profile's **Photos** tab or its **Tagged** tab; add both URLs to collect both
- Expands **albums**, a page's **Albums** list, or a direct album URL into its photos
- Accepts **single-photo permalinks** and photo paths
- Follows Facebook's cursor pagination 8 photos at a time, de-duplicates, and keeps going past the first 8-photo batch
- Applies `newerThan` / `olderThan` on Facebook's side for page and tagged tabs (Unix-second query arguments)
- Opens each photo for caption, timestamp, reactions (7 types), comments, shares, tagged people, album, post, and owner
- Copies images to durable storage by default and keeps the signed original plus its expiry

### Input

| Field | Type | Default | Description |
|---|---|---|---|
| `startUrls` | array | – | Page, profile, `/photos`, `/photos_by`, `/photos_stream`, `/photos_of`, `/photos_albums`, album, photo permalink, or bare handle |
| `resultsLimit` | integer | `100` | Global cap across all Start URLs |
| `newerThan` | string | – | Keep photos posted on or after this `YYYY-MM-DD` |
| `olderThan` | string | – | Keep photos posted on or before this `YYYY-MM-DD` |
| `photoTab` | select | `uploaded` | For page/profile URLs: `uploaded` or `tagged` |
| `includeAlbums` | boolean | `false` | Also walk every album on a page/profile |
| `includeDetails` | boolean | `true` | Open each photo; turn off for a fast grid-only export. Album date bounds still read only the timestamp needed for filtering |
| `storeImages` | boolean | `true` | Store permanent image and thumbnail copies in Apify storage |

`proxyConfiguration` is no longer an input. The actor selects its own connection path; old saved tasks that still send the key are accepted and the key is ignored.

#### Examples

A page's recent photos:

```json
{
  "startUrls": [{"url": "https://www.facebook.com/humansofnewyork/photos"}],
  "resultsLimit": 50
}
```

Tagged photos of a page:

```json
{
  "startUrls": [{"url": "https://www.facebook.com/nike"}],
  "photoTab": "tagged",
  "resultsLimit": 100
}
```

Every album on a page:

```json
{
  "startUrls": [{"url": "https://www.facebook.com/nasa/photos_albums"}],
  "resultsLimit": 300
}
```

A date window:

```json
{
  "startUrls": [{"url": "https://www.facebook.com/nationalgeographic/photos"}],
  "newerThan": "2025-01-01",
  "olderThan": "2025-12-31",
  "resultsLimit": 200
}
```

A single photo:

```json
{
  "startUrls": [{"url": "https://www.facebook.com/photo/?fbid=1547667900257527"}]
}
```

### Output per photo

Legacy fields keep their names: `photoId`, `photoUrl`, `imageUrl`, `caption`, `altText`, `timestamp`, `likesCount`, `commentsCount`, `reactionsByType`, `reactionsTotal`, `shareCount`, `taggedPeople`, `authorName`, `authorUrl`, `inputUrl`, `scrapedAt`.

Added fields include `imageUrlOriginal`, `imageExpiresAt`, `imageWidth`, `imageHeight`, `thumbnailUrl`, `thumbnailUrlOriginal`, `ocrText` (Facebook's accessibility caption, not a second OCR pass), `createdTime`, `likeCount`…`angryCount`, `uploaderId/Name/Url`, `pageId/Name/Url/Followers`, `albumId`, `albumName`, `postId`, `postUrl`, `sourceType`, and `detailsStatus`.

`likesCount` keeps its old meaning (total reactions); `likeCount` is the LIKE reaction only. Empty values are omitted. A photo without a caption simply has no `caption` key; a zero count is kept when Facebook reports zero. With `includeDetails` off, album rows have only the grid thumbnail and thumbnail dimensions; `imageUrl` requires details. Album date bounds still work in this mode: the actor reads just the timestamp and keeps the row grid-only.

### Pagination and limits

Facebook returns 8 photos per collection request. The actor follows the cursor until the source ends, `resultsLimit` is reached, three consecutive requests add no new ids, or a duplicate-heavy safety budget is hit. A collection also stops after 2,000 pages (about 16,000 photos) as a safety cap. When a target stops early, the status message notes the shortfall; the log may not name the exact stop cause. No 1,000-photo or 5,000-photo artificial cap is applied.

### Image links and storage

Facebook's CDN links are signed and expire. With `storeImages` on (default), each image and thumbnail is copied to Apify storage; when a copy succeeds, `imageUrl` / `thumbnailUrl` become permanent public links. `imageUrlOriginal` and `imageExpiresAt` preserve the source. If a copy fails, the signed source link is kept and the run continues. With `storeImages` off, the signed links are returned as-is and the expiry is still recorded.

### What this actor cannot collect

- **Photo location** — Facebook does not expose a place/location for photos on these surfaces
- **Multiple image resolutions** — the photo viewer exposes one image URI plus width/height
- **Private profiles, login-only pages, and groups** — only what a logged-out visitor sees
- **Comment bodies or reactor identities** — counts only; use the comments actor for threads
- **Videos and Reels** — the Photos collections contain photos; use the posts/reels actors for video
- **One row per album** — album metadata is attached to its photos; an albums-list URL expands into photos

### FAQ

**Do I need a Facebook account?**
No. The actor reads only public, logged-out content and never asks for cookies or credentials.

**Why did a target return no photos?**
It may not exist, be private, or hide its Photos tab from logged-out visitors. The final status message names failed targets.

**Why are some captions or owner names missing?**
Facebook genuinely has no caption for some photos, and occasionally omits the uploader's display name. Empty fields are omitted rather than filled with guesses.

**What does `detailsStatus` mean?**
`Complete` means the photo page was read; `GridOnly` means details were turned off; `Unavailable` means the photo page could not be read, so grid fields were kept.

**Why do some images need a slower request?**
Gallery pages are free to read. Individual photo pages are often served as a stripped page to datacenter IPs, so the actor automatically retries the detail request through a residential connection. The gallery does not use that path.

**How many photos can I get?**
As many as the page exposes to a logged-out visitor, up to `resultsLimit`. A collection stops after 2,000 pages (about 16,000 photos) even if more remain. Thousands of photos have been walked from one page; duplicate-heavy pages may stop at the safety budget and the status message notes the shortfall.

**Can I filter by date?**
Yes. Use `YYYY-MM-DD`. Page and tagged tabs send the window to Facebook. Albums have no server-side date filter, so each photo has to be opened and checked. This works even with `includeDetails` off: the actor reads only the timestamp needed for the bound, keeps the row grid-only, and stops with a clear status if the window is not reached within the scan budget. An album date run costs one detail request per photo examined.

**Are image links permanent?**
With `storeImages` on, yes, when the copy succeeds; a failed copy leaves the signed source link. With it off, Facebook's signed links expire.

**Is this affiliated with Facebook?**
No. This is an independent third-party tool that reads public pages; it is not affiliated with or endorsed by Meta.

### Data source

Public `facebook.com` pages, profiles, photo albums, and photo permalinks, read without login. The actor does not bypass private content or login walls.

# Actor input Schema

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

Facebook page, profile, photos tab, tagged tab, albums tab, album, or single-photo URL. Bare handles such as nasa are accepted too.

## `resultsLimit` (type: `integer`):

Global cap across all Start URLs. The actor keeps paginating until this many photos are collected or the source runs out.

## `newerThan` (type: `string`):

Only include photos posted on or after this date (YYYY-MM-DD). Applied by Facebook for page and tagged tabs. For albums the actor reads each photo timestamp (even with includeDetails off) and applies the bound; rows stay grid-only.

## `olderThan` (type: `string`):

Only include photos posted on or before this date (YYYY-MM-DD). Applied by Facebook for page and tagged tabs. For albums the actor reads each photo timestamp (even with includeDetails off) and applies the bound; rows stay grid-only.

## `photoTab` (type: `string`):

For page and profile URLs: photos uploaded by the page, or photos where the page is tagged by others. Ignored for album and photo URLs.

## `includeAlbums` (type: `boolean`):

For page and profile URLs, also walk every album listed on the page's Albums tab and collect its photos. A photos_albums URL implies this automatically.

## `includeDetails` (type: `boolean`):

Open each photo to add its caption, timestamp, reactions, comments, shares, tagged people, owner, album, and post link. Turn off for a fast grid-only export; album date bounds still read only the timestamp needed for filtering.

## `storeImages` (type: `boolean`):

Copy each full-size image and thumbnail to Apify storage and return non-expiring URLs. The signed Facebook originals and their expiry are kept in the record.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.facebook.com/humansofnewyork/photos"
    }
  ],
  "resultsLimit": 1,
  "photoTab": "uploaded",
  "includeAlbums": false,
  "includeDetails": true,
  "storeImages": true
}
```

# Actor output Schema

## `photos` (type: `string`):

Dataset of public Facebook photos with engagement and context fields. Empty fields are omitted.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.facebook.com/humansofnewyork/photos"
        }
    ],
    "resultsLimit": 1,
    "newerThan": "",
    "olderThan": "",
    "photoTab": "uploaded",
    "includeAlbums": false,
    "includeDetails": true,
    "storeImages": true
};

// Run the Actor and wait for it to finish
const run = await client.actor("vexaloom-labs/facebook-page-photo-gallery-and-engagement-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 = {
    "startUrls": [{ "url": "https://www.facebook.com/humansofnewyork/photos" }],
    "resultsLimit": 1,
    "newerThan": "",
    "olderThan": "",
    "photoTab": "uploaded",
    "includeAlbums": False,
    "includeDetails": True,
    "storeImages": True,
}

# Run the Actor and wait for it to finish
run = client.actor("vexaloom-labs/facebook-page-photo-gallery-and-engagement-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 '{
  "startUrls": [
    {
      "url": "https://www.facebook.com/humansofnewyork/photos"
    }
  ],
  "resultsLimit": 1,
  "newerThan": "",
  "olderThan": "",
  "photoTab": "uploaded",
  "includeAlbums": false,
  "includeDetails": true,
  "storeImages": true
}' |
apify call vexaloom-labs/facebook-page-photo-gallery-and-engagement-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,vexaloom-labs/facebook-page-photo-gallery-and-engagement-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/2EIffCKePvtaP7w1K/builds/fUY2cTF0SfpZTpIw6/openapi.json
