ScrapingBee Alternative for Screenshots

ScrapingBee can return screenshots, but it is a scraping API. Compare credits, parameters and output with a screenshot API, plus a test script in 3 languages.

By Simon Chiu · Updated

Disclosure: RenderScreenshot is our product, and we compete with ScrapingBee only on screenshots. ScrapingBee is a web scraping API; screenshots are one of its outputs. All ScrapingBee facts on this page come from ScrapingBee's documentation, checked on 2026-10-15.

If you call ScrapingBee with screenshot=true, you use a scraping product to do a rendering job. That works, and for some teams it is the right call. It also means you pay scraping prices per request, configure the page through scraping parameters, and get a narrower set of image controls. This page measures that trade-off on screenshot tasks only. If your main job is extracting data, keep ScrapingBee.

For the full list of screenshot services, see our screenshot API comparison.

TL;DR

Keep ScrapingBee if Use a screenshot API if
You need the HTML, extracted fields and an image from the same request You only need the image
Your targets block normal browsers and you need its premium or stealth proxies Your targets are your own pages, customer sites or ordinary public pages
You already pay for a large credit balance that covers scraping and screenshots You want one credit per screenshot, no matter which options you use
You use js_scenario flows to log in or click before the capture You need output controls: WebP, AVIF, PDF, resize, transparency, presets

Why the cost model matters

ScrapingBee charges credits per request, and the price of a request depends on how it is made. Its credit table lists:

ScrapingBee configuration Credits per request
Classic proxy, no JavaScript 1
Classic proxy, JavaScript rendering (the default) 5
Premium proxy, no JavaScript 10
Premium proxy, JavaScript rendering 25
Stealth proxy, JavaScript rendering 75

Source: ScrapingBee documentation, "Credit cost for your requests", checked on 2026-10-15. A screenshot needs a rendered page, so the cheapest screenshot is 5 credits. To compare plans, divide a plan's monthly credits by the credits per screenshot you actually use:

screenshots per month = plan credits / credits per screenshot request
                      = plan credits / 5      (classic proxy)
                      = plan credits / 25     (premium proxy)
                      = plan credits / 75     (stealth proxy)

The same balance also pays for your scraping calls, so screenshots and scraping compete for it. RenderScreenshot charges 1 credit per rendered screenshot. Blocking, full-page capture, dark mode and wait conditions do not change the price.

Cost at three volumes

RenderScreenshot prices as listed on /pricing, checked on 2026-10-15. For ScrapingBee, take the plan price from ScrapingBee's pricing page and apply the formula above; we did not reproduce their plan prices here because we could not load that page in our check.

Screenshots per month ScrapingBee credits needed (classic / premium proxy) RenderScreenshot
1,000 5,000 / 25,000 Starter, $10/month (2,500 credits)
10,000 50,000 / 250,000 Growth, $40/month (12,000 credits)
100,000 500,000 / 2,500,000 More than Scale's 60,000 credits ($100/month); contact us

At high volume on easy pages, a large ScrapingBee plan can cost less per image than a screenshot API. At premium-proxy rates the math reverses fast. Run the formula with your real proxy mix before you decide.

Parameter mapping

Task ScrapingBee RenderScreenshot
Endpoint and auth GET https://app.scrapingbee.com/api/v1/?api_key=... POST /v1/screenshot with Authorization: Bearer, or GET with a signed URL
Turn on a screenshot screenshot=true Always an image (or PDF)
Full page screenshot_full_page=true capture.mode: "full_page", capture.scroll.simulate: true for lazy images
One element screenshot_selector=<css> capture.mode: "element", capture.selector
Viewport window_width, window_height viewport.width, viewport.height, viewport.scale
Wait time wait (ms) wait.delay (ms)
Wait for an element wait_for=<css> wait.for_selector
Wait for a browser event wait_browser wait.until: load, domcontentloaded, networkidle
Ads block_ads=true block.ads: true
Cookie banners Click it with js_scenario block.cookie_banners: true
Chat widgets, trackers block_resources / custom JS block.chat_widgets, block.trackers
Run JS before capture js_scenario page.scripts, page.click
Country / proxy pool country_code, premium_proxy, stealth_proxy Not offered
Output format PNG output.format: png, jpeg, webp, avif, pdf
Dark mode, timezone, locale Custom JS or headers browser.dark_mode, browser.timezone, browser.locale
HTML of the page json_response=true Not offered; use a scraping tool

Our parameter references: capture, wait, blocking, network.

Measure both on your own URLs

We have not published head-to-head numbers yet, because a fair run needs a paid ScrapingBee plan, our shared test corpus, and the same day for both providers. The scripts below do that run for you. Each one requests the same full-page 1280 x 800 capture with ads blocked, times it, and checks that the response body really is an image (an error can come back as JSON or HTML, so the script checks the first bytes of the body as well as the status).

