# Screenshot & PDF Renderer: Full Page, Mobile & Dark Mode (`everyotherfriday/screenshot-renderer`) Actor

Capture URLs as PNG, JPEG, WebP or PDF with Chromium. Choose full-page or element captures, desktop/mobile/tablet sizes, dark mode, ad blocking and cookie-banner hiding. Export stored files and structured results; failed captures are not charged.

- **URL**: https://apify.com/everyotherfriday/screenshot-renderer.md
- **Developed by:** [Paul Vasquez](https://apify.com/everyotherfriday) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

$5.00 / 1,000 successful captures

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

## Screenshot / PDF Renderer

### What it does

This Python Apify Actor opens URLs in headless Chromium and saves PNG, JPEG, WebP, or PDF captures. Each successful URL produces a binary record in the default key-value store and one dataset row linking to it. Failed navigations produce error rows without a capture charge. One browser serves a bounded pool of workers; each capture gets its own context, keeping cookies and page state separate.

Use it for visual monitoring, responsive-layout checks, page archives, report attachments, or previews. It renders JavaScript, but does not log into websites, solve challenges, scroll through infinite feeds, or guarantee that a page has finished every asynchronous update.

### Input

`urls` is required: one to 1,000 absolute HTTP(S) URLs. Data URLs are also accepted for local fixtures. Strings are trimmed, while paths, queries, fragments, and duplicate entries are preserved. URL userinfo is rejected. Every duplicate is processed separately; identical URL/format pairs overwrite the same store record.

`format` defaults to `png`; alternatives are `jpeg`, `webp`, and `pdf`. `fullPage` defaults to true for images. PNG is lossless; JPEG and WebP accept `quality` from 0–100, default 80. WebP is encoded from Chromium's PNG output with Pillow. Quality does not affect PNG or PDF.

`device` selects desktop (1440×900, scale 1), mobile (390×844, scale 3), or tablet (820×1180, scale 2). Optional `width` and `height` override viewport dimensions independently; `deviceScaleFactor` overrides scale. Mobile/tablet enable touch, mobile layout, and an Android Chromium user agent. These are generic presets, not physical-device fidelity guarantees.

`darkMode`, `blockAds`, and `hideCookieBanners` default to false. Dark mode requests the browser's dark color scheme. Ad blocking matches exact hosts and subdomains in the original bundled `src/ad_hosts.txt` list. It never matches a hostname merely because the blocked name appears in a path or query. Service workers are disabled. Consent hiding injects targeted CSS for common banners; it does not submit consent, change cookies, remove every overlay, or unlock page scrolling.

`waitUntil` accepts `load` (default), `domcontentloaded`, or `networkidle`. `delayMs` adds 0–10,000 milliseconds afterward. `timeoutSecs`, default 45 and maximum 180, bounds the whole capture; cleanup can add five seconds. Persistent network traffic can make network-idle waits time out. `maxConcurrency` defaults to five and accepts 1–20.

`selector` captures the first matching visible element, overriding full-page image capture. For PDF, its subtree is isolated in place and printed with pagination; this is not a pixel-identical rectangular PDF crop. Missing or hidden targets time out.

`pdfOptions` accepts `paperFormat` (A4 default; A0–A6, Letter, Legal, Tabloid, Ledger), `landscape`, `printBackground` (true default), and a `margin` object with top/right/bottom/left values. Margins accept numbers in pixels or strings with px, in, cm, or mm units. PDFs use Chromium's print styles. `fullPage` and device scale do not control PDF pagination.

`proxyConfiguration` uses Apify's proxy editor schema, including `useApifyProxy`, proxy groups/country, or `proxyUrls`. Omitted configuration makes direct connections. Proxy URLs are assigned per capture and authentication is separated from the server address.

### Run locally

Use Python 3.12 from this directory:

```powershell
python -m venv .venv
.\.venv\Scripts\python.exe -m pip install -r requirements.txt
$env:PLAYWRIGHT_BROWSERS_PATH = Join-Path $PWD '.browsers'
.\.venv\Scripts\python.exe -m playwright install chromium
$env:APIFY_LOCAL_STORAGE_DIR = Join-Path $PWD 'storage/local'
New-Item -ItemType Directory -Force "$env:APIFY_LOCAL_STORAGE_DIR/key_value_stores/default" | Out-Null
Copy-Item INPUT.json "$env:APIFY_LOCAL_STORAGE_DIR/key_value_stores/default/INPUT.json"
.\.venv\Scripts\python.exe -m src
```

The Dockerfile uses `apify/actor-python-playwright:3.12` and installs Chromium matching the pinned Playwright package. The five-site PNG/PDF reproduction script is `validation/run_live.ps1`. See `VALIDATION.md` for environment limitations and measured outcomes.

### Output and pricing

Rows contain `url`, `finalUrl`, `status` (`success` or `error`), `format`, `width`, `height`, `fileSizeBytes`, `keyValueStoreKey`, `imageUrl`, `title`, `capturedAt`, and `error`. Image dimensions are actual output pixels. PDF dimensions describe its first page at 96 CSS pixels per inch. Error rows have no file link, zero bytes, and a concise error. HTTP errors and invalid TLS certificates fail without disabling certificate verification.

Keys combine a safe hostname label, URL/format hash, and extension. On Apify, links use the configured default store ID; SDK 4 names the configuration property `Actor.configuration`. Locally they are absolute file paths. A public URL's accessibility still depends on platform storage permissions and retention.

The price is **$0.005 per successful capture**. `Actor.charge` runs once after file storage and dataset emission. Configure the single custom `capture` event in Console before publication; the declaration alone does not activate billing. Local ignored-charge warnings are expected. Storage, dataset writes, and billing are not transactional across crashes; automatic retries are deliberately absent.

### Tests and limitations

Run `.\.venv\Scripts\python.exe -m unittest discover -s tests -v` after installing Chromium. Offline tests cover validation, presets, keys, consent CSS, host matching, error isolation, concurrency, storage links, and charging. The integration test uses a tiny local HTTP server and checks image/PDF content, element capture, HTTP failure, and timeout handling. Browser startup, storage infrastructure, or billing-service failures can fail the run. Large pages can exhaust memory; lower concurrency for demanding sites. No push or publication is part of this project task.

### Example output

One real dataset row from [validation/results.json](validation/results.json), trimmed by omitting fields without changing retained values:

```json
{
  "url": "https://example.com",
  "finalUrl": "https://example.com/",
  "status": "success",
  "format": "png",
  "width": 1440,
  "height": 900,
  "fileSizeBytes": 12088,
  "keyValueStoreKey": "example-com-4f5b6f48f2bb03938b39e49781748adb.png",
  "title": "Example Domain",
  "capturedAt": "2026-09-26T06:50:42.674919+00:00",
  "error": null
}
```

The local file path is omitted; the retained key identifies the saved PNG. This is one of the five successful PNG captures recorded in VALIDATION.md, which separately records five PDF captures. Dimensions and byte size describe this observation only.

### Use cases

- QA teams can capture release-candidate pages at desktop and mobile viewport settings for manual comparison.
- Digital agencies can attach client landing-page screenshots to recurring review reports.
- Research teams can archive public report pages as PDFs for a dated project record.
- Content operations teams can generate page previews for an internal editorial inventory.

**Pricing example:** 1,000 successful captures x $0.005 per `capture` event = **$5.00 in event fees**. Failed URLs cost nothing. Local runs do not bill.

Platform compute is included in that price; there is no separate usage charge.

# Actor input Schema

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

HTTP(S) URLs; data URLs are also supported for local fixtures. Duplicates are processed and charged individually.

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

Output file type. PNG is lossless; JPEG and WebP use the quality setting; PDF paginates the page.

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

Full scrollable image; selector takes precedence. PDFs always use pagination.

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

Desktop 1440x900 at 1x, mobile 390x844 at 3x, tablet 820x1180 at 2x. Width/height/scale override the preset.

## `width` (type: `integer`):

Viewport width in CSS pixels. Overrides the device preset.

## `height` (type: `integer`):

Viewport height in CSS pixels. Overrides the device preset.

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

Pixel density multiplier (1 = standard, 2 = retina). Overrides the device preset.

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

Emulate prefers-color-scheme: dark so sites with a dark theme render it.

## `blockAds` (type: `boolean`):

Block requests to a bundled list of common ad and tracker hosts before rendering.

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

Inject CSS that hides common cookie-consent banners and overlays before capture.

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

Navigation event to wait for before capturing. networkidle is slowest but most complete.

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

Extra wait in milliseconds after the page loads, for animations or lazy content.

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

First matching visible element; images clip to it. PDFs isolate its subtree and paginate it.

## `pdfOptions` (type: `object`):

paperFormat (A4 default); landscape; printBackground (true default); margin with top/right/bottom/left as pixels or unit strings.

## `quality` (type: `integer`):

JPEG and WebP only; ignored for PNG/PDF.

## `timeoutSecs` (type: `number`):

Whole capture deadline, including navigation, delay, selector and rendering; cleanup may add up to 5 seconds.

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

How many pages render at once in the single browser instance.

## `proxyConfiguration` (type: `object`):

Apify Proxy configuration or custom proxyUrls. Omitted means direct connections.

## Actor input object example

```json
{
  "urls": [
    "https://example.com",
    "https://www.wikipedia.org",
    "https://www.python.org"
  ],
  "format": "png",
  "fullPage": true,
  "device": "desktop",
  "darkMode": false,
  "blockAds": false,
  "hideCookieBanners": false,
  "waitUntil": "load",
  "delayMs": 0,
  "pdfOptions": {
    "paperFormat": "A4",
    "landscape": false,
    "printBackground": true
  },
  "quality": 80,
  "timeoutSecs": 45,
  "maxConcurrency": 5,
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}
```

# Actor output Schema

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

Per-URL rows with status, dimensions, file size and imageUrl.

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

Listing of stored PNG/JPEG/WebP/PDF records.

# 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.wikipedia.org",
        "https://www.python.org"
    ],
    "pdfOptions": {
        "paperFormat": "A4",
        "landscape": false,
        "printBackground": true
    },
    "proxyConfiguration": {
        "useApifyProxy": false
    }
};

// Run the Actor and wait for it to finish
const run = await client.actor("everyotherfriday/screenshot-renderer").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.wikipedia.org",
        "https://www.python.org",
    ],
    "pdfOptions": {
        "paperFormat": "A4",
        "landscape": False,
        "printBackground": True,
    },
    "proxyConfiguration": { "useApifyProxy": False },
}

# Run the Actor and wait for it to finish
run = client.actor("everyotherfriday/screenshot-renderer").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.wikipedia.org",
    "https://www.python.org"
  ],
  "pdfOptions": {
    "paperFormat": "A4",
    "landscape": false,
    "printBackground": true
  },
  "proxyConfiguration": {
    "useApifyProxy": false
  }
}' |
apify call everyotherfriday/screenshot-renderer --silent --output-dataset

```

## MCP server setup

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

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/9cn7SUTYQHBJTxIwg/builds/4k5GJ9RxCKodXAtaJ/openapi.json
