← Glossary / requests-html

What is requests-html?

requests-html is a Python library that combines standard HTTP fetching with built-in HTML parsing and basic JavaScript rendering via a bundled headless Chromium instance. While it excels at rapid prototyping by hiding the complexity of browser orchestration behind a single render method, its reliance on the deprecated Pyppeteer backend makes it a liability for high-concurrency production pipelines.

PythonPrototypingPyppeteerJS RenderingDOM Parsing
// 02 — definitions

The prototyping
shortcut.

A unified API for fetching, parsing, and rendering — and why it rarely survives the transition from local script to production pipeline.

Ask a DataFlirt engineer →

TL;DR

requests-html attempts to be the ultimate Swiss Army knife for Python scraping. It wraps requests, lxml, PyQuery, and Pyppeteer into one object. You fetch a page, extract links with CSS selectors, and render JavaScript with a single method call. But because it abstracts away connection pooling and browser lifecycle management, it scales poorly and leaks memory in production.

01Definition & structure
requests-html is a Python library designed to make web scraping as intuitive as the original requests library made HTTP fetching. It bundles several tools into a single package: requests for the network layer, PyQuery and lxml for DOM parsing, and Pyppeteer for headless browser automation. This allows developers to fetch a page, render its JavaScript, and extract elements using CSS selectors all from a single HTMLSession object.
02The render() trap
The library's main selling point is the response.html.render() method, which seamlessly executes JavaScript on the fetched page. However, this method hides massive complexity. Calling it spins up a full Chromium browser process in the background. Because the library struggles with process lifecycle management, these browser instances often fail to close properly, resulting in zombie processes that rapidly consume all available system memory.
03Parsing ergonomics
Beyond rendering, the library provides excellent parsing ergonomics. It supports jQuery-style CSS selectors, XPath, and automatic absolute link resolution (converting relative /about links to full URLs). It also includes a handy search() method for extracting text using simple templates instead of complex regular expressions. These features make it incredibly fast to write the initial extraction logic.
04How DataFlirt handles it
We do not use this library in our production stack. When onboarding a client's legacy scraper built with it, we immediately decouple the architecture. We migrate the network fetching to httpx or aiohttp, the parsing to parsel, and any required JavaScript rendering to our distributed Playwright fleet. This eliminates the memory leaks and allows us to scale the pipeline to millions of requests per day.
05The Pyppeteer deprecation
The fatal flaw of the library today is its hard dependency on Pyppeteer, an unofficial Python port of Puppeteer that has been abandoned by its maintainers. It relies on outdated versions of Chromium that are instantly flagged by modern anti-bot systems like Cloudflare and DataDome. Because the core library is also unmaintained, there is no easy way to swap the backend to a modern engine like Playwright.
// 03 — the performance cost

Why convenience
kills concurrency.

The abstraction that makes requests-html easy to write is exactly what makes it hard to scale. Every render call spins up a heavy browser context without proper lifecycle pooling.

Memory overhead per render = M = Chromium_base + (DOM_nodes × 1.2 KB)
Pyppeteer leaks memory over time, pushing M higher with every iteration. Browser orchestration metrics
Effective concurrency limit = C = System_RAM / (M × leak_factor)
Usually caps out at ~10-15 concurrent workers on standard instances before OOM kills occur. Production failure analysis
DataFlirt migration ROI = Δ = (httpx_throughput / requests_html_throughput) × 100
Moving static fetches to httpx typically yields a 400%+ throughput gain. DataFlirt internal benchmarks
// 04 — the memory leak trace

A prototype hitting
the production wall.

Trace logs from a legacy requests-html worker attempting to scrape a dynamic e-commerce catalog. The Pyppeteer backend struggles with zombie processes.

Python 3.9PyppeteerOOM Kill
edge.dataflirt.io — live
CAPTURED
// Starting requests-html worker pool
session.get: "https://target.com/category/shoes"
response.html.render: sleep=2, keep_page=True
pyppeteer.process: Chromium PID 14022 started
extracted.items: 42 memory.usage: 184MB

// Next iteration
session.get: "https://target.com/category/shirts"
response.html.render: sleep=2, keep_page=True
pyppeteer.process: Chromium PID 14089 started
system.warn: Zombie process detected: 14022 failed to terminate
extracted.items: 38 memory.usage: 412MB

