Stealth-Requests is a free, open source data extraction & web scraping project written in Python and released under MIT. It has 563 GitHub stars, 57 forks and 1 open issues, and was last pushed 6 months ago. On this registry it ranks #44 of 45 tracked projects in Data Extraction & Web Scraping, with 5 head-to-head comparisons available.

What is Stealth-Requests?

Stealth-Requests is a Python library that makes HTTP requests look like they come from a real Chrome browser and then parses whatever HTML comes back, aimed at developers and data teams extracting information from sites that block ordinary scrapers.

What it is

Stealth-Requests is an open-source Python package in the Data & Analytics / Data Extraction & Web Scraping category. It deliberately mimics the API of the well-known requests package, so existing scraping code can be reused almost unchanged: import stealth_requests as requests followed by requests.get('https://link-here.com') works the same way. Under the hood it does not use the standard library's HTTP stack at all but instead builds on curl_cffi. Responses come back as a StealthResponse object that carries every method and attribute of a standard requests response object, plus additional parsing capabilities layered on top. The project is released under the MIT licence, written in Python, and published on PyPI.

The concrete problem it solves is that a plain requests call is trivially identifiable and therefore frequently refused. Sites fingerprint TLS handshakes, reject default User-Agent strings, and reject requests that arrive with no plausible Referer chain. Stealth-Requests addresses each of these by impersonating a Chrome browser through curl_cffi, rotating User Agents between requests, and tracking the Referer header across requests made inside a StealthSession. It also replaces the usual second step of a scraping script, where a developer bolts on BeautifulSoup or Lxml to make sense of the response, by folding common extraction tasks directly into the response object.

Key capabilities

  • Impersonates a Chrome browser through curl_cffi to produce realistic HTTP requests that resist basic bot detection.
  • Rotates the User Agent automatically between requests, and updates the Referer header inside a StealthSession to simulate a realistic browsing chain.
  • Implements built-in retry logic that waits two seconds and retries when a request fails with status codes such as 429, 503, and 522; the number of attempts is controlled by the retry argument, for example requests.get(url, retry=3).
  • Exposes a meta property on StealthResponse that parses HTML metadata into title, author, description, thumbnail, canonical, twitter_handle, keywords, and robots.
  • Provides extraction properties for common page data: resp.emails, resp.phone_numbers, resp.images, and resp.links.
  • Parses HTML tables into dictionaries keyed by column header, with each value a list of that column's cell values.
  • Converts responses into Lxml and BeautifulSoup objects for further parsing, and converts full or partial HTML into Markdown.

Who uses it and how

  • Data extraction teams building scrapers against sites that refuse plain requests traffic, using the drop-in API to keep existing scripts intact.
  • Teams scraping within a session where a realistic Referer chain matters, by wrapping calls in StealthSession rather than issuing one-off requests.
  • High-volume or concurrent workloads that use AsyncStealthSession, since the package supports Asyncio in the same style as the requests package.
  • Contact and asset discovery workflows, pulling emails, phone_numbers, images, and links directly from a response without writing custom parsers.
  • Pipelines that need page content in Markdown or need structured table data, converting HTML sections rather than hand-writing extraction code.
  • Scrapers routed through proxies, since proxy support is a documented feature of the package.

Getting started

Install from PyPI with pip install stealth_requests, then use it as a drop-in replacement for requests or open a StealthSession context manager for header-tracking behaviour.

How it compares

The facts name no paid products this project replaces, so it is best understood against the tools it sits beside. It occupies the same API surface as the requests package while substituting curl_cffi for the transport layer, and it absorbs work normally delegated to BeautifulSoup and Lxml by returning parsed objects, metadata, extracted entities, and Markdown from the same response. In that sense it is a consolidation of a typical scraping stack rather than a substitute for a hosted scraping service.

When to use it — and when not

There is nothing to operate beyond a Python environment: no database, object storage, or SMTP service is required, and no server is deployed, so pip install stealth_requests is the entire footprint. It is a poor fit for teams that want a managed scraping service with an uptime commitment, since this is a library the caller must run and rate-limit themselves, including supplying any proxies. The honest weakness is maturity and reach: the project sits at 563 stars and 57 forks with a single open issue, so its community is small and long-term support depends on a narrow contributor base.

project readme (upstream, from github) — read inline

The Easiest Way to Scrape the Web

Python 3.10+ PyPI PyPI installs

Features

  • Realistic HTTP Requests:
    • Mimics Chrome browser for undetected scraping using curl_cffi
    • Automatically rotates User Agents between requests
    • Tracks and updates the Referer header to simulate realistic request chains
    • Built-in retry logic for failed requests (e.g. 429, 503, 522)
  • Faster and Easier Parsing:
    • Extract emails, phone numbers, images, and links from responses
    • Automatically extract metadata (title, description, author, etc.) from HTML-based responses
    • Seamlessly convert responses into Lxml and BeautifulSoup objects for more parsing
    • Easily convert full or specific sections of HTML to Markdown

