ScraperAPI vs ScrapingBee is a workflow choice, not a universal speed ranking. Compare the operation you need on the same authorized URLs, then check whether the delivered content satisfies your application. HTTP success alone is insufficient: an error page or incomplete HTML can still arrive with a successful status.
Compare equivalent requests before comparing prices
| Control | Record for both providers |
|---|---|
| Target URL and permission | The same owned or permitted page |
| Rendering | The documented setting used for that request |
| Output | HTML, extracted text or a structured object |
| Timeout and retries | The caller deadline, attempts and terminal error |
| Billing | The charged operation and feature modifiers from current terms |
ScraperAPI documentation and ScrapingBee documentation describe each product’s request options. Use those current contracts rather than copying one provider’s parameters into the other. A raw HTML response and an extracted JSON record cannot be compared by byte count alone.
| Documented interface | Control to record before a comparison |
|---|---|
| ScraperAPI synchronous fetch | Its documented page-HTML response and chosen rendering parameters |
| ScrapingBee HTML API | render_js defaults to true; set the intended behavior explicitly |
| ScrapingBee output options | return_page_text, return_page_markdown and json_response change what the consumer receives |
These controls explain why the same URL alone is not a fair comparison. For example, comparing a rendered request with a non-rendered request mixes different operations. Verify the selected endpoint’s current output format rather than apply an HTML parser to a transformed JSON response.
Define content acceptance independently of the vendor
The following runnable harness accepts request callbacks. Its local demonstration uses synthetic responses; it makes no competitor request and produces no vendor benchmark. For a live run, each callback must use that provider’s documented client and bounded network timeout. The HTML check excludes script, style and head text. It does not simulate a rendered DOM.
from html.parser import HTMLParser
from time import perf_counter
class PageText(HTMLParser):
def __init__(self):
super().__init__()
self.hidden = 0
self.parts = []
def handle_starttag(self, tag, attrs):
if tag in {"script", "style", "head"}:
self.hidden += 1
def handle_endtag(self, tag):
if tag in {"script", "style", "head"}:
self.hidden = max(0, self.hidden - 1)
def handle_data(self, data):
if not self.hidden:
self.parts.append(data)
def check_provider(name, target_url, call, required_terms):
started = perf_counter()
try:
response = call(target_url)
response.raise_for_status()
parsed = PageText()
parsed.feed(response.text)
text = " ".join(parsed.parts)
missing = [term for term in required_terms if term not in text]
return {"provider": name, "url": target_url,
"elapsed_seconds": perf_counter() - started,
"status": "usable" if text.strip() and not missing else "incomplete",
"missing": missing, "text": text}
except Exception as error:
return {"provider": name, "url": target_url,
"elapsed_seconds": perf_counter() - started,
"status": "failed", "error_type": type(error).__name__}
def compare_calls(target_url, providers, required_terms):
return [check_provider(name, target_url, call, required_terms)
for name, call in providers.items()]
if __name__ == "__main__":
import json
class FixtureResponse:
text = "<main><h1>Fixture article</h1><p>Required detail.</p></main>"
def raise_for_status(self):
return None
rows = compare_calls("https://example.test/owned-fixture",
{"synthetic_a": lambda url: FixtureResponse(),
"synthetic_b": lambda url: FixtureResponse()}, ["Required detail"])
print(json.dumps({"fixture_only": True, "rows": rows}, indent=2))
Inspect failures as part of the comparison
- An HTTP failure should be recorded as failed, with an error type rather than an empty success.
- An HTTP 200 page that lacks a required paragraph should be incomplete.
- A marker found only in a script should not satisfy the static-content test.
- For a JavaScript page, add a rendered ready-state check instead of declaring the static HTML test sufficient.
Store inputs, timestamps, provider settings, expected fields and the original response for every permitted sample. Repeat representative cases before reporting a timing distribution. The elapsed field in the synthetic example only measures local callback execution; it says nothing about either provider’s production latency or success rate.
Make the choice from the missing work
Choose the provider whose documented operation and observed output reduce the work your application still has to perform. If the task is search-result discovery, use the ScraperAPI alternatives decision map to distinguish a SERP service from a page-fetching service. If your own browser is the expensive stage, use the browser resource measurement method rather than transfer a generic savings percentage from a comparison post.
SearchCans publishes this guide. Its product examples are first-party illustrations, not an independent vendor performance study.