> ## Documentation Index
> Fetch the complete documentation index at: https://docs.zenrows.com/llms.txt
> Use this file to discover all available pages before exploring further.

> ## Agent Instructions
> The Zenrows API (Fetch, Extract via extract=auto, and Batch) is described in OpenAPI 3.1 at https://www.zenrows.com/openapi.json and listed in the RFC 9727 API catalog at https://www.zenrows.com/.well-known/api-catalog. Use the spec for exact parameter names and types.

# Take screenshots and save pages as PDF

> Take full-page and element screenshots, save pages as PDF and download files with Zenrows Browser.

Capture what a page looks like in Zenrows Browser, the browser for agents: a viewport or full-page screenshot, a single element, or a print-ready PDF. You can also download files the page links to, through the same session, IP and cookies.

**Uses:** [Browser](/browser-sessions/introduction) with `page.screenshot`, `locator.screenshot`, `page.pdf` and in-page `fetch`

## Ask your AI agent

With the [Zenrows MCP server](/mcp/overview) connected to Claude, Cursor or any MCP client, paste this prompt. Your agent picks the right Zenrows tools and settings on its own.

```text theme={"dark"}
Use the Zenrows browser to open https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie/.
Take a screenshot of the product summary, save the full page as a PDF, and describe
what the screenshot shows. Then close the browser session.
```

## Take screenshots and a PDF

<CodeGroup>
  ```python Python (Playwright) theme={"dark"}
  import asyncio
  from playwright.async_api import async_playwright

  CONNECTION_URL = "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY"

  async def main():
      async with async_playwright() as p:
          browser = await p.chromium.connect_over_cdp(CONNECTION_URL)
          try:
              page = await browser.contexts[0].new_page()
              await page.goto("https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie/")

              await page.screenshot(path="viewport.png")
              await page.screenshot(path="full-page.png", full_page=True)
              await page.locator(".summary").screenshot(path="product-summary.png")
              await page.pdf(path="product.pdf", format="A4", print_background=True)
              print("Saved viewport.png, full-page.png, product-summary.png and product.pdf")
          finally:
              await browser.close()

  asyncio.run(main())
  ```

  ```javascript Node.js (Playwright) theme={"dark"}
  import { chromium } from "playwright-core";

  const connectionURL = "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY";

  const browser = await chromium.connectOverCDP(connectionURL);
  try {
    const page = await browser.contexts()[0].newPage();
    await page.goto("https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie/");

    await page.screenshot({ path: "viewport.png" });
    await page.screenshot({ path: "full-page.png", fullPage: true });
    await page.locator(".summary").screenshot({ path: "product-summary.png" });
    await page.pdf({ path: "product.pdf", format: "A4", printBackground: true });
    console.log("Saved viewport.png, full-page.png, product-summary.png and product.pdf");
  } finally {
    await browser.close();
  }
  ```

  ```javascript Puppeteer theme={"dark"}
  import puppeteer from "puppeteer-core";

  const browser = await puppeteer.connect({
    browserWSEndpoint: "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY",
  });
  try {
    const page = await browser.newPage();
    await page.goto("https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie/");

    await page.screenshot({ path: "full-page.png", fullPage: true });
    const summary = await page.$(".summary");
    await summary.screenshot({ path: "product-summary.png" });
    await page.pdf({ path: "product.pdf", format: "A4", printBackground: true });
    console.log("Saved full-page.png, product-summary.png and product.pdf");
  } finally {
    await browser.close();
  }
  ```

  ```bash CLI theme={"dark"}
  SESSION=$(zenrows browser open https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie/ --json | jq -r .session_id)
  zenrows browser screenshot --session "$SESSION" --out viewport.png
  zenrows browser screenshot --session "$SESSION" --selector .summary --out product-summary.png
  zenrows browser pdf --session "$SESSION" --print-background --out product.pdf
  zenrows browser close --session "$SESSION"
  ```
</CodeGroup>

The screenshots and PDF render in the cloud browser and arrive as files on your machine.

## Download files through the session

To save a file the page links to (an image, a report, an export), read the link and fetch it from inside the page. The request runs in the remote browser, so it carries the session's IP, cookies and login, and returns the bytes to your script.

