Botasaurus is a Python web-scraping framework from Omkar Cloud that bills itself as an all-in-one kit for building scrapers. You write a plain function, decorate it with @browser, @request, or @task, and the framework wires a browser driver, a browser-like HTTP client, caching, parallelism, and multi-format output around it. It's a meta-package, which is the part that matters: pip install botasaurus doesn't drop one library into your environment, it assembles a small family of first-party wheels plus a wide transitive tree. That mechanical detail turned out to be the most interesting thing I could measure honestly.
Botasaurus markets itself around anti-detection, and that is exactly the axis this review does not touch. I inventoried the framework — what installs, what imports, what methods exist, what it weighs, what it licenses under — rather than pitting it against any live defense. Every footprint and import number below comes from pip, from python -c "import ...", and from introspecting classes that were constructed but never told to fetch a page; no browser was launched to produce any of them. Later I did launch browsers, but only at pages I wrote and served myself on 127.0.0.1, to see what the driver announces about itself and whether it can pull content off a page that builds itself with JavaScript. No live website was involved at any point, no anti-bot service was contacted or measured, and no CAPTCHA was touched. Effectiveness against real websites is out of scope by design, and I'd rather say that up front than imply a benchmark I didn't run.
With that fence built, the headline finding is a footprint story, and it's a benign one. A clean install produces a 122.3 MB site-packages directory across 44 packages, on a machine where the browser driver at the center of the whole thing is about 4 MB. The framework isn't heavy because the driver is heavy; it's heavy because "all-in-one" means it brings numpy, lxml, gevent, and a dozen other things to a job about fetching HTML. And the second half of the finding is a number you'll see quoted as a virtue and shouldn't trust: import botasaurus clocks in at 0.08 ms, which reads like a feather-light framework and is actually just an empty front door.
What Botasaurus actually is
Botasaurus — omkarcloud/botasaurus on GitHub, sitting at 5,561 stars, 486 forks, and 58 open issues when I pulled the metadata on July 14, 2026 — is a Python framework, not a single-purpose library. The versions I tested were botasaurus 4.0.97 for the meta-package and botasaurus-driver 4.0.92 for the engine underneath it. The meta-package declares requires-python >=3.7 (the driver, >=3.5), and the PyPI classifiers only claim support through 3.11. It installed and passed an import smoke test on Python 3.14.2 on my machine. That is evidence about this install, not a compatibility guarantee for every feature.
The category label is worth pinning down, because it decides what "good" means. Botasaurus sits at the framework end of the spectrum, the same neighborhood as Scrapy and Crawlee — you adopt its structure, its decorators, its conventions, and in return it manages the plumbing. That's a different proposition from a focused driver like nodriver, which gives you a Chrome DevTools Protocol connection and gets out of the way. Botasaurus bundles a driver (that's botasaurus-driver) but wraps it in a task runner, a caching layer, output serializers, and a request client. You're not buying a driver; you're buying an opinionated workflow with a driver inside.
The three decorators are the whole design in miniature, and all three entry points are real — I confirmed botasaurus.browser.browser, botasaurus.request.request, and botasaurus.task.task all exist and import. @browser runs your function against the humanized browser driver. @request runs it against a lightweight HTTP client built to look browser-shaped. @task is the generic wrapper for everything that isn't clearly one of the first two. Decorate, and Botasaurus supplies the surrounding machinery: parallel execution, driver reuse, result caching, and writers for JSON, CSV, Excel, and HTML. It's a coherent idea. Whether you want that much framework around a scraper is the real question, and it's a taste question, not a defect.
The take, and the boundary around it
Botasaurus is competent, well-shaped, and does the thing frameworks are supposed to do: it makes the common case short. The decorator model is clean. The MIT license is genuinely generous. The install works without drama. If I were grading the ergonomics of the API surface, it would grade well.
What I keep coming back to is that the framework's own headline pitch — anti-detection — is the one thing a responsible review can't speak to without pointing it at somebody's production defenses. The driver does ship an explicitly anti-detection-branded API surface: methods whose existence I confirmed but whose behavior I did not exercise against any target. That's the entirety of what I'll claim about it. I didn't point it at a protected site, didn't measure a success rate, didn't reverse-engineer a mechanism, and won't imply any of the three by phrasing. The methods are there in the class. What they do in the wild is a separate review that this isn't.
What follows is a capability, install, resource, and license inventory, plus what the driver does on a page I control — a narrower claim than most reviews of this tool make, and the narrowness is the point.
What it announces about itself

