๐ŸŽ‰ Premium Proxies ยท 3-Day Free TrialClaim Now โ†’
Guides

How to Scrape IMDb Data: Ratings, Cast, Reviews

Learn how to scrape IMDb data: titles, ratings, cast, and reviews. Pull IMDb's JSON-LD and hidden JSON, then use the official datasets for bulk facts.

S SparkProxy 0 20 min read
Share
How to Scrape IMDb Data: Ratings, Cast, Reviews

Most guides on how to scrape IMDb data hand you a pile of brittle CSS selectors that break the next time IMDb ships a build. There's a better way. Every IMDb title page already carries a clean, machine-readable Movie block in JSON-LD, a full __NEXT_DATA__ payload, and a GraphQL backend behind it, and IMDb publishes official bulk datasets you can download without scraping at all. This guide shows you the stable extraction paths for titles, ratings, cast, and reviews, the exact fields each one gives you, and where the legal line sits so you don't cross it by accident.

What you can scrape from IMDb and the ID system

Before writing any code, understand how IMDb identifies things. Two ID types run through the entire site:

EntityID prefixExampleURL pattern
Title (film, series, episode)`tt``tt0111161``https://www.imdb.com/title/tt0111161/`
Name (person)`nm``nm0000151``https://www.imdb.com/name/nm0000151/`

These IDs are called tconst and nconst in IMDb's own data. They're stable, so once you have a title's tconst you can build every URL you need: the main page, /fullcredits, /reviews, /ratings. tt0111161 is The Shawshank Redemption, and it's the working example throughout this guide.

Here's the data you can realistically collect per title, and the best source for each:

DataBest sourceWhy
Title, year, runtime, genresJSON-LD or datasetsPresent in the initial HTML and the bulk files
Aggregate rating and vote countJSON-LD or `title.ratings.tsv`One clean field, refreshed daily in datasets
Directors, writers, top castJSON-LD`director`, `creator`, `actor` arrays
Full cast and characters`__NEXT_DATA__` / GraphQL / `title.principals.tsv`JSON-LD only lists the headline cast
User reviewsReviews page / GraphQLPaginated, dynamic
Content rating, release dateJSON-LD`contentRating`, `datePublished`

The pattern to internalize: IMDb hands you structured JSON on almost every page. Parsing rendered HTML with selectors is the last resort, not the first.


Free trial

Scraping at scale? Skip the blocks.

Fast, unblockable datacentre proxies with unlimited bandwidth.

The fastest path: IMDb's per-page JSON-LD

Open any IMDb title page, view source, and search for application/ld+json. IMDb embeds a schema.org Movie (or TVSeries) object in the page head. It's server-rendered, so it's in the initial HTML before any JavaScript runs. That one detail saves you money later: you don't need a headless browser to read it.

Here's the shape of the block (numeric values reflect the moment you fetch, since ratings move):

{
  "@context": "https://schema.org",
  "@type": "Movie",
  "url": "/title/tt0111161/",
  "name": "The Shawshank Redemption",
  "genre": ["Drama"],
  "contentRating": "R",
  "datePublished": "1994-10-14",
  "duration": "PT2H22M",
  "aggregateRating": {
    "@type": "AggregateRating",
    "ratingValue": 9.3,
    "ratingCount": 2900000,
    "bestRating": 10,
    "worstRating": 1
  },
  "director": [{ "@type": "Person", "name": "Frank Darabont", "url": "/name/nm0001104/" }],
  "actor": [
    { "@type": "Person", "name": "Tim Robbins", "url": "/name/nm0000209/" },
    { "@type": "Person", "name": "Morgan Freeman", "url": "/name/nm0000151/" }
  ]
}

The duration is ISO 8601 (PT2H22M means 2 hours 22 minutes), and each actor and director carries a /name/nm... URL you can follow. Parsing it is a few lines:

import json
from bs4 import BeautifulSoup

def as_list(value):
    if value is None:
        return []
    return value if isinstance(value, list) else [value]

def parse_jsonld(html):
    soup = BeautifulSoup(html, "html.parser")
    block = soup.find("script", {"type": "application/ld+json"})
    data = json.loads(block.string)
    rating = data.get("aggregateRating") or {}
    return {
        "type": data.get("@type"),            # "Movie", "TVSeries", "TVEpisode"
        "title": data.get("name"),
        "content_rating": data.get("contentRating"),
        "genres": as_list(data.get("genre")),
        "runtime_iso": data.get("duration"),  # e.g. "PT2H22M"
        "release": data.get("datePublished"),
        "rating": rating.get("ratingValue"),
        "votes": rating.get("ratingCount"),
        "directors": [d.get("name") for d in as_list(data.get("director"))],
        "cast": [a.get("name") for a in as_list(data.get("actor"))],
    }

That handles the headline fields for titles, ratings, and top cast in one shot, no selector maintenance required. The actor array is capped at the page's featured cast, so for a full credit list you'll go one level deeper in section 6.

The JSON-LD also contains a single review object (the top-voted review) with reviewBody, author, and reviewRating. That's the cheapest way to grab one representative review per title without loading the reviews page.


Fetching IMDb reliably with the SparkProxy Scraping API

Reading JSON-LD is easy once you have the HTML. Getting the HTML reliably, at volume, from an anti-bot-aware site is the hard part. IMDb rate-limits by IP and geo-varies its content. The SparkProxy Scraping API takes a target URL and handles the proxy, rotation, and rendering for you. Base URL https://scrape.sparkproxy.io/api/v1, auth via the X-API-Key header.

A basic fetch:

curl "https://scrape.sparkproxy.io/api/v1?url=https://www.imdb.com/title/tt0111161/&render_js=false&country_code=us" \
  -H "X-API-Key: YOUR_API_KEY"

The same call in Python, wrapped so the rest of the guide can reuse it:

import requests

def fetch(url, render_js="false"):
    resp = requests.get(
        "https://scrape.sparkproxy.io/api/v1",
        headers={"X-API-Key": "YOUR_API_KEY"},
        params={
            "url": url,
            "render_js": render_js,   # JSON-LD is in raw HTML, so keep this off here
            "country_code": "us",     # IMDb geo-varies content; pin a region
        },
        timeout=90,
    )
    resp.raise_for_status()
    return resp.text

html = fetch("https://www.imdb.com/title/tt0111161/")
movie = parse_jsonld(html)
print(movie["title"], movie["rating"], movie["votes"])

Note render_js="false". Because the JSON-LD and __NEXT_DATA__ blocks are baked into the initial HTML, you don't pay for a browser render to read them. On the SparkProxy credit model that's the difference between a 1-credit and a 5-credit request per page, which matters a lot across thousands of titles. Turn render_js on only for the reviews pages that lazy-load content (section 7).

If you'd rather not parse HTML yourself, the API's extract_rules parameter pulls fields with CSS selectors server-side:

import json

params = {
    "url": "https://www.imdb.com/title/tt0111161/",
    "render_js": "false",
    "extract_rules": json.dumps({
        "title": "h1",
        "genres": {"selector": "div[data-testid='genres'] a", "type": "list"},
    }),
}

One caveat, and it's the reason JSON-LD wins: IMDb's data-testid attributes and class names change between builds, so selector-based extraction needs babysitting. The JSON-LD schema is far more stable because IMDb keeps it consistent for Google. Prefer the structured blocks; reach for selectors only for fields that live nowhere else.


Going deeper: __NEXT_DATA__ and the GraphQL backend

IMDb's front end is a Next.js app, which means every page ships a