How to take website screenshots with Crawl4AI

Capture website screenshots with Crawl4AI in Python, decode the PNG result, handle full-page and dynamic content, and use ScreenshotOne for managed screenshot quality.

Blog post7 min read

Written by

Dmytro Krasun

Published on

Crawl4AI is useful when you need to crawl a website, extract content, and attach an image of the page to the result. You can enable screenshot capture in the same Python workflow.

If your application only needs screenshots, ScreenshotOne gives you a focused HTTP API without operating a crawler. It is also worth trying when you need cleaner output and dedicated controls for banners, lazy-loaded content, or image delivery.

Let’s start with a working local script, then look at full-page capture, dynamic content, and the API alternative.

Install Crawl4AI and its browser dependencies

Create a virtual environment and install the core package:

Terminal window
python3 -m venv .venv
source .venv/bin/activate
python -m pip install crawl4ai
crawl4ai-setup
crawl4ai-doctor

The activation command above is for macOS and Linux. On Windows, activate the virtual environment with .venv\Scripts\Activate.ps1 in PowerShell.

The setup command prepares the required browser dependencies. The doctor command checks the installation. Follow the Crawl4AI installation guide for your operating system if either reports a missing dependency.

You do not need the optional LLM or machine-learning packages for a screenshot. The examples below use the BrowserConfig and CrawlerRunConfig API documented in the current Crawl4AI documentation.

Take a website screenshot and save the PNG

Save this script as screenshot.py:

import asyncio
import base64
from pathlib import Path
from crawl4ai import AsyncWebCrawler, BrowserConfig, CacheMode, CrawlerRunConfig
async def main():
browser_config = BrowserConfig(
headless=True,
viewport_width=1280,
viewport_height=800,
text_mode=False,
)
run_config = CrawlerRunConfig(
screenshot=True,
force_viewport_screenshot=True,
wait_until="load",
page_timeout=60_000,
cache_mode=CacheMode.BYPASS,
)
async with AsyncWebCrawler(config=browser_config) as crawler:
result = await crawler.arun(
url="https://example.com",
config=run_config,
)
if not result.success:
raise RuntimeError(result.error_message or "The crawl failed")
if not result.screenshot:
raise RuntimeError("The crawl returned no screenshot")
image = base64.b64decode(result.screenshot, validate=True)
Path("screenshot.png").write_bytes(image)
print("Saved screenshot.png")
if __name__ == "__main__":
asyncio.run(main())

Run it with:

Terminal window
python screenshot.py

This requests a viewport screenshot at 1280 by 800 CSS pixels. The browser configuration keeps image loading enabled, and the run configuration bypasses the crawl cache to request a fresh result.

The important detail is the output type: result.screenshot is a base64-encoded PNG string. Writing that string directly to a .png file will not create a valid image. Decode it to bytes first.

The script also checks both crawl success and screenshot presence. A crawl can return useful content without the image your application requested, so check the output you actually need.

Capture a full page, including lazy-loaded content

To capture the whole page, replace the run configuration with:

run_config = CrawlerRunConfig(
screenshot=True,
force_viewport_screenshot=False,
scan_full_page=True,
scroll_delay=0.3,
max_scroll_steps=30,
wait_until="load",
page_timeout=60_000,
cache_mode=CacheMode.BYPASS,
)

force_viewport_screenshot=False permits full-page capture. scan_full_page=True scrolls the page, which can trigger content that only loads after entering the viewport. scroll_delay controls the delay between scrolling steps, and max_scroll_steps caps the scan. Increase the cap for a longer finite page after inspecting the result.

These options are described in the Crawl4AI parameter reference.

Full-page capture and complete content are separate concerns. A tall image can still contain placeholders if the site did not finish loading the images or sections you expected.

Try a representative long page and inspect its bottom sections. Infinite feeds, virtualized lists, sticky headers, and continuously changing content may need a site-specific capture strategy. Scrolling an infinite feed also needs a defined stopping point rather than an expectation of capturing everything.

Wait for a JavaScript application before capture

A page’s load event can fire before an application has populated its report, table, or chart. Use a readiness signal that describes the content you want.

For a page you control, you might add data-ready="true" to its report after rendering. Then configure:

run_config = CrawlerRunConfig(
screenshot=True,
force_viewport_screenshot=True,
wait_until="domcontentloaded",
wait_for='css:.report[data-ready="true"]',
page_timeout=60_000,
cache_mode=CacheMode.BYPASS,
)

Replace the selector with one from your application. Waiting for a ready attribute is more useful than waiting for an empty container that exists before the data arrives.

Crawl4AI also supports a JavaScript condition through wait_for. For example, this checks for a ready report and loaded fonts:

run_config = CrawlerRunConfig(
screenshot=True,
wait_for="""js:() => {
const report = document.querySelector('.report[data-ready="true"]');
return Boolean(report) && document.fonts.status === "loaded";
}""",
cache_mode=CacheMode.BYPASS,
)

See the page interaction guide for CSS and JavaScript waits.

You can add screenshot_wait_for=1.0 when a page needs an extra second before capture. Treat that as a measured fallback: a fixed delay is less useful than a condition that identifies completed content.

The same readiness problem appears when using Playwright directly. The guide on waiting for page load in Playwright explains the difference between navigation events and application state.

Take screenshots of multiple URLs

Reuse the crawler for a small batch instead of opening a new browser for every URL:

