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_manager
django-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 admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include("sim.urls")),
]
- Update
sim/urls.py
from django.urls import path
from sim.views import CaptureView, capture_page
urlpatterns = [
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 os
from django.http import HttpResponse
from django.shortcuts import render
from django.views import View
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
class 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>
<a
id="download-link"
href="{% static 'sim/screenshot.png' %}"
download="screenshot.png"
>Download Image</a
>
<a id="preview-link" href="{% static 'sim/screenshot.png' %}" target="_blank">
<img
id="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>
<script
src="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>
<form
hx-post="/capture-image/"
hx-trigger="submit"
hx-swap="innerHTML"
hx-target="#preview"
hx-indicator="#spinner"
>
{% csrf_token %}
<input
type="url"
name="url"
required
placeholder="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).
P. S Want to build your Django frontend even faster?
I want to release high-quality products as soon as possible. Probably like you, I want to make my Django product ideas become reality as soon as possible.
That's why I'm building Photon Designer - an entirely visual editor for building Django frontend at the speed that light hits your eyes ๐ก