PINGDOM_CHECK

#ExtractSummit2026 The world's largest web scraping conference returns. Austin Oct 7–8 · Dublin Nov 10–11.

Register now
Data Services
Pricing
Login
Try Zyte APIContact Sales
  • Unblocking and Extraction

    Zyte API

    The ultimate API for web scraping. Avoid website bans and access a headless browser or AI Parsing

    Ban Handling

    Headless Browser

    AI Extraction

    SERP

    Enterprise

    DocumentationSupport

    Hosting and Deployment

    Scrapy Cloud

    Run, monitor, and control your Scrapy spiders however you want to.

    Coding Agent Add-Ons

    Agentic Web Data

    Plugins that give coding agents the context to build production Scrapy projects. Starts with Claude Code.

  • Data Services
  • Pricing
  • Browse

    • BlogArticles, podcasts, videos
    • Case studiesCustomer outcomes
    • White papersIn-depth reports
    • DocumentationGuides & API reference
    • EventsConferences, webinars, recordings

    Subscribe

    • NewsletterSwiftly delivered
    • Join our community2,000+ web scraping engineers
  • Product and E-commerce

    From e-commerce and online marketplaces

    Data for AI

    Collect and structure web data to feed AI

    Job Posting

    From job boards and recruitment websites

    Real Estate

    From Listings portals and specialist websites

    News and Article

    From online publishers and news websites

    Search

    Search engine results page data (SERP)

    Social Media

    From social media platforms online

  • Meet Zyte

    Our story, people and values

    Contact us

    Get in touch

    Support

    Knowledge base and raise support tickets

    Terms and Policies

    Accept our terms and policies

    Open Source

    Our open source projects and contributions

    Web Data Compliance

    Guidelines and resources for compliant web data collection

    Join the team building the future of web data
    We're Hiring
    Trust Center
    Security, compliance & certifications
Login
Try Zyte APIContact Sales
All articles
AI71, 71 articles
Data quality15, 15 articles
Developer interest60, 60 articles
Integration2, 2 articles
Open-source50, 50 articles
Proxies35, 35 articles
Scraping practice35, 35 articles
Scraping strategy46, 46 articles
Search results4, 4 articles
Web data74, 74 articles
Web scraping APIs49, 49 articles
Scrapy47, 47 articles
Scrapy Cloud26, 26 articles
Web Scraping Copilot11, 11 articles
Zyte API65, 65 articles
AI & Machine Learning3, 3 articles
Automotive3, 3 articles
E-commerce & retail33, 33 articles
Entertainment & Streaming2, 2 articles
Financial Services8, 8 articles
Government2, 2 articles
Market Research & Intelligence7, 7 articles
Media & publishing11, 11 articles
Real Estate2, 2 articles
Recruitment & HR3, 3 articles
Transportation & Logistics2, 2 articles
Travel & hospitality3, 3 articles
iPaaS2, 2 articles
Large language model29, 29 articles
MCP3, 3 articles
Python110, 110 articles
Scraping at Scale7, 7 articles
Scraping Fundamentals11, 11 articles
Web Scraping Industry Report20, 20 articles

Appearance

Discord Community
BlogLearnStop using Python requests for web scraping: Use these modern modules instead
LearnDeveloper interestOpen-source

Stop using Python requests for web scraping: Use these modern modules instead

A

Ayan Pahwa

·

6 min read · March 11, 2026

It has been developers’ HTTP library of choice for years. But, when it comes to web data extraction, there are alternatives worth considering.

Stop using Python requests for web scraping: Use these modern modules instead

By Ayan Pahwa, Developer Advocate, Zyte

It has been developers' HTTP library of choice for years. But, when it comes to web data extraction, there are alternatives worth considering.

While the 'Requests' library remains the default choice for many Python developers due to its reliability and extensive documentation, the Python HTTP landscape has evolved considerably.

Modern alternatives now offer significant advantages, including built-in asynchronous support, HTTP/2 compatibility, enhanced performance, and up-to-date TLS handling.

This article introduces and compares three such contemporary clients: HTTPX, curl_cffi, and rnet, detailing their unique features and practical applications.

The problem with Requests for web scraping

It's important to clarify Requests' limitations before proceeding; for simple API interactions with well-behaved endpoints, it still remains the de facto standard.