// 40 iterations later
memory.usage: 1.8GB — Threshold exceeded
worker.status: FATAL — Killed by OOM killer. Pipeline halted.
// 05 — failure modes

Where the library
breaks down.

Ranked by frequency of pipeline failures when clients attempt to scale requests-html scripts into production workloads.

MIGRATIONS ·  ·  ·  ·  ·  300+ pipelines
WINDOW ·  ·  ·  ·  ·  ·   12m trailing
UPDATED ·  ·  ·  ·  ·  ·  2026-05-19
01

Pyppeteer zombie processes

Memory leaks · Unclosed Chromium instances exhaust system RAM
02

Event loop conflicts

Async/Sync mix · Mixing standard requests with async renders deadlocks
03

Outdated browser fingerprints

Anti-bot flags · Old Chromium versions get flagged instantly
04

Blocking I/O on static fetches

Throughput cap · Lack of true async for the HTTP layer
05

Silent render timeouts

Data loss · JavaScript fails to execute within the sleep window
// 06 — the migration path

Deconstruct the monolith,

separate the fetcher from the parser.

When clients bring us pipelines built on this library, our first move is to break the monolith. We route static requests to high-concurrency asynchronous clients, and handle parsing with dedicated libraries. For routes that actually require JavaScript rendering, we dispatch them to our managed Playwright fleet. This separation of concerns eliminates the memory leaks and allows each layer to scale independently.

Migration: requests-html → DataFlirt Stack

Performance delta after refactoring a legacy scraper.

http.client requests-htmlhttpx
js.rendering PyppeteerManaged Playwright
dom.parsing PyQueryparsel
memory.per_worker 1.2GB85MB
concurrency.limit 12250+
zombie.processes 42/hr0
antibot.block_rate 18%0.4%

Stay ahead of the pipeline

Data engineering
intel, weekly.

Anti-bot shifts, scraping infrastructure updates, dataset delivery patterns, and business outcomes from our pipelines. Short, technical, no fluff.

// 07 — FAQ

Common
questions.

Common questions about requests-html, its limitations, and how to transition to production-grade tooling.

Ask us directly →
Is requests-html still actively maintained? +
No. The library hasn't seen a major update in years, and its underlying JavaScript rendering engine, Pyppeteer, is effectively dead. Relying on it for new projects introduces immediate technical debt and security vulnerabilities due to outdated browser binaries.
Why does the render method freeze my script? +
On its first run, the render method attempts to download a Chromium binary, which can hang silently. Furthermore, it mixes synchronous and asynchronous event loops under the hood. If you run it inside an existing async environment (like FastAPI or Jupyter), it frequently deadlocks.
Can I use requests-html to bypass Cloudflare or DataDome? +
Absolutely not. The bundled Chromium instance is outdated, lacks stealth patches, and leaks its headless state across dozens of navigator properties. Modern anti-bot systems will flag a requests-html fingerprint before the page even begins to load.
What is the best alternative if I just want the parsing syntax? +
If you like the PyQuery-style CSS selector syntax, use parsel or BeautifulSoup. They provide the same ergonomic extraction capabilities but are significantly faster and don't carry the massive overhead of a bundled browser engine.
How does DataFlirt handle scripts written in requests-html? +
We rewrite them. We map your CSS selectors to our extraction engine and replace the fetch layer with our distributed infrastructure. Static fetches move to our high-throughput HTTP fleet, and dynamic renders move to our managed Playwright clusters.
When is it actually appropriate to use requests-html? +
It is strictly a prototyping tool. It is useful for quick, one-off local scripts where you need to scrape a dynamic page in 10 lines of code and don't care about memory leaks, execution speed, or anti-bot detection. It should never be deployed to a server.
$ dataflirt scope --new-project --target=requests-html READY

Tell us what
to extract.
We do the rest.

20-minute scoping call. Pilot dataset within the week. Production within two. Whether you need a one-off catalogue dump or a continuous feed across millions of records — we scope, build, and operate the pipeline.

hello@dataflirt.com  ·  Bengaluru  ·  IST  ·  typical reply < 4h