Tutorial12 min read

Python Libraries for Web Scraping

Short answer

The Python libraries for web scraping fall into four groups: HTTP clients that fetch pages (Requests, httpx, aiohttp), parsers that extract data from HTML (Beautiful Soup, lxml, parsel, selectolax), frameworks that run whole crawls (Scrapy, Crawlee), and browser tools for JavaScript pages (Playwright, Selenium). Beginners should start with Requests + Beautiful Soup, add Playwright for pages built by JavaScript, and move to Scrapy at thousands of pages. On speed, in our benchmark of 2026-10-27, selectolax parsed a 55.7 KB page and extracted 100 fields in 0.64 ms, lxml in 0.80 ms and Beautiful Soup in 20 to 28 ms; for fetching, Requests and httpx tie when you reuse a Session or Client, and async clients fetched 200 pages at 100 ms latency in 0.24 to 0.63 s instead of 20 s. Every fetching library takes a proxy in one setting: ProxyField residential proxies cost from $1.95/GB, and you can start with the Easy Trial: 1 GB for $2.95.

residential IPs
412M+
from, traffic never expires
$1.95/GB
Easy Trial
1 GB for $2.95
On this page 10 sections
  1. Python web scraping libraries compared
  2. Requests vs httpx, lxml vs selectolax: which is fastest?
  3. How to choose: four questions
  4. HTTP clients: Requests, httpx, aiohttp, curl_cffi
  5. Parsers: Beautiful Soup, lxml, parsel, selectolax
  6. Frameworks: Scrapy and Crawlee for Python
  7. Browser tools: Playwright, Selenium, SeleniumBase
  8. Recommended stacks
  9. Proxies for Python scrapers
  10. Python Libraries for Web Scraping FAQ

Start here. pip install requests beautifulsoup4, then:

Python
import requests
from bs4 import BeautifulSoup
soup = BeautifulSoup(requests.get("https://quotes.toscrape.com/", timeout=30).text, "html.parser")
print(soup.title.get_text())              # Quotes to Scrape

Add Playwright when the data appears only after JavaScript runs, and Scrapy when you crawl thousands of pages.

Python web scraping libraries compared

Versions, licenses and release dates from PyPI. Each name links to the library's official documentation.