However, a major drawback of the Requests library when it comes to web scraping is its predictable HTTP client fingerprint. This fingerprint, a unique combination of TLS version, cipher suites, HTTP headers, and connection characteristics, is sent with every request, and is well-known and cataloged by anti-bot systems.

Consequently, if you're interacting with any endpoint, including APIs or services protected by anti-ban vendors, your request can be blocked purely based on how the requests library identifies itself. This happens even before your credentials or payload are scrutinized, highlighting a significant limitation when targeting systems that perform client-side validation.

In addition to issues like fingerprinting, a major limitation of the requests library is its lack of native asynchronous support. This absence of async capability is particularly problematic when handling workloads that involve numerous HTTP requests. Without it, the calls execute sequentially, and the program's thread remains blocked for the entire duration of each individual request.

For straightforward scenarios, the standard requests API call remains perfectly functional, as demonstrated in a quick example.

Clean and simple. For a one-off call to a standard REST API, this is fine. The gaps start showing when you need concurrency, HTTP/2, or when the target endpoint does any kind of client validation.

Install the Alternatives

1. HTTPX

HTTPX is the most direct upgrade from Requests as the API is nearly identical. If you know Requests, you already know most of HTTPX. What it adds is first-class async support, HTTP/2, and a more modern internal architecture.

Where it differs from Requests is the explicit use of a Client context manager (strongly recommended over module-level function calls) and the AsyncClient for async usage. This gives you connection pooling and proper resource cleanup by default.

HTTPX is the right starting point if you're looking for a migration that requires minimal code changes.

Example: Sync

Example: Async (calling the Zyte API)

Async is where HTTPX really earns its keep. Here it's used to fire multiple requests to the Zyte API concurrently, each request blocks on the server side until extraction is complete, but your event loop stays free to send others in parallel:

Notes:

  • raise_for_status() raises httpx.HTTPStatusError on 4xx/5xx responses.
  • HTTP/2 support requires pip install httpx[http2] and passing http2=True to the client.
  • The 60-second timeout accounts for the Zyte API's server-side blocking behavior — it holds the connection open until extraction completes.

2. curl_cffi

curl_cffi wraps libcurl with Python bindings and adds something HTTPX doesn't have: TLS fingerprint impersonation. It can show the TLS handshake of Chrome, Firefox, Safari, and other browsers. For API calls hitting endpoints protected by anti-ban or similar systems, this can be the difference between getting a response and getting a 403.

The interface closely mirrors Requests, with the addition of the impersonate parameter. It supports both sync and async usage. For most API calls where fingerprinting isn't a concern, curl_cffi behaves just like Requests, the impersonate parameter is opt-in.

Example: Sync

Example: Async (calling the Zyte API)

Notes:

  • impersonate="chrome" sends Chrome's TLS fingerprint on every request made through this session.
  • Other supported values include "firefox", "safari", "chrome110", and more — check the curl-cffi docs for the full list.
  • The sync interface (from curl_cffi import requests) is nearly identical to the requests module, making it the easiest drop-in if you only need sync.

3. rnet

rnet is the newest of the three. Like a lot of modern Python, it's built on Rust, making it async-first and performance-oriented. Like curl_cffi, it supports TLS impersonation, but its primary differentiator is throughput. It is designed for high-concurrency workloads where you're firing many requests simultaneously.

The API surface is different from Requests, so it's not a drop-in replacement. But the patterns are clean and modern, and for async-heavy workloads it's worth the minor adjustment.

Example: Sample library code

Notes:

  • rnet is async-first; sync support is limited.
  • Response body methods like .json() and .text() are awaitable.
  • The Rust core makes it particularly well-suited for high-throughput concurrent workloads.

Comparison Table

Feature Requests HTTPX curl_cffi rnet
Sync Support ✅ Yes ✅ Yes ✅ Yes ⚠️ Limited
Async support ❌ No ✅ Yes ✅ Yes ✅ Yes (primary)
HTTP/2 ❌ No ✅ With extra dependencies ✅ Via libcurl ✅ Built-in
Performance Baseline Good Good–High High
TLS changes ❌ No ❌ No ✅ Yes ✅ Yes

When to use which

Use Requests for simple, one-off scripts, internal tooling, or any situation where you're hitting a cooperative API endpoint and don't need concurrency. Nothing wrong with it in that context.

