Snapshot any site with Django in 3 minutes 🖼️
There will be 4 steps. With the final product that we'll make in 3 minutes, you could:
- 💵 Expand it and sell it as a product (e.g., Screenshotlayer)
- Create visual content for your blog posts by capturing live website previews
- Create OG social images to improve your sharing on social media (See my guide on how to add OG images: Create a open graph social image generator with Django 🌐)
We'll use a headless browser to visit a url, take a screenshot, and then render that screenshot into our page.
Here's what our final product will look like. Let's start 🏇🏿
Optional video tutorial (featuring me 🏇🏿) below:
1. Setup
1.1 Install the requirements and create a new Django app
- In your terminal:
pip install Django selenium Pillow webdriver_managerdjango-admin startproject core .python manage.py startapp sim
1.2 Install Google Chrome
The code is setup for Google Chrome. You can use Brave or Safari, but you'll need to modify the code (only 4-5 lines to change).
1.3 Update your settings.py
- Register our sim app by adding it to
INSTALLED_APPS
INSTALLED_APPS = [# ...'sim',]`
2. Add our urls
- Update
core/urls.py
from django.contrib import adminfrom django.urls import path, includeurlpatterns = [path('admin/', admin.site.urls),path('', include("sim.urls")),]
- Update
sim/urls.py
from django.urls import pathfrom sim.views import CaptureView, capture_pageurlpatterns = [path('capture/', capture_page, name='capture_page'),path('capture-image/', CaptureView.as_view(), name='capture_image'),]
3. Add our views to take the screenshot
- Add this to
sim/views.py
:
import osfrom django.http import HttpResponsefrom django.shortcuts import renderfrom django.views import Viewfrom selenium import webdriverfrom selenium.webdriver.chrome.options import Optionsfrom webdriver_manager.chrome import ChromeDriverManagerclass CaptureView(View):def post(self, request, *args, **kwargs) -> HttpResponse:url = request.POST.get('url')print(f'{url = }')if url:return self.capture_website(url)else:return HttpResponse('No url provided', status=400)def capture_website(self, url: str) -> HttpResponse:"""Visits the url to take a screenshot."""chrome_options = Options()chrome_options.add_argument("--headless")driver = webdriver.Chrome(options=chrome_options)driver.get(url)print(f'Getting image for {url = }')driver.set_window_size(1920, 1080)# Create a path for our screenshot file.static_dir = os.path.join('sim', 'static', 'sim')os.makedirs(static_dir, exist_ok=True)screenshot_path = os.path.join(static_dir, 'screenshot.png')driver.save_screenshot(screenshot_path)driver.quit()return render(self.request, 'preview.html')def capture_page(request) -> HttpResponse:"""Renders the initial page."""return render(request, 'capture.html')
4. Add templates to render the result
- Create templates folder at
sim/templates/
- Create
preview.html
insim/templates/
containing:
{% load static %}<style>#preview-link img {border-radius: 15px;width: 80%;margin: auto;}#preview-link {display: flex;justify-content: center;}</style><aid="download-link"href="{% static 'sim/screenshot.png' %}"download="screenshot.png">Download Image</a><a id="preview-link" href="{% static 'sim/screenshot.png' %}" target="_blank"><imgid="screenshot"src="{% static 'sim/screenshot.png' %}"alt="Website Screenshot"/></a>
- Create
capture.html
insim/templates/
containing:
<!doctype html><html lang="en"><head><meta charset="UTF-8" /><meta name="viewport" content="width=device-width, initial-scale=1" /><title>Website Capture</title><scriptsrc="https://unpkg.com/htmx.org@1.9.6"integrity="sha384-FhXw7b6AlE/jyjlZH5iHa/tTe9EpJ1Y55RjcgPbjeWMskSxZt1v9qkxLJWNJaGni"crossorigin="anonymous"></script><style>body {font-family: Arial, sans-serif;margin: 0;padding: 0;display: flex;flex-direction: column;align-items: center;justify-content: center;min-height: 100vh;background-color: #f0f0f0;}h1 {font-weight: normal;}form {margin-bottom: 20px;}input,button {padding: 10px;margin: 5px;border: 1px solid #ccc;border-radius: 4px;}button:disabled {background-color: #ccc;}#loading {display: none;}#preview[loading] #loading {display: block;}#preview[loading] img,#preview[loading] p,#preview[loading] a {display: none;}#preview {text-align: center;}.htmx-indicator {opacity: 0;transition: opacity 500ms ease-in;}.htmx-request .htmx-indicator {opacity: 1;}.htmx-request.htmx-indicator {opacity: 1;}#spinner {position: fixed;margin: auto;}#spinner:before {content: '';position: absolute;top: 50%;left: 50%;width: 80px;height: 80px;margin-top: -40px;margin-left: -40px;border: 4px solid #f1cbcb;border-top-color: transparent;border-radius: 50%;animation: spin 2s linear infinite;}@keyframes spin {0% {transform: rotate(0deg);}100% {transform: rotate(360deg);}}</style></head><body><h1>Screenshot any website</h1><formhx-post="/capture-image/"hx-trigger="submit"hx-swap="innerHTML"hx-target="#preview"hx-indicator="#spinner">{% csrf_token %}<inputtype="url"name="url"requiredplaceholder="Enter URL"value="{{ url }}"/><button type="submit">Capture</button></form><div id="spinner" class="spinner htmx-indicator"></div><div id="preview"><!-- Content will be replaced by the server response --></div></body></html>
Run your Django server
python manage.py runserver
Now, you can visit the page at http://127.0.0.1:8000/capture/
, submit a url, and receive the screenshot back automatically.
Finished 🎉 Here some ideas to extend this
-
Deploy this online and sell it as a service 💵. If you do, I'd recommend using serverless functions when deploying this: each request will take a few seconds. Here's my guide on how to add serverless functions with Django as simply as possible: How to add serverless functions to Django in 6 minutes 🧠
-
Add mobile device previews by adapting the code to capture mobile views (You'll only need to change one line of the above code).