Go fetched 1 000 pages in 0.49 seconds. Rust came second at 0.66 s, C# third at 0.79 s. PHP finished at 1.06 s, ahead of Python async (1.54 s) and Node.js (1.73 s). Ruby closed the mainstream group at 1.92 s. R, using httr2’s parallel pool, took 4.06 seconds, the slowest of the nine concurrent stacks and 4.7 times faster than Python with requests running sequentially (19.3 s).
Eight of nine concurrent implementations finished under two seconds. Whether to use concurrency matters more than which language to use.
Speed Comparison
Every language ran the same task. From a local mock server, each scraper fetched 1 000 pages, extracted five fields (title, price, rating, availability, category), and wrote results to CSV. The server added a fixed 10 ms delay per request to approximate a CDN-backed site. All concurrent stacks used 50 workers. Each language ran three times and the table shows the stable average. Language sections follow community adoption order, not benchmark position.
| Language | Stack | Time (50 workers) | Peak memory | LOC |
|---|---|---|---|---|
| Go | net/http + goquery | 0.49 s | ~8 MB | 65 |
| Rust | reqwest + tokio + scraper | 0.66 s | — | 85 |
| C# | HttpClient + HtmlAgilityPack | 0.79 s | ~2 MB (GC) | 51 |
| PHP | cURL multi + DOMDocument | 1.06 s | 2 MB | 72 |
| Java | HttpClient + virtual threads | 1.14 s | 94 MB (JVM) | 77 |
| Python | aiohttp + lxml | 1.54 s | 5.5 MB | 42 |
| Node.js | fetch + cheerio | 1.73 s | 124 MB (V8) | 46 |
| Ruby | net/http + Nokogiri | 1.92 s | — | 45 |
| R | httr2 + rvest | 4.06 s | 19.5 MB | ~35 |
Memory figures are self-reported by each runtime and are not directly comparable. Java and Node.js numbers reflect JVM and V8 baseline allocation, not scraping overhead. Python sync (requests, sequential) is excluded from the table (it took 19.3 seconds).

Among the eight mainstream languages, the gap between first and last place is 1.43 seconds. Real servers respond in 100 to 500 ms, and a 1.43-second runtime difference vanishes against that background. R is the outlier at 4.06 s, a reasonable fit for research workflows that feed directly into an R analysis but impractical for any pipeline handling serious volume. Pick any of the other eight languages from this list, add concurrency, and your scraper will be fast enough for the overwhelming majority of projects.
That said, all of this assumes you are picking a language from scratch. Most teams are not. A Python scraper written by someone who knows Python will ship faster and hold together better than a theoretically optimal Go version written by someone who installed Go last Tuesday. Pick the one you already know, or at minimum, the one you have actually used before.
Python
Python dominates web scraping on GitHub with 130 000 public scraper repositories, 3.6 times the count for JavaScript in second place. The ecosystem justifies that lead. BeautifulSoup, Scrapy, Playwright, Selenium, and httpx each solve a distinct problem, and the overlap between them means you rarely need to reach outside the standard toolkit.

The gap holds across query variants. ‘Web scraping’ and ‘crawler’ searches return the same order.
The benchmark measured aiohttp with lxml at 1.54 seconds for 1 000 pages. That puts Python mid-pack on raw throughput, faster than Node.js and Ruby, slower than PHP and Java. What the number does not capture is how quickly you can write the scraper. The aiohttp + lxml version took 42 lines, the fewest of any concurrent implementation in the test.
The async version and the sequential version are not interchangeable. requests in a loop, which is the default approach in most tutorials, took 19.3 seconds for the same job — a 12× penalty for skipping asyncio. Every Python scraper that hits more than a handful of pages should use aiohttp, httpx, or at minimum requests with concurrent.futures.ThreadPoolExecutor.
Key libraries:
- BeautifulSoup + requests: simple sites, small volumes
- Scrapy: large crawls, built-in scheduling and pipelines
- Selenium / Playwright: JavaScript-rendered pages
- aiohttp / httpx: concurrent HTTP fetching

