---
title: "YouTube scraper"
description: "Scrape YouTube videos and channels as JSON: title, views, likes, duration, upload date, tags and channel. One API request from Python, Node.js, the CLI or MCP."
canonical: https://www.zenrows.com/scrape/youtube
updated: 2026-10-20
---

# YouTube scraper

The Zenrows YouTube scraper turns a YouTube video or channel URL into structured JSON: video ID, title, description, view count, like count, duration, upload date, tags, genre, thumbnail and channel name. You send the URL, Fetch in Adaptive Stealth Mode opens the page, and Extract returns the fields. Run it from an API call, the CLI or an AI agent.

- Site: youtube.com
- Returns: Videos, views, likes, tags and channels

## Run it: one YouTube video in, JSON out

```json Input
{
  "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  "mode": "auto",
  "extract": "auto"
}
```

```json Output
{
  "channel": {
    "channel_url": "http://www.youtube.com/@RickAstleyYT",
    "name": "Rick Astley"
  },
  "engagement": {
    "like_count": 19489981,
    "view_count": 1828456716
  },
  "video": {
    "video_id": "dQw4w9WgXcQ",
    "title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
    "description": "The official video for “Never Gonna Give You Up” by Rick Astley. Never: The Autobiography 📚 OUT NOW! Follow this link to get your copy and listen to Rick’s …",
    "upload_date": "2009-10-24T23:57:33-07:00",
    "duration_iso": "PT3M34S",
    "genre": "Music",
    "is_family_friendly": true,
    "tags": ["rick astley", "Never Gonna Give You Up", "nggyu", "never gonna give you up lyrics"],
    "thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
    "canonical_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "short_url": "https://youtu.be/dQw4w9WgXcQ",
    "embed_url": "https://www.youtube.com/embed/dQw4w9WgXcQ"
  }
}
```

Request and output of a live call to a YouTube video page on Oct 20, 2026, trimmed for length. A field is returned when the page shows it.

## What data can you scrape from YouTube?

A video page and a channel page each come back as their own record. These are the fields teams read most, with the path where each one lives in the JSON.

| Field | What it holds | Example |
|---|---|---|
| `video.video_id` | The 11-character ID in every YouTube URL, the key to join your datasets | `dQw4w9WgXcQ` |
| `video.title` | The video title as the uploader wrote it | `Rick Astley - Never Gonna Give You Up…` |
| `video.description` | The description shown under the player | `The official video for “Never Gonna Give You Up”…` |
| `video.upload_date` | Publish date and time with its UTC offset | `2009-10-24T23:57:33-07:00` |
| `video.duration_iso` | Length in ISO 8601 duration format | `PT3M34S` |
| `video.genre` | The category YouTube files the video under | `Music` |
| `video.tags[]` | The keyword tags the uploader set, hidden on the page itself | `["rick astley", "nggyu", …]` |
| `video.is_family_friendly` | YouTube's family-friendly flag | `true` |
| `video.thumbnail_url` | The full-size thumbnail image | `https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg` |
| `video.short_url` | The youtu.be share link | `https://youtu.be/dQw4w9WgXcQ` |
| `video.embed_url` | The URL for an embedded player | `https://www.youtube.com/embed/dQw4w9WgXcQ` |
| `engagement.view_count` | Total views, as a number | `1828456716` |
| `engagement.like_count` | Total likes, as a number | `19489981` |
| `channel.name` | The channel that published the video | `Rick Astley` |
| `channel.channel_url` | The channel's handle URL | `http://www.youtube.com/@RickAstleyYT` |
| `channel.channel_id` (channel page) | The permanent `UC…` channel ID | `UCBJycsmduvYEL83R_U4JriQ` |
| `channel.description` (channel page) | The channel's About text | `MKBHD: Quality Tech Videos…` |
| `channel.avatar_url` (channel page) | The channel's profile picture | `https://yt3.googleusercontent.com/…` |

## How to scrape YouTube with Zenrows

