Website Screenshot, PDF & Visual Diff
Pricing
from $3.00 / 1,000 screenshots
Website Screenshot, PDF & Visual Diff
Screenshot or PDF any list of URLs: desktop/tablet/mobile, full page or viewport, PNG/JPEG/WebP. Hides cookie banners, blocks ads, dark mode, lazy-load scroll. Visual diff flags changed pages with mismatch %, diff image and changed regions. Pay only for successful captures.
Pricing
from $3.00 / 1,000 screenshots
Rating
0.0
(0)
Developer
Haniel Azevedo
Maintained by CommunityActor stats
0
Bookmarked
2
Total users
1
Monthly active users
4 days ago
Last modified
Categories
Security
Limited permissionsShare
Give it a list of URLs. It returns clean screenshots (full page or viewport, PNG / JPEG / WebP) on desktop, tablet and mobile, optional PDFs, and an optional visual diff: was the page changed since last time, by how much (%), and where.
Pricing: $0.003 per screenshot, $0.003 per PDF and $0.002 per visual diff, plus a $0.00005 start fee per GB of memory. Failed URLs are free.
It runs a real Chromium browser. It hides cookie banners, can block ads and trackers, scrolls to load lazy images, waits for fonts, and switches off animations so captures stay stable from run to run.
Who it is for
- Agencies and QA teams: check that a deploy did not break the layout (staging vs production) on 3 devices at once.
- Marketing and SEO: archive landing pages and competitor pages and get a flag when one changes.
- Compliance and legal: PDF + PNG snapshots of terms, pricing or ad pages, with a capture time.
- Developers and AI agents: one API call returns image URLs you can put into reports, tickets or LLM prompts.
What you get
| Feature | Details |
|---|---|
| Devices | Desktop 1440×900, Desktop HD 1920×1080, Laptop 1280×800, Tablet 820×1180 @2x, Mobile 390×844 @3x, Small mobile 360×740 @2x, or a custom viewport |
| Area | Full page (capped by maxHeight, default 16,000 px), viewport only, or one element (elementSelector) |
| Formats | PNG, JPEG, WebP (quality setting); PDF (A4/Letter/Legal/A3/A5/Tabloid, margins, landscape, scale, screen or print CSS) |
| Clean captures | Cookie banners hidden or accepted (OneTrust, Cookiebot, Didomi, Usercentrics, iubenda and generic patterns), your own selectors to hide or remove, ad/tracker blocking, dark mode, custom CSS, animations off |
| Waiting | load / domcontentloaded / networkidle (capped), wait for a CSS selector, extra delay, lazy-load scrolling |
| Visual diff | Compare with the previous run (baseline saved in a named key-value store) or with a baseline URL / image (staging vs production). Returns mismatch %, changed pixels, changed regions (boxes) and a red-overlay diff image |
| Reliability | Retries with backoff on network errors, clear error codes, one bad URL never stops the run |
| Output | One dataset row per URL × device, plus the files in the run's key-value store and a run summary (OUTPUT) |
How to use it
- Paste your URLs into URLs (or upload a text/CSV file of URLs).
- Pick Devices and choose Full page or viewport.
- Optional: turn on PDF, Block ads and trackers or Dark mode.
- Optional, change monitoring: set Visual diff to
previousRun. The first run saves the baselines. Each later run reportsdiff.changedanddiff.mismatchPercent. - Click Start. Open the Overview tab of the dataset to see thumbnails, or download it as JSON / CSV / Excel.
Example input
{"urls": [{ "url": "https://example.com" }, { "url": "https://apify.com" }],"devices": ["desktop", "mobile"],"fullPage": true,"format": "png","cookieBanners": "hide"}
Example output row
{"url": "https://en.wikipedia.org/wiki/Screenshot","device": "desktop","ok": true,"statusCode": 200,"title": "Screenshot - Wikipedia","screenshotUrl": "https://api.apify.com/v2/key-value-stores/…/records/en-wikipedia-org-wiki-Screenshot-8c8873f3-desktop.png?signature=…","width": 1440,"height": 4098,"truncated": false,"cookieBannersHidden": 0,"timings": { "navigationMs": 455, "stabiliseMs": 1287, "screenshotMs": 362, "totalMs": 2491 },"diff": {"changed": true,"mismatchPercent": 16.9,"changedRegions": [{ "x": 0, "y": 412, "width": 1440, "height": 3010 }],"diffImageUrl": "https://api.apify.com/v2/key-value-stores/…-diff.png?signature=…"}}
A failed URL still gets a row, with ok: false, an errorCode and a readable errorMessage:
| errorCode | Meaning |
|---|---|
INVALID_URL | The URL could not be parsed (nothing is charged) |
DNS, CONNECTION, SSL | The site could not be reached |
HTTP_4XX, HTTP_5XX | The site answered with an error. Set captureErrorPages: true to capture it anyway |
TIMEOUT | The page or the capture took longer than timeoutSecs |
BLOCKED | The site served a bot check or a rate limit. It is not bypassed (see Limitations) |
WAIT_SELECTOR_TIMEOUT, SELECTOR_NOT_FOUND | Your waitForSelector / elementSelector did not appear |
NOT_HTML | The URL is a download (zip, etc.), not a page |
BUDGET_EXCEEDED | Your maximum charge per run was reached |
Monitoring pages for changes (schedule)
- Create a Task from this Actor with your URLs and
diffMode: "previousRun". Give it its own Baseline store name (for examplepricing-pages-baseline), so separate monitors never mix. - Add a Schedule to the task (for example daily at 08:00).
- Add an Integration (email, Slack, webhook, Zapier, Make, n8n) that fires when a run finishes. The run summary (
OUTPUT) holdschangedCountandchangedUrls, so you can alert only when something changed. - Tune the noise.
diffThresholdPercent(default 0.1 %) is how much of the page must change beforechangedis true.pixelThreshold(0–1) ignores small colour shifts from anti-aliasing. Hide rotating banners or dates withhideSelectors.
updateBaseline: "always" (default) compares each run with the run before it. "never" keeps comparing against the first ("golden") capture.
API, integrations and AI agents (MCP)
Run it and get the results in one call:
curl -X POST "https://api.apify.com/v2/acts/hanielazevedo~website-screenshot-pdf-visual-diff/run-sync-get-dataset-items?token=YOUR_TOKEN" \-H "Content-Type: application/json" \-d '{"urls":[{"url":"https://example.com"}],"devices":["mobile"],"fullPage":false}'
Python:
from apify_client import ApifyClientclient = ApifyClient("YOUR_TOKEN")run = client.actor("hanielazevedo/website-screenshot-pdf-visual-diff").call(run_input={"urls": [{"url": "https://example.com"}]})for row in client.dataset(run["defaultDatasetId"]).iterate_items():print(row["url"], row["screenshotUrl"])
AI agents (Claude, Cursor, ChatGPT and others) can call it as a tool through the Apify MCP server (https://mcp.apify.com). Add this Actor to the server's tools, then ask "screenshot these 5 pages on mobile and tell me which ones changed".
The image links in the output are signed, so they open without your token. They point to the run's storage, which Apify keeps for your plan's data retention period. Download the files if you need them for longer.
Pricing
Pay per event. Platform usage is included, and you are charged only for files that were actually saved:
| Event | Price | When |
|---|---|---|
screenshot | $0.003 | Each saved screenshot (one per URL and device) |
pdf | $0.003 | Each saved PDF |
diff | $0.002 | Each visual comparison against a baseline |
| Actor start | $0.00005 per GB of memory | Once per run ($0.0001 at the default 2 GB) |
Examples: 1 URL on desktop costs about $0.0031. 100 URLs × 3 devices costs about $0.90. A daily change check of 50 pages (screenshot + diff) costs about $0.25 per run. Failed URLs and invalid input are not charged. Set a maximum total charge per run, and the Actor stops before it goes over.
Tips for cost and speed
- Viewport captures (
fullPage: false) and JPEG/WebP are the fastest and smallest. - Very long pages are cut at
maxHeight(default 16,000 px) and flagged withtruncated: true. Lower it for faster runs. - The default memory is 2 GB. Big batches of long pages run faster with 4–8 GB, because the platform adds CPU with memory and the Actor renders one capture per CPU core.
maxConcurrency(default 3) sets how many pages load at the same time.
FAQ
Does it work on pages behind a login? No. It captures public pages only. It does not log in, solve captchas or get around bot protection.
Why is my screenshot cut off? The page is taller than maxHeight, and the row shows truncated: true. Raise maxHeight (up to 30,000 px), or use a PDF for very long documents.
A cookie banner still shows up. Add its CSS selector to hideSelectors, or try cookieBanners: "accept".
Why does the diff flag a page as changed every time? Rotating content (ads, carousels, timestamps) changes on every load. Hide those elements with hideSelectors, turn on blockAdsAndTrackers, or raise diffThresholdPercent.
Can I use my own proxy? Yes. Use proxyUrl (stored as a secret input).
What am I charged for? Only successful work: each screenshot, each PDF and each diff comparison. Failed URLs and invalid input are not charged. Platform usage is included. See Pricing.
Limitations
- Public pages only: no login, captcha solving or bot-protection bypass. Sites that serve a challenge page return
BLOCKED. - Full-page captures stop at
maxHeight. Infinite-scroll feeds are captured up to that height. - Video, canvas/WebGL animations and some cross-origin iframes may render as their first frame or as blank.
- Diffs compare pixels. A page that moves everything down by 1 line shows a large change even when the text barely changed.
- Please respect each website's terms of use and robots rules, and capture only content you are allowed to store.
Compared with the most-used free screenshot Actor (checked 31 Oct 2026)
| This Actor | apify/screenshot-url | |
|---|---|---|
| Device presets (desktop / tablet / mobile in one run) | ✅ | ❌ (viewport width only) |
| Output formats | PNG, JPEG, WebP + PDF (paper size, margins, landscape) | PNG or PDF |
| Visual diff with mismatch %, regions and diff image | ✅ | ❌ |
| Cookie banner handling | ✅ automatic | ❌ (manual selectorsToHide) |
| Ad / tracker blocking, dark mode, custom CSS | ✅ | ❌ |
| Element-only capture | ✅ | ❌ |
| Lazy-load scrolling, hide selectors | ✅ | ✅ |