# Website Screenshot & PDF: Full Page, Element, Mobile, Bulk (`charlesmarkette/website-screenshot-pdf-full-page-element-bulk`) Actor

Capture websites in bulk as full-page PNG or JPEG screenshots or PDFs in real Chrome. Shoot one element by CSS selector, use mobile or tablet views, dark mode, hide cookie banners and wait for content. Get a link to every file. Failed pages are free.

- **URL**: https://apify.com/charlesmarkette/website-screenshot-pdf-full-page-element-bulk.md
- **Developed by:** [Charles Markette](https://apify.com/charlesmarkette) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $2.00 / 1,000 screenshot or pdfs

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

**Website Screenshot & PDF** opens each URL in a real headless Chrome (Chromium) and saves a **full-page or viewport screenshot** (PNG or JPEG), a screenshot of **one element** picked by CSS selector, or a **PDF** of the page. It handles lists of thousands of URLs, phone and tablet presets, retina pixel density, **dark mode**, **cookie-banner hiding**, scrolling to load lazy images, and waiting for an element to appear. Each file is saved to the run's key-value store, and each URL gets a row with a **link to its file**, the final URL after redirects, HTTP status, page title, image size and any error.

### Why this Actor

- **Renders like a real browser.** Pages run their JavaScript, web fonts and CSS in Chromium, so you get what a visitor sees, not the raw HTML.
- **Full page, viewport or one element.** Full-page captures include everything below the fold (lazy images are scrolled into view first). Very tall pages are cut at 16,384 px and flagged with `truncated: true` instead of failing.
- **Cleaner captures.** Common cookie and consent pop-ups are hidden with CSS (nothing is clicked, so no consent is given on your behalf), and you can hide your own list of elements such as chat widgets.
- **Bulk-friendly and predictable.** Several pages load in parallel in one browser; one slow or broken page never stops the run, and if the browser crashes it is restarted. Every URL has a hard time limit.
- **Safe with any link list.** Every request the page makes (the page itself, each redirect, images, scripts, WebSockets) is checked, and private, loopback, link-local and cloud-metadata addresses are refused. Downloads are blocked.

### Use cases

- **Visual archives and compliance records:** a dated full-page PNG or PDF of each page (`capturedAt` is in every row).
- **Competitor and pricing monitoring:** schedule a daily run that captures just the `#pricing` element of each competitor's page.
- **Responsive checks:** the same URLs on desktop, tablet and mobile, in light and dark mode.
- **Reports and link previews:** thumbnails of the sites in a lead list or a scraper's output (pick the dataset, set the URL field).
- **Releases and QA:** screenshots of every page in a sitemap after a deploy, to compare with the previous run.
- **Saving articles or invoices as PDF:** A4 or Letter, with backgrounds, text stays selectable.

### How to use it

1. Add **URLs**, or pick a **Dataset with URLs** and set the **URL field** (dotted paths such as `page.url` work).
2. Pick a **Format** (PNG, JPEG or PDF), **Full page** on or off, and a **Device**. Optionally set **Only this element**, **Wait for element**, **Dark mode** or elements to hide.
3. Run it. Open the files from the `fileUrl` column, or from the run's **Storage → Key-value store** tab.

**Input example**

```json
{
  "urls": [
    "https://apify.com",
    {"url": "https://www.wikipedia.org", "label": "Wikipedia home"},
    {"url": "https://example.com/pricing", "label": "Pricing table", "selector": "#pricing"}
  ],
  "format": "png",
  "fullPage": true,
  "device": "desktop",
  "viewportWidth": 1280,
  "viewportHeight": 800,
  "hideCookieBanners": true,
  "delayMs": 500
}
```

**Output example** (one row)

```json
{
  "url": "https://apify.com/",
  "label": null,
  "finalUrl": "https://apify.com/",
  "statusCode": 200,
  "title": "Apify: Full-stack web scraping and data extraction platform",
  "format": "png",
  "key": "screenshot-3f1c9a2b7d4e8f60.png",
  "fileUrl": "https://api.apify.com/v2/key-value-stores/<storeId>/records/screenshot-3f1c9a2b7d4e8f60.png",
  "fileSizeBytes": 1843211,
  "width": 1280,
  "height": 7342,
  "pageCount": null,
  "truncated": false,
  "device": "desktop",
  "navigationTimedOut": false,
  "capturedAt": "2026-10-22T09:15:04Z",
  "error": null,
  "errorMessage": null
}
```

`width` and `height` are the image's size in pixels (twice the CSS size at a device scale factor of 2). For PDFs they are empty and `pageCount` holds the number of pages. When you use a dataset, the item's own fields are kept next to these columns (turn off *Keep the item's original fields* to drop them). A run summary is saved as `OUTPUT` in the key-value store.

The file key is built from the URL and the capture options, so running the same URL with the same options again writes to the same key, and a URL repeated in one run is captured once.

**Error codes** (`error`, with a plain-English `errorMessage`): `invalidUrl`, `blockedPrivateAddress`, `dnsFailed`, `connectionRefused`, `tlsError`, `timeout`, `tooManyRedirects`, `download`, `navigationFailed`, `selectorNotFound`, `invalidSelector`, `waitForSelectorTimeout`, `pageCrashed`, `captureFailed`.

### How much does it cost?

Pay per event: **$2.00 per 1,000 screenshots or PDFs** ($0.002 per file saved); **failed pages are free**. Apify platform usage is included. Each run also has Apify's run start fee of **$0.005** per GB of run memory: **$0.01** at the default 2 GB.

One saved file (PNG, JPEG or PDF) is one charge. Rows with an `error` (invalid URL, blocked address, page that didn't load, element not found, timeout) save no file and are **free**. A page that loads with an error status such as 404 is still captured and charged, because you get its screenshot; check `statusCode`.

**Worked examples:**

- 1,000 homepages as full-page PNGs: 1,000 × $0.002 + $0.01 run start = **$2.01**.
- A daily capture of 10 competitor pricing sections: $0.02 per run, plus $0.01 run start = $0.03 per run, about **$0.90 per month**.

If you set a maximum cost per run, the Actor stops cleanly at that limit: it doesn't start pages the budget can't pay for, and files that couldn't be paid for are removed. If a run is restarted (for example after a timeout or migration), it continues after the rows already in its dataset.

### FAQ and limits

#### Will every site look right?

Most do, because this is a real Chromium. But sites with heavy bot protection (Cloudflare challenges, CAPTCHAs) may show a challenge page or fail, and pages behind a login show the login page. The Actor doesn't use proxies, log in or solve challenges. Some sites show a consent wall that isn't one of the known banners, or lock scrolling until consent is given; those can still appear in the screenshot.

#### Where are the files, and for how long?

In the run's default key-value store, under the `key` in each row; `fileUrl` is a direct link (signed when your account restricts storage access). Storage retention follows your Apify plan, so download the files or copy them elsewhere if you need them long-term.

#### Why is a tall page cut off?

A single image is limited to 16,384 pixels tall (and wide) in image pixels, so at a scale factor of 2 that's 8,192 CSS pixels of page. Longer pages are captured from the top down to the limit and the row has `truncated: true`. Use PDF for very long pages: it has no height limit.

#### What happens when a page never finishes loading?

If the *Wait until* moment doesn't come within the navigation timeout (common with `networkidle` on sites that keep connections open), the page is captured as it is and the row has `navigationTimedOut: true`. If the server doesn't respond at all, the row is a free `timeout` error.

#### Can I capture internal or localhost pages?

No. Private, loopback, link-local and cloud-metadata addresses are always refused, including when a public page redirects to one or tries to load images or scripts from one.

#### Other limits

- Only http and https URLs. Downloads (links to zip, exe and similar files) are refused with error `download`.
- Each URL is loaded in its own fresh browser context: no cookies, logins or storage carry over between URLs.
- The user agent is a standard Chrome one for the chosen device, plus a token naming this tool. Pages load in English (`en-US`).
- The mobile and tablet presets emulate screen size, pixel density, touch and user agent in Chromium; they don't reproduce Safari-specific rendering.
- PDFs use the page's print styles, like Chrome's "Save as PDF".

### Other tools by the same author

| Actor | Use it to |
|---|---|
| [Bulk URL Status Checker](https://apify.com/charlesmarkette/bulk-url-status-checker-redirects-ssl) | Check status codes, redirects and SSL for a URL list first, then capture only the pages that load. |
| [PageSpeed & Lighthouse Audit](https://apify.com/charlesmarkette/pagespeed-lighthouse-audit-core-web-vitals) | Get Lighthouse scores and Core Web Vitals for the same pages. |
| [Dataset Delivery](https://apify.com/charlesmarkette/dataset-delivery-export-to-sftp-ftp-dropbox-google-drive) | Deliver the results table, with its file links, as CSV or Excel to SFTP, Dropbox or Google Drive. |
| [Google Sheets Import & Export](https://apify.com/charlesmarkette/google-sheets-sync) | Send the rows (URL, title, status, file link) to a Google Sheet. |

# Actor input Schema

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

Pages to capture, one per line. Bare domains (example.com) get https://. In JSON you can also pass objects: {"url": "https://example.com", "label": "Home", "selector": "#pricing"} to name a row or capture one element of that page only.

## `datasetId` (type: `string`):

Optional. Capture the URL in each item of a dataset (for example from a scraper or sitemap run). Use {{resource.defaultDatasetId}} in an integration.

## `urlField` (type: `string`):

The item field that holds the URL. Dotted paths work (page.url). Default: url.

## `keepItemFields` (type: `boolean`):

On: each output row is the dataset item plus the screenshot columns. Off: only the screenshot columns.

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

PNG (sharp, lossless), JPEG (smaller files) or PDF (printed like Chrome's 'Save as PDF', with selectable text).

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

On: the whole scrollable page (cut at 16,384 px tall; such rows get truncated: true). Off: only the visible viewport. Not used for PDF.

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

Optional. Capture just the first element matching this CSS selector, for example #pricing or .hero. A page without a match gets a free row with error selectorNotFound. Images only (not PDF).

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

1–100. Only used for JPEG.

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

Desktop uses the viewport size below. Mobile is 390×844 at 3× (touch, mobile user agent). Tablet is 820×1180 at 2×.

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

Browser window width in CSS pixels, 320–3840.

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

Browser window height in CSS pixels, 240–4320. This is the screenshot height when Full page is off.

## `deviceScaleFactor` (type: `integer`):

Optional. Pixel density, 1–4: 2 gives retina-sharp images twice as wide and tall. Overrides the device preset's value (mobile 3, tablet 2, desktop 1).

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

Tell the page the system prefers a dark colour scheme (prefers-color-scheme: dark). Only sites that support dark mode change.

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

When the page counts as loaded. 'load' suits most sites; 'networkidle' waits until there is no network traffic for 0.5 s (good for dashboards, can be slow); 'domcontentloaded' is fastest. If the moment never comes within the navigation timeout, the page is still captured and the row gets navigationTimedOut: true.

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

Optional. Wait until an element matching this selector is visible before capturing. Pages where it never appears get a free row with error waitForSelectorTimeout.

## `delayMs` (type: `integer`):

Extra wait before capturing, for animations and late content. 0–30000.

## `scrollToLoad` (type: `boolean`):

Scroll down the page in steps before capturing so lazy-loaded images appear, then scroll back to the top. Capped at 40 steps. Used for full-page, element and PDF captures.

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

Hide common cookie and consent pop-ups (OneTrust, Cookiebot, Didomi, Usercentrics, Quantcast and others) with CSS. Nothing is clicked, so no consent is given. Best effort: unknown banners stay visible.

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

Optional. Elements to make invisible before capturing, for example chat widgets or ads: #intercom-container, .ad-slot. Their space is kept, so the layout doesn't move.

## `blockMedia` (type: `boolean`):

Don't download video and audio files. Faster pages with autoplay videos; the player area may show as empty.

## `pdfPaperFormat` (type: `string`):

Only used for PDF.

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

Include background colours and images in the PDF.

## `concurrency` (type: `integer`):

How many pages to load at the same time in the browser (1–10). Each extra page needs more memory; heavy sites need more than simple ones. If pages crash, lower this or raise the run's memory.

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

How long to wait for a page to respond and load (5–180). Each URL as a whole (loading, waits and capture) gets at most twice this plus the extra delay and 60 seconds.

## `maxResults` (type: `integer`):

Optional cap on output rows (each row is one URL).

## Actor input object example

```json
{
  "urls": [
    "https://apify.com",
    "https://www.wikipedia.org"
  ],
  "urlField": "url",
  "keepItemFields": true,
  "format": "png",
  "fullPage": true,
  "jpegQuality": 80,
  "device": "desktop",
  "viewportWidth": 1280,
  "viewportHeight": 800,
  "darkMode": false,
  "waitUntil": "load",
  "delayMs": 500,
  "scrollToLoad": true,
  "hideCookieBanners": true,
  "blockMedia": false,
  "pdfPaperFormat": "A4",
  "pdfPrintBackground": true,
  "concurrency": 3,
  "navigationTimeoutSecs": 45
}
```

# Actor output Schema

## `items` (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://apify.com",
        "https://www.wikipedia.org"
    ]
};

// Run the Actor and wait for it to finish
const run = await client.actor("charlesmarkette/website-screenshot-pdf-full-page-element-bulk").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://apify.com",
        "https://www.wikipedia.org",
    ] }

# Run the Actor and wait for it to finish
run = client.actor("charlesmarkette/website-screenshot-pdf-full-page-element-bulk").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://apify.com",
    "https://www.wikipedia.org"
  ]
}' |
apify call charlesmarkette/website-screenshot-pdf-full-page-element-bulk --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,charlesmarkette/website-screenshot-pdf-full-page-element-bulk"
        }
    }
}
```

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/F8GYDW2dJdPHcpAzo/builds/96MYNSuo9ChgDENjB/openapi.json