Here's a question you can answer without going anywhere near a real defense: when Botasaurus drives a browser, what does that browser volunteer about itself to the page it's looking at? I wrote a page that reads the obvious things — navigator.webdriver, the user-agent, platform, languages, plugin and hardware counts, the shape of window.chrome, what the Permissions API says, window and screen geometry — served it on 127.0.0.1, and pointed four stacks at it: Botasaurus, nodriver, and stock Playwright and stock Puppeteer as controls. All four drove the same Chrome build (Chrome for Testing 151.0.7922.10), so anything that differs is the library and not the browser. Headless and headed, three runs each. Every value below held across all three.
| Stack | Mode | navigator.webdriver | User-agent token | navigator.languages |
|---|---|---|---|---|
| Botasaurus 4.0.92 | headless | false | HeadlessChrome/151.0.0.0 | ["en-US"] |
| Botasaurus 4.0.92 | headed | false | Chrome/151.0.0.0 | ["en-US"] |
| nodriver 0.50.3 | headless / headed | false | HeadlessChrome/151 / Chrome/151 | ["en-US"] |
| Playwright 1.56.0 | headless / headed | true | HeadlessChrome/151 / Chrome/151 | ["en-US","en"] |
| Puppeteer 24.16.0 | headless / headed | true | HeadlessChrome/151 / Chrome/151 | ["en-US"] |
The differences depend on which control you use. Against stock Puppeteer, Botasaurus changed navigator.webdriver; the language list and zero-sized window geometry matched, while the user-agent differed only in version formatting. Against stock Playwright, the observed deltas also included the language list and window geometry. Against nodriver, the boolean and languages matched, with only user-agent formatting differing in the fields shown here. These are default disclosure observations, not an anti-detection score.
Two details make it more interesting than the bare false. The first is how the value gets there. In all four stacks the property is still the browser's own native getter on Navigator.prototype — function get webdriver() { [native code] } — never an own-property planted on the instance, never a replaced function. So Botasaurus isn't rewriting the property after the page loads; the value is decided when the browser starts, and the property itself is left untouched.
The second detail is the one that qualifies the marketing. In headless mode, Botasaurus's user-agent still announces HeadlessChrome/151.0.0.0 — identical to stock Puppeteer, identical to stock Playwright. Run headed and it becomes Chrome/151.0.0.0, again identically. The Driver constructor takes a user_agent parameter, so setting one is a keyword argument away, but nothing in the default configuration masks the most famous self-identifying string in browser automation.
Nearly everything else was the same across all four stacks, which is worth saying plainly because it narrows the story — every property below read the same on Botasaurus, nodriver, Playwright and Puppeteer:
| Property | Value, identical on all four stacks |
|---|---|
platform | MacIntel |
vendor | Google Inc. |
| Plugins | five |
| MIME types | two |
pdfViewerEnabled | true |
| Logical cores | twelve |
| Reported device memory | 16 GB |
| Touch points | zero |
window.chrome | present, with app/csi/loadTimes and no runtime |
| WebGL renderer string | identical across all four |
The old chestnut where the Permissions API and Notification.permission contradict each other didn't appear anywhere — all four reported default and prompt in agreement. I also scanned document and window for the cdc_-style leftovers that older WebDriver stacks were known for: empty in all four.
One last thing worth knowing before you deploy: for all its 122 MB, Botasaurus does not ship or download a browser. find_chrome_executable() resolves to whatever Chrome your machine already has — on mine, /Applications/Google Chrome.app, version 150.0.7871.187 — and that's the version the user-agent then discloses. Your fleet announces whatever Chrome your fleet happens to have installed, which for a framework this opinionated is a surprisingly unopinionated default.
Say what this is and what it isn't. It's a record of what an automated stack discloses when nobody has asked it to hide — useful if you're on the defending side, useful if you want to know what your own tooling broadcasts. It is not a measure of whether any of that matters to any particular service. I did not test that, and no row above should be read as implying an outcome.
A more forgiving default read on this fixture
Announcing yourself is one thing; returning the right HTML is the job. I ran Botasaurus against the same three-content-class fixture the rest of this benchmark repo uses, so the numbers line up with every other tool measured here. The page carries three things: A, a static link whose marker is a literal in the served bytes; B, a node built by an inline script during parse, with its marker and URL assembled from fragments so that only executing the JavaScript reveals them; and C, a node injected 800 ms after the load event, assembled the same way. Class C is the adversarial one — a read taken at the load event cannot see it.
| Stack | Default read | With an explicit wait |
|---|---|---|
| Botasaurus 4.0.92 | 2 of 3 (A + B, misses C) | 3 of 3 |
| nodriver 0.50.3 | 2 of 3 | 3 of 3 |
| Playwright 1.56.0 | 2 of 3 | 3 of 3 |
| Puppeteer 24.16.0 | 2 of 3 | 3 of 3 |
Botasaurus lands where the heavyweights land. driver.get() followed straight by driver.page_html is an at-load snapshot: it renders JavaScript properly — class B proves that, since class B exists nowhere in the served bytes — but at an 800 ms delay it misses class C. Add driver.wait_for_element("#delayed-injected") and you get all three. Stable across three reps and three separate runs of the whole suite, with no flakes.
The interesting part shows up when you sweep the injection delay to find where each stack's default read gives up:
| Class-C injected after | Botasaurus | nodriver | Playwright | Puppeteer |
|---|---|---|---|---|
| 0 ms | found | found | found | found |
| 100 ms | found | — | — | — |
| 200 ms | found | — | — | — |
| 300 ms | found | — | — | — |
| 400 ms and up | — | — | — | — |
Every other stack loses class C the moment injection lands 100 ms or more after load. Botasaurus still catches it at 300 ms, and only gives up at 400. This is the framework being a framework, and the cause is right there in the constructor: wait_for_complete_page_load=True is the default, so get() returns meaningfully later than a bare load event. Concretely, its default read costs 401–431 ms of wall clock where nodriver's costs 119–129 ms and Puppeteer's costs 125–171 ms.
On this local delayed-injection fixture, the trade was roughly 250 ms per navigation for a later default snapshot. That caught content injected up to 300 ms after load in these runs; it does not establish that Botasaurus is more correct across arbitrary sites. If you write quick scrapers without explicit wait conditions, the cushion may prevent a missed late node. At high navigation volume, or when you already wait for a precise condition, it is simply overhead.
Two more timings for scale. Bringing the browser up put Botasaurus essentially level with nodriver and Puppeteer, and well behind Playwright:
| Stack | Browser launch, across runs |
|---|---|
| Botasaurus 4.0.92 | 986–1151 ms |
| nodriver 0.50.3 | 910–1583 ms |
| Puppeteer 24.16.0 | 969–1008 ms |
| Playwright 1.56.0 | 282–365 ms |
And wait_for_element() costs nothing extra up to a 300 ms delay — get() had already settled past the injection — then steps to about 1.42 s at 400–800 ms and 2.43 s at 1500 ms.
The 122 MB question: what a meta-package actually installs

