# Letterboxd Films Scraper (`axiomworks/letterboxd-films-scraper`) Actor

Scrape public Letterboxd film and list pages to build structured film records. Capture titles, years, ratings, genres, credits, identifiers and optional reviews, with list metadata when applicable. Choose embedded popular reviews or paginate through recent reviews with a configurable per-film limit.

- **URL**: https://apify.com/axiomworks/letterboxd-films-scraper.md
- **Developed by:** [Axiom Works](https://apify.com/axiomworks) (community)
- **Categories:** Social media
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.98 / 1,000 results

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.
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

## Letterboxd Films Scraper

**Returns:** one row per film with title, year, rating, ratingCount, genres, directors, cast, and reviews.
**Input:** `{"startUrls":[{"url":"https://letterboxd.com/official/list/letterboxds-top-500-films/"}],"maxItems":10}`
**Best for:** film catalog research, rating tracking, and list analysis.

```json
{"id":"43015","title":"Harakiri","year":1962,"rating":4.68,"ratingCount":268176,"genres":["History","Drama","Action"],"directors":["Masaki Kobayashi"]}
```

### What does Letterboxd Films Scraper do?

Letterboxd Films Scraper reads public Letterboxd film pages and public film lists, then writes one structured dataset row for each unique film. It collects the film details visible on the site, including ratings, credits, posters, and optional public reviews. You can give it a list URL, individual film URLs, or film slugs. A list supplies the film order and list name as well as the film details. The Letterboxd scraper processes multiple inputs in one run and deduplicates films that occur in more than one source.

The Actor is useful when you need repeatable film data without manually opening hundreds of pages. For example, you can compare the titles and average ratings in a public list, collect director and cast information for a set of films, or track changes in rating counts across runs. The output is a dataset that can be exported, filtered, and joined with your own records by `filmId`, `slug`, or `id`. The `sourceUrl` field links each result to the film page read for that row.

This Actor uses public pages and does not require a Letterboxd login. It does not claim to be an official Letterboxd API or an affiliated Letterboxd service. Its output reflects what the public pages showed when the run took place. Ratings, reviews, list order, and other changing fields may differ on a later run.

### What data can you get?

Each row represents a film, even when the film was discovered through a list. Reviews are nested inside that row. Optional fields are `null` or empty when the page does not provide them or the corresponding input option is disabled.

| Field | Meaning |
| --- | --- |
| `id`, `filmId`, `slug` | Stable film identifiers and the URL slug. |
| `title`, `originalTitle`, `year` | Display title, original title when present, and release year. |
| `url`, `sourceUrl` | Canonical film URL and page used for extraction. |
| `rating`, `ratingCount`, `reviewCount` | Average rating, rating volume, and review volume shown on the film page. |
| `genres`, `themes` | Public genre and theme names on the film page. |
| `directors`, `cast`, `crew` | Director names, cast entries with character and URL, and crew names by role. |
| `tagline`, `description`, `runtimeMinutes`, `releaseDate` | Film description and release details available on the page. |
| `studios`, `countries`, `languages` | Production details and language codes or names. |
| `posterUrl`, `backdropUrl`, `trailerUrl` | Media URLs exposed by the film page. |
| `imdbId`, `tmdbId`, `tmdbType` | External catalog identifiers shown on the page. |
| `listName`, `listUrl`, `listPosition` | Source list details when the film came from a list. |
| `reviews`, `reviewsSort` | Nested public reviews and the selected review order. |
| `scrapedAt` | Time the row was extracted. |

A review entry can include its review ID, public author username and display name, star rating, like and comment counts, date when shown, spoiler marker, text, truncation flag, and review URL. Popular reviews are embedded in the film page. Recent reviews come from public paginated review pages. Some popular review text ends in an ellipsis on Letterboxd; `truncated` flags that text instead of pretending the full review was retrieved.

### How to use

Open the Actor input and add one or more film or list URLs under `startUrls`. The prefilled example uses a public Letterboxd list, so you can run it as supplied to see the dataset shape. Change `maxItems` to the number of unique films you need. A value of 7 outputs at most 7 film rows, regardless of how many films the list contains. You can also enter film slugs under `films`, such as `parasite-2019`, when you already know the desired titles.

For a list, the Actor reads the page in list order and follows its next-page link if more films are needed. It stores the 1-based position in `listPosition`. For a film URL, it normalizes film subpaths to the main film page. The Actor accepts multiple sources in the same run and skips duplicate film URLs. The first source to introduce a film supplies its list context.

Use `includeReviews` to control whether the Actor attaches review entries. `reviewsSort` selects `popular` or `recent`. Popular reviews require no extra review-page requests and usually provide up to 12 entries. Recent reviews may take longer because the Actor reads review pages until `maxReviewsPerFilm` is reached or no more reviews are present. Set the review limit to zero to omit review entries while keeping the review option available for later runs. Set `includeCrew` to false if you do not need the crew map.

The supported discovery URL is `/lists/popular/`, which supplies public list links from its first page. It is distinct from Letterboxd's film popularity grid. Film search, genre browsing, and the film popularity grid are not supported input sources because those public paths may return a challenge page. The Actor warns and skips unsupported URLs. A missing film or list produces no dataset row and is recorded in `RUN_SUMMARY` in the default key-value store.

### Input

| Input | Description | Default |
| --- | --- | --- |
| `startUrls` | Film, list, `/lists/popular/`, person (`/director/quentin-tarantino/`, `/actor/tom-hanks/`, `/writer/greta-gerwig/`), studio or member films (`/dave/films/`) URLs. Supply this or `films`. | No API default; the Console has a prefilled public list. |
| `films` | Film slugs, film page URLs, IMDb IDs such as `tt0114369`, or `tmdb:603`. Supply this or `startUrls`. | Empty. |
| `maxItems` | Maximum film rows, from 1 to 1000. | 50. |
| `includeReviews` | Attach reviews to film rows. | `true`. |
| `reviewsSort` | Choose `popular` or `recent`. | `popular`. |
| `maxReviewsPerFilm` | Maximum attached reviews per film, from 0 to 500. | 12. |
| `includeCrew` | Include crew names grouped by role. | `true`. |
| `maxListsFromPopular` | Number of popular lists to expand when that URL is supplied. | 3. |
| `proxyConfiguration` | Optional Apify Proxy configuration. | Proxy disabled. |

```json
{
  "films": ["parasite-2019", "harakiri", "seven-samurai"],
  "maxItems": 3,
  "includeReviews": true,
  "reviewsSort": "recent",
  "maxReviewsPerFilm": 24,
  "includeCrew": true
}
```

Director, actor and member pages fill `listName` and `listUrl` with the page they came from, so every row records its source. IMDb and TMDB IDs are resolved to the Letterboxd film page before scraping.

The Actor requires at least one URL or film slug in API calls. The Console prefill is an example, while an API caller should supply its own target. `maxItems` counts films rather than nested reviews. The Actor requests pages at a modest rate and retries temporary server or blocking responses. If a requested film is unavailable, the other inputs can still produce results.

### Output

The following shortened row is based on a local run of the prefilled public list. It keeps the real field names and representative values from that run. Ratings and counts are a snapshot and can change.

```json
{
  "id": "43015",
  "filmId": 43015,
  "slug": "harakiri",
  "title": "Harakiri",
  "originalTitle": "切腹",
  "year": 1962,
  "url": "https://letterboxd.com/film/harakiri/",
  "sourceUrl": "https://letterboxd.com/film/harakiri/",
  "rating": 4.68,
  "ratingCount": 268176,
  "reviewCount": 68809,
  "genres": ["History", "Drama", "Action"],
  "directors": ["Masaki Kobayashi"],
  "runtimeMinutes": 135,
  "studios": ["Shochiku"],
  "countries": ["Japan"],
  "languages": ["ja"],
  "listName": "Letterboxd's Top 500 Films",
  "listPosition": 1,
  "reviewsSort": "popular"
}
```

The full row can also include cast and crew arrays, media URLs, text description, and nested reviews. To inspect what a run did when no rows appear, open the default key-value store and read `RUN_SUMMARY`. It records the total pushed film rows, per-source counts, and skipped sources. Dataset rows are reserved for actual films; failures are never inserted as placeholder rows.

### How much does it cost?

The Actor is charged per film result pushed to the dataset. Review entries are attached to film results and are not separate dataset rows. The `maxItems` limit controls how many film records can be pushed. See the Actor's Pricing tab for the current pricing and billing details. The Actor does not assign a charge to a skipped input or a URL that yields no film row.

To estimate work before running a large list, start with a small `maxItems`, inspect the dataset, then raise the limit. Recent reviews require additional page requests, so those runs can take longer than runs using embedded popular reviews. The number of items available depends on the public source list and whether its linked film pages can be read at run time.

### Use with AI agents (MCP)

An AI agent can discover this Actor through Apify MCP and run it with JSON input. The tool description exposes film URLs, list URLs, the result limit, review controls, and the output fields. Configure the Apify MCP server with the Store path for this Actor:

```json
{
  "mcpServers": {
    "apify": {
      "url": "https://mcp.apify.com?tools=axiomworks/letterboxd-films-scraper"
    }
  }
}
```

Example prompts include: “Get the first 10 films from Letterboxd's Top 500 list with ratings and directors”; “Compare ratings and rating counts for these seven film slugs”; and “Collect 24 recent public reviews for each of these film URLs.” Give the agent a target URL or slug and a numeric `maxItems`. If you need the complete review text, check the `truncated` flag because a public film page may show an excerpt.

For downstream workflows, `id` and `filmId` help an agent match rows across runs. `sourceUrl` lets it cite the page used for a film, and `scrapedAt` tells it when the extraction occurred. For a list analysis task, `listName`, `listUrl`, and `listPosition` preserve source context without making separate rows for list entries.

### Integrations and API

Use the Apify API to run the Actor, then read items from its default dataset. Replace the token placeholder with your own Apify API token in your local environment. These examples show the same small list job in three common clients. Keep the token outside source control.

Python:

```python
from apify_client import ApifyClient
import os

client = ApifyClient(os.environ["APIFY_TOKEN"])
run = client.actor("axiomworks/letterboxd-films-scraper").call(run_input={
    "startUrls": [{"url": "https://letterboxd.com/official/list/letterboxds-top-500-films/"}],
    "maxItems": 10,
})
for item in client.dataset(run["defaultDatasetId"]).iterate_items():
    print(item["title"], item["rating"])
```

JavaScript:

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: process.env.APIFY_TOKEN });
const run = await client.actor('axiomworks/letterboxd-films-scraper').call({
  films: ['harakiri', 'parasite-2019'],
  maxItems: 2,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach(item => process.stdout.write(`${item.title}: ${item.rating}\n`));
```

cURL:

```bash
curl -X POST \
  'https://api.apify.com/v2/acts/axiomworks~letterboxd-films-scraper/run-sync-get-dataset-items' \
  -H "Authorization: Bearer $APIFY_TOKEN" \
  -H 'Content-Type: application/json' \
  -d '{"films":["harakiri","parasite-2019"],"maxItems":2}'
```

The dataset is JSON. Export it through Apify's dataset tools when a spreadsheet or other format is needed. Keep the numeric rating alongside `ratingCount`: a high rating based on a small sample may need different treatment from a high rating based on many ratings. The Actor returns the source values and leaves that interpretation to your workflow.

### FAQ

**Why are there fewer rows than `maxItems`?** The limit is a ceiling. A list may end early, a film URL may be unavailable, or a public page may return a challenge. Inspect `RUN_SUMMARY` for skipped URLs and per-source counts.

**Are review records separate dataset items?** No. Every dataset item is one film. Public review objects, when requested, are nested in its `reviews` array. The film result limit therefore stays predictable even when review limits change.

**Can I search Letterboxd by a title phrase?** No. Provide a film slug or public film URL. Public list pages can be used for discovery within a known list, while the live film search and popularity grid are outside the supported URL set.

**Why is a review marked truncated?** Letterboxd sometimes shows an excerpt on a public film page. The Actor preserves that displayed text and marks the excerpt. It does not invent missing text.

**What happens with repeated films?** The Actor deduplicates by canonical film URL within the run. The first occurrence supplies list context; it does not push another row for the same film on a later list page.

**Do I need a proxy?** The default uses no proxy. The optional `proxyConfiguration` input lets you supply Apify Proxy settings, and temporary blocking responses can trigger retries. Results still depend on what Letterboxd serves at run time.

### Is it legal to scrape Letterboxd?

This Actor reads publicly accessible film and review pages without a login. Whether a particular use is permitted depends on your location, the applicable rules, and what you do with the data. Review the site's current terms and any relevant legal requirements before using results commercially or redistributing review text. Avoid collecting more personal data than your use requires. Public review author names and review links are included only as they appear on the source pages. This Actor is independently developed and is not affiliated with Letterboxd.

### Feedback

If a field becomes empty after a site change, send the film URL, the field name, and the run identifier through the Actor's feedback channel. A list URL and expected position help diagnose list-order issues. For missing reviews, include the selected `reviewsSort` and `maxReviewsPerFilm` values. Sharing the input and the affected `sourceUrl` makes it possible to reproduce the extraction problem without guessing which page was read.

# Actor input Schema

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

Film, list, /lists/popular/, person (letterboxd.com/director/<slug>/, /actor/<slug>/, /writer/...), studio or member films (letterboxd.com/<user>/films/) URLs to scrape. Supply this or films.

## `films` (type: `array`):

Film slugs, film page URLs, IMDb IDs (tt0114369) or 'tmdb:496243' to scrape, such as parasite-2019; supply this or startUrls.

## `maxItems` (type: `integer`):

Maximum unique film records to output, 1-1000; default 50.

## `includeReviews` (type: `boolean`):

Attach public reviews to each film; default true.

## `reviewsSort` (type: `string`):

Choose popular reviews embedded on film pages or recent reviews from review pages; default popular.

## `maxReviewsPerFilm` (type: `integer`):

Maximum reviews per film, 0-500; default 12. Popular mode has at most 12.

## `includeCrew` (type: `boolean`):

Include the crew role map from each film page; default true.

## `maxListsFromPopular` (type: `integer`):

Maximum lists to expand from /lists/popular/, 1-20; default 3.

## `proxyConfiguration` (type: `object`):

Configure Apify Proxy for Letterboxd requests; default is no proxy.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://letterboxd.com/official/list/letterboxds-top-500-films/"
    }
  ],
  "films": [
    "parasite-2019"
  ],
  "maxItems": 10,
  "includeReviews": true,
  "reviewsSort": "popular",
  "maxReviewsPerFilm": 12,
  "includeCrew": true,
  "maxListsFromPopular": 3,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

## `results` (type: `string`):

No description

# 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://letterboxd.com/official/list/letterboxds-top-500-films/"
        }
    ],
    "films": [],
    "maxItems": 10,
    "includeReviews": true,
    "reviewsSort": "popular",
    "maxReviewsPerFilm": 12
};

// Run the Actor and wait for it to finish
const run = await client.actor("axiomworks/letterboxd-films-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://letterboxd.com/official/list/letterboxds-top-500-films/" }],
    "films": [],
    "maxItems": 10,
    "includeReviews": True,
    "reviewsSort": "popular",
    "maxReviewsPerFilm": 12,
}

# Run the Actor and wait for it to finish
run = client.actor("axiomworks/letterboxd-films-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://letterboxd.com/official/list/letterboxds-top-500-films/"
    }
  ],
  "films": [],
  "maxItems": 10,
  "includeReviews": true,
  "reviewsSort": "popular",
  "maxReviewsPerFilm": 12
}' |
apify call axiomworks/letterboxd-films-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,axiomworks/letterboxd-films-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/Ej84xQsOGRkhyzLAw/builds/ZJHd7LDZ7QaOze4AT/openapi.json
