# Bulk Website Screenshot: Full-Page, Mobile, PDF (`webstack-labs/bulk-website-screenshot`) Actor

Screenshot hundreds of web pages, or render your own HTML, in one run: full page or first screen, desktop, tablet or mobile, PNG, JPEG, WebP or PDF. Hides cookie banners, loads lazy images. $0.002 per screenshot; failed pages are free.

- **URL**: https://apify.com/webstack-labs/bulk-website-screenshot.md
- **Developed by:** [Webstack Labs](https://apify.com/webstack-labs) (community)
- **Categories:** Developer tools, Automation, SEO tools
- **Security:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 screenshots

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

An Actor is a serverless cloud program that runs on the Apify platform. It has two run modes.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.

Apify vocabulary and the platform model are defined once, in the agent quickstart at https://apify.com/agents.md.

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.

Do not guess an integration path. Every one of them is in the agent quickstart at https://apify.com/agents.md: the Apify MCP server, Agent Skills with the Apify CLI, the JavaScript and Python clients, the REST API, and the account-free path for an agent with no human to sign in. It also carries the rule on stating cost before the first paid run.

For examples already wired to this Actor's own input schema, see the [API](#api) section below.

Each client library has reference documentation the quickstart does not restate: [JavaScript/TypeScript](https://docs.apify.com/api/client/js/docs.md) (`npm install apify-client`) and [Python](https://docs.apify.com/api/client/python/docs.md) (`pip install apify-client`).

# README

## Bulk Website Screenshot: full-page, mobile and PDF captures of many pages (and your own HTML)

Paste a list of URLs, get a clean screenshot of every page: the whole page or just the first
screen, as desktop, laptop, tablet or mobile, in PNG, JPEG, WebP or PDF. Or send your own HTML
(invoices, reports, social cards) and get it back as an image or PDF. Cookie banners are hidden and
lazy images are loaded before the capture. A **website screenshot API** and an **HTML to PDF / HTML to
image** converter in one: URL or HTML in, PNG, JPEG, WebP or PDF out, at $0.002 per page.
**You pay per page captured. Pages that fail are free.**

### What it does

- Opens each page once in a real Chrome browser (Playwright) and captures it.
- **Full page or first screen.** Endless pages are cut at a height you choose (default 12,000 px)
  and marked `truncated`.
- **Devices:** desktop 1920x1080, laptop 1366x768, tablet 820x1180 (2x), mobile 390x844 (3x, touch),
  or your own viewport size.
- **Formats:** PNG, JPEG or WebP (quality you choose), or PDF (A4, with backgrounds and selectable text).
- **HTML to image / PDF:** put raw HTML documents in `htmlPages` and each is rendered in the same
  browser, with the same devices, formats and options as web pages. Linked images, fonts and CSS
  are loaded. Mix URLs and HTML in one run.
- **One element only:** give a CSS selector (`#pricing`, `header`) to capture just that part.
- **Clean captures:** hides the consent pop-ups of common cookie tools (OneTrust, Cookiebot,
  Quantcast, Didomi, Usercentrics, CookieYes and more) and any elements you list, such as chat
  widgets. Nothing is clicked or accepted.
- **Waiting that works:** load / DOM ready / network idle, an extra delay, or wait for an element.
- **Dark mode** for sites that support it.
- Every row has the page title, final URL after redirects, HTTP status, file size and a direct
  download link.

### Who uses it

- **Marketers and SEO teams:** archive landing pages, competitors' pricing pages and ads pages,
  before/after a redesign.
- **Agencies:** visual reports of client sites on desktop and mobile.
- **Developers and QA:** quick visual checks of many URLs after a release.
- **Researchers and compliance:** dated evidence of what a public page showed.
- **Directories and link previews:** thumbnails for lists of websites.

### Input

```json
{
  "urls": ["https://example.com", "apify.com", "https://www.python.org/about/"],
  "fullPage": true,
  "device": "desktop",
  "format": "png",
  "hideCookieBanners": true,
  "hideSelectors": ["#intercom-container"],
  "maxConcurrency": 6
}
```

Bare domains become `https://<domain>/`. Duplicates are removed.

#### Raw HTML input

```json
{
  "htmlPages": [
    {"name": "invoice-42", "html": "<html><body><h1>Invoice 42</h1><p>Total: $10.00</p></body></html>"},
    "<div style=\"width:1200px;height:630px;background:#123;color:#fff\">Social card</div>"
  ],
  "format": "webp",
  "fullPage": false,
  "viewportWidth": 1200,
  "viewportHeight": 630
}
```

Rows for HTML documents have `"url": null`, `"source": "html"` and `"htmlName"` (your name, or
`html-1`, `html-2`, ...), which also goes into the file name (`shot-00002-invoice-42.webp`).

### Output

One row per page in the dataset; the files are in the run's key-value store.

```json
{
  "url": "https://example.com/",
  "finalUrl": "https://example.com/",
  "statusCode": 200,
  "title": "Example Domain",
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/<store-id>/records/shot-00001-example-com.png",
  "screenshotKey": "shot-00001-example-com.png",
  "format": "png",
  "contentType": "image/png",
  "device": "desktop",
  "fullPage": true,
  "viewportWidth": 1920,
  "bytes": 21874,
  "pageHeight": 1080,
  "capturedHeight": 1080,
  "truncated": false,
  "durationSecs": 2.1,
  "capturedAt": "2026-10-29T01:00:00+00:00",
  "error": null
}
```

A page that cannot be captured gives a row with `error` (free), for example
`blocked_by_robots_txt`, `http_error` (with `statusCode`), `timeout`, `dns_error`,
`connection_error`, `tls_error`, `selector_not_found` or `wait_for_selector_not_found`.
The `SUMMARY` record of the key-value store counts captures and failures by reason.

### Pricing

- **$0.002 per page captured** ($2 per 1,000), in any format and device.
- Failed pages are **free**.
- Apify adds its small standard start fee per run.
- No separate charge for platform usage.

### Use it from AI agents (MCP)

The Actor is available to AI agents (Claude, ChatGPT, Cursor and others) through Apify's MCP server
(`mcp.apify.com`) and the API. Minimal input:

```json
{"urls": ["https://example.com"]}
```

Output row: `{"url": "https://example.com/", "format": "png", "device": "desktop", "statusCode": 200,
"title": "Example Domain", "screenshotUrl": "https://api.apify.com/v2/key-value-stores/.../records/shot-00001-example-com.png",
"bytes": 78288, "error": null, ...}`. Cost of this example: $0.002 plus the start fee. Open
`screenshotUrl` to get the image or PDF. Every row has the same fields; pages that fail come back as free
rows with an `error` code.

### How it compares

Screenshot and HTML-to-PDF Actors on Apify Store with the most monthly users, and the price each one
lists for its main event on 2026-11-06 (check their pages for changes and other prices):

| Actor | Listed price | Unit |
|---|---|---|
| **This Actor** | **$0.002** | page captured (PNG, JPEG, WebP or PDF; URL or your HTML) |
| apify/screenshot-url | free | you pay the platform usage of the run |
| cheapapi/website-screenshot-api | $0.00089 | screenshot |
| dz_omar/screenshot | $0.01 | screenshot generation |
| dz_omar/ultimate-screenshot | $0.007 | page capture |
| automation-lab/html-to-pdf-converter | $0.00575 | page converted |
| autofacts/universal-web-printer | $0.004 | render |

What you get here for the page price: many URLs and raw HTML documents in one run, full page or first
screen, four device presets or your own viewport, PNG, JPEG, WebP or PDF, one element by CSS selector,
cookie banners hidden without clicking anything, robots.txt respected, and failed pages free. If you only
need a single PNG of one URL now and then, the cheapest per-shot Actor may cost you less; this one is
built for batches and for mixing web pages with your own HTML.

### How it works

1. The URL list is cleaned (scheme added, duplicates removed, invalid entries reported).
2. Each site's `robots.txt` is read once. Pages it closes to automated access are skipped.
3. Each page opens in a fresh browser context with the chosen device. The browser identifies
   itself as Chrome plus `WebsiteScreenshotBot`.
4. After loading, the Actor hides the banners, scrolls once to load lazy images (full page only),
   waits as asked and captures.
5. The file goes to the key-value store and the row to the dataset; only successful rows are charged.

### FAQ

**Can it log in or capture pages behind a login?** No. It captures public pages only.

**Does it accept cookie consent?** No. It hides the banner with CSS. Some sites show reduced
content until consent is given; those pages are captured as a first-time visitor sees them.

**Why was a page skipped with `blocked_by_robots_txt`?** The site asks bots not to visit that
page. You can turn off "Respect robots.txt" for sites you own or have permission to capture.

**Some sites show a "verify you are human" page.** Sites with bot protection may show their
challenge page or answer 403. The Actor does not use proxies or try to get around such protection;
an HTTP 4xx/5xx answer is a free row.

**How long are the files kept?** As long as the run's storage is kept in your Apify account
(by default, unnamed storages are kept for a limited time; copy files you need to keep).

**How fast is it?** Usually 2-6 seconds per page, 3 pages at a time with the default 2 GB memory.
Raise "Pages at a time" together with the run memory for big lists.

### Limitations

- Full-page captures are cut at the max height (up to 30,000 px).
- Videos, canvas animations and some web fonts may look different from a desktop browser.
- PDF output uses the A4 page format with the screen layout of the page.
- No proxies, so pages that are only available in some countries show what a visitor from the
  Apify data center would see.

### Credits

Built on [Playwright](https://playwright.dev/) (Apache-2.0) and the Apify SDK for Python (Apache-2.0).

# Actor input Schema

## `urls` (type: `array`):

One per line. Full URLs (https://example.com/pricing) or bare domains (example.com, which becomes https://example.com/). Duplicates are removed. Up to 10,000 per run.

## `htmlPages` (type: `array`):

Render your own HTML instead of (or as well as) web pages: invoices, reports, e-mail templates, social cards, certificates. Each item is an HTML string or an object {"html": "<h1>Invoice 42</h1>...", "name": "invoice-42"} (the name goes into the file name). Images, fonts and CSS linked from the HTML are loaded from their URLs. Up to 1,000 documents of 5 MB each per run; charged like a page.

## `fullPage` (type: `boolean`):

Capture the whole scrollable page (up to the max height below). Off = only the first screen (the viewport).

## `device` (type: `string`):

Screen size to emulate. desktop 1920x1080, laptop 1366x768, tablet 820x1180 (2x pixels, touch), mobile 390x844 (3x pixels, touch).

## `format` (type: `string`):

PNG (sharp, larger files), JPEG (smaller files), WebP (smallest files, for the web; images are cut at 16,383 pixels) or PDF (A4, printable, selectable text).

## `jpegQuality` (type: `integer`):

10-100. Only used for JPEG and WebP.

## `maxHeight` (type: `integer`):

Full-page captures are cut at this height (CSS pixels) so endless pages stay usable. Rows show "truncated": true when this happened.

## `viewportWidth` (type: `integer`):

Overrides the device width (240-3840). Leave empty to use the device preset.

## `viewportHeight` (type: `integer`):

Overrides the device height (240-2160). Leave empty to use the device preset.

## `selector` (type: `string`):

Optional CSS selector, for example #pricing or header. Only the first visible match is captured. Pages without a match return a free row with the error selector_not_found.

## `darkMode` (type: `boolean`):

Ask the page for its dark color scheme (prefers-color-scheme: dark), where the site supports it.

## `hideCookieBanners` (type: `boolean`):

Hide the consent pop-ups of common cookie tools (OneTrust, Cookiebot, Quantcast, Didomi, Usercentrics, CookieYes and others) with CSS. Nothing is clicked or accepted.

## `hideSelectors` (type: `array`):

CSS selectors of elements to hide before capturing, for example chat widgets or ads (#intercom-container, .ad-slot).

## `scrollToLoadLazyContent` (type: `boolean`):

Scroll through the page once before a full-page capture so lazy-loaded images appear.

## `waitUntil` (type: `string`):

When the page counts as loaded. load (default) fits most sites; networkidle waits for quiet network (slower, good for dashboards and single-page apps); domcontentloaded is fastest.

## `delaySecs` (type: `number`):

Seconds to wait after loading, for animations and late content (0-30).

## `waitForSelector` (type: `string`):

Optional CSS selector to wait for (up to 20 seconds) before capturing, for example .chart-loaded.

## `pageTimeoutSecs` (type: `integer`):

Give up on a page after this many seconds of loading (10-180). Pages that time out are free.

## `maxConcurrency` (type: `integer`):

How many pages are captured in parallel (1-8). Default: 6 for desktop and tablet, 3 for mobile (mobile pages are rendered at 3x scale). Suits the default 2 GB memory; give the run more memory for higher values or very long pages.

## `respectRobotsTxt` (type: `boolean`):

Skip pages that the site's robots.txt closes to automated access (returned as free rows). Turn off only for sites you own or have permission to capture.

## Actor input object example

```json
{
  "urls": [
    "https://example.com",
    "https://www.python.org"
  ],
  "htmlPages": [
    {
      "html": "<html><body><h1>Invoice 42</h1><p>Total: $120</p></body></html>",
      "name": "invoice-42"
    }
  ],
  "fullPage": true,
  "device": "desktop",
  "format": "png",
  "jpegQuality": 80,
  "maxHeight": 12000,
  "darkMode": false,
  "hideCookieBanners": true,
  "hideSelectors": [],
  "scrollToLoadLazyContent": true,
  "waitUntil": "load",
  "delaySecs": 1,
  "pageTimeoutSecs": 45,
  "respectRobotsTxt": true
}
```

# Actor output Schema

## `results` (type: `string`):

No description

## `files` (type: `string`):

No description

## `summary` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "urls": [
        "https://example.com",
        "https://www.python.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("webstack-labs/bulk-website-screenshot").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = { "urls": [
        "https://example.com",
        "https://www.python.org",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("webstack-labs/bulk-website-screenshot").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "urls": [
    "https://example.com",
    "https://www.python.org"
  ]
}' |
apify call webstack-labs/bulk-website-screenshot --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,webstack-labs/bulk-website-screenshot"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/jSmAE1pQMgY3Zqw9g/builds/0DlbpECIK2j2s2my6/openapi.json