Here's the arithmetic, because it's the most useful thing I can hand you. A clean pip install botasaurus in a fresh virtual environment produced a 122.3 MB site-packages tree spanning 44 dist-info packages. Strip out pip itself (10.9 MB, which is venv overhead rather than anything Botasaurus asked for) and you're at roughly 111 MB of framework and dependencies. The browser driver — the component doing the actual browser automation — is about 4 MB of that. So something like 107 MB is everything else the meta-package decided you needed.
Where does it go? The five heaviest transitive dependencies alone account for the bulk of it (each row is a install_footprint.heaviest_deps_mb entry in artifacts/raw/runs/resource_baseline.run1.json; the total is my addition, not a field in the file):
| Package | Size on disk |
|---|---|
| numpy | 30.9 MB |
| lxml | 19.2 MB |
| botasaurus_requests | 12.6 MB |
| gevent | 11.3 MB |
| pygments | 8.4 MB |
| Five packages combined | 82.4 MB (30.9 + 19.2 + 12.6 + 11.3 + 8.4) |
numpy being the single largest item is the one that made me raise an eyebrow — that's a linear-algebra library sitting inside a tool whose job is fetching and parsing web pages. It's not wrong, exactly; frameworks accrete utility dependencies, and something in the tree evidently wants array math. It's just a lot of machine for the errand.
For scale, the focused driver nodriver weighs about 17.2 MB across 6 packages on the same machine — call it roughly 7x lighter. (That figure isn't from this pack: it's install_footprint.site_packages_total_mb in nodriver's own artifacts/raw/runs/resource_baseline.run1.json, measured in a separate staggered run on the same host, and 122.3 ÷ 17.2 = 7.1.) Neither number is a defect and this isn't a capability ranking; it's the mechanical cost difference between a batteries-included framework and a focused driver. In a container, the measured site-packages footprint contributes to the application layer. It is not the full image size, and this test did not measure build or cold-deploy time.
One more footprint wrinkle: during introspection, the first use of from botasaurus.request import request triggered a one-off download of about 12.8 MB. The captured run did not identify the artifact and destination well enough to treat that figure as a stable installed-footprint increment. It does show that this code path may need network access on first use, which is worth reproducing in your own image before an air-gapped deployment.
The import number that lies
Cold-start import timing is where a lazy read of the numbers goes wrong. Measured across seven fresh subprocess imports, import botasaurus at the top level came in at a median of 0.08 ms. Quote that in isolation and it sounds like the lightest framework in the category.
It isn't. It's fast because there's almost nothing there. The top-level botasaurus package exposes no __version__ and a nearly empty public namespace — importing it barely does any work because it barely contains anything. The number that actually matters for a CLI tool or a serverless cold start is importing the engine: from botasaurus_driver import Driver runs about 135 ms, stable to within a couple of milliseconds across runs. That's the real fixed cost you pay before a single page is fetched. And once the driver module is imported, resident memory sits around 29–30 MB — again, before any Chrome process exists. Launch an actual browser and that grows substantially; I didn't measure memory with one running, so I won't put a number on it.
The lesson is small but sharp: import botasaurus being instant is a property of the top-level package being hollow, not of the framework being cheap. If you're sizing a cold start, measure the import you'll actually depend on.
API shape: 99 methods behind a near-empty front door

The engine's Driver class exposes 99 public methods — a broad surface covering navigation, element queries, cookies and local storage, mouse and keyboard actions, screenshots, tab management, CDP passthrough, and file uploads. The constructor takes 18 parameters, which is a fair map of the tunable surface: headless, proxy, profile, tiny_profile, block_images, block_images_and_css, wait_for_complete_page_load, chrome_executable_path, extensions, arguments, user_agent, window_size, lang, and a handful more. As a construction-time API, it covers the usual browser-wrapper controls.
The shape gotcha is at the top level, and it's benign but real. import botasaurus gives you a near-empty namespace — no __version__, essentially no top-level public names. Everything you actually use lives in submodules: from botasaurus.browser import browser, Driver, from botasaurus.request import request, from botasaurus.task import task. If you go looking for botasaurus.__version__ to log which build you're running, you won't find it; you have to reach for importlib.metadata instead. Nothing about this breaks anything. It's just not the layout most Python developers reflexively expect, and knowing it saves you a confused five minutes on day one.
One documentation observation about the methods I flagged earlier is fair game and stays inside my fence: of the anti-detection-named methods that exist, only 2 carry any in-code docstring. The rest are self-describing by name only, with per-method documentation living on the external docs site rather than in the installed source. That's a locality note, not a quality judgment — plenty of good libraries keep their prose docs off the code — but if your workflow is "read the source to understand the method," most of that particular surface will tell you its name and nothing else.
License: MIT, all the way down to the driver
Both the meta-package and botasaurus-driver declare MIT, carrying the standard License :: OSI Approved :: MIT License classifier. MIT is permissive: no copyleft obligation, no requirement to open your own code, minimal friction for commercial adoption. That's a genuine and non-trivial contrast with nodriver, the adjacent anti-detect driver, which ships under AGPL-3.0 — a copyleft license whose network-use clause makes a lot of legal departments nervous. If licensing is a gating factor for you, Botasaurus's MIT stance is a real point in its column.
The caveat is the meta-package shape again. That permissive MIT applies to Botasaurus's own first-party wheels. It does not automatically vouch for the roughly 40 transitive packages the install pulls in, each of which carries its own license. I confirmed the top-level MIT on the packages Omkar Cloud publishes; I did not audit the license of every dependency in the tree. For a hobby project that distinction rarely matters. For a whole-tree adoption inside a company that cares about its software bill of materials, the 40-package tree is a thing to run through your own license scanner before you commit — not because I found a problem, but because I didn't look, and a meta-package is exactly where an unexpected license hides.
Pros and cons
Pros:
- Clean three-decorator design (
@browser/@request/@task), all three entry points confirmed present — the framework makes the common case short. - MIT license on both the meta-package and the driver, a real contrast with the AGPL-3.0 of a comparable driver. Permissive, commercial-friendly, no copyleft.
- A broad driver surface: 99 public methods and an 18-parameter constructor covering the usual browser-automation needs.
- Installed and passed import smoke tests on Python 3.14.2 and 3.12.13, beyond its classifier list (which stops at 3.11); runtime compatibility was not established.
- The widest default-read correctness window of any stack I measured: it still catches content injected 300 ms after load, where nodriver, Playwright and Puppeteer all lose it at 100 ms.
wait_for_complete_page_load=Trueis doing real work. - Reports
navigator.webdriverasfalseby default where both stock controls reporttrue, without patching the property — the descriptor stays the browser's own native getter. - Batteries included by design — caching, parallelism, driver reuse, and JSON/CSV/Excel/HTML output are part of the framework, not bolt-ons.
Cons:
- Heavy on disk: 122.3 MB across 44 packages, roughly 7x a focused driver, driven by dependencies like numpy (30.9 MB) and lxml (19.2 MB) rather than by the ~4 MB driver itself.
- The reassuring 0.08 ms top-level import is misleading; the engine import you actually depend on is ~135 ms, and post-import memory is ~29–30 MB before any browser.
- That wider default-read window isn't free: 401–431 ms per navigate-and-read against 119–129 ms for a lean driver on the same page and the same Chrome.
- Headless runs still announce
HeadlessChromein the user-agent by default, exactly like the stock controls; theuser_agentconstructor parameter exists but nothing sets it for you. - For 122 MB it still ships no browser — it drives whatever Chrome the host already has, so your disclosed browser version is whatever your fleet happens to have installed.
- First use of
@requesttriggered a one-off ~12.8 MB download in this run; the artifact and destination were not captured well enough to call it a stable footprint increment. - Top-level package is nearly empty and exposes no
__version__; the real API and the version both live somewhere less obvious. - Most of the anti-detection-named methods carry no in-code docstring, so reading the source tells you names, not behavior.
Outside what any of these numbers cover, and therefore untested here: everything about real-world anti-bot effectiveness (out of scope by design), per-page memory, proxy and profile handling, throughput at scale, and any platform other than macOS arm64. The footprint and import figures were produced without launching a browser at all; the recall and disclosure figures come from browsers that only ever talked to a fixture on 127.0.0.1.
Who it's for, and who should skip it
Botasaurus fits if you want a framework rather than a part. If you're starting a scraping project from a blank file and you'd rather adopt a structure than assemble one — decorators for the entry points, caching and parallelism handled, output writers built in — this is a coherent, MIT-licensed option. MIT is permissive, but your use and distribution model still deserves its normal compliance review. Teams that already think in Scrapy-or-Crawlee terms will find the framing familiar.
Skip it, or at least think twice, if your deployment target is size-sensitive. A 122 MB install with numpy and gevent in the tree is a lot to ship into a slim container when your actual need is "drive a browser and pull some fields." A focused driver gets you the automation at a fraction of the weight, at the cost of writing the surrounding plumbing yourself. And skip it entirely if what you want is a decision about anti-detection effectiveness, because that's the one thing I deliberately didn't test — you'd be trusting a marketing claim I neither confirmed nor refuted.
Alternatives, and where Thunderbit fits
The honest framing first: Botasaurus is free, MIT-licensed, and self-hosted. You run the fleet, you manage the updates, and you own the entire dependency tree — all 44 packages of it — including patching, license compliance, and whatever numpy decides to do in a future release. For a lot of teams that ownership is exactly what they want, and no managed service beats "a framework you already have" on raw cost.
Within open source, the useful comparisons are by shape. If you're at the framework end with Botasaurus, Scrapy and Crawlee are the obvious peers to weigh against it — mature, opinionated, and each with its own conventions to adopt. If you want LLM-ready Markdown out of a page rather than a framework to structure a crawler, Crawl4AI and the content-focused Trafilatura are pointed at that job specifically. If you like the Python-and-anti-detect angle but want something lighter than a meta-package, Scrapling is worth a look, and if a compiled language is on the table, the Go library Colly trades JavaScript rendering away for speed and a tiny footprint. Any browser-driving option, Botasaurus included, inherits the cost profile our Playwright and Puppeteer comparison lays out — real browsers are not cheap to run, which is part of why the framework around them weighs what it does.
Where a managed API enters is a different point in the same pipeline. Botasaurus is the self-hosted developer stack; Thunderbit sells the managed one, aimed at the same developers. The Open API is two endpoints. POST /distill (1 credit) returns a page as clean, LLM-ready Markdown, with rendering and anti-bot handled server-side, so you don't provision a browser or a dependency tree at all. POST /extract (20 credits) returns structured JSON against a JSON Schema you define, with renderMode set to none, basic, or full depending on how much browser the page actually needs. Both have batch versions for up to a hundred URLs at once. There's an MCP server for agents and coding assistants — thunderbit_suggest_fields is free and tells you what a page exposes before you spend anything — and a CLI via npx @thunderbit/thunderbit-cli for cron and CI. For non-developers who'd rather not touch any of that, the Chrome extension runs the same engine as a no-code tool, and the walkthroughs on Thunderbit's YouTube channel cover the common workflows.
The trade-off is where the work lives, not which tool is better. Botasaurus keeps the framework, browser fleet, dependencies, infrastructure, and maintenance on your side of the line without a vendor fee per request; compute, bandwidth, proxies, and operations still cost money. A managed API takes rendering and schema-shaped output off your plate and charges per call — you can size that against a self-hosted setup on the pricing page.
Try Thunderbit for Web Data Extraction
Verdict
Botasaurus is a reasonable candidate if you want an all-in-one Python framework and accept its dependency footprint. The three-decorator design is clean, the driver surface is broad, and it passed install/import smoke tests on Python versions newer than its classifiers claim. On my local fixture its default snapshot also caught content injected 300 ms after load, where the other tested stacks missed it at 100 ms; that is a fixture result, not a general ranking.
Size the claims correctly, though. It's a 122 MB, 44-package install where the driver is about 4 MB and the rest is numpy, lxml, gevent, and company — roughly 7x a focused driver, and that weight is your container image and your cold-deploy time. The 0.08 ms top-level import is a hollow front door, not a light framework; the ~135 ms engine import is the number that bills you. That forgiving default read costs about 250 ms on every navigation. And on the default-disclosure question I could actually answer, the picture is narrower than the marketing implies: one boolean differs from stock Puppeteer, the headless user-agent still says HeadlessChrome, and every other property I measured was identical across all four stacks. The anti-detection pitch that sells the tool is the one thing this review doesn't grade — I confirmed those methods exist, drove the driver against a page on my own machine, and stopped there, on purpose. Know the footprint, ignore the flattering import number, and treat the stealth marketing as an open question, and Botasaurus is a fair framework that does exactly what a framework does. Expect a featherweight driver and you'll be surprised at docker build time.
Try Thunderbit for Web Data Extraction Get Started Free
FAQs
Is Botasaurus free, and what license is it under?
It's free and MIT-licensed — both the botasaurus meta-package and the botasaurus-driver engine carry the OSI-approved MIT classifier. MIT is permissive, so there's no copyleft obligation and it's friendly to commercial use, which is a meaningful contrast with comparable anti-detect drivers that ship under AGPL-3.0. One caveat: MIT covers Omkar Cloud's own packages, not automatically the roughly 40 transitive dependencies the install pulls in, so run your own license scan before a whole-tree corporate adoption.
How big is a Botasaurus install, and why does import botasaurus look instant anyway?
A clean pip install botasaurus produced a 122.3 MB site-packages tree across 44 packages on my machine. The browser driver itself is only about 4 MB — the weight comes from the meta-package pulling a broad dependency tree, led by numpy (30.9 MB), lxml (19.2 MB), botasaurus_requests (12.6 MB), gevent (11.3 MB), and pygments (8.4 MB). It's roughly 7x the footprint of a focused driver like nodriver on the same machine. Nothing there is a defect; it's the cost of "batteries included," and it matters most for container image size. The import time is where that footprint hides: import botasaurus measures about 0.08 ms, but only because the top-level package is nearly empty — no __version__, almost no public names, so importing it does almost nothing. The import that costs you is the engine, from botasaurus_driver import Driver, at about 135 ms, with resident memory around 29–30 MB after that import and before any browser starts. Sizing a serverless cold start? Measure the engine import, not the hollow top-level one.
What does Botasaurus expose about itself, and was it tested against real anti-bot systems?
The first half was measured; the second half was deliberately not. On a page I served from 127.0.0.1, driving the same Chrome build as the controls: navigator.webdriver comes back false, where stock Playwright and stock Puppeteer both report true. That value is set at browser-launch time, not by patching the property — the descriptor is still Chrome's own native getter. Beyond that boolean, almost everything matched the controls exactly: same platform string, five plugins, twelve cores, 16 GB reported device memory, same window.chrome shape, no Permissions-API contradiction, and no cdc_-style leftovers on document or window. Headless runs still announce HeadlessChrome/151.0.0.0 in the user-agent, same as both controls — the Driver constructor takes a user_agent parameter, but nothing sets it for you. As for effectiveness: I never pointed the driver at a live site, never contacted an anti-bot service, and never touched a CAPTCHA. The driver does include a set of methods named for anti-detection, whose existence I confirmed, but I did not call them, exercise their behavior against any target, measure a success rate, or describe a mechanism. The disclosure table above tells you what the stack announces, and nothing about who is listening or what they do about it.
Does Botasaurus handle JavaScript-rendered content properly?
Yes, and its default is more forgiving than most. On a fixture with three content classes, driver.get() + driver.page_html returned 2 of 3 at an 800 ms injection delay — it executes JavaScript correctly, but reads before very late content lands — and driver.wait_for_element() returned 3 of 3. The distinctive part is where the default read gives up: Botasaurus still catches content injected 300 ms after load, where nodriver, Playwright and Puppeteer all lose it at 100 ms. That's wait_for_complete_page_load=True in the constructor, and it costs about 250 ms per navigation.
How do I actually import and use Botasaurus after installing it?
Not the way you'd guess. The top-level botasaurus namespace is nearly empty, so the real API lives in submodules: from botasaurus.browser import browser, Driver, from botasaurus.request import request, and from botasaurus.task import task. You decorate a plain function with @browser, @request, or @task and the framework handles the surrounding driver, caching, and output. Since there's no botasaurus.__version__, use importlib.metadata if you need to log which build you're running.