1. **Create a free Zenrows account** and copy your API key from the dashboard.
2. **Pick the YouTube URL.** A watch page (`/watch?v=ID`) returns the video record. A channel page (`/@handle` or `/channel/UC…`) returns the channel record. Shorts links (`/shorts/ID`) carry the same 11-character ID as a watch URL.
3. **Send it with the default configuration**: Adaptive Stealth Mode opens the page and Extract returns the fields. YouTube counts are the same worldwide, so no country is needed.
4. **Read `parsed`** from the response. To keep only views, likes and the title, list those paths and the response returns just them.
5. **Run your video list in Batch** once one URL returns what you want, and repeat the job on a schedule to build view history.

```python Python
# pip install requests
import json
import requests

params = {
    "apikey": "YOUR_ZENROWS_API_KEY",
    "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "mode": "auto",      # Adaptive Stealth Mode
    "extract": "auto",   # structured JSON
}
response = requests.get("https://api.zenrows.com/v1/", params=params, timeout=180)
response.raise_for_status()

data = response.json()["parsed"]
print(data["video"]["title"], data["engagement"]["view_count"])
print(json.dumps(data, indent=2, ensure_ascii=False))
```

```javascript Node.js
// Node 18+ (built-in fetch)
const params = new URLSearchParams({
  apikey: "YOUR_ZENROWS_API_KEY",
  url: "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
  mode: "auto",
  extract: "auto",
});

const response = await fetch(`https://api.zenrows.com/v1/?${params}`, {
  signal: AbortSignal.timeout(180_000),
});
if (!response.ok) throw new Error(`${response.status}: ${await response.text()}`);

const { parsed } = await response.json();
console.log(parsed.video.title, parsed.engagement.view_count, parsed.engagement.like_count);
```

```bash cURL
curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ&mode=auto&extract=auto"

# Only the fields you need
curl "https://api.zenrows.com/v1/?apikey=YOUR_ZENROWS_API_KEY&url=https%3A%2F%2Fwww.youtube.com%2Fwatch%3Fv%3DdQw4w9WgXcQ&mode=auto&extract=auto&extract_fields=video.title%2Cengagement.view_count%2Cengagement.like_count%2Cvideo.upload_date"
```

```python Python SDK
# pip install zenrows
from zenrows import ZenRowsClient

