---
slug: "headless-chromium-take-screenshot-bottle-gevent"
title: "A Server that Returns Website Screenshots Using Headless Chromium with Bottle and Gevent"
description: "Build a tiny HTTP server in Python (bottle + gevent) that takes a screenshot of any URL using headless Chromium and returns it as an image."
url: "https://www.ytyng.com/en/blog/headless-chromium-take-screenshot-bottle-gevent"
publish_date: "2021-08-20T12:27:01Z"
created: "2021-08-20T12:27:01Z"
updated: "2026-05-11T13:10:36.796Z"
categories: []
keywords: ""
featured_image_url: "https://media.ytyng.com/resize/20230812/272c07052bdb403bbe2b62c2fed903b4.png.webp?width=768"
has_video: false
has_music: false
video_urls: []
music_urls: []
lang: "en"
---

# A Server that Returns Website Screenshots Using Headless Chromium with Bottle and Gevent

<p>This is the code for a web application that takes a URL included as a parameter in the request URL, requests it with a headless Chrome, takes a screenshot, and returns the response.</p>
<p>The service is provided using Python, Bottle, and gevent.</p>
<p></p>
<pre><span>from </span>gevent <span>import </span>monkey<br />monkey.patch_all()<br /><br /><span>import </span>hashlib<br /><span>import </span>os<br /><span>import </span>subprocess<br /><br /><span>from </span>bottle <span>import </span>route, run, request, abort, HTTPResponse<br /><br />thumbnail_dir = <span>'/tmp/web-thumbnail'<br /></span><span><br /></span>veil_file_dir = os.path.join(os.path.dirname(__file__), <span>'static/veil.png'</span>)<br /><br /><br /><span>def </span><span>get_screenshot_filename</span>(url):<br />    h = hashlib.sha256(url.encode(<span>'utf-8'</span>, <span>'ignore'</span>))<br />    <span>return </span>os.path.join(thumbnail_dir, <span>'{}.png'</span>.format(h.hexdigest()))<br /><br /><br /><span>def </span><span>take_shapshot</span>(url):<br />    <span>"""<br /></span><span>    Generates a snapshot using headless Chromium<br /></span><span>    """<br /></span><span>    </span>command = [<br />        <span>'/usr/bin/chromium-browser'</span>,<br />        <span>'--no-sandbox'</span>,<br />        <span>'--headless'</span>,<br />        <span>'--disable-gpu'</span>,<br />        <span>'--no-zygote'</span>,<br />        <span># '--disable-software-rasterizer',<br /></span><span>        </span><span>'--screenshot={}'</span>.format(get_screenshot_filename(url)),<br />        <span>'--hide-scrollbars'</span>,<br />        <span>'--window-size=400,400'</span>,<br />        <span>'--lang=ja-JP'</span>,<br />        url,<br />    ]<br /><br />    <span>return </span>subprocess.check_call(command)<br /><br /><br /><span>@route</span>(<span>'/'</span>)<br /><span>def </span><span>index</span>():<br />    <span>"""<br /></span><span>    Thumbnail generation page<br /></span><span>    """<br /></span><span>    </span>url = request.query.u<br />    <span>if not </span>url:<br />        abort(<span>400</span>, <span>'Please specify parameter u'</span>)<br />        <span>return<br /></span><span>    </span>append_headers = []<br />    screenshot_filename = get_screenshot_filename(url)<br />    <span>if </span>os.path.exists(screenshot_filename):<br />        append_headers.append(<br />            (<span>'X-CacheFileResult'</span>, <span>f'Cache file found:</span><span>{</span>screenshot_filename<span>}</span><span>'</span>))<br />    <span>else</span>:<br />        ret = take_shapshot(url)<br />        append_headers.append((<span>'X-TakeResult'</span>, <span>str</span>(ret)))<br /><br />    <span>if </span>os.path.exists(screenshot_filename):<br />        <span>with </span><span>open</span>(screenshot_filename, <span>'rb'</span>) <span>as </span>fp:<br />            data = fp.read()<br />    <span>else</span>:<br />        <span># Could not create the file<br /></span><span>        </span><span>with </span><span>open</span>(veil_file_dir, <span>'rb'</span>) <span>as </span>fp:<br />            data = fp.read()<br />        append_headers.append(<br />            (<span>'X-CacheFileResult'</span>, <span>f'NotFound:</span><span>{</span>screenshot_filename<span>}</span><span>'</span>))<br />    r = HTTPResponse(<span>status</span>=<span>200</span>, <span>body</span>=data)<br />    r.set_header(<span>'Content-type'</span>, <span>'image/png'</span>)<br />    <span>for </span>header_name, header_value <span>in </span>append_headers:<br />        r.set_header(header_name, header_value)<br />    <span>return </span>r<br /><br /><br />run(<span>host</span>=<span>'0.0.0.0'</span>, <span>port</span>=<span>8080</span>, <span>server</span>=<span>'gevent'</span>)</pre>
