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

Capture screenshots of many websites in one run: full page or viewport, PNG, JPEG or PDF, desktop or mobile devices, dark mode, single-element capture, hidden selectors and cookie banners. Public image URLs in the dataset.

- **URL**: https://apify.com/datlab/bulk-website-screenshot.md
- **Developed by:** [DatLab](https://apify.com/datlab) (community)
- **Features:** Limited permissions
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $0.60 / 1,000 screenshot takens

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, PNG/JPEG/PDF

A **website screenshot API** for **bulk URL screenshots**: capture **full page or viewport** shots of many websites in one run (PNG, JPEG or PDF, desktop or mobile) and get a **public image URL** for each in the dataset. Logins are not supported.

- **Full page or viewport.** PNG, JPEG (with quality) or PDF. PDFs are sized to the screenshot viewport so the page does not reflow to a paper width and clip wide content.
- **Desktop or mobile.** Any desktop width/height and pixel ratio (retina), or emulate iPhone 15, Pixel 7 or iPad Pro 11. Dark mode too.
- **Capture just one element**, e.g. `#pricing`, a chart or a hero section.
- **Clean shots.** Hide cookie banners and any selectors (chat widgets, popups). Scroll first to load lazy images. Wait for load, network idle, a selector or a fixed delay.
- **Batch-friendly and cheap.** One shared browser for all URLs, low memory, a fixed price per screenshot with platform usage included.
- **Polite.** Respects robots.txt, rate-limits per host, never bypasses logins or captchas.

### Use cases

- Visual monitoring and archiving of landing pages, competitors or your own sites (schedule it).
- Thumbnails and previews for directories, link-in-bio pages or CMSs.
- QA: check how pages render on mobile and desktop, in light and dark mode.
- Evidence snapshots (PDF) of public pages.

### Input

| Field | Default | What it does |
|---|---|---|
| `urls` | – | Pages to capture. |
| `format` | `png` | `png`, `jpeg` or `pdf`. |
| `fullPage` | `true` | Whole page or just the viewport. |
| `viewportWidth` / `viewportHeight` | 1280 / 800 | Desktop window size. |
| `device` | `desktop` | `iPhone 15`, `Pixel 7`, `iPad Pro 11`. |
| `deviceScaleFactor`, `darkMode`, `jpegQuality` | 1, `false`, 80 | Image options. |
| `maxFullPageHeight` | 15000 | Very long pages are cut here (`truncatedAtHeight`). |
| `elementSelector` | – | Capture only this element. |
| `waitUntil`, `delay`, `waitForSelector` | `load`, 0, – | When to capture. Puppeteer-style `networkidle0/2` are accepted too. |
| `scrollToBottom`, `delayAfterScrolling`, `waitUntilNetworkIdleAfterScroll`, `waitUntilNetworkIdleAfterScrollTimeout` | `false`, 2500, `false`, 30000 | Lazy-loading support. |
| `selectorsToHide`, `hideCookieBanners` | –, `true` | Hide elements (nothing is clicked or accepted). |
| `pdfFormat`, `pdfPrintBackground` | `A4`, `true` | `pdfPrintBackground` is honoured. `pdfFormat` is kept for compatibility; the PDF page is sized to the rendered viewport so layout matches the screenshot. |
| `maxConcurrency`, `navigationTimeoutSecs`, `maxRequestRetries`, `minDelayPerHostMs` | 2, 60, 2, 500 | Speed and politeness. |
| `proxy` | off | Optional datacenter proxy. |

```json
{
  "urls": [{ "url": "https://apify.com" }, { "url": "https://crawlee.dev" }],
  "format": "jpeg",
  "fullPage": true,
  "device": "iPhone 15"
}
```

### Output

```json
{
  "url": "https://crawlee.dev/",
  "loadedUrl": "https://crawlee.dev/",
  "screenshotUrl": "https://api.apify.com/v2/key-value-stores/…/records/crawlee-dev-0d35627b.png",
  "screenshotKey": "crawlee-dev-0d35627b.png",
  "format": "png",
  "fullPage": true,
  "truncatedAtHeight": null,
  "width": 1280,
  "height": 2874,
  "sizeBytes": 335092,
  "device": "desktop",
  "title": "Crawlee · Build reliable crawlers. Fast.",
  "httpStatusCode": 200,
  "capturedAt": "2026-11-11T20:50:02.120Z",
  "error": null
}
```

Failed or blocked URLs have `screenshotUrl: null` and an `error`. The files are in the run's key-value store, and `RUN_SUMMARY` holds the counts.

### Pricing

Pay per event, with Apify platform usage included.

| Event | Price | When |
|---|---|---|
| Screenshot | **$0.60 per 1,000** | Each screenshot or PDF saved |
| Run start | **$0.003 per run** | Once per run (covers browser start-up) |

Failed pages, robots.txt-blocked pages and HTTP errors are **not charged**. Platform usage is included in the event price. Example: 1,000 URLs in one run ≈ $0.60; a single URL ≈ $0.0036. For comparison, [cheapapi/website-screenshot-api](https://apify.com/cheapapi/website-screenshot-api) starts at $0.61 per 1,000 **plus** platform usage (checked 12 Nov 2026).

### FAQ

**Why did a site return an error instead of a screenshot?** Some sites block automated browsers (HTTP 403) or disallow it in robots.txt. The Actor reports that and doesn't try to get around it. You're not charged.

**Why is my screenshot cut off?** Pages taller than `maxFullPageHeight` (15,000 px by default) are cut. Raise the limit or capture a single element.

**Images are missing at the bottom of the page.** Turn on `scrollToBottom` so lazy-loaded images render first.

**How do I get retina-quality images?** Set `deviceScaleFactor` to 2, or pick a mobile device (they use 2.6–3×).

**Can it screenshot pages behind a login?** No. Cookies and logins aren't supported, by design.

### Legal & data use

- Captures only public pages you list, obeys robots.txt and `Crawl-delay`, rate-limits per host and never bypasses logins, captchas, paywalls or other access controls.
- It stores only images of pages you request and doesn't extract personal data.
- You're responsible for having the right to capture and use the screenshots (copyright, trademarks, site terms, applicable law).

### Running on Apify

You can run this Actor on the Apify platform from the Console, the API, a schedule, integrations (webhooks, Slack, Google Sheets, Make, Zapier) or MCP.

### Other DatLab actors

- [Web Page to Markdown](https://apify.com/datlab/web-page-to-markdown) — turn a list of URLs into Markdown for LLMs and RAG
- [Website to Markdown Crawler](https://apify.com/datlab/site-to-markdown-crawler) — crawl a site into Markdown, with sitemaps and llms.txt
- [Sitemap Change Monitor](https://apify.com/datlab/sitemap-change-monitor) — extract sitemap URLs and watch new, removed and updated pages
- [ATS Job Listings](https://apify.com/datlab/ats-job-listings) — Personio, Teamtailor, Workable and Recruitee, one schema
- [SAM.gov Contract Opportunities](https://apify.com/datlab/sam-gov-contract-opportunities) — US federal opportunities from the official daily extract, no API key

# Actor input Schema

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

Pages to capture. All URLs are processed in one run with a shared browser.

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

Output file type.

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

Capture the whole scrollable page. Off = only the visible viewport.

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

Browser window width (ignored when a mobile device is selected).

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

Browser window height; the screenshot height when Full page is off.

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

Emulate a mobile or tablet device (viewport, pixel ratio, user agent, touch).

## `deviceScaleFactor` (type: `number`):

2 = retina-quality images (larger files).

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

Ask the page for its dark color scheme (prefers-color-scheme: dark).

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

Only for JPEG.

## `maxFullPageHeight` (type: `integer`):

Very long pages are cut at this height (reported in truncatedAtHeight).

## `elementSelector` (type: `string`):

Optional. Screenshot only the first element matching this selector, e.g. #pricing or main.

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

Which navigation event to wait for before capturing. networkidle waits until there are no network requests for 500 ms.

## `delay` (type: `integer`):

Extra wait after the page loads (animations, late content).

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

Optional. Wait until this element appears (max 30 s).

## `scrollToBottom` (type: `boolean`):

Scroll through the page first so lazy-loaded images and sections render.

## `delayAfterScrolling` (type: `integer`):

Wait after scrolling, before capturing.

## `waitUntilNetworkIdleAfterScroll` (type: `boolean`):

Instead of the fixed delay, wait until network requests stop (overrides Delay after scrolling).

## `waitUntilNetworkIdleAfterScrollTimeout` (type: `integer`):

Maximum wait for network idle after scrolling.

## `selectorsToHide` (type: `string`):

Comma-separated CSS selectors to hide before capturing, e.g. .chat-widget, #newsletter-popup.

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

Hide common cookie-consent banners (OneTrust, Cookiebot, Didomi, Usercentrics...). Nothing is clicked or accepted.

## `pdfFormat` (type: `string`):

Kept for compatibility. The PDF is sized to the screenshot viewport so the page does not reflow to a paper width and clip wide content.

## `pdfPrintBackground` (type: `boolean`):

Only for PDF.

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

Pages captured at once. 2 fits the default 1 GB memory; use 4–6 with 2–4 GB.

## `navigationTimeoutSecs` (type: `integer`):

Maximum time to load one page.

## `maxRequestRetries` (type: `integer`):

Retries for pages that fail to load.

## `minDelayPerHostMs` (type: `integer`):

Politeness: minimum gap between page loads on the same host. robots.txt Crawl-delay is honored when larger.

## `proxy` (type: `object`):

Optional. Datacenter proxies only (e.g. for a country-specific view); residential groups are not supported.

## Actor input object example

```json
{
  "urls": [
    {
      "url": "https://apify.com"
    },
    {
      "url": "https://crawlee.dev"
    }
  ],
  "format": "png",
  "fullPage": true,
  "viewportWidth": 1280,
  "viewportHeight": 800,
  "device": "desktop",
  "deviceScaleFactor": 1,
  "darkMode": false,
  "jpegQuality": 80,
  "maxFullPageHeight": 15000,
  "waitUntil": "load",
  "delay": 0,
  "scrollToBottom": false,
  "delayAfterScrolling": 2500,
  "waitUntilNetworkIdleAfterScroll": false,
  "waitUntilNetworkIdleAfterScrollTimeout": 30000,
  "hideCookieBanners": true,
  "pdfFormat": "A4",
  "pdfPrintBackground": true,
  "maxConcurrency": 2,
  "navigationTimeoutSecs": 60,
  "maxRequestRetries": 2,
  "minDelayPerHostMs": 500,
  "proxy": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

One item per URL with the public screenshot URL, size and status.

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

Key-value store with the PNG/JPEG/PDF files.

## `runSummary` (type: `string`):

RUN_SUMMARY record with captured, failed and robots-blocked counts.

# 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": [
        {
            "url": "https://apify.com"
        },
        {
            "url": "https://crawlee.dev"
        }
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("datlab/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": [
        { "url": "https://apify.com" },
        { "url": "https://crawlee.dev" },
    ] }

# Run the Actor and wait for it to finish
run = client.actor("datlab/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": [
    {
      "url": "https://apify.com"
    },
    {
      "url": "https://crawlee.dev"
    }
  ]
}' |
apify call datlab/bulk-website-screenshot --silent --output-dataset

```

## MCP server setup

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