client = ZenRowsClient("YOUR_ZENROWS_API_KEY")
response = client.get(
    "https://www.youtube.com/@mkbhd",
    params={"mode": "auto", "extract": "auto"},
)
channel = response.json()["parsed"]["channel"]
print(channel["name"], channel["channel_id"], channel["channel_url"])
```

## Input

The scraper takes one YouTube URL per request, plus the options below. A Batch job applies the same options to every URL, so the input for ten thousand videos is a list of watch URLs with these settings.

| Parameter | Type | Default | Description |
|---|---|---|---|
| `url` | string | required | A YouTube watch page (`/watch?v=ID`), Shorts page (`/shorts/ID`), or channel page (`/@handle`, `/channel/UC…`) |
| `mode` | string | `auto` | Adaptive Stealth Mode: starts with the lightest setup and adds rendering and protected-site handling only when the page needs them |
| `extract` | string | `auto` | Returns the video or channel fields as JSON in `parsed`, next to the raw `html` |
| `extract_fields` | string | all fields | Comma-separated paths to keep, such as `video.title,engagement.view_count` |
| `proxy_country` | string | any | Two-letter country code. Useful for videos that are blocked or age-gated in some countries |
| `wait_for` | string | none | A CSS selector to wait for before the page is read |
| `session_id` | integer | none | Reuse the same session across several requests, up to 10 minutes |

```json Batch input
{
  "type": "regular",
  "status": "closed",
  "zenrows_params": {
    "mode": "auto",
    "extract": "auto"
  },
  "tasks": [
    { "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "external_id": "dQw4w9WgXcQ" },
    { "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw", "external_id": "jNQXAC9IVRw" },
    { "url": "https://www.youtube.com/@mkbhd", "external_id": "channel-mkbhd" }
  ]
}
```

## Output

Every response is JSON with two keys: `html`, the page as YouTube served it, and `parsed`, the structured record. A watch page returns `video`, `engagement`, `channel` and `comments`. A channel page returns `channel` and `social_links`. Both samples below are trimmed outputs of live requests on Oct 20, 2026.

```json Video page
{
  "channel": {
    "channel_url": "http://www.youtube.com/@RickAstleyYT",
    "name": "Rick Astley"
  },
  "engagement": {
    "like_count": 19489981,
    "view_count": 1828456716
  },
  "video": {
    "canonical_url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
    "description": "The official video for “Never Gonna Give You Up” by Rick Astley. Never: The Autobiography 📚 OUT NOW! Follow this link to get your copy and listen to Rick’s …",
    "duration_iso": "PT3M34S",
    "embed_url": "https://www.youtube.com/embed/dQw4w9WgXcQ",
    "genre": "Music",
    "is_family_friendly": true,
    "short_url": "https://youtu.be/dQw4w9WgXcQ",
    "tags": ["rick astley", "Never Gonna Give You Up", "nggyu", "never gonna give you up lyrics", "rick rolled", "80s music"],
    "thumbnail_url": "https://i.ytimg.com/vi/dQw4w9WgXcQ/maxresdefault.jpg",
    "title": "Rick Astley - Never Gonna Give You Up (Official Video) (4K Remaster)",
    "upload_date": "2009-10-24T23:57:33-07:00",
    "video_id": "dQw4w9WgXcQ"
  }
}
```

```json Channel page
{
  "channel": {
    "avatar_url": "https://yt3.googleusercontent.com/qu4TmIaYUlS41-dJ9gZ7DUR3nilvmB5_11i6OKSdvNnBNiyOusZP1bMN6ICnuxtjFBb6ioKgRQ=s900-c-k-c0x00ffffff-no-rj",
    "channel_id": "UCBJycsmduvYEL83R_U4JriQ",
    "channel_url": "https://www.youtube.com/channel/UCBJycsmduvYEL83R_U4JriQ",
    "description": "MKBHD: Quality Tech Videos | YouTuber | Geek | Consumer Electronics | Tech Head | Internet Personality!…",
    "name": "Marques Brownlee"
  }
}
```

Counts come back as plain integers, so `view_count` and `like_count` go straight into a database column or a chart. The duration is ISO 8601: `PT3M34S` is 3 minutes 34 seconds, and `isodate.parse_duration()` in Python turns it into seconds. Need a spreadsheet? Load `parsed` into pandas with `pd.json_normalize(...)` and write CSV or Excel. Need the page for an LLM? Ask for Markdown instead of JSON.

## Scrape YouTube from your coding agent

Claude Code, Cursor, Codex and any MCP client can scrape YouTube from a prompt.

The agent sends the same Fetch and Extract request a developer would, so it reads the JSON shown above. Connect the Zenrows MCP server once, or install the CLI and let the agent run it in the terminal.

```bash Connect the MCP server (Claude Code)
claude mcp add zenrows -e ZENROWS_API_KEY=YOUR_ZENROWS_API_KEY -- npx -y @zenrows/mcp
```

```json MCP config (Cursor, Claude Desktop, Windsurf)
{
  "mcpServers": {
    "zenrows": {
      "command": "npx",
      "args": ["-y", "@zenrows/mcp"],
      "env": { "ZENROWS_API_KEY": "YOUR_ZENROWS_API_KEY" }
    }
  }
}
```

```bash Or run the CLI
npx -y @zenrows/cli init
zenrows extract "https://www.youtube.com/watch?v=dQw4w9WgXcQ"
```

```text Then ask your agent
Use Zenrows to get the title, upload date, view count and like count of
these YouTube videos, add a likes-per-1,000-views column, and return a
table sorted by that ratio:
https://www.youtube.com/watch?v=dQw4w9WgXcQ
https://www.youtube.com/watch?v=jNQXAC9IVRw
```

## Scrape YouTube at scale with Batch and Crawl

**Batch** runs a list of YouTube URLs as one job, in parallel, with your own ID on each result. Put the video ID in `external_id` and every row joins back to your list. Run the same job every day and the difference between two runs is the daily view and like growth of each video.

**Crawl** finds the URLs for you. Start it on a channel page, keep only the links that contain `/watch?v=`, and it returns every video page it reaches from that channel. Send those URLs to Batch for a full channel audit.

```bash Batch (cURL)
curl -X POST "https://async.api.zenrows.com/v1/jobs" \
  -H "X-API-Key: YOUR_ZENROWS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "type": "regular",
    "status": "closed",
    "zenrows_params": { "mode": "auto", "extract": "auto" },
    "tasks": [
      { "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ", "external_id": "dQw4w9WgXcQ" },
      { "url": "https://www.youtube.com/watch?v=jNQXAC9IVRw", "external_id": "jNQXAC9IVRw" }
    ]
  }'