BeautifulSoup4 and lxml at 450 M monthly downloads each reflect how often Python is the parsing layer even in pipelines where another language handles the HTTP side.
Pick Python when the team already works in Python, the data feeds into a pandas or machine learning pipeline, or the scraper is a prototype that may grow into something more complex. Google Colab makes it easy to run small scrapers without local setup.
Node.js
Node.js sat at 1.73 seconds in the benchmark, slower than Python async on this particular task and faster than Ruby. For scraping, the more relevant characteristic of Node.js is native context. If the site you are scraping uses JavaScript, the people who built it used JavaScript, and the scraper ecosystem reflects that.
The fetch API has been built into Node since version 18, which removes the most common reason to reach for axios. For HTML parsing, cheerio gives you jQuery-style selectors against a server-rendered DOM. For JavaScript-heavy pages, Puppeteer and Playwright both run a real Chromium instance and expose a clean async API.
Node.js also runs inside Google Apps Script, a restricted version of JavaScript. If you need to scrape into a Google Sheet on a schedule, a few dozen lines of Apps Script can do the job without any infrastructure.
Key libraries:
- fetch (built-in, Node 18+) + cheerio: static HTML, zero install needed
- Puppeteer: Chrome DevTools integration, fine-grained control
- Playwright: cross-browser, stronger for testing-adjacent use cases
- axios: when you need request interceptors or need to support Node < 18