Use HTTPX when you need async, want the closest migration path from Requests, or need HTTP/2. It's the safest default upgrade for most projects.

Use curl_cffi when TLS fingerprint control matters, whether that's because you're hitting an anti-ban wall or an API with strict client validation, or any service that checks how a client identifies itself at the TLS layer.

Use rnet when raw async performance is the priority. Its Rust foundation makes it the strongest choice for high-concurrency workloads where you're firing many requests simultaneously and need low overhead.

The optimal choice is determined by several factors: your concurrency requirements, the target endpoint's sensitivity to client identification, and the desired similarity between the new code and your existing requests implementation.

In this article

  • Stop using Python requests for web scraping: Use these modern modules instead
  • The problem with Requests for web scraping
  • Install the Alternatives
  • 1. HTTPX
  • Example: Sync
  • Example: Async (calling the Zyte API)
  • 2. curl_cffi
  • Example: Sync
  • Example: Async (calling the Zyte API)
  • 3. rnet
  • Example: Sample library code
  • Comparison Table
  • When to use which

Other lessons

Learn Scrapy

  • Scrapy Tutorial Part 1: First Spider
  • Scrapy Tutorial Part 2: Page Objects
  • Scrapy Tutorial Part 3: Web Scraping CoPilot

What is web scraping?

  • What Is Web Scraping?
  • What are the elements of a web scraping project?
  • Python Web Scaping Tools & Libraries
  • How to architect a web scraping solution: The step-by-step guide
  • Web crawling vs web scraping
  • Is Web & Data Scraping Legally Allowed?
  • Compliant Web Scraping Checklist
  • Best practices for web scraping
  • A Guide to Web Scraping With Java
  • Transition from Zenrows to Zyte API
  • Guide to Web Scraping APIs
  • Screen Scraping Explained
  • Large Scale Web Scraping with Python
  • Large Scale Web Scraping with Python
  • Building a Web Crawler in Python
  • A Practical Guide to XML Parsing with Python
  • Learn How to Scrape a Website
  • Advanced Use Cases for Session Management
  • Golang Web Scraping in 2025
  • Web Scraping Dynamic Websites With Zyte API
  • What is Data Parsing in Web Scraping?
  • Scrape Web Pages and Files Using Python, wget, and Zyte

Web Scraping How-to Videos

  • Web scraping videos

SERP Data Collection at Scale

  • SERP data collection at scale and why efficiency matters
  • Why Page One SERP data is no longer enough
  • Why pagination logic becomes operational debt
  • Why SERP data costs exploded

What is web scraping used for?

  • What is web scraping used for?
  • Pricing Intelligence Web Scraping
  • Web Scraping For Market Research
  • Use web scraping to build a data-driven product
  • Use web scraping for alternative data for finance
  • Use web scraping for brand monitoring
  • Use web scraping to automate MAP compliance
  • Web Scraping For Lead Generation
  • Web Scraping For Recruitment
  • Use web scraping for business automation
  • Using Data Extraction Tools for Efficient Website Scraping
  • Why Might a Business Use Web Scraping to Collect Data?
  • How to Scrape Images from Any Website: A Complete Guide
  • How to Scrape Search Engine Results

The New Guide to Web Scraping at Scale

  • Introduction
  • 1. A plan is a pathway to success
  • 2. Get serious about legal compliance
  • 3. The quality of your web data is of utmost importance
  • 4. Scaling and maintaining crawling and extracting solutions
  • 5. Adding AI to the web scraping stack
  • 6. The In-house vs outsourced question
  • 7. Questions to ask when scaling web scraping

Essential Web Scraping Techniques

  • TLS Fingerprint and how it blocks requests
  • How to scrape with a browser effectively
  • API First data extraction

More learn articles

Keep learning

All learn articles →
What are residential proxies bannerUse case

What is a residential proxy?

Learn what residential proxies are, how they compare to datacenter proxies, and why modern web scraping needs more than IP diversity.

10 min read

Zyte Case Studies — every customer story, in one placeUse case

How much do rotating proxies cost?

Learn how much rotating proxies cost, what affects pricing, and why total web scraping costs often go beyond proxy subscriptions.

10 min read

Zyte Case Studies — every customer story, in one placeUse case

How do rotating proxies work?

Learn how rotating proxies work, when to use them for web scraping, and why IP rotation alone is not enough for reliable data access.