# Read the results when the job finishes
curl "https://async.api.zenrows.com/v1/jobs/JOB_ID/results?status=successful" \
  -H "X-API-Key: YOUR_ZENROWS_API_KEY"
```

```bash Batch (CLI)
# videos.jsonl: one {"url": "...", "external_id": "...", "extract": "auto"} per line
zenrows batch create videos.jsonl --wait
zenrows batch results <job-id> --out youtube-videos.jsonl
```

```bash Crawl (cURL)
curl -X POST "https://api.zenrows.com/v1/crawls" \
  -H "X-API-Key: YOUR_ZENROWS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
    "url": "https://www.youtube.com/@mkbhd/videos",
    "depth": 1,
    "max_pages": 200,
    "include_patterns": ["/watch?v="]
  }'
```

**Scheduled jobs** run the same Batch job on a timetable, on Zenrows' side: every few hours, once a day, at set times on chosen days, or once at a future date. Each run keeps its own results, a webhook can tell your app when a run finishes, and you can pause or resume the schedule at any time, with no cron server to keep alive.

```bash Scheduled job (cURL)
# Runs every day on Zenrows' side; each run keeps its own results
curl -X POST "https://async.api.zenrows.com/v1/jobs" \
  -H "X-API-Key: YOUR_ZENROWS_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
      "type": "scheduled",
      "status": "closed",
      "schedule": {
        "rate": {
          "every": 1,
          "unit": "day"
        }
      },
      "zenrows_params": {
        "mode": "auto",
        "extract": "auto"
      },
      "tasks": [
        {
          "url": "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
          "external_id": "youtube"
        }
      ]
    }'

# Or at set times: "schedule": { "calendar": { "times_of_day": ["09:00"],
#   "cadence": { "weekly": { "days": ["mon", "thu"] } } }, "timezone": "Europe/Madrid" }
```

## What teams do with YouTube data

| Use case | Data they pull | Who runs it |
|---|---|---|
| Creator and influencer vetting | Channel name, ID and description, plus views and likes on recent videos | Influencer marketing agencies and brand teams |
| Campaign and sponsorship reporting | Daily view and like counts on sponsored videos, upload date | Brand marketers and media buyers |
| Competitor channel analysis | Titles, tags, durations, genres and engagement across a rival's uploads | Content strategists and YouTube agencies |
| SEO and topic research | Tags, titles and descriptions of the top videos for a subject | SEO teams and content marketers |
| Music and media analytics | View and like totals per track video, tracked over time | Labels, publishers and media researchers |
| AI training data and RAG | Titles, descriptions and tags as clean JSON, or the page as Markdown | AI and data teams |

## Tips for scraping YouTube

- **Normalise every link to the video ID.** `youtu.be/ID`, `/shorts/ID`, `/embed/ID` and `/watch?v=ID&t=42s` all point at the same video. Rebuild `https://www.youtube.com/watch?v=ID` and drop `&list=`, `&t=` and `si=` parameters before you send the URL.
- **Store the channel ID, not only the handle.** A creator can change `@handle`; the `UC…` ID from the channel page never changes, so key your tables on `channel.channel_id`.
- **Track counts as time series.** One scrape is a snapshot. Request the same videos daily with Batch and keep `engagement.view_count` per date to see velocity, which is what most campaign reports need.
- **Read the tags.** YouTube hides tags in the page, and `video.tags[]` returns them. They show how a competitor targets search, and they are a cheap topic signal for clustering.
- **Parse the duration once.** `duration_iso` is ISO 8601. Convert it to seconds when you load it, then filter Shorts (60 seconds or less) from long-form uploads.
- **Use the upload date's offset.** `upload_date` carries the uploader's timezone (`-07:00`). Convert to UTC before comparing videos from different channels.