Puppeteer leads by GitHub stars. The more useful observation is that Playwright is at nearly the same count despite spanning Python, Java, C#, and Ruby as well, which reflects how much cross-language work goes through the Node.js API specifically.
Pick Node.js when the scraper is maintained by a frontend team, the data feeds into a JavaScript application, or you need Google Sheets integration through Apps Script.
Go
Go finished first in the benchmark at 0.49 seconds, 0.17 seconds ahead of Rust. The stack is net/http plus goquery for CSS selectors, with 50 goroutines sharing a connection pool.
The goroutine model explains the result. A goroutine costs a few kilobytes of stack. The runtime schedules thousands of them across a small number of OS threads, so 50 concurrent HTTP requests add almost no overhead. Java virtual threads reach a similar model, but goroutines have been the default since Go 1.0 rather than a feature added in version 21. The standard library covers HTTP, connection pooling, JSON, and CSV with no external dependencies. The benchmark needed goquery for CSS selectors and nothing else.
Key libraries:
net/http(stdlib): HTTP, built-in connection pooling- Colly: scraping framework, rate limiting, parallel crawling
- goquery: jQuery-like CSS selectors
Pick Go when throughput is a hard requirement and you want the benchmark winner with a simpler dependency story than Rust, or the scraper is part of a microservice already written in Go.
Java
Java finished fourth at 1.14 seconds, using java.net.http.HttpClient with virtual threads introduced in Java 21. Virtual threads are the key detail here. Before Java 21, getting 50 concurrent HTTP requests in Java meant either a thread pool with real OS threads (expensive memory) or reactive libraries like Reactor (complex code). Virtual threads make concurrency in Java as simple as writing sequential code inside Thread.ofVirtual().start().
The 94 MB heap figure in the table is JVM startup cost, not scraping overhead. A Python process doing the same job sits at 5.5 MB because the interpreter is lighter. That difference matters when you run dozens of scraper instances on a single machine but is irrelevant when the scraper runs alone.
Java’s real advantage for scraping is at the enterprise end of the scale. Static typing catches a class of bugs that surface only under load in dynamic languages. The JVM’s JIT compilation means the longer the process runs, the faster it gets. For a scraper that processes hundreds of millions of pages over weeks, Java competes with Go and Rust in sustained throughput.
Key libraries:
java.net.http.HttpClient(built-in, Java 11+): concurrent HTTP, virtual thread compatible- JSoup: HTML parsing with CSS selectors
- HtmlUnit: headless browser for JavaScript-rendered pages
Pick Java when the scraper is part of a larger JVM-based system, the team already works in Java or Kotlin, or the project needs to run reliably at very high volume for extended periods.
PHP
PHP finished third in the benchmark at 1.06 seconds, faster than Java and faster than Python async. That result surprises most people, and it should recalibrate the reputation PHP carries in scraping discussions.
cURL multi, the engine under the benchmark, is a C library that manages a pool of connections and multiplexes responses. PHP wraps it, and the wrapper is thin enough that the performance lands close to what you would get writing the same logic in C. DOMDocument, also built in, handles HTML parsing with no Composer dependency at all.
PHP’s practical advantage for scraping is deployment context. If you maintain a WordPress plugin or a Laravel application and need to pull data from an external site, PHP is the right choice because the code runs in the same process as the rest of the application. You get the same HTTP client, the same database connection, and the same logging infrastructure. Spinning up a Python sidecar for a scraper that runs inside a PHP codebase is usually not worth the operational overhead.
Key libraries:
- cURL multi + DOMDocument: concurrent fetching, built-in, zero Composer dependencies
- Guzzle: cleaner API for complex request logic, middleware support
- Symfony DomCrawler: CSS selectors on top of DOMDocument
Pick PHP when the project already runs on PHP, such as a WordPress site, a Laravel application, or a shared hosting environment where spinning up another runtime is not an option.
Ruby
Ruby finished last among concurrent implementations at 1.92 seconds, 1.26 seconds behind Rust. In practice, that gap is irrelevant for any scraper that is not chasing maximum throughput. At 50 concurrent workers, 1.92 seconds for 1 000 pages is 519 pages per second, fast enough for everything except the most aggressive crawls.
Ruby occupies the same niche in scraping as PHP. It rarely appears in standalone scrapers but makes sense when the code lives inside a Rails or Sinatra application. Rails and Sinatra handle session state, authentication, job queues, and database writes. A scraper written in Ruby and plugged into Sidekiq runs in the same environment as the rest of the application, shares credentials, and gets monitored by the same tooling.
Nokogiri is the dominant HTML parser and handles malformed real-world HTML well. net/http in the standard library is sufficient for simple concurrent fetching through threads. typhoeus wraps cURL multi if you need higher throughput.
Key libraries:
net/http(stdlib) + Nokogiri: concurrent with threads, no extra dependencies- Mechanize: stateful scraping, form submission, cookie handling
- Typhoeus: cURL-backed HTTP for higher concurrency
Pick Ruby when the project runs on Rails or Sinatra, the scraper is a background job in a Sidekiq queue, or the team is already proficient in Ruby.
C# (.NET)
C# finished second at 0.79 seconds, 0.13 seconds behind Rust. The gap narrows in practice because the first benchmark run includes JIT compilation time. Once the JIT warms up, HttpClient with async/await and a SemaphoreSlim for backpressure produces throughput close to what you get from Rust.
The .NET ecosystem for web scraping is thinner than Python but not sparse. HtmlAgilityPack handles malformed HTML reliably. AngleSharp adds a proper CSS selector engine and a simulated DOM. Playwright has first-class .NET bindings, and Selenium works without any friction against the existing .NET package ecosystem.
Visual Studio and Rider provide the best IDE experience of any language on this list for debugging concurrent code. If your scraper has a concurrency bug, a race condition, or a resource leak, the .NET tooling makes it significantly easier to find than a similar bug in Python or Node.js.
Key libraries:
HttpClient(built-in): async HTTP, connection pooling out of the box- HtmlAgilityPack: XPath-based HTML parsing, tolerates malformed HTML
- AngleSharp: CSS selectors, standards-compliant parser
- Playwright for .NET: JavaScript-rendered pages
Pick C# when the scraper is part of a .NET application, the team works in the Microsoft ecosystem, or you need the tooling advantages of Visual Studio for debugging concurrent workloads.
Rust
Rust finished second at 0.66 seconds, behind Go and 0.13 seconds ahead of C#. Getting there took five external crates (reqwest, tokio, scraper, csv, futures), a Cargo.toml, and 85 lines of code — the heaviest setup of any language in this test. Python async covers the same task in 42 lines. The compiler rejects most concurrency bugs before the code ever runs, which is the real argument for Rust in production scrapers.
A Rust scraper running 500 concurrent connections costs almost no memory per connection. Go reaches similar throughput with less code and a more straightforward dependency story. For most scraping workloads the difference between Go and Rust is irrelevant. At hundreds of millions of pages per day, Rust’s memory efficiency and compile-time safety guarantees start to matter.
Key libraries:
- reqwest + tokio: async HTTP with connection pooling
- scraper: CSS selectors on top of
html5ever spider: full crawling framework, sitemap support
Pick Rust when the team already works in Rust, or the scraper is a long-lived service where compile-time safety guarantees and minimal memory per connection matter more than development speed.
R
R is a narrow but legitimate choice for scraping. The audience is researchers and analysts who need data for statistical analysis and do not want to switch languages to collect it. httr2 replaced httr as the standard HTTP client and supports parallel requests through req_perform_parallel. rvest provides CSS selectors and XPath on top of xml2.
In the benchmark, httr2 with 50 parallel workers fetched 1 000 pages in 4.06 seconds, the slowest of the nine concurrent stacks and still 4.7 times faster than sequential Python. R’s interpreted scheduler adds overhead at scale. Network throughput is fully saturated at 50 workers in both R and Python, so the 2.5-second gap over Python async reflects interpreter speed and callback scheduling overhead. For a researcher running a one-off data collection, 4 seconds is fine. For anyone building a regular pipeline, the gap is too large to absorb.
The limiting factor is ecosystem depth. There are no R equivalents of Puppeteer or Playwright for JavaScript-rendered content. For anything beyond static HTML, the typical approach is to call a Python subprocess or use an external scraping API.
Key libraries:
- httr2 + rvest: parallel HTTP + HTML parsing, standard workflow
- xml2: XPath and CSS selectors
- RSelenium: browser automation, requires a running Selenium server
Pick R when the data feeds directly into an existing analysis workflow in R and spinning up a Python environment adds friction the team is not willing to absorb.
JavaScript-Rendered Pages
The benchmark above measures static HTML fetching. A different tool is needed when the page executes JavaScript before content appears (SPAs, infinite scroll, dynamically loaded prices, login-gated dashboards).
Playwright, Puppeteer, and Selenium cover this across all the languages in this article.
| Framework | Language bindings | Engine | Typical use |
|---|---|---|---|
| Playwright | Python, Node.js, Java, C#, Ruby | Chromium / Firefox / WebKit | Cross-browser, strong async API, active development |
| Puppeteer | Node.js | Chromium | DevTools-level control, Chrome-first teams |
| Selenium | Python, Node.js, Java, C#, Ruby, PHP | Any WebDriver-compatible browser | Legacy systems, large existing test suites |
Playwright has the widest language support and the most active release cadence. Puppeteer is the choice when the team lives in Node.js and needs low-level Chrome DevTools access. Selenium works across every language but adds WebDriver overhead that Playwright avoids.
For JavaScript rendering, language speed stops mattering. All three frameworks drive an actual browser binary, and that binary sets the pace. A Python Playwright script and a Node.js Playwright script take the same time per page. Pick the language your team already writes.
FAQ
What is the fastest language for web scraping?
Go reached 0.49 seconds for 1 000 pages in our benchmark, followed by Rust at 0.66 seconds and C# at 0.79 seconds. In practice, the difference between any concurrent implementation and the next is smaller than the variance from server response times. The fastest scraper in any language is one that sends requests in parallel rather than one after another.
Is Python or JavaScript better for web scraping?
Both are competitive when used with their async models. In our benchmark, Python async (aiohttp + lxml) finished in 1.54 seconds. Node.js (fetch + cheerio) finished in 1.73 seconds. The real differentiator is the data destination: Python connects naturally to data analysis tools, JavaScript connects naturally to frontend applications and Google Sheets via Apps Script. The full Python vs JavaScript for web scraping comparison covers library selection, async patterns, and production tradeoffs beyond what a speed benchmark captures.
Can PHP handle concurrent web scraping?
Yes, and it performs better than its reputation suggests. cURL multi, a built-in extension with no Composer dependency, put PHP in third place in our benchmark at 1.06 seconds. The standard advice to avoid PHP for scraping applies to sequential scripts, not to code that uses cURL multi.
Does the choice of HTML parser affect scraping speed?
At the scale of hundreds or thousands of pages, parsing time is small compared to network wait time. lxml (Python) parses faster than BeautifulSoup. scraper (Rust) and Nokogiri (Ruby) are C-backed and fast. The choice of parser matters most when the scraper runs locally against already-fetched HTML and the network is not a factor.


![PHP Web Scraping: The Complete Guide [2026]](/_astro/preview.DL-P6qti_Z2vM5Dt.webp)