How to read the results:

  1. Use at least 5 URLs: a static page, a long page with lazy images, a page with a cookie banner, a single-page app, and the hardest page you capture in production.
  2. Run each URL 3 times or more. Report p50 and p95, not the average.
  3. Keep cache.enabled: false on our side, or you will measure our cache rather than a render.
  4. Open the images. Check that the bottom of long pages rendered and that no banner covers the content.

Node.js (Node 22+)

// Same screenshot task through ScrapingBee and RenderScreenshot.
// Prints latency, bytes, and the PNG/JPEG signature check for each provider.
// Usage: SCRAPINGBEE_API_KEY=... RS_API_KEY=... node compare.mjs https://example.com
const target = process.argv[2] ?? 'https://example.com';
const RUNS = Number(process.env.RUNS ?? 3);

const providers = {
  async scrapingbee(url) {
    const q = new URLSearchParams({
      api_key: process.env.SCRAPINGBEE_API_KEY,
      url,
      screenshot: 'true',
      screenshot_full_page: 'true',
      window_width: '1280',
      window_height: '800',
      block_ads: 'true',
    });
    return fetch(`https://app.scrapingbee.com/api/v1/?${q}`, { signal: AbortSignal.timeout(90_000) });
  },
  async renderscreenshot(url) {
    return fetch('https://api.renderscreenshot.com/v1/screenshot', {
      method: 'POST',
      headers: { Authorization: `Bearer ${process.env.RS_API_KEY}`, 'Content-Type': 'application/json' },
      body: JSON.stringify({
        url,
        viewport: { width: 1280, height: 800 },
        capture: { mode: 'full_page', scroll: { simulate: true } },
        block: { ads: true, cookie_banners: true },
        cache: { enabled: false }, // measure renders, not cache hits
      }),
      signal: AbortSignal.timeout(90_000),
    });
  },
};

const isImage = (b) =>
  (b[0] === 0x89 && b[1] === 0x50 && b[2] === 0x4e && b[3] === 0x47) || (b[0] === 0xff && b[1] === 0xd8);

let failed = false;
for (const [name, call] of Object.entries(providers)) {
  for (let i = 1; i <= RUNS; i++) {
    const t0 = performance.now();
    try {
      const res = await call(target);
      const body = Buffer.from(await res.arrayBuffer());
      const ms = Math.round(performance.now() - t0);
      const ok = res.ok && isImage(body);
      if (!ok) failed = true;
      console.log(JSON.stringify({ provider: name, run: i, status: res.status, ms, bytes: body.length, image: ok,
        credits: res.headers.get('x-credits-used') }));
    } catch (err) {
      failed = true;
      console.log(JSON.stringify({ provider: name, run: i, error: err.message }));
    }
  }
}
process.exitCode = failed ? 1 : 0;

credits reads our X-Credits-Used header, so it is empty for ScrapingBee rows. Use the credit table above for those.

Python

"""Same screenshot task through ScrapingBee and RenderScreenshot (Python 3.9+, requests)."""
import json
import os
import sys
import time

import requests

TARGET = sys.argv[1] if len(sys.argv) > 1 else "https://example.com"


def scrapingbee(url):
    return requests.get("https://app.scrapingbee.com/api/v1/", params={
        "api_key": os.environ["SCRAPINGBEE_API_KEY"], "url": url, "screenshot": "true",
        "screenshot_full_page": "true", "window_width": 1280, "window_height": 800, "block_ads": "true",
    }, timeout=90)


def renderscreenshot(url):
    return requests.post("https://api.renderscreenshot.com/v1/screenshot",
                         headers={"Authorization": f"Bearer {os.environ['RS_API_KEY']}"},
                         json={"url": url, "viewport": {"width": 1280, "height": 800},
                               "capture": {"mode": "full_page", "scroll": {"simulate": True}},
                               "block": {"ads": True, "cookie_banners": True},
                               "cache": {"enabled": False}},
                         timeout=90)


failed = False
for name, call in (("scrapingbee", scrapingbee), ("renderscreenshot", renderscreenshot)):
    t0 = time.perf_counter()
    try:
        r = call(TARGET)
        ok = r.ok and (r.content[:4] == b"\x89PNG" or r.content[:2] == b"\xff\xd8")
        failed |= not ok
        print(json.dumps({"provider": name, "status": r.status_code, "ms": round((time.perf_counter() - t0) * 1000),
                          "bytes": len(r.content), "image": ok,
                          "credits": r.headers.get("x-credits-used")}))
    except requests.RequestException as e:
        failed = True
        print(json.dumps({"provider": name, "error": str(e)}))
sys.exit(1 if failed else 0)

Ruby