LibraryGroupVersion (release)LicenseJavaScriptProxy settingUse it for
RequestsHTTP client2.34.2 (May 2026)Apache-2.0Noproxies={"http": ..., "https": ...}Simple fetching, first scripts
httpxHTTP client, sync and async0.28.1 (Dec 2024)BSD-3-ClauseNoproxy="http://..."Async fetching, HTTP/2
aiohttpAsync HTTP client3.14.5 (Oct 2026)Apache-2.0 AND MITNoproxy= per requestHigh-concurrency async fetching
curl_cffiHTTP client on libcurl0.17.0 (Oct 2026), betaMITNoproxies= like RequestsRequests that should look like a browser's at the TLS level
Beautiful SoupParser4.15.0 (Jun 2026)MITNo(parses only)Forgiving parsing, easy API
lxmlParser6.1.3 (Sep 2026)BSD-3-ClauseNo(parses only)Fast parsing with XPath and CSS
parselParser (Scrapy's selectors)1.12.1 (Sep 2026)BSD-3-ClauseNo(parses only)CSS and XPath in one API
selectolaxParser1.0.2 (Oct 2026)MITNo(parses only)The fastest CSS-selector parsing in our test
MechanicalSoupBrowser-like client1.4.0 (May 2025)MITNoThrough its Requests sessionForms and simple logins, no JavaScript
ScrapyFramework2.19.0 (Sep 2026)BSD-3-ClauseVia a pluginmeta={"proxy": ...}Large, recurring crawls
Crawlee for PythonFramework1.11.0 (Oct 2026)Apache-2.0Yes (Playwright crawler)ProxyConfigurationCrawls that mix HTTP and browser pages
PlaywrightBrowser automation1.64.1 (Oct 2026)Apache-2.0Yesproxy={"server", "username", "password"}JavaScript-heavy sites
SeleniumBrowser automation4.51.0 (Oct 2026)Apache-2.0YesBrowser flag or proxy capabilityTeams that already use Selenium
SeleniumBaseFramework on Selenium4.56.4 (Oct 2026)MITYesproxy="user:pass@host:port"Selenium with helpers built in
ScraplingFetching and parsing toolkit0.5 (Oct 2026), betaBSD-3-ClauseOptional browser fetchersPer fetcherAdaptive selectors, one package

All of these are open source, so the library costs nothing; at scale you pay for servers and proxy traffic.

Requests vs httpx, lxml vs selectolax: which is fastest?

Fetching: Requests and httpx are equally fast one page at a time, as long as you reuse one requests.Session or httpx.Client; the network decides the time, not the library. For many pages, go async: httpx's AsyncClient or aiohttp fetched 200 pages at 100 ms latency in 0.63 s and 0.24 s, against 20.3 s one at a time. Parsing: selectolax is the fastest, lxml is close behind, and Beautiful Soup is 25 to 45 times slower than either; that matters only from hundreds of thousands of pages.

We measured both on 2026-10-27, on a server with an AMD Ryzen 9 5950X (8 vCPUs), Linux and Python 3.12.3, with the library versions in the table above. The fetch tests called a local test server in a separate process that returned a 16 KB page, so they measure the client, not the internet; the 100 ms delay stands in for a real site's latency. Each figure is the median of 3 runs.

Fetching: seconds for the whole batch (lower is better)

Client, as called1,000 requests, no latency200 requests, 100 ms latency
aiohttp ClientSession, all requests at once (asyncio.gather)0.15 s0.24 s
httpx AsyncClient, all requests at once21.1 s (1.4 s with at most 100 in flight)0.63 s
Requests Session shared by 20 threads0.69 s1.03 s
httpx Client, one by one0.43 s20.3 s
Requests Session, one by one0.53 s20.3 s
requests.get() in a loop0.85 s20.4 s
httpx.get() in a loop15.7 s23.5 s

Parsing: milliseconds per page for a 55.7 KB product listing: build the tree, then extract 100 product names (lower is better; median of 3 runs of 1,000 pages)

ParserBuild the treeQuery 100 namesTotal per page1M pages of CPU time
selectolax (Lexbor), CSS0.60 ms0.04 ms0.64 msabout 11 min
lxml.html, XPath0.69 ms0.11 ms0.80 msabout 13 min
parsel, CSS0.70 ms0.25 ms0.95 msabout 16 min
lxml.html, CSS (cssselect)0.69 ms0.38 ms1.07 msabout 18 min
Beautiful Soup with the lxml parser17.0 ms3.1 ms20.1 msabout 5.6 h
Beautiful Soup with html.parser25.2 ms3.1 ms28.3 msabout 7.9 h

What the numbers say:

  • Reuse one client. httpx.get() in a loop was 37 times slower than one httpx.Client (15.7 s against 0.43 s per 1,000), because each call builds a new client and its SSL context. requests.get() costs less, but a Session still keeps connections open.
  • Set a timeout everywhere. Requests has no default timeout and can wait forever on a stalled server; httpx's default is 5 seconds; aiohttp's is 5 minutes in total. Pass timeout= on every call or client.
  • Cap the requests in flight. httpx's pool allows 100 connections and keeps 20 alive by default (aiohttp: 100 connections). Queuing all 1,000 requests on httpx at once took 21.1 s; the same 1,000 with an asyncio.Semaphore(100) took 1.4 s. On a real site, cap it far lower, at the pace the site allows.
  • Beautiful Soup's parser choice matters less than people think. Switching it to lxml saved about 30%; the tree that Beautiful Soup builds on top costs the rest. If parsing is your bottleneck, change the library, not the backend.
  • Query style matters. lxml's XPath query was 3.5 times faster than its CSS query on the same tree. With one simple query, lxml and selectolax come out nearly even.
  • The network still wins. At 100 ms per page, a one-by-one scraper spends 100 ms waiting for each page and under 1 ms parsing it with lxml or selectolax.

Measure on a sample of your own pages before optimizing; this script reproduces the parse test on any saved page:

Python
# pip install beautifulsoup4 lxml parsel selectolax
import time
import lxml.html
from bs4 import BeautifulSoup
from parsel import Selector
from selectolax.lexbor import LexborHTMLParser

html = open("page.html", encoding="utf-8").read()   # any saved page

def bench(name, parse, n=1000):
    t = time.perf_counter()
    for _ in range(n):
        parse(html)
    print(f"{name:30} {(time.perf_counter() - t) / n * 1000:.2f} ms per page")

bench("Beautiful Soup + html.parser", lambda h: [a.get_text() for a in BeautifulSoup(h, "html.parser").select("h3 a")])
bench("Beautiful Soup + lxml", lambda h: [a.get_text() for a in BeautifulSoup(h, "lxml").select("h3 a")])
bench("lxml + XPath", lambda h: lxml.html.fromstring(h).xpath("//h3/a/text()"))
bench("parsel", lambda h: Selector(text=h).css("h3 a::text").getall())
bench("selectolax (Lexbor)", lambda h: [a.text() for a in LexborHTMLParser(h).css("h3 a")])

More ways to speed a scraper up, from concurrency to caching: how to make web scraping faster.

How to choose: four questions

  1. Is the data in the HTML?

    Open the page source (Ctrl+U). If yes, an HTTP client plus a parser is enough. If the data appears only after scripts run, check the network tab for a JSON request you can call directly; only if there is none, use Playwright or Selenium.

  2. How many pages?

    Dozens to a few thousand: a script with Requests or httpx. Tens of thousands or a daily crawl: Scrapy or Crawlee, which bring queues, retries and exports.

  3. Sync or async?

    Requests is synchronous and simplest. httpx and aiohttp fetch many pages at once from one process; Scrapy is asynchronous inside.

  4. How fast must parsing be?

    For most jobs the network is the bottleneck, not the parser (see the tables above). From hundreds of thousands of pages, use selectolax or lxml instead of Beautiful Soup.

HTTP clients: Requests, httpx, aiohttp, curl_cffi

Requests is the default way to fetch a page in Python: one call, readable code, sessions that keep cookies. It is synchronous, so one script fetches one page at a time unless you add threads.

Python
# pip install requests beautifulsoup4
import requests
from bs4 import BeautifulSoup

proxy = "http://<login>-country-us-city-new_york:<password>@gate.proxyfield.io:7000"
with requests.Session() as s:
    s.proxies = {"http": proxy, "https": proxy}
    r = s.get("https://quotes.toscrape.com/", timeout=30)
    soup = BeautifulSoup(r.text, "html.parser")
    print([q.get_text() for q in soup.select("span.text")][:3])

httpx has a Requests-like API plus an async client and optional HTTP/2, which makes it the usual upgrade when a script must fetch hundreds of pages concurrently. aiohttp is the older async client with the same purpose, and the fastest in our fetch test. curl_cffi wraps libcurl and can make its TLS handshake match a chosen browser's; some sites treat Python's default TLS handshake differently from a browser's, which is when people reach for it. It is still marked beta. Through ProxyField, the Acceptable Use Policy applies whatever client you use: public pages only, and follow the site's terms; fraud, attacks and access to data behind logins you are not entitled to are not allowed.

Python
# pip install httpx  ->  async fetching of many pages, at most 10 in flight
import asyncio, httpx

PROXY = "http://<login>-country-us-city-new_york:<password>@gate.proxyfield.io:7000"

async def main(urls):
    sem = asyncio.Semaphore(10)
    async with httpx.AsyncClient(proxy=PROXY, timeout=30) as client:
        async def fetch(u):
            async with sem:
                return await client.get(u)
        pages = await asyncio.gather(*(fetch(u) for u in urls))
        print([p.status_code for p in pages])

asyncio.run(main([f"https://quotes.toscrape.com/page/{n}/" for n in range(1, 6)]))

Proxy setup for each client, with SOCKS5 and rotation: Python proxy setup. Async patterns in depth: async web scraping in Python.

Parsers: Beautiful Soup, lxml, parsel, selectolax

A parser turns the HTML string into a tree you can query.

  • Beautiful Soup is the most forgiving and the easiest to read: soup.select("div.price"), tag.get_text(), tag["href"]. It builds its own tree on top of a parser; passing "lxml" instead of "html.parser" saved about 30% in our test. Tutorial: Beautiful Soup web scraping.
  • lxml is a fast C-based parser with full XPath 1.0 and CSS selectors through the cssselect package.
  • parsel is the selector library inside Scrapy: one object with .css() and .xpath(), usable without Scrapy.
  • selectolax is a Cython parser on the Lexbor engine, built for speed with CSS selectors. Since version 1.0, import LexborHTMLParser from selectolax.lexbor: the old from selectolax.parser import HTMLParser (the Modest engine) now raises an ImportError that says the backend is deprecated.

Which selectors to write, CSS or XPath, and how to extract links and article text: HTML scraping with CSS selectors and XPath.

Frameworks: Scrapy and Crawlee for Python

When a script turns into a crawler, with link following, retries, deduplication, pacing and exports, a framework saves you from writing that plumbing.

  • Scrapy has been the standard for more than a decade: asynchronous downloads, spiders, item pipelines, middlewares and feed exports. It does not render JavaScript by itself. See what Scrapy is and the Scrapy tutorial.
  • Crawlee for Python combines HTTP crawlers (with Beautiful Soup or parsel) and a Playwright crawler behind one API, with request queues, sessions and proxy rotation.

Browser tools: Playwright, Selenium, SeleniumBase

Use a browser only for pages that need one: it costs far more CPU, memory and traffic per page than an HTTP request.

  • Playwright for Python is the one to start a new project with: it installs its own Chromium, Firefox and WebKit builds (playwright install), has sync and async APIs, and its locators wait for elements by themselves. Tutorial: Playwright web scraping.
  • Selenium drives browsers through WebDriver and is everywhere in QA teams; choose it if your team already has Selenium code. Selenium Manager downloads drivers by itself since 4.6. Tutorial: web scraping with Selenium and Python.
  • SeleniumBase adds waits, helpers and a test runner on top of Selenium.

Pyppeteer, a Python port of Puppeteer, is unmaintained: its README says "This repo is unmaintained" and asks users to "consider playwright-python as an alternative" (github.com/pyppeteer/pyppeteer). Scraping dynamic pages step by step: Python dynamic web scraping.

Proxies for Python scrapers

Every library above sends requests from your server's IP unless you set a proxy, and sites limit how many requests one IP may send. With a rotating gateway, one proxy URL in the library's setting gives each request a new IP; <login>-country-us-city-new_york in the username asks for exit IPs in New York. Region, session and session time go in the same username: -region-<region>, -session-<id> and -ttl-<seconds> (30 to 129,600 seconds, 20 minutes by default), as in <login>-country-us-region-new_york-city-new_york-session-he9f2qga-ttl-600; every part after the login is optional.

ProxyField residential proxies give you a new IP per request or a sticky session from 30 s to 36 h, over HTTP, HTTPS or SOCKS5 on gate.proxyfield.io:7000, with unlimited concurrent threads and country, region and city targeting. They cost $2.95/GB for 1 GB and fall to $1.95/GB from 1,000 GB, with no subscription; traffic never expires, and all purchases are final. HTML fetched by an HTTP client is small, so a GB covers many pages; browser pages cost more. Start with residential proxies and the Easy Trial: 1 GB for $2.95.

Python Libraries for Web Scraping FAQ

What are the best Python libraries for web scraping, and which ones should a beginner start with?

ProxyField residential proxies, from $1.95/GB, plug into every fetching library here; the libraries themselves are Requests and httpx for fetching, Beautiful Soup, lxml and selectolax for parsing, Scrapy for large crawls and Playwright for JavaScript pages. A beginner should start with Requests + Beautiful Soup (pip install requests beautifulsoup4), add Playwright when the data appears only after JavaScript runs, and move to Scrapy at thousands of pages.

Which Python scraping libraries are fastest: Requests or httpx for fetching, and lxml or selectolax for parsing?

For fetching one page at a time, Requests and httpx tie when you reuse a Session or Client (0.53 s and 0.43 s per 1,000 local requests in our test, 2026-10-27); for many pages, async wins: httpx's AsyncClient fetched 200 pages at 100 ms latency in 0.63 s and aiohttp in 0.24 s, against 20.3 s one by one. For parsing, selectolax was fastest (0.64 ms per 55.7 KB page), lxml close (0.80 ms with XPath) and Beautiful Soup 20 to 28 ms. Never call httpx.get() in a loop: it was 37 times slower than one Client.

What is a Python web scraping library?

A package that does one part of scraping: an HTTP client that fetches pages, a parser that extracts data from HTML, a framework that runs whole crawls, or a browser tool that renders JavaScript. A typical scraper combines one client with one parser, and each fetching library takes a proxy in one setting.

Why use Python libraries for web scraping?

They handle the hard parts for you: HTTP details, broken HTML, selectors, concurrency, retries and exports. Python also has the widest choice of scraping libraries and data tools, so the scraped rows go straight into pandas, a database or a notebook.

What are the best Scrapy alternatives?

ProxyField proxies plug into each of them through one proxy URL; the usual alternatives are Crawlee for Python, if you want HTTP and Playwright crawlers behind one API, or a plain httpx plus parsel script for smaller jobs. For pages that need a browser, Playwright with your own queue is the usual choice.

Which Python library is used for web scraping?

Usually two together: Requests or httpx to fetch the page, and Beautiful Soup or lxml to parse it. Scrapy is the usual choice for large, recurring crawls, and Playwright or Selenium for pages that build their content with JavaScript.

What is the Python Requests library?

Requests is the standard HTTP client for simple Python scripts: one call such as requests.get(url) fetches a page, and a session keeps cookies, headers and open connections between requests. It is synchronous, has no default timeout, and neither parses HTML nor runs JavaScript, so scrapers pair it with a parser such as Beautiful Soup. A proxy goes into its proxies argument.

What is Scrapling?

A BSD-licensed Python toolkit, version 0.5 and still marked beta, that combines HTTP and browser fetchers with a parser whose selectors can find elements again after a layout change. It is younger than Scrapy or Beautiful Soup, so test it before you build on it.

Should I use Requests or httpx for scraping?

Requests for simple synchronous scripts, httpx when you need async fetching or HTTP/2 with a nearly identical API; one by one, they were equally fast in our test. Both take a proxy URL in one argument, so switching later is a small change.

What is lxml used for in web scraping?

Parsing HTML and XML fast and querying it with XPath or CSS selectors: in our test it built a 55.7 KB page's tree in 0.69 ms, against 17 to 25 ms for Beautiful Soup. Many tools use it underneath, including Beautiful Soup when you pass the "lxml" parser and Scrapy's selectors through parsel.

Show 1 more questionShow fewer questions

What is curl_cffi, and when do I need it?

A Python HTTP client built on libcurl (0.17.0, October 2026, still beta) that can make its TLS handshake look like a chosen browser's. It helps when a site answers a browser but treats Python's default client differently.

Easy Trial: 1 GB for $2.95.

Try for the price of 1 GB, no subscription, traffic never expires.