Bulk Website Screenshots: Full Page, PDF, No Cookie Banners
Pricing
from $0.52 / 1,000 screenshots
Bulk Website Screenshots: Full Page, PDF, No Cookie Banners
Screenshot a list of URLs or call it as an instant API: full-page or viewport, PNG, JPEG, WebP or PDF, desktop, tablet or mobile, dark mode. Cookie banners and ads hidden. $0.75 per 1,000 screenshots ($0.52 on Business) + usage. No screenshot fee for failed pages.
Pricing
from $0.52 / 1,000 screenshots
Rating
0.0
(0)
Developer
Open Data Tools
Maintained by CommunityActor stats
0
Bookmarked
1
Total users
0
Monthly active users
14 hours ago
Last modified
Categories
Share
Bulk Website Screenshots: Full Page, PDF, No Cookie Banners
This tool takes screenshots of a list of web pages in one run. Paste in your URLs and get a clean image (or PDF) of each page: the whole page from top to bottom or just the first screen, on desktop, laptop, tablet or mobile, in PNG, JPEG, WebP or PDF. Cookie pop-ups and ads are hidden automatically, and lazy-loaded images are loaded before the capture so long pages don't come out half empty. You pay $0.75 per 1,000 screenshots on the Free plan ($0.52 on Business) plus Apify platform usage. Pages that fail have no screenshot fee, though time spent counts as usage.
Who it's for
- Agencies and freelancers capturing client sites, competitor pages or portfolio thumbnails.
- SEO and marketing teams keeping a visual record of landing pages, ads and search pages over time.
- Developers and QA teams checking how pages look on different devices after a release.
- Researchers, archivists and compliance teams saving proof of what a page showed on a given day.
- AI agents and automations that need a picture of a page.
Why use it
- Full-page shots that work: the page is scrolled first so lazy-loaded images and sections appear, then captured in one piece. Very long pages are cut at a height you choose instead of failing, and empty space at the bottom of a page is trimmed off so the image ends where the content ends.
- No cookie banners, ads or empty ad boxes: uses the same block lists as browser ad blockers (EasyList, EasyPrivacy and Fanboy's Cookie List) plus our own rules for the big consent providers (OneTrust, Cookiebot, Usercentrics, Quantcast, Didomi, TrustArc, Sourcepoint and more). Sign-up, newsletter, donation and "turn off your ad blocker" pop-ups are hidden too, and pages they freeze are unlocked. Nothing is clicked or accepted on your behalf.
- No screenshot fee for failed pages: pages that don't load, return an error, show a bot check, come out blank or nearly blank, show the site's own error page (such as "Oops, unexpected error" or "Something went wrong"), show only loading placeholders (grey boxes where the content should be) or are file downloads get a row with a plain-English reason and no screenshot fee, though time spent counts as usage.
- Slow pages still get captured: the Time limit per page (seconds) (
pageTimeoutSecs, 25 seconds by default) covers loading only. If a page is still loading when it is reached, the tool stops waiting and captures what has appeared so far, with the note captured before the page finished loading. This also covers pages whose fonts or scripts never finish. Scrolling and taking the screenshot get their own time after that (about 22 seconds at the default settings, or less if loading ran long). If a full-page shot still can't be taken in time (for example on a page whose animations keep the browser busy), the first screen is captured instead, markedtruncatedwith the note only the first screen was captured. Only if nothing at all has appeared by the limit is the page marked failed (no screenshot fee, though time spent counts as usage), with the tip to raise Time limit per page (seconds). - Every format and device: PNG, JPEG, WebP or A4 PDF; desktop, HD, Retina, laptop, tablet, mobile, iPhone or Android; light or dark mode; transparent backgrounds; or your own screen size.
- Just the part you want: capture a single element with a CSS selector, wait for a specific element to appear, or hide extra things like chat widgets.
- Fast and lean in bulk: several pages are captured at once, set automatically from the run's processor power. Video, trackers and decorative animation are switched off, so each capture uses far less computing time.
- Stays within your budget: set a maximum cost per run and the run stops taking new screenshots once it is reached (see Staying within budget).
How to use it
- Add your pages under URLs to capture. Bare domains like
example.comwork too. - Choose a Format and Device. Leave Full page on to capture whole pages.
- Click Start.
- Open the Output tab. The overview shows a thumbnail, the URL, the status and any reason a page wasn't captured. Each row has a
screenshotUrllink to the file. All files are also in the run's key-value store. - To capture pages regularly, create a schedule with the same input and connect an integration (Google Drive, Slack, Zapier, Make, a webhook) to send the files on.
Pricing
You pay per screenshot captured and saved, plus Apify platform usage (the computing time of your run). Failed, blocked, blank, error-page, placeholder-only and skipped pages have no screenshot fee, though the time spent trying them counts as usage.
| Your Apify plan | Per 1,000 screenshots |
|---|---|
| Free | $0.75 |
| Starter | $0.70 |
| Scale | $0.62 |
| Business and above | $0.52 |
Each run also has a start fee of $0.0002 at the default 4 GB memory ($0.00005 per GB). An Instant API instance pays the same each time it starts.
Platform usage depends on the pages. We measured it on build 0.1.8 with the default settings (4 GB memory, full page, desktop). The figures below are total usage, including storage and data transfer, per 1,000 screenshots captured, at the Free plan rate:
- Typical sites (small business sites, blogs, documentation, SaaS homepages): about $1.50 of usage per 1,000, so about $2.25 in total with the $0.75 screenshot fee. In a test of 50 such sites, 48 were captured, 1 was blocked and 1 failed.
- Hard sites (big news sites and online shops with heavy bot protection, very long pages and slow scripts): about $3.56 of usage per 1,000 captured, so about $4.31 in total. In a test of 50 such sites, 29 were captured, 16 were blocked and 5 failed. Blocked and failed pages have no screenshot fee, but the time spent on them still counts as usage, which is why this figure is higher.
Usage is lower on higher Apify plans and for first-screen-only captures.
Example on the Free plan: 500 typical pages costs $0.38 in screenshot fees plus about $0.75 of usage, about $1.13 in total.
Staying within budget
- Batch runs: in Apify Console, set Maximum cost per run in the run options before you click Start. When starting a run through the API, pass
maxTotalChargeUsd(in the Pythonapify-client,max_total_charge_usd). Once the limit is reached the run stops taking new screenshots, the remaining pages getskippedrows with no screenshot fee, and the run ends cleanly. A run stopped this way is not marked Failed. - Instant API: Apify's Standby mode currently has no query parameter or header for setting a maximum charge per request. An answer of
402means the API has reached its spending limit for now; nothing is charged. To cap your total spending, set a usage limit on your Apify account (Billing, Limits), or use batch runs with a maximum cost per run.
Good to know
- Pages are loaded with animations reduced and video switched off, which looks the same in a still image and makes captures faster.
- Some sites use strong bot protection and show a check page instead of the real page. Those rows are marked
blockedand have no screenshot fee, though time spent counts as usage. - Only public websites can be captured. Private and internal network addresses are refused.
- Mobile, tablet, Retina, iPhone and Android captures use a higher pixel density, so images are 2 to 3 times the listed width.
- WebP images can't be taller than 16,383 pixels, so very long WebP captures are cut at that height.
- Full-page captures stop at Max page height (px) (15,000 px by default) and at 40 million pixels in total, so they stay quick to capture. That only matters at high pixel density: on
desktop-retina, for example, the limit is about 6,900 px of page. Taller pages are cut, markedtruncatedand noted, for example only the top 15,000 px of the page was captured (height limit). - If a full page can't be captured in time, the top four screens are captured instead (a shorter page is retaken whole), or the first screen if even that isn't possible. When less than the whole page is captured, the row shows
truncated: trueand a note saying which. Pages that scroll inside a box instead of the browser window (common on app-style sites) are opened up so the whole page is captured; if the box can't be opened up, only the first screen is captured, withtruncated: trueand a note. - Empty space at the bottom of a full-page capture (a screen or more of the page's plain background) is trimmed off. Coloured sections such as footers are kept. A page cut at the height limit still shows
truncated: trueafter trimming. - A page counts as blank (no screenshot fee) when it is all one colour, or almost all one colour with no text or only "Loading...". A page counts as an error page (no screenshot fee) when its text is mainly an error message such as "Oops", "unexpected error", "something went wrong", "page not found" or "service unavailable", with little else on it, or when the page's main content is such a message (or its main heading is, with little else under it), even with the site's normal header, menus and footer around it. Help and FAQ pages that mention an error within longer content, articles that explain an error (such as "Error 404: what it means") and restaurant menus are captured and charged as normal. Minimalist pages with real content are captured and charged as normal.
- A page counts as loading placeholders (no screenshot fee) when most of it is flat light-grey boxes where the content should be and it has little text, usually because it was still loading at the time limit. Pages with grey sections, card grids, product pictures or minimalist designs are captured and charged as normal.
- Files larger than 9 MB can't be saved. Very tall PNGs are saved as JPEG instead (noted in the row), or lower Max page height (px).
API and reference
This section is for developers and AI agents.
Input
| Field | Type | Default | Description |
|---|---|---|---|
startUrls | array | required | Pages to capture, as {"url": "..."} objects or plain strings. Bare domains get https:// added. Duplicates are captured once. Non-http(s) URLs get a failed row with no screenshot fee. |
format | png, jpeg, webp, pdf | png | File type. PDF is A4 with backgrounds, laid out like the screen version. |
fullPage | boolean | true | Whole page (after scrolling to load lazy content) or only the first screen. Ignored for PDF and element captures. |
device | desktop, desktop-hd, desktop-retina, laptop, tablet, mobile, iphone, android | desktop | 1440x900, 1920x1080, 1440x900 @2x, 1280x800, 820x1180 @2x, 390x844 @2x, 393x852 @3x, 412x915 @2.625x, each with a matching browser identity. |
hideCookieBanners | boolean | true | Hide consent pop-ups (Fanboy's Cookie List plus our own rules) and unlock scrolling. Nothing is accepted. |
hidePopups | boolean | true | Hide other pop-ups covering the page (sign-up, newsletter, donation, app, ad-blocker notices). |
blockAds | boolean | true | Block ads and trackers (EasyList, EasyPrivacy) and hide empty ad slots. |
darkMode | boolean | false | Ask the site for its dark theme (prefers-color-scheme: dark). |
transparentBackground | boolean | false | PNG/WebP: pages without their own background come out see-through. |
waitUntil | domcontentloaded, load, networkidle | load | How long to wait before capturing. Pages that never reach it are captured anyway with a note. |
delaySecs | integer | 0 | Extra pause before capturing (0 to 30). Visible images are always waited for. |
maxPageHeight | integer | 15000 | Full-page captures are cut at this height in CSS pixels (500 to 30,000); the row shows truncated: true. |
quality | integer | 80 | JPEG and WebP quality (10 to 100). |
selector | string | none | Capture only the first element matching this CSS selector. No match gives a failed row with no screenshot fee. |
waitForSelector | string | none | Wait until an element matching this selector is visible before capturing. If it never appears, the row is failed (no screenshot fee). |
hideSelectors | string[] | [] | Extra CSS selectors to hide before capturing. |
viewportWidth, viewportHeight | integer | device size | Override the screen size. |
pageTimeoutSecs | integer | 25 | Time limit for loading each page (15 to 180). A page still loading at the limit is captured as it is and marked captured before the page finished loading (charged only if something real is on it: a blank page or bot check has no screenshot fee). A page that sends nothing at all in time is marked failed (no screenshot fee, though time spent counts as usage) and is not tried again; raise this for very slow sites. Quick network errors, and pages the browser closed unexpectedly, are tried once more on a fresh page. Pages that show a bot check or refuse access (HTTP 401/403/429) are given up on straight away and marked blocked, also with no screenshot fee. |
maxConcurrency | integer | from memory | Pages captured at once (1 to 16). Default: 3 per 4 GB (one processor core per 4 GB on Apify). |
Output
One dataset row per URL.
| Field | Type | Description |
|---|---|---|
url | string | Input URL |
finalUrl | string | URL after redirects |
status | string | ok, failed, blocked or skipped. Only ok rows are charged. |
reason | string | Why a page wasn't captured |
httpStatus | integer | HTTP status of the page |
pageTitle | string | Page title |
screenshotUrl | string | Direct link to the file |
screenshotKey | string | Key of the file in the run's default key-value store |
format | string | png, jpeg, webp or pdf |
device | string | Device used |
fullPage | boolean | Whole page captured |
width, height | integer | Image size in pixels (null for PDF) |
bytes | integer | File size |
truncated | boolean | Page was cut: taller than maxPageHeight (or the 40-million-pixel limit), too tall to capture in full in time, or only the first screen could be captured (see note) |
note | string | Anything worth knowing, e.g. captured before the page finished loading, only the first screen was captured or only the top of the page was captured (too tall to capture in time) |
loadMs | integer | Time to load and capture |
capturedAt | string | ISO 8601 time |
Example row:
{"url": "https://example.com","finalUrl": "https://example.com/","status": "ok","reason": null,"httpStatus": 200,"pageTitle": "Example Domain","screenshotUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/example-com-desktop-1a2b3c4d5e.png","screenshotKey": "example-com-desktop-1a2b3c4d5e.png","format": "png","device": "desktop","fullPage": true,"width": 1440,"height": 900,"bytes": 18734,"truncated": false,"note": null,"loadMs": 1820,"capturedAt": "2026-10-22T15:00:00+00:00"}
The key-value store also holds SUMMARY (counts and why the run stopped, if it stopped early).
Run outcome
- A run succeeds if at least one page was captured.
- A run that captured nothing (every page failed or was blocked) ends as Failed with the reason in the status message, so schedules and integrations don't treat it as a success. The exception is a run stopped by your own maximum cost per run.
- If the run timeout gets close, remaining pages get
skippedrows with no screenshot fee and the run ends cleanly.
API example
Uses apify-client 3.x. On older versions, use run["defaultDatasetId"].
from apify_client import ApifyClientclient = ApifyClient("<YOUR_API_TOKEN>")run = client.actor("opendata-tools/bulk-website-screenshots").call(run_input={"startUrls": [{"url": "https://example.com"}, {"url": "https://apify.com"}],"format": "png","device": "desktop","fullPage": True,})for item in client.dataset(run.default_dataset_id).iterate_items():print(item["status"], item["url"], item["screenshotUrl"] or item["reason"])
Instant screenshot API (one URL, one image)
Need a screenshot inside your own app, one page at a time? Call the always-on API and get the image straight back. Most answers take 5 to 30 seconds. The first call after an idle spell takes about 20 seconds longer while the API starts. No run to start, no dataset to read.
curl -H "Authorization: Bearer <YOUR_API_TOKEN>" \"https://opendata-tools--bulk-website-screenshots.apify.actor/screenshot?url=https://example.com&format=png&fullPage=true" \-o example.png
Python:
import requestsr = requests.get("https://opendata-tools--bulk-website-screenshots.apify.actor/screenshot",params={"url": "https://example.com", "format": "png", "fullPage": "true"},headers={"Authorization": "Bearer <YOUR_API_TOKEN>"},timeout=180,)if r.status_code == 200:open("example.png", "wb").write(r.content)else:print(r.status_code, r.json())
JavaScript (Node 18+):
const params = new URLSearchParams({ url: "https://example.com", format: "png", fullPage: "true" });const res = await fetch(`https://opendata-tools--bulk-website-screenshots.apify.actor/screenshot?${params}`, {headers: { Authorization: "Bearer <YOUR_API_TOKEN>" },});if (res.ok) {const fs = await import("node:fs/promises");await fs.writeFile("example.png", Buffer.from(await res.arrayBuffer()));} else {console.log(res.status, await res.json());}
- Same options as above, as query parameters (or a JSON body with
POST).hideSelectorsis a comma-separated list. response=jsonsaves the file and returns its link plus width, height, page title and final URL, instead of the image.- Answers:
200the screenshot (charged),422the page could not be captured (no screenshot fee, though time spent counts as usage; the reason is included),400bad input,429busy (wait forRetry-Afterand try again),402the maximum charge is reached (nothing charged; see Staying within budget). - Image answers carry
X-Screenshot-Width,X-Screenshot-Height,X-Screenshot-Final-Url,X-Screenshot-TruncatedandX-Screenshot-Noteheaders. A page captured at the time limit says captured before the page finished loading inX-Screenshot-Note(or innotewithresponse=json). - Same price per screenshot as batch runs. The API runs on your Apify account: you pay usage while it handles your requests and for about 60 seconds after the last one, then it shuts down. Failed and blocked pages have no screenshot fee, though time spent counts as usage.
- Each API instance captures 3 pages at once and queues up to 6 more. Requests beyond that, or ones that would wait more than 60 seconds, get
429withRetry-After(5 or 10 seconds). - Sending more than 9 requests at once can start extra API instances, and each one is billed for its start-up and about 60 seconds of idle time.
Credits
Ad, tracker and cookie-banner blocking uses EasyList, EasyPrivacy and Fanboy's Cookie List (GPLv3 / CC BY-SA 3.0), read by Brave's adblock engine.