# Same screenshot task through ScrapingBee and RenderScreenshot (Ruby 3+, stdlib only).
require 'net/http'
require 'json'

target = ARGV[0] || 'https://example.com'

def scrapingbee(url)
  uri = URI('https://app.scrapingbee.com/api/v1/')
  uri.query = URI.encode_www_form(api_key: ENV.fetch('SCRAPINGBEE_API_KEY'), url: url, screenshot: true,
                                  screenshot_full_page: true, window_width: 1280, window_height: 800,
                                  block_ads: true)
  Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 90) { |h| h.get(uri.request_uri) }
end

def renderscreenshot(url)
  uri = URI('https://api.renderscreenshot.com/v1/screenshot')
  body = { url: url, viewport: { width: 1280, height: 800 },
           capture: { mode: 'full_page', scroll: { simulate: true } },
           block: { ads: true, cookie_banners: true }, cache: { enabled: false } }
  Net::HTTP.start(uri.host, uri.port, use_ssl: true, read_timeout: 90) do |h|
    h.post(uri.path, body.to_json, 'Authorization' => "Bearer #{ENV.fetch('RS_API_KEY')}",
                                   'Content-Type' => 'application/json')
  end
end

failed = false
{ 'scrapingbee' => method(:scrapingbee), 'renderscreenshot' => method(:renderscreenshot) }.each do |name, call|
  t0 = Process.clock_gettime(Process::CLOCK_MONOTONIC)
  res = call.call(target)
  ms = ((Process.clock_gettime(Process::CLOCK_MONOTONIC) - t0) * 1000).round
  body = res.body.b
  ok = res.is_a?(Net::HTTPSuccess) && (body.start_with?("\x89PNG".b) || body.start_with?("\xFF\xD8".b))
  failed ||= !ok
  puts({ provider: name, status: res.code.to_i, ms: ms, bytes: body.bytesize, image: ok,
         credits: res['x-credits-used'] }.to_json)
end
exit(failed ? 1 : 0)

Tested on 2026-10-15 with Node 22.18.0, Python 3.14 (requests 2.34.2) and Ruby 4.0.7 against a local mock that stands in for both APIs. The mock confirmed the query strings and JSON bodies. Real latency and image quality need your own keys.

Response and workflow differences

  • What comes back. ScrapingBee returns the image bytes for screenshot=true, or a JSON body with the screenshot and the HTML when you add json_response=true. We return the image bytes with metadata in headers (X-Screenshot-Width, X-Screenshot-Height, X-Credits-Used, X-Cache-URL), or JSON with a CDN URL when you send Accept: application/json. See response headers.
  • Embedding. ScrapingBee requests carry the API key in the query string, so they belong on your server. Our signed URLs carry only a public key ID, an expiry and an HMAC signature, so you can put them in an <img> tag or an og:image meta tag.
  • Caching. Every ScrapingBee request is a fresh request. Our renders are cached for 24 hours by default (up to 30 days), and the X-Cache-URL gives you a public CDN link to the stored image. See caching.
  • Batches. We accept up to 100 URLs per call on the largest plan through the batch endpoint. With ScrapingBee you run your own loop within its concurrency limit.

When to keep ScrapingBee

Keep it when the screenshot is a side product of scraping: you already fetch the page for data, and the image is evidence of what you scraped. One request then gives you both, and moving only the screenshot to another vendor adds a second call per page. Keep it also when your targets block datacenter traffic and you need its premium or stealth proxy pool. We do not run an anti-bot proxy tier.

Switch the screenshots when they are the product: link previews, social cards, archives, visual monitoring, reports. Then image controls, caching and a flat per-image price matter more than extraction. To check this on your own URLs, get a free key and run the comparison script.

Frequently asked questions

How many ScrapingBee credits does one screenshot cost?
ScrapingBee's documentation lists 5 credits for a request on the classic proxy with JavaScript rendering (the default), 25 with premium proxies and JavaScript, and 75 with stealth proxies. A screenshot needs a rendered page, so plan on at least 5 credits per screenshot.
Can ScrapingBee return a WebP or PDF screenshot?
We found no output-format parameter for screenshots in ScrapingBee's documentation in October 2026. Check their docs before you rely on a format other than the default.
Can I get the HTML and the screenshot in one call?
With ScrapingBee, yes, through json_response. RenderScreenshot returns the image plus metadata headers (title, size, cache URL); it is not a scraping tool, so keep ScrapingBee if you need the full HTML or extraction rules.
Does RenderScreenshot offer premium or stealth proxies?
No. We do not sell proxy tiers, and every render costs 1 credit. If your targets block datacenter traffic, keep ScrapingBee or another scraping API for those URLs.

Related in this topic

Try RenderScreenshot free

150 free credits every month, no credit card required.