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:
python3 -m venv .venvsource .venv/bin/activatepython -m pip install crawl4aicrawl4ai-setupcrawl4ai-doctorThe 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 asyncioimport base64from 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:
python screenshot.pyThis 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 asyncioimport base64from 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
| Problem | What to check |
|---|---|
| The PNG cannot be opened | Decode result.screenshot from base64 before saving it. |
| The result has no screenshot | Verify screenshot=True, inspect result.success, and handle a missing image explicitly. |
| Images or styles are missing | Keep text_mode=False; check whether your configuration blocks images or CSS. |
| A chart or table is empty | Wait for its completed state, not just the document load event. |
| Lower sections contain placeholders | Try full-page scanning and inspect the site’s lazy-loading behavior. |
| The result is unexpectedly old | Use CacheMode.BYPASS when a fresh crawl is required. |
| A banner covers the content | Try remove_consent_popups=True, or add a page interaction to dismiss it. |
| The same URL produces different images | Fix 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 osfrom pathlib import Pathfrom urllib.parse import urlencodefrom 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:
- Cookie-banner removal and chat-widget removal to uncover the page content.
- Full-page scrolling to trigger lazy-loaded content before capture.
- Motion reduction to reduce changes from supported animations and media.
- Viewport and image options for consistent dimensions and output formats.
- Caching and S3-compatible storage for delivering the rendered images.
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.