import asyncio
import base64
from pathlib import Path
from crawl4ai import AsyncWebCrawler, BrowserConfig, CacheMode, CrawlerRunConfig
async def main():
urls = ["https://example.com", "https://screenshotone.com"]
output = Path("screenshots")
output.mkdir(exist_ok=True)
browser_config = BrowserConfig(
headless=True,
viewport_width=1280,
viewport_height=800,
text_mode=False,
)
run_config = CrawlerRunConfig(
screenshot=True,
force_viewport_screenshot=False,
scan_full_page=True,
max_scroll_steps=30,
wait_until="load",
cache_mode=CacheMode.BYPASS,
)
async with AsyncWebCrawler(config=browser_config) as crawler:
for index, url in enumerate(urls, start=1):
try:
result = await crawler.arun(url=url, config=run_config)
if not result.success or not result.screenshot:
raise RuntimeError(result.error_message or "No screenshot returned")
destination = output / f"page-{index}.png"
destination.write_bytes(
base64.b64decode(result.screenshot, validate=True)
)
print(f"Saved {destination}")
except Exception as error:
print(f"Failed {url}: {error}")
if __name__ == "__main__":
asyncio.run(main())

This processes URLs sequentially and reports failures per page. For larger jobs, Crawl4AI also has arun_many(). Choose concurrency based on the browser memory available and the target sites’ capacity, and keep per-URL success and screenshot checks.

If you only need images, a screenshot API can move browser operation, rendering, and output delivery out of this batch script.

Common screenshot problems

ProblemWhat to check
The PNG cannot be openedDecode result.screenshot from base64 before saving it.
The result has no screenshotVerify screenshot=True, inspect result.success, and handle a missing image explicitly.
Images or styles are missingKeep text_mode=False; check whether your configuration blocks images or CSS.
A chart or table is emptyWait for its completed state, not just the document load event.
Lower sections contain placeholdersTry full-page scanning and inspect the site’s lazy-loading behavior.
The result is unexpectedly oldUse CacheMode.BYPASS when a fresh crawl is required.
A banner covers the contentTry remove_consent_popups=True, or add a page interaction to dismiss it.
The same URL produces different imagesFix viewport and page state; investigate fonts, animations, personalization, and changing data.

There is no universal delay that fixes every page. Start with the missing visual element and identify what must happen before it can render.

Crawl4AI also has remove_overlay_elements and remove_consent_popups controls in its parameter reference. Test them when overlays cover your content. A managed screenshot API is useful when you want the browser operation and those rendering adjustments handled by a service.

Use ScreenshotOne when you need only screenshots or cleaner output

Crawl4AI is a useful choice when crawling and content extraction are part of the same job. For a website preview, screenshot archive, report image, or AI vision workflow that only needs images, ScreenshotOne is a focused alternative.

You can request a full-page screenshot with banner and ad blocking through its HTTP API:

import os
from pathlib import Path
from urllib.parse import urlencode
from urllib.request import urlopen
options = {
"access_key": os.environ["SCREENSHOTONE_ACCESS_KEY"],
"url": "https://example.com",
"full_page": "true",
"viewport_width": "1280",
"viewport_height": "800",
"block_cookie_banners": "true",
"block_chats": "true",
"block_ads": "true",
"reduce_motion": "true",
"format": "png",
}
request_url = "https://api.screenshotone.com/take?" + urlencode(options)
with urlopen(request_url, timeout=90) as response:
image = response.read()
Path("screenshot.png").write_bytes(image)

Set SCREENSHOTONE_ACCESS_KEY in your environment before running the script. This example uses Python’s standard library, so it does not require a browser or a separate HTTP package in your application.

ScreenshotOne handles the browser rendering and returns PNG bytes directly. It does not return Crawl4AI’s base64 screenshot string, so the example saves the response without decoding it.

For better control of screenshot quality, ScreenshotOne provides:

Use the blockers when you want a clean preview, and leave them off when an overlay or ad belongs in the record you are capturing. Motion reduction is best-effort; changing page content can still make repeated captures differ.

If your Crawl4AI screenshots already meet your needs, keep using them. If output quality takes repeated custom fixes, test ScreenshotOne on the same URLs and compare the completeness, overlays, fonts, and layout. You can start with 100 free screenshots.

For another managed option, see the screenshot-focused comparison of ScreenshotOne and ScrapingBee.

Frequently Asked Questions

If you read the article, but still have questions. Please, check the most frequently asked. And if you still have questions, feel free reach out at support@screenshotone.com.

Can Crawl4AI take website screenshots?

Yes. Set screenshot=True in CrawlerRunConfig. Crawl4AI returns the image as a base64-encoded PNG string in result.screenshot. Decode it before writing an image file.

How do I take a full-page screenshot with Crawl4AI?

Enable screenshot=True and leave force_viewport_screenshot=False. For pages with lazy-loaded content, scan_full_page=True can scroll the page before capture. Check the output on long or animated pages.

Do I need an LLM to take screenshots with Crawl4AI?

No. Local screenshot capture uses browser rendering and does not require an LLM or an extraction API key.

When should I use ScreenshotOne instead of Crawl4AI?

Use ScreenshotOne when you need only screenshots, want managed browser infrastructure, or need dedicated controls for cleaner output, such as cookie-banner removal, full-page scrolling, image formats, and motion reduction.

Read more Screenshot rendering

Interviews, tips, guides, industry best practices, and news.

View all posts
How to take website screenshots with Java

How to take website screenshots with Java

The article examines how you can take screenshots of any URL with Java by using Selenium, Puppeteer alternatives, Playwright, or screenshot API as a service.

Read more

Automate website screenshots

Exhaustive documentation, ready SDKs, no-code tools, and other automation to help you render website screenshots and outsource all the boring work related to that to us.