## Frequently asked questions

### Is it legal to scrape YouTube?

Collecting publicly available data from YouTube, such as video titles, view counts, likes and channel names shown to every visitor, is common practice for analytics and market research. Channel and comment data can include personal data, which laws such as the GDPR and the CCPA protect, so collect only what you have a legitimate reason to use. Videos are also protected by copyright. Check YouTube's terms for your use case and ask your lawyer if you are unsure.

### Is there an official YouTube API?

Yes. The YouTube Data API v3 returns video, channel, playlist and comment data, and needs a Google Cloud project and an API key. It runs on a daily quota, 10,000 units by default, and a single search costs 100 of them, so large research jobs hit the cap quickly and need a quota increase request. A scraper reads the public page instead, with no quota, and returns the fields the page shows.

### What YouTube pages can I scrape?

Watch pages ( /watch?v=ID ), Shorts ( /shorts/ID ) and channel pages ( /@handle , /channel/UC… ) on youtube.com. A watch page returns the video, its engagement and its channel. A channel page returns the channel's ID, name, description and avatar. See the YouTube channel scraper and the YouTube comments scraper for those page types.

### Can I scrape YouTube dislikes?

No scraper can. YouTube removed the public dislike count from every video in late 2021, and the number now reaches only the channel owner in YouTube Studio. The like count is still public and comes back in engagement.like_count .

### Can I scrape YouTube views and likes over time?

Yes. Each request returns the current view and like totals. Run the same list of videos as a Batch job every day, store the results with the date, and the difference between two days is that day's growth.

### How do I scrape YouTube with Python?

Send a GET request to https://api.zenrows.com/v1/ with your API key, the YouTube URL, Adaptive Stealth Mode and Extract turned on, as in the Python example above. The parsed key of the response holds the video as a Python dictionary. The zenrows package on PyPI wraps the same request.

### How many YouTube videos can I scrape?

Send one URL per request, or a whole list as a Batch job that runs the URLs in parallel and keeps your own ID on each result. One Batch job accepts more than 10,000 URLs, sent inline or as a CSV file.

### Can AI agents scrape YouTube with Zenrows?

Yes. Connect the Zenrows MCP server to Claude, Cursor, Codex or any MCP client, or give your agent the Zenrows CLI. Ask for the videos or channels you want in plain language and the agent calls Fetch and Extract for you.

### Can I scrape YouTube on a schedule?

Yes. Create a scheduled Batch job with your YouTube URLs and the same settings as a single request, and Zenrows runs it on its side: every few hours, once a day, at set times on chosen days, or once at a future date. Each run keeps its own results, a webhook can notify your app when a run finishes, and the schedule can be paused or resumed at any time.

### Which integrations work with the YouTube scraper?

Zenrows works with n8n, Make, Zapier, Clay, Dify, LangChain, LlamaIndex, CrewAI and Mastra, plus the Python, Node.js and Go SDKs and any HTTP client. A common setup sends daily view counts to Google Sheets or a warehouse from the workflow tool you already use. See the integrations page for the full list.

### What format does the data come in?

JSON by default, with the structured record in parsed and the original page in html . You can also ask for the page as Markdown or plain text, which is what most LLM and RAG pipelines want.

## More YouTube scrapers

- [YouTube channel scraper](https://www.zenrows.com/scrape/youtube-channel.md)

All scrapers: https://www.zenrows.com/scrape. Start free: https://app.zenrows.com/register. Docs: https://docs.zenrows.com
