ScrapingBee Alternatives: Cost per Rendered Page and the Proxy Route
ZenRows, ScraperAPI, Zyte API and Firecrawl compared with ScrapingBee on what a rendered page costs in credits, and when your own scraper and proxies fit.
ScrapingBee sells a credit-metered HTML API. The plan price buys a block of credits, and how many pages those credits fetch depends on which features each request switches on. That is the number worth comparing when you look at alternatives: not the plan price, and not the headline credit count, but what one rendered page costs on the request you actually send. This page works that out for ScrapingBee, ZenRows, ScraperAPI, Zyte API and Firecrawl, then covers the other kind of alternative, running your own scraper through proxies. The ZenRows alternatives guide explains the API-or-proxies split in general; this one stays on the arithmetic.
Vendor figures were checked October 19, 2026, on each vendor's own pricing page or documentation. Nothing was benchmarked, so this page compares billing, not output quality.
How ScrapingBee bills
The entry plans are $19 a month for 75,000 credits and 25 concurrent requests on the Hobby plan and $49 a month for 250,000 credits and 50 concurrent requests on the Freelance plan, with prices listed before VAT. New accounts get 1,000 free API credits with no card, and paid plans carry credits valid for one month, with no rollover.
Each request then draws on that balance by weight: 1 credit without JavaScript rendering, 5 with it, the default; 10 or 25 with premium proxies; 75 with stealth proxies and rendering. Two details in that line change the arithmetic more than any plan choice:
- Rendering is on unless you turn it off. A request with no options set pays the rendered weight. If a target serves its data in the initial HTML, switching rendering off for it cuts that request to a fifth.
- Auto-Mode moves the decision to ScrapingBee. It offers Auto-Mode, which charges only for the configuration that succeeds and 0 credits when every one fails, so you pay for the cheapest setup that worked rather than guessing per site.
Billing on failure is also specific: by default a target's 403 or 503 reaches you as a 500, and a 500 from ScrapingBee is not billed. Asking for the target's own status code changes that, because every answer below 500 is then billed. Beyond plain HTML the API has an option that returns the page's main content as Markdown, screenshots of the viewport, the full page or one element picked by CSS selector, and an option to use its features with your own proxy provider.
ScrapingBee and its alternatives side by side
The first five rows are managed scraping APIs that render and route for you. The last row is a proxy service, which leaves rendering to your own code.
ScraperAPI's weights are per parameter, and some domains carry their own credit cost, such as Google and Bing at 25 credits a request. ZenRows' Build figure is its monthly-billing price from the API docs; its pricing page defaults to annual billing and shows a lower monthly number.
What a rendered page costs on each entry plan
Take a workload where every page needs JavaScript rendering and some share also needs premium routing. Divide each plan's credits by the weight of that request, and you get the most pages the plan can fetch if every request is that kind:
| Plan | Credits | Rendered pages | Rendered with premium proxies |
|---|---|---|---|
| ScrapingBee Hobby | 75,000 | 15,000 at 5 credits | 3,000 at 25 credits |
| ScrapingBee Freelance | 250,000 | 50,000 at 5 credits | 10,000 at 25 credits |
| ZenRows Build | 45,000 | 9,000 at 5 credits | 1,800 at 25 credits |
| ScraperAPI Hobby | 100,000 | 10,000 at 10 credits | 4,000 at 25 credits |
| Firecrawl Hobby | 5,000 | 5,000 at 1 credit on a basic scrape | No premium weight on its pricing page |
Then divide the plan price by the page count to get a cost per rendered page, which is the figure to put side by side. Three things move it once real traffic starts:
- The mix. Few workloads are all one kind. If two thirds of your pages need only the static fetch, a ScrapingBee request with rendering off costs 1 credit, and the plan stretches a long way further than the table shows.
- Per-domain weights. ScraperAPI prices some search and marketplace domains above its parameter weights, so check your exact targets in its dashboard before sizing a plan from the table.
- What failure costs. A ScrapingBee 500 is free and a Firecrawl 403 is not. On a target that refuses a third of your requests, that difference can outweigh the plan price.
Zyte API is missing from the table because its price per response depends on the tier it assigns to each site, so the only honest figure comes from sending your own URLs through it. For a per-request view of proxies against APIs, the per-request cost guide does the same division from the proxy side.
When your own scraper with proxies is the better alternative
A proxy service is a different purchase: you buy exits and run the browser yourself, so rendering or a hard target does not multiply a per-request price. It still costs something: a rendered page downloads scripts and images and holds a connection longer, which shows up on a per-gigabyte or per-connection plan. It tends to fit when one of these is true:
- Most targets return their data in the initial HTML, so a hosted browser is a cost you do not need.
- You already run Playwright, Puppeteer or Scrapy, with retry and parsing code you trust.
- The volume is large and steady enough that paying per page adds up faster than paying per connection.
- You need control over sessions, headers and retry timing that a hosted API does not expose.
Node4 sells three products for this. Rotating Unmetered has no byte meter and is priced by how many connections you hold open at once, from $29.00/month for 250 connections ($0.116 per connection) on the unmetered pricing page. Rotating Datacenter draws from the same pool and is billed by the gigabyte, from $5.90/month for 10 GB ($0.59/GB). Residential proxies cover sites that refuse datacenter addresses, from $7.47/month for 3 GB ($2.49/GB). The rotating gateway picks an exit from the pool for each HTTPS tunnel or plain-HTTP request, and a session id holds one exit for up to 30 minutes. Node4 adds no rendering, extraction or retry layer, and its datacenter exits are in the US, Italy and Spain.
Before switching, measure how much of your target list works without a browser at all. This script fetches a list of URLs through the gateway and sorts the answers by what a plain HTTP client actually received:
import os
import sys
from collections import Counter
from html.parser import HTMLParser
import requests
PROXY = (
"http://" + os.environ["NODE4_USER"] + ":" + os.environ["NODE4_PASS"]
+ "@gw-rotating_datacenter.node4.io:8083"
)
MIN_WORDS = int(os.environ.get("MIN_WORDS", "150")) # set per site
EXPECT = os.environ.get("EXPECT_TEXT", "").lower() # text every good page has
REFUSAL_HINTS = ("verify you are human", "access denied", "unusual traffic", "request blocked")
GATE_HINTS = ("accept all cookies", "sign in to continue", "log in to continue")
class VisibleText(HTMLParser):
"""Text a reader would see, from <main>/<article> when the page has one."""
SKIP = {"script", "style", "noscript", "template", "svg"}
def __init__(self):
super().__init__()
self.skip = self.depth = 0
self.has_main = False
self.body, self.main = [], []
def handle_starttag(self, tag, attrs):
if tag in self.SKIP:
self.skip += 1
if tag in ("main", "article"):
self.depth += 1
self.has_main = True
def handle_endtag(self, tag):
if tag in self.SKIP and self.skip:
self.skip -= 1
if tag in ("main", "article") and self.depth:
self.depth -= 1
def handle_data(self, data):
if not self.skip:
(self.main if self.depth else self.body).append(data)
def classify(r: requests.Response) -> str:
if r.status_code in (401, 403, 407, 429, 451):
return "refused"
if r.status_code != 200:
return "other_status"
page = VisibleText()
page.feed(r.text)
text = " ".join(page.main if page.has_main else page.body).lower()
if any(h in text[:2000] for h in REFUSAL_HINTS):
return "refused"
if any(h in text[:2000] for h in GATE_HINTS) or "/login" in r.url or "/signin" in r.url:
return "consent_or_login"
if (EXPECT and EXPECT in text) or (not EXPECT and len(text.split()) >= MIN_WORDS):
return "static_candidate"
return "thin_needs_review"
counts = Counter()
for url in (line.strip() for line in open(sys.argv[1]) if line.strip()):
try:
r = requests.get(url, proxies={"http": PROXY, "https": PROXY}, timeout=30)
label = classify(r)
except requests.RequestException:
label = "network_error"
counts[label] += 1
print(label, url, sep="\t")
print(dict(counts))The page size alone says nothing: a JavaScript shell can be 9,000 characters of script around an empty <main>. So the script counts the words a reader would see, inside <main> or <article> when the page has one, and labels each URL:
static_candidate: the plain response already holds at leastMIN_WORDSvisible words. When you setEXPECT_TEXTfor a site (a phrase every good page carries, such as a price label), that phrase decides instead of the word count. A plain client through proxies may be enough for these. Spot-check a sample, because a word count does not prove the fields you need are there.thin_needs_review: too little visible text. This is either a page built by JavaScript, where a browser or the site's JSON endpoint would help, or a genuinely short page; lowerMIN_WORDSor setEXPECT_TEXTfor that site and run it again.refusedandconsent_or_login: a 401/403/407/429/451, a challenge page, a cookie wall or a login redirect. These are not rendering problems, and a browser does not fix them by itself.other_statusandnetwork_error: the answer was some other status, or no answer arrived at all.
The phrase lists are examples, not a complete detector; add the wording your targets use. Run it on a few hundred real URLs. A high static_candidate share suggests your own scraper through proxies covers most of the job. A high thin_needs_review share that survives a closer look means you would be running a browser for those pages, and one of the APIs above may cost less than the engineering time. A high refusal share is a question about rate, address type and the site's terms before it is a question about rendering.
There is also a middle path: ScrapingBee keeps its rendering and extraction features when you bring your own proxies. Check how such requests are billed on your plan before planning around it. The scraping API vs proxies guide works through that trade-off in more detail.
The decision
Stay with ScrapingBee if the rendered weight fits your budget and you can switch rendering off for static targets. Look at ZenRows if its weights suit a premium-heavy mix, at ScraperAPI once you have checked its per-domain costs for your targets, at Zyte API if you want to pay per successful response at a site-specific rate, and at Firecrawl if your pages are mostly simple and you want Markdown-first output. Move to your own scraper with Node4 proxies when most pages work without a browser or the volume makes a per-page price the largest line in the budget. The Firecrawl alternatives and Jina Reader alternatives guides cover the Markdown-first services in more depth.
Node4's Scrape API, in development
Node4 is building a Scrape API that will render a page and return HTML, Markdown or a screenshot from one request. It is not available yet, and it has no published price or launch date; the waitlist is open for anyone who wants to hear when it opens. The proxy products above are what Node4 sells today.