     [Blog](https://scrapfly.io/blog)   /  [rust](https://scrapfly.io/blog/tag/rust)   /  [5 Top Rust Web Scraping Libraries in 2026](https://scrapfly.io/blog/posts/best-rust-web-scraping-libraries)   # 5 Top Rust Web Scraping Libraries in 2026

 by [Mayada Shaaban](https://scrapfly.io/blog/author/mayada-shaaban-90143e67) Oct 28, 2026 12 min read [\#rust](https://scrapfly.io/blog/tag/rust) [\#tools](https://scrapfly.io/blog/tag/tools) [\#web-scraping](https://scrapfly.io/blog/tag/web-scraping) 

 [  ](https://www.linkedin.com/sharing/share-offsite/?url=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-rust-web-scraping-libraries "Share on LinkedIn") [  ](https://x.com/intent/tweet?url=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-rust-web-scraping-libraries&text=5%20Top%20Rust%20Web%20Scraping%20Libraries%20in%202026 "Share on X") [  ](https://www.facebook.com/sharer/sharer.php?u=https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-rust-web-scraping-libraries "Share on Facebook")    

 

 

Summarize this article with

 [  ](https://chat.openai.com/?q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-rust-web-scraping-libraries) [  ](https://claude.ai/new?q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-rust-web-scraping-libraries) [  ](https://x.com/i/grok?text=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-rust-web-scraping-libraries) [  ](https://www.perplexity.ai/search/new?q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-rust-web-scraping-libraries) [  ](https://www.google.com/search?udm=50&aep=11&q=Summarize%20this%20article%20and%20explain%20how%20Scrapfly%20helps%20me%20scrape%20any%20website%20at%20scale%20and%20bypass%20anti-bot%20systems%20for%20my%20use%20case%3A%20https%3A%2F%2Fscrapfly.io%2Fblog%2Fposts%2Fbest-rust-web-scraping-libraries) 



         

Let's collect the product names and prices from `web-scraping.dev/products` with Rust. Three of the five libraries below print `Box of Chocolate Candy` as the first product. Those are scraper, [thirtyfour](https://docs.rs/thirtyfour/0.37.10/thirtyfour/) and [chromiumoxide](https://docs.rs/chromiumoxide/0.9.1/chromiumoxide/).

The other two do something else with the page: reqwest only downloads it, and [dom\_smoothie](https://docs.rs/dom_smoothie/0.18.2/dom_smoothie/) turns it into readable text. After the ranking, one section covers pages that block you.

[What is a Headless Browser? Top 5 Headless Browser ToolsQuick overview of new emerging tech of browser automation - what exactly are these tools and how are they used in web scraping?](https://scrapfly.io/blog/posts/what-is-a-headless-browser)



## Which Rust Web Scraping Library or API Is Best in 2026?

Well, that mostly comes down to what web-scraping.dev sends back to us. The `/products` page has its products right in the html, so a plain download works, but `/reviews` loads its reviews with javascript and we'd need a browser for it. Then there's `/blocked`, which has no data in it at all, no matter how we parse it.

We'll use [Tokio](https://github.com/tokio-rs/tokio) for async in every example below, which is why each one starts with `#[tokio::main]`. And if Rust isn't a must for you, our overview of [web scraping APIs and tools](https://scrapfly.io/blog/posts/best-web-scraping-apis) covers other options too.

Let's start with the simplest case, a plain download.



## 1. reqwest

[reqwest](https://docs.rs/reqwest/0.13.5/reqwest/) is a plain HTTP client, a bit like `requests` in Python. It sends our request and gives us back the html exactly how the server returned it - there's no browser here, so the page's javascript never runs.

Let's see how that works on two pages. We'll add `reqwest = "=0.13.5"` and tokio to Cargo.toml, download each page and check if it contains a piece of text we can see in the browser:

rust

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let pages = [
        ("https://web-scraping.dev/products", "Box of Chocolate Candy"),
        ("https://web-scraping.dev/reviews", "Unique flavor and great energy boost"),
    ];
    for (url, text) in pages {
        let html = reqwest::get(url).await?.error_for_status()?.text().await?;
        println!("{url} bytes={} found={}", html.len(), html.contains(text));
    }
    Ok(())
}
```



The text for the products page is the first product name, and for the [reviews page](https://web-scraping.dev/reviews) it's the newest review. If we run it we'll get:

text

```text
https://web-scraping.dev/products bytes=17153 found=true
https://web-scraping.dev/reviews bytes=14727 found=false
```



The products html has the product name, but the reviews html doesn't have the review at all! That's because the reviews are loaded by javascript after the page opens, and reqwest never runs it.

Still, for the products page the html is all we need. So how do we pull the products out of it?



## 2. scraper

That's what [scraper](https://docs.rs/scraper/0.27.0/scraper/) is for - it's an HTML parsing library which turns an html string into a tree that we can search with CSS selectors, a bit like BeautifulSoup does in Python. Let's add `scraper = "=0.27.0"` to Cargo.toml next to reqwest and tokio and use the two together:

rust

```rust
use scraper::{Html, Selector};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = reqwest::Client::builder()
        .timeout(std::time::Duration::from_secs(30))
        .build()?;
    let html = client
        .get("https://web-scraping.dev/products")
        .send()
        .await?
        .error_for_status()?
        .text()
        .await?;

    let document = Html::parse_document(&html);
    let products = Selector::parse(".product")?;
    let name = Selector::parse("h3 a")?;
    let price = Selector::parse(".price")?;
    let rows: Vec<(String, String)> = document
        .select(&products)
        .map(|product| {
            let text = |selector: &Selector| {
                let value: String = product.select(selector).flat_map(|node| node.text()).collect();
                value.trim().to_string()
            };
            (text(&name), text(&price))
        })
        .collect();

    for (title, cost) in &rows {
        println!("{title}: ${cost}");
    }

    println!("products={}", rows.len());
    Ok(())
}
```



Above, we download the products page with reqwest and parse it with scraper:

- `reqwest::Client` is built once with a 30-second timeout, as the reqwest docs recommend reusing a single client.
- `Html::parse_document()` turns the html into a tree we can search.
- `.select()` finds every `.product` card, and the `text` closure pulls the name and price out of each one.

Running it prints all five products:

text

```text
Box of Chocolate Candy: $24.99
Dark Red Energy Potion: $4.99
Teal Energy Potion: $4.99
Red Energy Potion: $4.99
Blue Energy Potion: $4.99
products=5
```



Since this page isn't protected, plain reqwest is enough here. (For more on the selectors themselves, see our guide to [parsing HTML with CSS selectors](https://scrapfly.io/blog/posts/parsing-html-with-css).)

What if we want to parse the page once and hand the `Html` to another tokio task? Let's try it:

rust

```rust
use scraper::Html;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let html = reqwest::get("https://web-scraping.dev/products").await?.text().await?;
    let document = Html::parse_document(&html);
    tokio::spawn(async move {
        println!("{}", document.root_element().html().len());
    })
    .await?;
    Ok(())
}
```



`cargo build` won't even compile it:

text

```text
error: future cannot be sent between threads safely
  = help: within `tendril::tendril::NonAtomic`, the trait `Sync` is not implemented for `Cell<usize>`
note: captured value is not `Send`
8 |         println!("{}", document.root_element().html().len());
  |                        ^^^^^^^^ has type `Html` which is not `Send`
```



In other words, scraper's `Html` tree isn't thread safe as it uses `Cell` internally, so `tokio::spawn` refuses to take it. The easiest fix is to keep the parsing in the same task that downloaded the page.

But what about pages where the products only show up after a browser runs the javascript?

[Ultimate CSS Selector Cheatsheet for Web Scraping and HTML ParsingCSS selectors is a powerful HTML querying protocol which is used by browsers to determine what HTML elements to style. It's also incredibly useful in HTML parsing when web scraping or processing HTML data, as the same queries can be used to select values as well.](https://scrapfly.io/blog/posts/css-selector-cheatsheet)



## 3. thirtyfour

Then we need a real browser. [thirtyfour](https://docs.rs/thirtyfour/0.37.10/thirtyfour/) is pretty much Selenium for Rust - it uses the same WebDriver protocol to open Chrome, load the page and run its javascript.

We don't have a ChromeDriver (or a [Selenium Grid](https://scrapfly.io/blog/posts/intro-to-web-scraping-using-selenium-grid)) running, so instead of connecting with `WebDriver::new` we'll use `WebDriver::managed`, which downloads the right driver and starts it for us. Let's add `thirtyfour = "=0.37.10"` and tokio to Cargo.toml:

rust

```rust
use thirtyfour::prelude::*;

#[tokio::main]
async fn main() -> WebDriverResult<()> {
    let mut caps = DesiredCapabilities::chrome();
    caps.set_headless()?;
    let driver = WebDriver::managed(caps).await?;

    driver.goto("https://web-scraping.dev/products").await?;
    let products = driver.find_all(By::Css(".product h3 a")).await?;
    for product in &products {
        println!("{}", product.text().await?);
    }
    println!("products={}", products.len());

    driver.quit().await?;
    Ok(())
}
```



This opens headless Chrome, loads the products page and grabs every `.product h3 a` link:

text

```text
Box of Chocolate Candy
Dark Red Energy Potion
Teal Energy Potion
Red Energy Potion
Blue Energy Potion
products=5
```



All five products are there, and `managed` fetched a ChromeDriver for our Chrome 154 on its own. Note the `driver.quit()` at the end too - Rust can't run async code when the driver is dropped, so without it the browser gets closed by blocking the executor.



Scrapfly

#### Scale your web scraping effortlessly

Scrapfly handles proxies, browsers, and anti-bot bypass — so you can focus on data.

[Try Free →](https://scrapfly.io/register)## 4. chromiumoxide

Do we need ChromeDriver at all? Not with chromiumoxide, which talks to Chrome over the Chrome DevTools Protocol (CDP) directly, though Chrome or Chromium still has to be installed. Let's add `chromiumoxide = "=0.9.1"`, tokio and `futures = "0.3"` to Cargo.toml:

rust

```rust
use chromiumoxide::{Browser, BrowserConfig};
use futures::StreamExt;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let (mut browser, mut handler) = Browser::launch(BrowserConfig::builder().build()?).await?;
    let handle = tokio::spawn(async move {
        while let Some(event) = handler.next().await {
            if event.is_err() {
                break;
            }
        }
    });

    let page = browser.new_page("https://web-scraping.dev/products").await?;
    let products = page.find_elements(".product h3 a").await?;
    for product in &products {
        println!("{}", product.inner_text().await?.unwrap_or_default());
    }
    println!("products={}", products.len());

    browser.close().await?;
    browser.wait().await?;
    handle.await?;
    Ok(())
}
```



Here `Browser::launch` gives us a `Browser` and a `Handler`, and we poll the `Handler` in its own tokio task like the official example does, since no command reaches Chrome otherwise. At the end `browser.wait()` waits for Chrome to exit, so no zombie process is left behind.

Let's run it:

text

```text
Box of Chocolate Candy
Dark Red Energy Potion
Teal Energy Potion
Red Energy Potion
Blue Energy Potion
products=5
```



Why does the first build take so long? The CDP bindings are generated code, roughly 60,000 lines before macro expansion according to the docs.



## 5. dom\_smoothie

What if we just want the article text? dom\_smoothie finds the main content of a page by itself, so we don't write any selectors. It doesn't fetch pages, so we pair it with reqwest, adding `dom_smoothie = "=0.18.2"`, `reqwest = "=0.13.5"` and tokio to Cargo.toml:

rust

```rust
use dom_smoothie::Readability;

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let url = "https://web-scraping.dev/product/1";
    let html = reqwest::get(url).await?.error_for_status()?.text().await?;

    let mut readability = Readability::new(html, Some(url), None)?;
    let article = readability.parse()?;

    println!("title={}", article.title);
    println!("text_chars={}", article.text_content.chars().count());
    Ok(())
}
```



Running it prints:

text

```text
title=web-scraping.dev product Box of Chocolate Candy
text_chars=3022
```



That's the title plus 3,022 characters of page text.

Finding new urls to visit is a separate job, which our [scraper API vs crawler API](https://scrapfly.io/blog/posts/scraper-api-vs-crawler-api) comparison covers.

[Top Web Crawler Tools in 2026Web crawling has evolved dramatically in 2026. What used to require complex infrastructure and constant maintenance can now be accomplished with a few clicks or lines of code. But with so many options available, choosing the right tool can make or break your data collection project.](https://scrapfly.io/blog/posts/top-web-crawler-tools)



## When the Page Blocks You: Scrapfly Web Scraping API

Scrapfly publishes this guide, so its API sits outside the ranking. None of the five libraries above gets past a block page, so here's what one looks like from Rust. web-scraping.dev has a `/blocked` page, so let's request it with reqwest and count the products like we would on `/products`:

rust

```rust
use scraper::{Html, Selector};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let response = reqwest::get("https://web-scraping.dev/blocked").await?;
    let status = response.status();
    let html = response.text().await?;
    let document = Html::parse_document(&html);
    let products = document.select(&Selector::parse(".product")?).count();
    let title: String = document.select(&Selector::parse("title")?).flat_map(|n| n.text()).collect();
    println!("status={status} products={products} title={title:?}");
    Ok(())
}
```



text

```text
status=200 OK products=0 title="web-scraping.dev - You've been blocked"
```



The status is 200, so `error_for_status()` wouldn't catch it either. The [Scrapfly Rust SDK](https://scrapfly.io/docs/sdk/rust) sends the request through Scrapfly's API instead, and we parse the html it returns with scraper again.

Let's add it to Cargo.toml next to scraper and tokio, which needs the `macros` and `rt-multi-thread` features for `#[tokio::main]`:

toml

```toml
[dependencies]
reqwest = "=0.13.5"
scrapfly-sdk = "=0.5.1"
scraper = "=0.27.0"
tokio = { version = "1", features = ["macros", "rt-multi-thread"] }
```



Now we can request the products page through the [Scrapfly Web Scraping API](https://scrapfly.io/products/web-scraping-api):

rust

```rust
use scraper::{Html, Selector};
use scrapfly_sdk::{Client, ScrapeConfig};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let client = Client::builder().api_key(std::env::var("SCRAPFLY_KEY")?).build()?;
    let config = ScrapeConfig::builder("https://web-scraping.dev/products").unblocker(true).build()?;
    let result = client.scrape(&config).await?;

    let html = Html::parse_document(&result.result.content);
    for name in html.select(&Selector::parse(".product h3 a")?) {
        println!("{}", name.text().collect::<String>());
    }
    Ok(())
}
```



The key comes from the `SCRAPFLY_KEY` environment variable, and `unblocker(true)` is the part that deals with blocks like the one above. Running it prints:

text

```text
Box of Chocolate Candy
Dark Red Energy Potion
Teal Energy Potion
Red Energy Potion
Blue Energy Potion
```



What each request costs is on the [Scrapfly pricing page](https://scrapfly.io/pricing).

For pages that don't block anyone, like this product listing, the libraries above are all we need.



## FAQ

Can we scrape the /reviews page without a browser?Remember the review reqwest couldn't find? The page gets it from `/api/graphql`, and the query it sends sits in a `<script id="reviewsGQL">` tag. So we can post that query ourselves, with `serde_json` and reqwest's `json` feature:

rust

```rust
use scraper::{Html, Selector};
use serde_json::{json, Value};

#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    let html = reqwest::get("https://web-scraping.dev/reviews").await?.text().await?;
    let query: String = Html::parse_document(&html)
        .select(&Selector::parse("#reviewsGQL")?)
        .flat_map(|node| node.text())
        .collect();

    let body = json!({"query": query, "variables": {"first": 3}});
    let data: Value = reqwest::Client::new()
        .post("https://web-scraping.dev/api/graphql")
        .json(&body)
        .send()
        .await?
        .json()
        .await?;

    for edge in data["data"]["reviews"]["edges"].as_array().unwrap() {
        println!("{} {}", edge["node"]["date"], edge["node"]["text"]);
    }
    Ok(())
}
```



text

```text
"2023-05-18" "Unique flavor and great energy boost. It's the perfect gamer's drink!"
"2023-05-17" "Good flavor and keeps me energized. The bottle design is really fun."
"2023-05-16" "Excellent energy drink for gamers. The tropical flavor is refreshing."
```



That first one is the review our reqwest check reported as `found=false`. Our [scraping GraphQL](https://scrapfly.io/blog/posts/web-scraping-graphql-with-python) tutorial has more on this.







Can I mix reqwest's blocking client with tokio?rust

```rust
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
    // needs reqwest = { version = "=0.13.5", features = ["blocking"] }
    let html = reqwest::blocking::get("https://web-scraping.dev/products")?.text()?;
    println!("bytes={}", html.len());
    Ok(())
}
```



text

```text
Cannot drop a runtime in a context where blocking is not allowed. This happens when a runtime is dropped from within an asynchronous context.
```



It panics, so it's either `reqwest::get` with tokio or the blocking client without it.









## Summary

In this article, we've taken a look at five Rust web scraping libraries, and one API for blocked pages, by scraping the same web-scraping.dev products page with each one. We used reqwest and scraper for plain html pages, thirtyfour and chromiumoxide for pages that need javascript and dom\_smoothie for pulling out the page text.

For this, we used Rust with tokio, and to avoid being blocked we used ScrapFly's Rust SDK with `unblocker(true)`, since plain reqwest only got a "You've been blocked" page with a 200 status. For more on ScrapFly see our [documentation](https://scrapfly.io/docs/sdk/rust) and try it out for free!



### Web Scraping API

Scrape any website with our powerful API. Anti-bot bypass, JavaScript rendering, and rotating proxies built-in.



[Try Web Scraping API](https://scrapfly.io/docs/scrape-api/getting-started)



Legal Disclaimer and PrecautionsThis tutorial covers popular web scraping techniques for education. Interacting with public servers requires diligence and respect:

- Do not scrape at rates that could damage the website.
- Do not scrape data that's not available publicly.
- Do not store PII of EU citizens protected by GDPR.
- Do not repurpose *entire* public datasets which can be illegal in some countries.

Scrapfly does not offer legal advice but these are good general rules to follow. For more you should consult a lawyer.

 

   [  Add as a preferred source ](https://google.com/preferences/source?q=scrapfly.io) Table of Contents















 

  Table of Contents

- [Which Rust Web Scraping Library or API Is Best in 2026?](#which-rust-web-scraping-library-or-api-is-best-in-2026)
- [1. reqwest](#1-reqwest)
- [2. scraper](#2-scraper)
- [3. thirtyfour](#3-thirtyfour)
- [4. chromiumoxide](#4-chromiumoxide)
- [5. dom\_smoothie](#5-dom-smoothie)
- [When the Page Blocks You: Scrapfly Web Scraping API](#when-the-page-blocks-you-scrapfly-web-scraping-api)
- [FAQ](#faq)
- [Summary](#summary)
 
    Join the Newsletter  Get monthly web scraping insights 

 

  



Scale Your Web Scraping

Anti-bot bypass, browser rendering, and rotating proxies, all in one API. Start with 1,000 free credits.

  No credit card required  1,000 free API credits  Anti-bot bypass included 

 [Start Free](https://scrapfly.io/register) [View Docs](https://scrapfly.io/docs/onboarding) 

 Not ready? Get our newsletter instead. 

 

 ## Related Articles

 [     

 tools web-scraping 

### 4 Top Elixir Web Scraping Libraries in 2026

Four current Elixir libraries for HTTP retrieval, HTML parsing, crawl orchestration, and browser control, plus managed p...

 

 ](https://scrapfly.io/blog/posts/best-elixir-web-scraping-libraries) [     

 ai tools 

### 5 MCP Servers for Web Scraping and Browser Automation in 2026

Compare five MCP servers by the work they can actually do: protected-site scraping, browser control, debugging, static f...

 

 ](https://scrapfly.io/blog/posts/best-mcp-servers-for-web-scraping) [     

 python ai 

### What is Parsing? From Raw Data to Insights

Learn about the fundamentals of parsing data, across formats like JSON, XML, HTML, and PDFs. Learn how to use Python par...

 

 ](https://scrapfly.io/blog/posts/what-is-parsing-turning-data-into-insights) 

  ## Related Questions

- [ Q What are some PhantomJS alternatives for automating browsers? ](https://scrapfly.io/blog/answers/what-are-some-phantomjs-alternatives)
 
  



   



 Scale your web scraping effortlessly, **1,000 free credits** [Start Free](https://scrapfly.io/register)