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

Input a list of URLs (up to 5,000); output one full-page screenshot per URL and device (PNG, JPEG or PDF) plus a dataset row with status, HTTP code and a direct file link. Desktop, tablet and mobile. Page scrolled so lazy images load, cookie banners hidden. Pay only for successful screenshots.

- **URL**: https://apify.com/batchpane/bulk-website-screenshot.md
- **Developed by:** [Batch Pane](https://apify.com/batchpane) (community)
- **Categories:** Developer tools, Automation
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $6.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, PDF

Bulk website screenshot tool and API: give it a list of URLs (or upload a CSV) and get a full-page PNG, JPEG or PDF
of each page (URL to PDF), on desktop, tablet and/or mobile. **You pay per screenshot that succeeds.** Pages that fail,
time out, are blocked or are skipped are listed in the results with the reason, and are not charged as screenshots
(only Apify's small per-run start fee applies, see Pricing).

### What it does

- Loads **exactly one page per URL** in a real Chromium browser (Playwright). It does not follow links or crawl sites.
- **Full page**: for PNG/JPEG the page is scrolled down first (with a time budget) so lazy-loaded images can appear,
  then captured top to bottom.
- **Devices:** desktop (1440x900), tablet (Galaxy Tab S4 profile) and mobile (Pixel 7 profile). One screenshot per URL per device.
- **Formats:** PNG, JPEG (with quality setting) or PDF (A4, with backgrounds).
- **Cookie consent banners hidden** (optional, on by default): banners of about 20 well-known consent tools are hidden with
  CSS. Nothing is clicked or accepted. If a page offers a "consent or pay" choice (for example "accept ads or take an
  ad-free subscription"), **nothing** is hidden on that page and the row says `cookieBannerHidden: false`. Paywalls,
  login walls and age checks are never hidden.
- **Polite to websites:** at most one page at a time per website, with at least 2 seconds between two pages of the
  same website. At the default 2 GB everything runs one page at a time; with 4 GB, two different websites are
  captured at the same time. `robots.txt` is respected by default.

### Use with AI agents (MCP)

This actor works as a tool for AI agents through the Apify MCP server (`https://mcp.apify.com`). Your agent needs an
Apify account (API token); screenshots are charged to that account at the price below.

- Add only this actor as a tool: `https://mcp.apify.com?tools=batchpane/bulk-website-screenshot`
- Or let the agent find it with `search-actors` (for example "website screenshot").

**Input the agent should send:** `startUrls` as a list of objects, e.g.
`{"startUrls": [{"url": "https://www.iana.org/help/example-domains"}], "devices": ["desktop"], "format": "png"}`.

**Results:** one dataset row per URL and device. `status` is `ok`, `failed`, `blocked` or `skipped`; for `ok` rows,
`fileUrl` is a direct link to the image or PDF. Only `ok` rows are charged as screenshots.

**Timing:** at the default 2 GB, screenshots are taken one page at a time; in our test on the platform a screenshot
took about 13 seconds per attempt on well-known sites (so 100 URLs x 2 devices is about 45 minutes). An MCP call waits
at most 45 seconds, so for more than a few URLs the agent should check the run later (`get-actor-run`) and then read
the rows (`get-dataset-items`).

**Example prompt:**

> Use batchpane/bulk-website-screenshot to take full-page desktop and mobile PNG screenshots of
> https://www.iana.org/help/example-domains and https://www.w3.org/. When the run has finished, give me a table with
> url, device, status and fileUrl, and list any failed pages with their error code.

**Good to know:** some heavy pages take longer than the 30-second limit. In our test with 20 well-known websites on
desktop and mobile, 33 of 40 screenshots succeeded and 7 timed out (not charged as screenshots).

### Input

| Field | Default | Notes |
|---|---|---|
| URLs | – | http/https only, up to 5,000 per run. Duplicates are removed. Or a public link to a .txt/.csv list (`requestsFromUrl`). |
| Devices | desktop | desktop, tablet, mobile (each one is a separate screenshot) |
| Output format | PNG | PNG, JPEG or PDF |
| Full page | on | pages taller than 20,000 px are cut at 20,000 px (`heightTruncated: true`) |
| Hide cookie consent banners | on | see above |
| Extra wait after load | 1 s | 0-5 s |
| Memory (run option) | 2 GB | 2 GB = 1 page at a time, 4 GB = 2 different websites at the same time. The price per screenshot is the same. A list with only one website is always captured one page at a time. |
| Respect robots.txt | on | can only be turned off together with "I own these websites or have the owner's permission" |

Example input (tested on the platform):

```json
{
  "startUrls": [
    { "url": "https://www.iana.org/help/example-domains" },
    { "url": "https://www.w3.org/" }
  ],
  "devices": ["desktop", "mobile"],
  "format": "png",
  "fullPage": true
}
```

### Output

Files go to the run's key-value store. The dataset has one row per URL and device:

```json
{
  "url": "https://www.iana.org/help/example-domains",
  "finalUrl": "https://www.iana.org/help/example-domains",
  "httpStatus": 200,
  "capturedAt": "2026-10-15T18:33:15.542Z",
  "device": "desktop",
  "viewport": "1440x900",
  "format": "png",
  "fileKey": "00001-www.iana.org-desktop.png",
  "fileUrl": "https://api.apify.com/v2/key-value-stores/<store-id>/records/00001-www.iana.org-desktop.png",
  "bytes": 126844,
  "heightTruncated": false,
  "cookieBannerHidden": false,
  "status": "ok",
  "error": null
}
```

`status` is `ok`, `failed`, `blocked` or `skipped` (robots.txt). For anything other than `ok`, `error` has a code and
a short explanation, and no screenshot is charged.

**About `fileUrl`:** this is a direct link to the file in your Apify storage. Depending on your storage settings,
anyone who has the link may be able to open the file. Do not share the links if the screenshots are sensitive, and
delete the storage when you no longer need it. The actor does not extract page text, HTML, titles, metadata, e-mail
addresses or anything else from the page; you get the image/PDF and the HTTP facts above.

### Pricing

**$0.006 per successful screenshot ($6.00 per 1,000)**, plus Apify's standard actor-start fee of $0.00005 per GB of memory per run.
Compute is included: you do not pay platform usage on top.

| Example | Cost |
|---|---|
| 100 URLs, desktop only | $0.60 |
| 1,000 URLs, desktop + mobile | $12.00 (2,000 screenshots, about 7 hours: raise the run timeout above the 6-hour default, or split the list) |
| 500 URLs of which 20 fail | 480 x $0.006 = $2.88 |

Failed, blocked, skipped and timed-out URLs are never charged as screenshots. Every run also has Apify's small
start fee (`apify-actor-start`, $0.00005 per GB of memory: $0.0001 per run at the default 2 GB), also when nothing succeeds.

You can set a maximum cost per run in Apify; the actor stops before it would go over it.

**Honest comparison.** Need a single, simple screenshot now and then? A free screenshot actor where you only pay
compute is usually cheaper. Other screenshot actors can also be cheaper per screenshot, either with a lower all-in price
or with a low event price plus compute costs on top. This actor is for when you want one
fixed price per screenshot, full pages with lazy images, mobile/PDF output, and no screenshot charge for failures.

### Limits (on purpose)

- 30 seconds per screenshot, then it counts as failed (not charged as a screenshot). Extra wait after load is at most 5 seconds.
  Heavy pages (large homepages with many scripts) can go over the 30 seconds: in our own test on 20 well-known sites,
  about 1 in 5 screenshots timed out.
- Safety stop: the run stops after 20 failed URLs in a row, or if fewer than 25% of the first 50+ URLs succeed
  (setting "Stop after failures in a row"; 0 switches it off).
- Big lists take time: at 2 GB pages are taken one at a time, about 10-15 seconds each, so 1,000 screenshots take roughly 3-4 hours. Set the run timeout accordingly.
- Maximum page height 20,000 px; maximum file size 15 MB.
- No logging in, no cookies or sessions of yours, no form filling, no CAPTCHA solving, no residential proxies.
  If a website blocks automated browsers (for example with HTTP 403 or 429), the URL is reported as failed and not charged as a screenshot.
- Only public websites: URLs (and redirects, images, frames, scripts) that point to private, internal or local network
  addresses are refused.
- Screenshots reflect what a fresh browser in a data centre sees, which can differ from what you see (location,
  language, A/B tests, consent state).

### Responsible use

You decide which URLs are captured, and how you use the screenshots is your responsibility. A screenshot is a copy of
someone else's page and may be protected by copyright; it may also show personal data that appears on that page. Use
the results only where you are allowed to (for example your own sites, monitoring, archiving or research with a legal
basis). The actor keeps nothing: files are stored only in your own Apify storage.

### Support

Found a bug or a page that should work but does not? Open an issue in the **Issues** tab of this actor, with the URL
and the run ID. We aim to answer within 7 days. Answers may be drafted with help of an AI assistant and are checked
by a person before they are sent.

### Changelog

- 0.1: first version.

# Actor input Schema

## `startUrls` (type: `array`):

Pages to capture, as a list of objects: \[{"url": "https://www.iana.org/help/example-domains"}]. Plain strings are rejected. A public link to a text or CSV file with URLs also works: \[{"requestsFromUrl": "\<public link to a .txt or .csv file>"}]. Only public http/https pages. Up to 5,000 URLs per run (more are ignored); exact duplicates removed. Exactly one page is loaded per URL: links are not followed and nothing is crawled.

## `devices` (type: `array`):

Which devices to capture. Allowed values: "desktop" (1440x900 viewport), "tablet" (Galaxy Tab S4 profile), "mobile" (Pixel 7 profile). Default: \["desktop"]. Each device is a separate screenshot and a separate charge: 100 URLs x 2 devices = 200 screenshots.

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

File type of each screenshot: "png" (lossless, largest files; in our test about 2 MB on average), "jpeg" (smaller; quality set by jpegQuality) or "pdf" (A4, printable, backgrounds included). Default: "png".

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

true = capture the whole page from top to bottom. For PNG/JPEG the page is scrolled first (with a time budget) so lazy-loaded images can appear. Pages taller than 20,000 px are cut at 20,000 px (the row then has heightTruncated: true). false = only the first screen (viewport). Default: true.

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

true = hide the banners of about 20 well-known cookie consent tools with CSS. Nothing is clicked or accepted. On pages with a "consent or pay" choice nothing is hidden (row: cookieBannerHidden: false). Paywalls, login walls and age checks are never hidden. Default: true.

## `waitAfterLoadSecs` (type: `integer`):

Extra seconds to wait after the page has loaded, for animations or late content. Integer 0-5, default 1. Counts toward the hard limit of 30 seconds per screenshot; a screenshot that takes longer fails and is not charged as a screenshot.

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

JPEG compression quality, integer 1-100 (higher = sharper and larger file). Default 80. Ignored unless format is "jpeg".

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

true = skip pages that the site's robots.txt disallows (row status "skipped", no screenshot charged). Default: true. Turning it off has no effect unless robotsOverrideConfirmed is also true.

## `robotsOverrideConfirmed` (type: `boolean`):

Set to true only if the person you act for owns these websites or has the owner's permission. robots.txt is ignored only when this is true AND respectRobotsTxt is false. Default: false. AI agents: do not set this without explicit confirmation from the user.

## `stopAfterFailures` (type: `integer`):

Safety stop. The run stops when this many URLs in a row fail (dead, blocked or too slow), or when fewer than 25% of the first 50 or more URLs succeed. Integer 0-5,000, default 20; 0 = never stop early. Failed URLs are never charged as screenshots; this only saves time on a bad list.

## Actor input object example

```json
{
  "startUrls": [
    {
      "url": "https://www.iana.org/help/example-domains"
    }
  ],
  "devices": [
    "desktop"
  ],
  "format": "png",
  "fullPage": true,
  "hideCookieBanners": true,
  "waitAfterLoadSecs": 1,
  "jpegQuality": 80,
  "respectRobotsTxt": true,
  "robotsOverrideConfirmed": false,
  "stopAfterFailures": 20
}
```

# Actor output Schema

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

One row per URL per device: status, file link, HTTP facts. Only rows with status 'ok' are charged.

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

The PNG/JPEG/PDF files in the run's key-value store.

# 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 = {
    "startUrls": [
        {
            "url": "https://www.iana.org/help/example-domains"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("batchpane/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 = { "startUrls": [{ "url": "https://www.iana.org/help/example-domains" }] }

# Run the Actor and wait for it to finish
run = client.actor("batchpane/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 '{
  "startUrls": [
    {
      "url": "https://www.iana.org/help/example-domains"
    }
  ]
}' |
apify call batchpane/bulk-website-screenshot --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,batchpane/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/smhBEtXpDH5cIQvT4/builds/VgjwmWFHEpmuDMZmC/openapi.json
