DEV Community

Cover image for Reverse Engineering YouTube's Web API: Why I Ditched Selenium and the Official Data API
Vasyl Smutok
Vasyl Smutok

Posted on

Reverse Engineering YouTube's Web API: Why I Ditched Selenium and the Official Data API

When building a YouTube scraper, you usually have two bad choices:

  1. The Official API: High latency, strict quotas (10k units vanish in seconds), and a heavy registration process.
  2. Browser Automation (Selenium/Playwright): Heavy, slow, and fragile.

I wanted a third way. A way that was fast, lightweight, and didn't require a Google Cloud project. That's how I ended up reverse-engineering InnerTube.

What is InnerTube? 🔍

InnerTube is the internal API that powers everything YouTube: the web app, mobile apps, and even smart TVs. It’s a unified endpoint (/youtubei/v1/...) that accepts JSON and returns massive, highly nested structures.

The challenge? It’s not documented.

The Performance Gap: Pure HTTP vs. Selenium

I ran a quick benchmark scraping comments from a trending video. The results were mind-blowing:

Method Time (100 comments) Memory Usage Reliability
Selenium (Headless) ~8.5 seconds ~450 MB Low (DOM changes)
Official API ~1.2 seconds ~40 MB High (but Quota-limited)
ytscrape (InnerTube) ~0.4 seconds ~35 MB High

By skipping the browser rendering engine and talking directly to the data layer, ytscrape is roughly 20x faster than browser automation.

The Technical Hurdles đźš§

Reverse-engineering this wasn't just about finding the right URL. Here are the three biggest challenges I had to solve in ytscrape:

1. The Context Object

InnerTube won't talk to you unless you provide a valid "context" — a complex object containing client versions, capability flags, and visitor data. I had to build a mechanism to extract this dynamically from the YouTube homepage to keep the library resilient.

2. Continuation Tokens (The Infinite Scroll)

YouTube doesn't use standard page numbers. It uses continuation tokens. Each response contains a encrypted string that you must send back to get the next chunk of data. Implementing this as a transparent Python generator was key:

# Transparent pagination in ytscrape
for comment in yt.comments(video_id):
    # The library handles the token exchange behind the scenes
    process(comment)
Enter fullscreen mode Exit fullscreen mode

3. Typing the Chaos

The JSON returned by InnerTube is... messy. A single video title might be nested 8 levels deep. I used Python Dataclasses to map these structures into something developers actually want to use. No more data['contents'][0]['item']['text'].

Why Async Matters Here ⚡

Because ytscrape is pure HTTP, it plays incredibly well with asyncio. When you're fanning out to scrape 50 different video IDs, the official API would throttle you, and Selenium would crash your RAM. With AsyncYouTube, you can handle high-concurrency crawls with minimal resource footprint.

async with AsyncYouTube(max_concurrency=10) as yt:
    tasks = [yt.video(url) for url in urls]
    videos = await asyncio.gather(*tasks)
Enter fullscreen mode Exit fullscreen mode

Lessons Learned

Building ytscrape taught me that sometimes the best API is the one that's already there, hidden in the Network tab of your browser.

If you’re interested in high-performance scraping or want to see how I mapped the InnerTube responses to Python models, check out the source code:

👉 GitHub: https://github.com/vsmutok/ytscrape

What’s your experience with internal APIs? Have you ever ditched an official SDK for a custom implementation? Let's discuss in the comments!

Top comments (2)

Collapse
 
meng_xipeng_139608bde0640 profile image
meng xipeng •

Nice write-up, especially the continuation-token-as-generator design.

One caveat from building something similar for the player endpoint (captions rather than comments): the results depended a lot on where it ran. From a small VPS it passed, but from a big-cloud container IP I got 1 transcript in 5, and from a datacenter proxy pool 0 in 8, mostly LOGIN_REQUIRED ("confirm you're not a bot"). A residential pool passed 28 of 28 videos. The ANDROID client context needed no key or PO token in my tests, while the WEB context wanted a PO token.

Have you tried ytscrape from a server or CI IP? If it holds up there that's a big deal. If not, a line in the README would save people a surprise.

Collapse
 
vsmutok profile image
Vasyl Smutok •

Thanks for the detailed feedback and insights — really appreciate it!

You're spot on regarding datacenter vs. residential IPs. Like most non-official InnerTube clients, ytscrape runs into YouTube's bot-detection (LOGIN_REQUIRED / consent walls) much more frequently when executed from major cloud providers (AWS, GCP, Hetzner) or CI runners compared to local residential connections.

A few details on how ytscrape currently handles this:

  • Transcripts: We already use the ANDROID client context for the player endpoint when pulling caption tracks (inspired by youtube-transcript-api), which helps bypass some of the WEB PO-token requirements for captions.
  • Search, Comments & Metadata: These rely on the WEB context and initial visitor token extraction from the homepage.
  • Proxy Support: The client natively supports passing and rotating proxies (including residential pools via proxies / session config) and custom retry/rate-limiting policies to mitigate IP reputation issues.

You're completely right about setting user expectations — adding a dedicated section in the README/docs about IP reputation, server/datacenter caveats, and recommended proxy setups will save people a lot of debugging time. I'll make sure to add that.

Thanks again for the great suggestion!