# Letterboxd Scraper — Films, Ratings, Reviews & Member Activity (`haketa/letterboxd-scraper`) Actor

Scrape Letterboxd film data (rating, cast, director, genre, runtime, watched/liked counts), member reviews with star ratings, and any member's full watch & rating history. Paste film URLs and/or usernames.

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

## Pricing

from $2.75 / 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 Scraper — Films, Ratings, Reviews & Member Activity

> **Extract structured data from Letterboxd at scale: film details (weighted rating, cast, director, genre, runtime, watched/liked/list counts), member reviews (reviewer, date, text, likes & comments), and any member's full watch & rating history.** Paste film URLs and/or usernames and get clean JSON/CSV/Excel in seconds. Built for film researchers, data journalists, recommender systems, marketers and fans.

[![Films](https://img.shields.io/badge/Films-Rating%20%2B%20Cast%20%2B%20Genre-2c3440)]()
[![Reviews](https://img.shields.io/badge/Member-Reviews-00e054)]()
[![Activity](https://img.shields.io/badge/Watch%20%26%20Rating-History-40bcf4)]()
[![Export](https://img.shields.io/badge/Export-JSON%20%2F%20CSV%20%2F%20Excel-ff8000)]()

***

### What This Actor Does

**Letterboxd** is the world's largest social network for film lovers — hundreds of millions of ratings, reviews and watch logs. This Actor turns that public activity into a structured dataset. Three kinds of records, mix and match in a single run:

| Record `type` | You provide | You get |
|---|---|---|
| **`film`** | A film URL or slug | Weighted average rating, rating count, watched-by / liked-by / list counts, cast, director, genres, country, language, runtime, tagline, description, poster |
| **`review`** | A film URL (with reviews enabled) | Each member review: reviewer, display name, watched date, full review text, likes & comment count |
| **`activity`** | A member username | That member's recent watch & rating history: film, their star rating, watched date, rewatch flag, liked flag, review text |

Everything is public data you can see on letterboxd.com — this Actor just collects it into rows you can analyze.

***

### Why Use This

- **The social layer, not just metadata.** IMDb and TMDb give you a film's facts. Letterboxd gives you what real viewers *think* — the weighted community rating, the review text, the taste of individual members. That's the data you can't get anywhere else.
- **Three data types, one Actor.** Research a film, pull its reviews, and profile a member's taste — all in one run, all in one clean dataset.
- **Fast and reliable.** Pure-HTTP with a browser-grade fingerprint. No headless browser, so it's cheap and quick even across many films and members.
- **Flatten the mess.** Ratings, cast, genres and counts live in different places on the page — this Actor normalizes it all into tidy, flat rows.

***

### Quick Start

#### Run it in the console (no code)

1. Open the Actor in Apify Console.
2. **Films:** paste one or more Letterboxd film URLs (e.g. `https://letterboxd.com/film/parasite-2019/`) or plain slugs (`parasite-2019`).
3. **Members:** paste one or more usernames (e.g. `davidehrlich`) to also pull their watch history.
4. Toggle **Include film reviews** if you want reviews for each film.
5. Click **Start**, then export as **JSON, CSV, Excel or HTML**, or push to Google Sheets, a webhook or a database.

#### Run it via API (Python)

```python
from apify_client import ApifyClient

client = ApifyClient("YOUR_APIFY_TOKEN")

run_input = {
    "filmUrls": [
        "https://letterboxd.com/film/parasite-2019/",
        "https://letterboxd.com/film/past-lives/",
    ],
    "usernames": ["davidehrlich"],
    "includeReviews": True,
    "maxReviewsPerFilm": 100,
}

run = client.actor("YOUR_USERNAME/letterboxd-scraper").call(run_input=run_input)

for rec in client.dataset(run["defaultDatasetId"]).iterate_items():
    if rec["type"] == "film":
        print(rec["name"], rec["year"], rec["rating"], "·", rec["watchedBy"], "watches")
```

#### Analyze a member's taste (Python)

```python
run = client.actor("YOUR_USERNAME/letterboxd-scraper").call(run_input={
    "usernames": ["davidehrlich"],
    "maxUserActivities": 200,
})

rows = [r for r in client.dataset(run["defaultDatasetId"]).iterate_items() if r["type"] == "activity"]
rated = [r for r in rows if r.get("memberRating") is not None]
print("avg rating:", sum(r["memberRating"] for r in rated) / len(rated))
print("top films:", [r["filmTitle"] for r in rated if r["memberRating"] >= 4.5][:10])
```

#### Pull a film's reviews (Node.js)

```javascript
import { ApifyClient } from 'apify-client';
const client = new ApifyClient({ token: 'YOUR_APIFY_TOKEN' });

const run = await client.actor('YOUR_USERNAME/letterboxd-scraper').call({
    filmUrls: ['https://letterboxd.com/film/parasite-2019/'],
    includeReviews: true,
    maxReviewsPerFilm: 200,
});
const { items } = await client.dataset(run.defaultDatasetId).listItems();
const reviews = items.filter(r => r.type === 'review');
console.log(`${reviews.length} reviews, most commented:`);
console.log(reviews.sort((a,b) => (b.commentCount||0) - (a.commentCount||0)).slice(0, 5));
```

***

### Input Parameters

| Field | Type | Description |
|---|---|---|
| `filmUrls` | array | Letterboxd film URLs or slugs. Each returns one `film` record (and reviews if enabled). |
| `usernames` | array | Letterboxd usernames or profile URLs. Each returns that member's watch/rating `activity`. |
| `includeReviews` | boolean | Also collect member reviews for each film. Default `false`. |
| `includeStats` | boolean | Include watched-by / liked-by / list-appearance counts per film. Default `true`. |
| `maxReviewsPerFilm` | integer | Cap on reviews collected per film. Default `40`. |
| `maxUserActivities` | integer | Cap on activity entries per member. Default `100`. |
| `maxItems` | integer | Optional hard cap on total records. `0` = no limit. |
| `proxyConfiguration` | object | Apify Proxy. Datacenter is enough and enabled by default. |

You can provide **films, members, or both** in a single run.

***

### Output

#### `type: "film"`

```json
{
  "type": "film",
  "slug": "parasite-2019",
  "filmId": "426406",
  "name": "Parasite",
  "year": "2019",
  "tagline": "Act like you own the place.",
  "director": ["Bong Joon Ho"],
  "cast": ["Song Kang-ho", "Lee Sun-kyun", "Cho Yeo-jeong", "Choi Woo-shik"],
  "genres": ["Thriller", "Comedy", "Drama"],
  "country": ["South Korea"],
  "language": "Korean",
  "runtimeMinutes": 133,
  "rating": 4.52,
  "ratingCount": 1300000,
  "watchedBy": 7590088,
  "likedBy": 3909420,
  "listAppearances": 895159,
  "posterUrl": "https://a.ltrbxd.com/resized/film-poster/...jpg",
  "url": "https://letterboxd.com/film/parasite-2019/"
}
```

#### `type: "review"`

```json
{
  "type": "review",
  "filmSlug": "parasite-2019",
  "filmName": "Parasite",
  "username": "philbertdy",
  "displayName": "Philbert Dy",
  "watchedDate": "2019-08-14",
  "reviewText": "There is a house on a hill, and there are people in it…",
  "commentCount": 248,
  "liked": false,
  "reviewUrl": "https://letterboxd.com/philbertdy/film/parasite-2019/"
}
```

#### `type: "activity"`

```json
{
  "type": "activity",
  "username": "davidehrlich",
  "filmTitle": "Past Lives",
  "filmYear": "2023",
  "memberRating": 4.5,
  "liked": true,
  "rewatch": false,
  "watchedDate": "2023-06-02",
  "reviewText": "…",
  "filmUrl": "https://letterboxd.com/davidehrlich/film/past-lives/"
}
```

***

### Use Cases

#### 1. Film research & analytics

Pull the community rating, rating volume and popularity (watched/liked/lists) for any set of films. Compare how audiences score titles across a director, a genre or an awards slate.

#### 2. Review sentiment & text mining

Collect hundreds of reviews per film with full text, likes and comment counts — a ready-made corpus for sentiment analysis, topic modeling, or LLM summarization of what viewers actually say.

#### 3. Recommender systems & taste graphs

A member's watch history with their own star ratings is the perfect signal for collaborative filtering. Build taste profiles, find similar members, or seed a recommendation engine.

#### 4. Talent & title tracking

Monitor how a film's rating and popularity evolve after release, festival buzz or streaming drops. Track a director's or actor's catalog in one dataset.

#### 5. Marketing & influencer discovery

Find the members whose reviews get the most likes and comments for films in your niche — the tastemakers worth engaging for film marketing and PR.

#### 6. Data journalism

Quantify audience reception for a story: average ratings, the split between critics and crowds, the most-liked takes on a controversial release.

***

### Tips

- **Film slugs:** the slug is the last path segment of a film URL — `letterboxd.com/film/parasite-2019/` → `parasite-2019`. You can pass either the full URL or just the slug.
- **Reviews are optional** and add a few extra requests per film — enable `includeReviews` only when you need them.
- **Member feeds** return recent activity newest-first; raise `maxUserActivities` for deeper history.
- **Schedule it** with Apify Schedules to track ratings and reviews over time.

***

### Frequently Asked Questions

**Do I need a Letterboxd account?**
No. The Actor reads publicly visible data — no login required.

**Can I get a member's full history?**
It returns recent watch & rating activity, newest first. Increase `maxUserActivities` for more.

**Why is a member's star rating in `activity` but not always in `review`?**
A member's own rating is captured in their `activity` records. Reviews are collected for their text, author and engagement; not every review carries a visible star rating.

**What export formats are supported?**
JSON, CSV, Excel, HTML, or via API — plus Google Sheets, webhooks, Make and Zapier.

**Can I scrape many films and members at once?**
Yes. Provide arrays of film URLs and usernames; the Actor processes them all in one run and dedups results.

***

### Legal & Responsible Use

This Actor collects only publicly available information for research, analytics and personal use. You are responsible for how you use the data. Please:

- Respect Letterboxd's Terms of Service and robots directives.
- Comply with applicable data-protection laws when handling member data.
- Do not use the data for spam, harassment, or any unlawful purpose.
- Use reasonable request volumes and scheduling.

This project is an independent tool and is not affiliated with, endorsed by, or sponsored by Letterboxd.

# Actor input Schema

## `filmUrls` (type: `array`):

Letterboxd film URLs (e.g. https://letterboxd.com/film/parasite-2019/) or plain slugs (e.g. parasite-2019). Each returns film details (rating, cast, genre, runtime, watched/liked counts).

## `usernames` (type: `array`):

Letterboxd usernames (e.g. davidehrlich) or profile URLs. Each returns the member's recent watch & rating history (film, rating, watched date, review, liked, rewatch).

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

For each film URL, also collect member reviews (reviewer, star rating, date, review text, likes/comments).

## `includeStats` (type: `boolean`):

For each film, also fetch watched-by / liked-by / list-appearance counts.

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

Maximum number of reviews to collect per film (when reviews are enabled).

## `maxUserActivities` (type: `integer`):

Maximum number of watch/rating entries to collect per member from their feed.

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

Optional hard cap on total records across all films and members. 0 = no limit.

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

Apify Proxy. Datacenter is enough for Letterboxd and is enabled by default; add residential only if you hit rate limits.

## Actor input object example

```json
{
  "filmUrls": [
    "https://letterboxd.com/film/parasite-2019/"
  ],
  "usernames": [
    "davidehrlich"
  ],
  "includeReviews": true,
  "includeStats": true,
  "maxReviewsPerFilm": 20,
  "maxUserActivities": 50,
  "maxItems": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}
```

# Actor output Schema

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

film | review | activity

## `slug` (type: `string`):

Letterboxd film slug

## `filmId` (type: `string`):

Letterboxd film ID

## `name` (type: `string`):

Film title

## `year` (type: `string`):

Release year

## `director` (type: `string`):

Directors

## `cast` (type: `string`):

Cast members

## `genres` (type: `string`):

Genres

## `runtimeMinutes` (type: `string`):

Runtime in minutes

## `rating` (type: `string`):

Weighted average rating (0-5)

## `ratingCount` (type: `string`):

Number of ratings

## `watchedBy` (type: `string`):

Members who watched

## `likedBy` (type: `string`):

Members who liked

## `listAppearances` (type: `string`):

List appearances

## `posterUrl` (type: `string`):

Poster image URL

## `username` (type: `string`):

Member username

## `displayName` (type: `string`):

Member display name

## `memberRating` (type: `string`):

Member's star rating (0-5)

## `watchedDate` (type: `string`):

Date watched

## `liked` (type: `string`):

Member liked the film

## `rewatch` (type: `string`):

Was a rewatch

## `reviewText` (type: `string`):

Review text

## `filmTitle` (type: `string`):

Film title (member activity)

## `filmUrl` (type: `string`):

Film link

## `scrapedAt` (type: `string`):

ISO timestamp

# 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 = {
    "filmUrls": [
        "https://letterboxd.com/film/parasite-2019/"
    ],
    "usernames": [
        "davidehrlich"
    ],
    "includeReviews": true,
    "includeStats": true,
    "maxReviewsPerFilm": 20,
    "maxUserActivities": 50,
    "maxItems": 0,
    "proxyConfiguration": {
        "useApifyProxy": true
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("haketa/letterboxd-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 = {
    "filmUrls": ["https://letterboxd.com/film/parasite-2019/"],
    "usernames": ["davidehrlich"],
    "includeReviews": True,
    "includeStats": True,
    "maxReviewsPerFilm": 20,
    "maxUserActivities": 50,
    "maxItems": 0,
    "proxyConfiguration": { "useApifyProxy": True },
}

# Run the Actor and wait for it to finish
run = client.actor("haketa/letterboxd-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 '{
  "filmUrls": [
    "https://letterboxd.com/film/parasite-2019/"
  ],
  "usernames": [
    "davidehrlich"
  ],
  "includeReviews": true,
  "includeStats": true,
  "maxReviewsPerFilm": 20,
  "maxUserActivities": 50,
  "maxItems": 0,
  "proxyConfiguration": {
    "useApifyProxy": true
  }
}' |
apify call haketa/letterboxd-scraper --silent --output-dataset

```

## MCP server setup

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