10 min read

Services

Zyte Data

Coding tools & hacks straight to your inbox. Bi-weekly dosage of all things code.

Explore Zyte Data

Web Scraping API

Zyte API

Coding tools & hacks straight to your inbox. Bi-weekly dosage of all things code.

Sign Up

Developers

Zyte Developers

Coding tools & hacks straight to your inbox. Bi-weekly dosage of all things code.

Join Us
    • Zyte API
    • Ban Handling
    • AI Extraction
    • SERP
    • Enterprise
    • Scrapy Cloud
    • Agentic Web Data
    • Pricing
1`import requests
2
3response = requests.get(
4    "https://jsonplaceholder.typicode.com/posts/1",
5    timeout=10,
6)
7response.raise_for_status()
8data = response.json()
9print(data["title"])`
Copy
1`pip install httpx          or    uv add httpx
2pip install curl-cffi      or    uv add curl-cffi
3pip install rnet           or    uv add rnet && uv add asyncio`
Copy
1`import httpx
2
3with httpx.Client(timeout=10.0) as client:
4    response = client.get("https://jsonplaceholder.typicode.com/posts/1")
5    response.raise_for_status()
6    data = response.json()
7
8print(data["title"])`
Copy
1`import os
2import asyncio
3import httpx
4
5API_KEY = os.environ["ZYTE_API_KEY"]
6ENDPOINT = "https://api.zyte.com/v1/extract"
7
8urls = [
9    "https://example.com",
10    "https://httpbin.org",
11]
12
13async def fetch(client: httpx.AsyncClient, url: str) -> dict:
14    response = await client.post(
15        ENDPOINT,
16        json={"url": url, "browserHtml": True},
17        auth=(API_KEY, ""),
18    )
19    response.raise_for_status()
20    return response.json()
21
22async def main():
23    async with httpx.AsyncClient(timeout=60.0) as client:
24        results = await asyncio.gather(*[fetch(client, url) for url in urls])
25    for result in results:
26        print(result["url"], "—", len(result["browserHtml"]), "chars")
27
28asyncio.run(main())`
Copy
1`from curl_cffi import requests
2
3response = requests.get(
4    "https://jsonplaceholder.typicode.com/posts/1",
5    impersonate="chrome",
6    timeout=10,
7)
8response.raise_for_status()
9data = response.json()
10print(data["title"])`
Copy
1`import os
2import asyncio
3from curl_cffi.requests import AsyncSession
4
5API_KEY = os.environ["ZYTE_API_KEY"]
6ENDPOINT = "https://api.zyte.com/v1/extract"
7
8payload = {
9    "url": "https://example.com",
10    "browserHtml": True,
11}
12
13async def call_zyte_api():
14    async with AsyncSession(impersonate="chrome") as session:
15        response = await session.post(
16            ENDPOINT,
17            json=payload,
18            auth=(API_KEY, ""),
19            timeout=60,
20        )
21        response.raise_for_status()
22        data = response.json()
23        print(data["url"], "—", len(data["browserHtml"]), "chars")
24
25asyncio.run(call_zyte_api())`
Copy
1`import asyncio
2from rnet import Impersonate, Client
3
4
5async def main():
6    # Build a client
7    client = Client(impersonate=Impersonate.Firefox139)
8
9    # Use the API you're already familiar with
10    resp = await client.get("https://tls.peet.ws/api/all")
11    
12    # Print the response
13    print(await resp.text())
14
15
16if __name__ == "__main__":
17    asyncio.run(main())`
Copy
    • Product & E-commerce
    • Data for AI
    • Job Posting
    • Real Estate
    • News & Articles
    • Search
    • Social Media
    • Blog
    • Learn
    • Case Studies
    • Webinars
    • White Papers
    • Join our community
    • Documentation
    • Meet Zyte
    • Contact us
    • Jobs
    • Support
    • Terms and Policies
    • Trust Center
    • Do not sell
    • Cookie settings
    • Web Data Compliance
    • Open Source
    • What is Web Scraping
    • Web Scraping in Python: Ultimate Guide
    • Stop getting blocked, start scraping
  • EWDCI logoMost loved workplace certificateZyte rewardISO 27001 iconG2 rewardG2 rewardG2 reward

    © Zyte Group Limited 2026
    XFacebookInstagramYouTubeLinkedInDiscord