Install

$ pip install stealth_requests

Table of Contents

Sending Requests

Stealth-Requests mimics the API of the requests package, allowing you to use it in nearly the same way.

You can send one-off requests like this:

import stealth_requests as requests

resp = requests.get('https://link-here.com')

Or you can use a StealthSession object which will keep track of certain headers for you between requests such as the Referer header.

from stealth_requests import StealthSession

with StealthSession() as session:
    resp = session.get('https://link-here.com')

Stealth-Requests has a built-in retry feature that automatically waits 2 seconds and retries the request if it fails due to certain status codes (like 429, 503, etc.).

To enable retries, just pass the number of retry attempts using the retry argument:

import stealth_requests as requests

resp = requests.get('https://link-here.com', retry=3)

Sending Requests With Asyncio

Stealth-Requests supports Asyncio in the same way as the requests package:

from stealth_requests import AsyncStealthSession

async with AsyncStealthSession() as session:
    resp = await session.get('https://link-here.com')

Accessing Page Metadata

The response returned from this package is a StealthResponse, which has all of the same methods and attributes as a standard requests response object, with a few added features. One of these extra features is automatic parsing of header metadata from HTML-based responses. The metadata can be accessed from the meta property, which gives you access to the following metadata:

  • title: str | None
  • author: str | None
  • description: str | None
  • thumbnail: str | None
  • canonical: str | None
  • twitter_handle: str | None
  • keywords: tuple[str] | None
  • robots: tuple[str] | None

Here's an example of how to get the title of a page:

import stealth_requests as requests

resp = requests.get('https://link-here.com')
print(resp.meta.title)

Extracting Emails, Phone Numbers, Images, and Links

The StealthResponse object includes some helpful properties for extracting common data:

import stealth_requests as requests

resp = requests.get('https://link-here.com')

print(resp.emails)
# Output: ('[email protected]', '[email protected]')

print(resp.phone_numbers)
# Output: ('+1 (800) 123-4567', '212-555-7890')

print(resp.images)
# Output: ('https://example.com/logo.png', 'https://cdn.example.com/banner.jpg')

print(resp.links)
# Output: ('https://example.com/about', 'https://example.com/contact')

Extracting HTML Tables

The StealthResponse object can parse HTML tables into dictionaries, where each key is a column header and the value is a list of that column's cell values.

For example, given a page with this table:

Name Age
Jacob 30
Jake 25

You can extract it like this:

import stealth_requests as requests

resp = requests.get('https://link-here.com')

# Each table becomes a dict: {column_name: [values]}
for table in resp.tables:
    print(table)
# Output: {'Name': ['Jacob', 'Jake'], 'Age': ['30', '25']}

Tables without recognizable headers are automatically skipped.

More Parsing Options

To make parsing HTML faster, I've also added two popular parsing packages to Stealth-Requests: Lxml and BeautifulSoup4. To use these add-ons, you need to install the parsers extra:

$ pip install 'stealth_requests[parsers]'

To easily get an Lxml tree, you can use resp.tree() and to get a BeautifulSoup object, use the resp.soup() method.

For simple parsing, I've also added the following convenience methods, from the Lxml package, right into the StealthResponse object:

  • text_content(): Get all text content in a response
  • xpath(): Go right to using XPath expressions instead of getting your own Lxml tree.

Converting Responses to Markdown

In some cases, it’s easier to work with a webpage in Markdown format rather than HTML. After making a GET request that returns HTML, you can use the resp.markdown() method to convert the response into a Markdown string, providing a simplified and readable version of the page content!

markdown() has two optional parameters:

  1. content_xpath An XPath expression, in the form of a string, which can be used to narrow down what text is converted to Markdown. This can be useful if you don't want the header and footer of a webpage to be turned into Markdown.
  2. ignore_links A boolean value that tells Html2Text whether to include links in the Markdown output.

Using Proxies

Stealth-Requests supports proxy usage through a proxies dictionary argument, similar to the standard requests package.

You can pass both HTTP and HTTPS proxy URLs when making a request:

import stealth_requests as requests

proxies = {
    "http": "http://username:password@proxyhost:port",
    "https": "http://username:password@proxyhost:port",
}

resp = requests.get('https://link-here.com', proxies=proxies)

Contributing

Contributions are welcome! Feel free to open issues or submit pull requests.

Before submitting a pull request, please format your code with Ruff: uvx ruff format stealth_requests/

↑ Back to top

Frequently asked questions

Is Stealth-Requests free to use?

Stealth-Requests is open source under the MIT licence. There is no licence fee and no seat count — you can self-host it or, where the project offers one, pay a vendor for a managed version instead.

What does Stealth-Requests do?

Undetected web-scraping & seamless HTML parsing in Python!

What is Stealth-Requests written in?

Stealth-Requests is primarily written in Python. Its source is publicly available at https://github.com/jpjacobpadilla/Stealth-Requests, and it has 563 GitHub stars.