```python Python (Playwright) theme={"dark"}
import asyncio, base64
from playwright.async_api import async_playwright

CONNECTION_URL = "wss://browser.zenrows.com?apikey=YOUR_ZENROWS_API_KEY"

# Runs inside the remote browser: same IP, cookies and session as the page
FETCH_AS_BASE64 = """async (url) => {
    const response = await fetch(url, { credentials: "include" });
    const bytes = new Uint8Array(await response.arrayBuffer());
    let binary = "";
    for (const b of bytes) binary += String.fromCharCode(b);
    return btoa(binary);
}"""

async def main():
    async with async_playwright() as p:
        browser = await p.chromium.connect_over_cdp(CONNECTION_URL)
        try:
            page = await browser.contexts[0].new_page()
            await page.goto("https://www.scrapingcourse.com/ecommerce/product/abominable-hoodie/")

            # Find the file link on the page, then download it through the browser
            file_url = await page.get_attribute(".woocommerce-product-gallery__image a", "href")
            data = await page.evaluate(FETCH_AS_BASE64, file_url)
            filename = file_url.rsplit("/", 1)[-1]
            with open(filename, "wb") as f:
                f.write(base64.b64decode(data))
            print(f"Saved {filename} ({len(base64.b64decode(data))} bytes)")
        finally:
            await browser.close()

asyncio.run(main())
```

Swap the selector for your site's export or download link. The same pattern works for files behind a login: log in first, then fetch the file in the same page.

## Example output

```text theme={"dark"}
Saved viewport.png, full-page.png, product-summary.png and product.pdf
```

The files from the Python run:

```text theme={"dark"}
viewport.png:        PNG image data, 1680 x 869
full-page.png:       PNG image data, 1665 x 2099
product-summary.png: PNG image data, 417 x 517
product.pdf:         PDF document, version 1.4, 2 pages
```

The download:

```text theme={"dark"}
Saved mh09-blue_main.jpg (203443 bytes)
```

## Customize it

| Change | How |
| - | - |
| JPEG with a smaller file size | `page.screenshot(path="shot.jpg", type="jpeg", quality=70)` |
| A fixed viewport size | `await page.set_viewport_size({"width": 1280, "height": 800})` before the screenshot |
| Landscape or Letter PDF | `page.pdf(path="page.pdf", format="Letter", landscape=True)` |
| Screenshot what visitors in a country see | Add `&proxy_country=jp` to the connection URL, see [Country targeting](/browser-sessions/features/country) |
| A screenshot without a browser session | Use Fetch with `screenshot=true`, see [Capture screenshots and files with Fetch](/academy/fetch/screenshots-and-files) |
| Screenshots of many pages | One session per page, see [Run browser sessions in parallel](/academy/browser/parallel-sessions) |

## Troubleshooting

* **The screenshot shows a half-loaded page**: the page was still rendering. Wait for the element you care about with `page.wait_for_selector(...)` before the screenshot.
* **Lazy-loaded images are blank in the full-page screenshot**: they load as you scroll. Scroll to the bottom first (see [Scroll and paginate](/academy/browser/infinite-scroll-and-pagination)), then take the screenshot.
* **The PDF has no background colors**: set `print_background=True` (`printBackground: true` in Node.js).
* **A download triggered by a click comes back as `canceled`**: the file lands inside the cloud browser. Read the link's URL and fetch it in the page, as shown above, to get the bytes in your script.

## Related recipes

<CardGroup cols={2}>
  <Card title="Fill and submit multi-step forms" icon="forms" href="/academy/browser/fill-forms">
    Submit a form, then capture the result as proof.
  </Card>

  <Card title="Log in once and reuse the session" icon="login" href="/academy/browser/login-and-reuse-sessions">
    Download files that sit behind a login.
  </Card>

  <Card title="Capture screenshots, PDFs, and files" icon="photo" href="/academy/fetch/screenshots-and-files">
    One request per screenshot, PDF or file, no session to manage.
  </Card>

  <Card title="Let your AI assistant browse with Browser tools" icon="message-chatbot" href="/academy/browser/ai-assistant-browsing">
    Ask Claude or Cursor for screenshots and PDFs in plain language.
  </Card>
</CardGroup>


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.