# TVmaze Scraper (`real-estate-api/tvmaze-scraper`) Actor

TVmaze Scraper is a Python tool designed to extract TV show metadata, episode guides, cast details, air dates, and schedules from TVmaze. It exports structured television data into clean CSV or JSON formats for entertainment analytics and media tracking.

- **URL**: https://apify.com/real-estate-api/tvmaze-scraper.md
- **Developed by:** [real-estate-api](https://apify.com/real-estate-api) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.99 / 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

## TV Shows Scraper: Episodes, Cast, Ratings & TV Schedules (TVmaze, Free)

<p align="center">
  <img src="https://i.imgur.com/Uh4bbul.jpeg" alt="TV Shows Scraper banner" style="max-width:100%; border-radius:14px; width:680px;" />
</p>

<p align="center">
  <b>No API key</b> · <b>Free TVmaze API</b> · <b>Episodes · Cast · Crew · Ratings · Schedules</b>
</p>

A fast, no-key scraper for the free TVmaze API. Search a title, look a show up by IMDb or TheTVDB id, and export episodes with air dates and ratings, plus cast, crew, seasons, networks, and the daily TV schedule by country.

***

### What you can do with it

| Goal | How |
|------|-----|
| Build episode guides | Pull a full season list with air dates, runtimes, and ratings |
| Show "what's on tonight" | Export the broadcast schedule for a country and date, web schedule optional |
| Track premieres for PR or ad sales | Collect premiere dates, network, and status without copying pages by hand |
| Resolve an IMDb or TheTVDB id | Send `tt0903747` or `tvdb:81189` and get the TVmaze show, cast, and ids |
| Build recommendation datasets | Page through the show index, or take shows updated in the last day, week, or month |
| Fill fan wikis | Grab alternate titles, seasons, and crew when you turn those options on |

***

### What it extracts

**Show rows** carry the TVmaze id and URL, the show name (`showTitle`, also stored as `name` and `title`), kind (`showKind`, also `type`), language, genres, status, runtimes, premiere and end dates, schedule time and days, rating, network or web channel, IMDb, TheTVDB, and TVRage ids, image URL, a plain-text summary, and optional akas, seasons, cast, crew, and episodes.

**Episode and schedule rows** carry the episode id and URL, the show id and title, season, number, episode name, episode kind, air date, air time, airstamp with its offset, runtime, rating, summary, image, network, web channel, and the schedule country when you requested one.

**People rows** carry the person id, name, birthday, death day, gender, country, image, and cast credits (show, character, self, voice). **Update rows** are a show id plus an ISO timestamp.

A miss or a hard failure becomes its own row: `input`, `mode`, `error` (`NOT_FOUND`, `RATE_LIMITED`, or `HTTP_<code>`), and `statusCode`. Duplicate ids in a run are dropped, and the run stops at `maxItems`.

***

### Input

| Parameter | Type | Default | Description |
|-----------|------|---------|-------------|
| `mode` | string | `search` | Search, show details, episodes, people, schedule, updates, or the show index. |
| `queries` | array | `breaking bad` | Show titles or person names, one per line. |
| `bestMatchOnly` | boolean | `false` | One singlesearch hit per query. A miss becomes `NOT_FOUND`. |
| `showIds` | array | `169` | TVmaze id, show URL, IMDb id (`tt0903747`), or TheTVDB id (`tvdb:81189`). |
| `includeEpisodes` | boolean | `true` | Attach episodes in shows mode. |
| `includeSpecials` | boolean | `false` | Call the episodes endpoint with `specials=1`. |
| `includeCast` | boolean | `true` | Embed cast on the show request. |
| `includeCrew` | boolean | `false` | Extra request for crew. |
| `includeSeasons` | boolean | `false` | Extra request for seasons. |
| `includeAkas` | boolean | `false` | Extra request for alternate titles. |
| `episodeRows` | boolean | `false` | Write episodes as their own rows in shows mode. |
| `countries` | array | `US` | ISO-2 codes for the broadcast schedule. |
| `dates` | array | today (UTC) | `YYYY-MM-DD`. Empty means today. |
| `includeWebSchedule` | boolean | `false` | Also fetch the streaming/web schedule for each date. |
| `updatedSince` | string | `day` | `day`, `week`, or `month` for the updates feed. |
| `fetchUpdatedShows` | boolean | `false` | Load a full show row for each updated id. |
| `indexStartPage` | integer | `0` | First show-index page. The run stops on the first 404. |
| `maxItems` | integer | `200` | Row cap. Maximum 20,000. |
| `callsPer10s` | integer | `18` | Calls allowed in any 10-second window. Maximum 20. |
| `timeoutSecs` | integer | `300` | Stop the run after this many seconds. |
| `requestTimeoutSecs` | integer | `30` | Per-request timeout. |
| `proxyConfiguration` | object | no proxy | Optional. Apify Proxy stays off unless you turn it on. |

How the lists map to each mode: search uses `queries`; schedule uses `countries` and `dates`; people search uses `queries`, then loads cast credits for each person; the index walks `/shows?page=` from `indexStartPage` until TVmaze returns 404.

#### Example input

```json
{
    "mode": "shows",
    "showIds": ["169", "tt0903747", "tvdb:81189"],
    "includeEpisodes": true,
    "includeCast": true,
    "episodeRows": false,
    "maxItems": 200,
    "proxyConfiguration": { "useApifyProxy": false }
}
```

***

### Output

```json
{
    "tvmazeId": 169,
    "tvmazeUrl": "https://www.tvmaze.com/shows/169/breaking-bad",
    "showTitle": "Breaking Bad",
    "name": "Breaking Bad",
    "showKind": "Scripted",
    "status": "Ended",
    "premiered": "2008-01-20",
    "ended": "2019-10-11",
    "genres": ["Drama", "Crime", "Thriller"],
    "rating": 9.2,
    "network": {"name": "AMC", "country": "US", "timezone": "America/New_York"},
    "imdbId": "tt0903747",
    "thetvdbId": 81189,
    "episodesCount": 62,
    "cast": [{"personId": 14245, "name": "Bryan Cranston", "character": "Walter White"}],
    "source": "TVmaze (CC BY-SA)"
}
```

| Field | Type | Description |
|-------|------|-------------|
| `showTitle` | string | Show name. Also stored as `title` and `name` on show rows. |
| `showKind` | string | Scripted, Reality, and similar. Also stored as `type` on the show row. |
| `episodeKind` | string | `regular`, `significant_special`, and other TVmaze episode kinds. Also stored as `type`. |
| `summary` | string | HTML stripped to plain text. |
| `network` | object | `name`, `country`, `timezone`. Null on streaming-only shows. |
| `webChannel` | object | `name` and `country` when the show is on a web channel. |
| `cast` | array | Person, character, birthday, gender, country, self, voice. |
| `crew` | array | `crewRole` (also stored as `type`), person id, and name. |
| `airstamp` | string | ISO 8601 time with the original offset. |
| `country` | string | Country you requested for a schedule row. |
| `error` | string | `NOT_FOUND`, `RATE_LIMITED`, or `HTTP_<code>`. |
| `source` | string | `TVmaze (CC BY-SA)` on every row. |

The dataset views are `shows`, `episodes`, `schedule`, and `errors`.

***

### How it works

1. Reads `mode` and the lists for that mode.
2. Calls only public `https://tvmaze.com` routes: search, singlesearch, IMDb/TheTVDB lookup, show with episode and cast embeds, episodes, seasons, cast, crew, akas, country schedule, web schedule, people search, cast credits, the show index, and show updates.
3. Sends the User-Agent string TVmaze asks clients to send.
4. Keeps at most `callsPer10s` calls in any 10-second window (default 18, hard max 20) and at most 4 requests in flight. On HTTP 429 it waits 10 seconds plus a short jitter and retries up to 3 times.
5. Strips HTML from summaries, drops duplicate ids, and stops at `maxItems` or `timeoutSecs`. Nested episode arrays are capped at 500 per show so a daily news show does not land as one giant row. Turn on `episodeRows` for one row per episode, still under `maxItems`.

***

### FAQ

**Do I need a TVmaze API key?**
No. This actor uses the public API only. It does not call the premium or user API.

**How many calls will it make?**
By default, 18 calls in each 10-second window, under TVmaze's 20 per 10 seconds per IP. Show details prefer embeds, so episodes and cast share one request. Crew, seasons, akas, and specials are separate requests and run only when you enable them.

**What happens when a show or singlesearch query is missing?**
You get an error row with `error` set to `NOT_FOUND`. The show index treats 404 as the last page and stops, which is how TVmaze ends the index. An empty schedule is not an error.

**Can it look up an IMDb id or a TheTVDB id?**
Yes. `tt0903747`, `imdb:tt0903747`, and `tvdb:81189` resolve through the lookup endpoint and then load as a show. A TVmaze show URL works too.

**Why is the nested episode list shorter than `episodesCount`?**
`episodesCount` is the full count. The nested `episodes` array stops at 500 (or at `maxItems` when that is lower) and sets `episodesTruncated` to true. Use episodes mode, or `episodeRows`, for one row per episode up to the run cap.

***

### Trademark notice

TVmaze and related names, logos, and trademarks belong to their respective owners. Their use in this documentation is solely to identify the source of the data and does not imply affiliation, sponsorship, endorsement, or partnership.

### Disclaimer

This actor is an independent tool and is not affiliated with, endorsed by, sponsored by, or otherwise officially associated with TVmaze.

### Is web scraping legal?

Collecting information that is already public is generally legal. Scraping is not the same as hacking, and no single law bans it. The usual limits apply: do not break into accounts, do not take private or confidential data, and be careful with personal data and copyrighted works. A site's terms can also restrict how you access it. Facts such as a public name, price, or rating are not the same as republishing someone's article or photo.

This actor reads public pages and official public APIs. It does not log in and it does not bypass access controls.

This is not legal advice. For the longer explanation, see Apify's article [Is web scraping legal?](https://blog.apify.com/is-web-scraping-legal/).

# Actor input Schema

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

What to collect: show search, one show, all episodes, people, a country or web schedule, recently updated shows, or the show index.

## `queries` (type: `array`):

Show titles for search mode, or person names for people mode. One query per line.

## `bestMatchOnly` (type: `boolean`):

Use TVmaze singlesearch and keep one show per query. A miss is saved as a NOT_FOUND row.

## `showIds` (type: `array`):

TVmaze show ids (169), show URLs, IMDb ids (tt0903747 or imdb:tt0903747), or TheTVDB ids (tvdb:81189). Used by shows and episodes modes.

## `includeEpisodes` (type: `boolean`):

In shows mode, load episodes with the show. Turn off to skip that request.

## `includeSpecials` (type: `boolean`):

Ask for specials=1 on the episodes endpoint. This replaces the episodes embed so specials are included.

## `includeCast` (type: `boolean`):

In shows mode, embed the cast on the show request.

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

In shows mode, load crew as an extra request.

## `includeSeasons` (type: `boolean`):

In shows mode, load seasons as an extra request.

## `includeAkas` (type: `boolean`):

In shows mode, load alternate titles (akas) as an extra request.

## `episodeRows` (type: `boolean`):

In shows mode, write each episode as its own dataset row instead of nesting them on the show.

## `countries` (type: `array`):

ISO 3166-1 alpha-2 country codes for schedule mode, one per line. Empty uses US.

## `dates` (type: `array`):

Dates for the country schedule and the web schedule. Empty means today (UTC).

## `includeWebSchedule` (type: `boolean`):

Also call the web schedule for each date. The show sits under the embedded show object.

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

How far back the updates feed should look: the last day, week, or month.

## `fetchUpdatedShows` (type: `boolean`):

After the updates feed, load each show and save a show row as well as the update row.

## `indexStartPage` (type: `integer`):

First page of the show index. Page 0 is the start. The run stops at the first 404.

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

Stop after this many dataset rows, including error rows. Free cap 20,000. Nested episode lists are also capped.

## `callsPer10s` (type: `integer`):

Ceiling for TVmaze calls in any 10 second window. TVmaze allows at least 20 calls per 10 seconds per IP. Default is 18.

## `timeoutSecs` (type: `integer`):

Stop the run after this many seconds and keep rows already saved.

## `requestTimeoutSecs` (type: `integer`):

How long to wait for one TVmaze response before treating it as a failed request.

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

Optional proxy for TVmaze requests. Leave Apify Proxy off unless your IP is rate limited.

## Actor input object example

```json
{
  "mode": "search",
  "queries": [
    "breaking bad"
  ],
  "bestMatchOnly": false,
  "showIds": [
    "169",
    "tt0903747",
    "tvdb:81189"
  ],
  "includeEpisodes": true,
  "includeSpecials": false,
  "includeCast": true,
  "includeCrew": false,
  "includeSeasons": false,
  "includeAkas": false,
  "episodeRows": false,
  "countries": [
    "US",
    "GB"
  ],
  "dates": [
    "2026-10-12"
  ],
  "includeWebSchedule": false,
  "updatedSince": "day",
  "fetchUpdatedShows": false,
  "indexStartPage": 0,
  "maxItems": 200,
  "callsPer10s": 18,
  "timeoutSecs": 300,
  "requestTimeoutSecs": 30,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Show name, status, premiere, rating, and TVmaze URL.

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

Show, season, episode number, name, air date, and rating.

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

Air date, show, episode name, country, and TVmaze URL.

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

Inputs that failed, with the error code and HTTP status.

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

Every row from the run, without a table view applied.

# 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 = {
    "queries": [
        "breaking bad"
    ],
    "showIds": [
        "169"
    ],
    "countries": [
        "US"
    ],
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("real-estate-api/tvmaze-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 = {
    "queries": ["breaking bad"],
    "showIds": ["169"],
    "countries": ["US"],
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("real-estate-api/tvmaze-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 '{
  "queries": [
    "breaking bad"
  ],
  "showIds": [
    "169"
  ],
  "countries": [
    "US"
  ],
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call real-estate-api/tvmaze-scraper --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,real-estate-api/tvmaze-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/6WNKcU1qgpwZ4Ckk9/builds/dueOql5DVEFygczeW/openapi.json
