Генератор скриншотов сайтов на Python

Недавно я столкнулся с довольно простой задачей: создать генератор скриншотов сайтов. Идея такая: вводим любой URL в поле ввода и показываем под ним сгенерированый скриншот страницы.
Для разработки этого приложения я использовал Python/Django и Selenium WebDriver. Selenium это великолепный инструмент для автоматизации веб-задач.
Устанавливаем Python Selenium так:
pip install selenium
Если вы хотите получить скриншот средствами только Python, это так же просто:


from selenium import webdriver

""" Save a screenshot from spotify.com in current directory. """
DRIVER = "chromedriver"
driver = webdriver.Chrome(DRIVER)
driver.get("https://www.spotify.com")
screenshot = driver.save_screenshot("my_screenshot.png")
driver.quit()

Selenium требует драйвера для взаимодействия с браузером и я использую Chrome Driver. Скачайте его отсюда и скопируйте в любую папку вашей PATH, чтобы он был доступен отовсюду.
В моём случае драйвер находится в папке /usr/local/bin/chromedriver
Вы также можете сохранять скриншоты на диск:
driver.save_screenshot(IMAGE_PATH)
или получить скриншот как двоичный объект:
driver.get_screenshot_as_png()
Мы сделаем поддержку обоих вариантов. Начнём разработку приложения:
# Создадим проект
django-admin.py startproject screenshot_generator

# Создадим приложение
django-admin.py startapp app

В файле settings.py не забудьте добавить созданную ‘app’ и задать значения переменных для статических данных.


# settings.py
INSTALLED_APPS = [

‘app’
]

STATIC_URL = ‘/static/’
MEDIA_ROOT = os.path.join(BASE_DIR, ‘media’)
MEDIA_URL = ‘/media/’

В файле urls.py проекта включим url нашего приложения и зададим директорию для медиаконтента:


# urls.py
from django.conf.urls import url, include
from django.conf import settings
from django.views.static import serve
from app import urls as app_urls
urlpatterns = [
url(r’^’, include(app_urls)),
url(r’^media/(?P.*)$’, serve, {
‘document_root’: settings.MEDIA_ROOT,
}),
]

Теперь перейдём к нашему приложению:
Сначала создадим представление главной страницы в файле urls.py:


# urls.py
from django.conf.urls import url
from django.views.generic import TemplateView

urlpatterns = [
url(r’^$’, TemplateView.as_view(template_name=”home.html”)),
]

Затем создадим простой шаблон HTML с формой ввода желаемого URL:

# home.html

Enter URL:

{{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a} csrf_token {33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}}


Как видите, действия в форме вызывают метод get_screenshot, который у нас находится в views.py. В этом методе сконцентрирована вся магия создания скриншота.
Запустим приложение и перейдём по адресу http://localhost:8000/ чтобы увидеть главную страницу:
python manage.py runserver
Генератор скриншотов сайтов на Python
Перед созданием get_screenshot, мы должны добавить представление в файл urls.py. Финальный urls.py будет таким:

# urls.py
from django.conf.urls import url
from django.views.generic import TemplateView
from app import views

urlpatterns = [
url(r’^$’, TemplateView.as_view(template_name=”home.html”)),
url(r’^get_screenshot’, views.get_screenshot, name=”get_screenshot”),
]

Теперь создадим get_screenshot. Пошагово:
# views.py

def get_screenshot(request):
width = 1024
height = 768

Вы можете задать ширину и высоту для драйвера, чтобы получить скриншот нужного размера. Я задаю размеры по-умолчанию для случая, когда они не заданы в URL.
Более подробная документация по API веб-драйвера Selenium и его параметрам находится здесь.
В нашем представлении вначале нам нужно проверить метод запроса и указан ли в нём параметр ‘url’. А также проверить, не пустой ли url и не равен ли null:
if request.method == ‘POST’ and ‘url’ in request.POST:
url = request.POST.get(“url”, “”)
if url is not None and url != ‘’:

Затем проверим параметры url, если пользователь задал их:
params = urlparse.parse_qs(urlparse.urlparse(url).query)
if len(params) > 0:
if ‘w’ in params: width = int(params[‘w’][0])
if ‘h’ in params: height = int(params[‘h’][0])

# Ex: https://www.netflix.com/?w=800&h=600
После этого выберем корректный драйвер, передадим ему url и зададим размер окна:
driver = webdriver.Chrome(DRIVER)
driver.get(url)
driver.set_window_size(width, height)

Теперь проверим, задал ли пользователь параметры сохранения. От этой переменной зависит, будем ли мы сохранять скриншот на диск или хранить его в двоичном формате:
if ‘save’ in params and params[‘save’][0] == ‘true’:

# Ex: https://www.netflix.com/?save=true
Если это условие выполняется, мы сохраним скриншот в папку media. Наименование скриншота будет формироваться склеиванием текущего времени и строки “_image.png”.
Мы должны передать полный путь методу save_screenshot. Также убедимся, что папка для медиаконтента существует:
now = str(datetime.today().timestamp())
img_dir = settings.MEDIA_ROOT
img_name = “”.join([now, ‘_image.png’])
full_img_path = os.path.join(img_dir, img_name)
if not os.path.exists(img_dir):
os.makedirs(img_dir)
driver.save_screenshot(full_img_path)
screenshot = open(full_img_path, “rb”).read()
var_dict = {‘screenshot’:img_name, ‘save’:True}

В случае, если параметр сохранения = false, мы просто получаем двоичные данные методом get_screenshot_as_png() и сохраняем их в специальную переменную:
screenshot = driver.get_screenshot_as_png()
image_64_encode = base64.encodestring(screenshot)
var_dict = {‘screenshot’:image_64_encode}

В обоих случаях var_dict это словарь, содержащий переменные, необходимые для нашего шаблона главной страницы.
В конце уничтожаем объект драйвера и обрабатываем наш шаблон:

# Финальный views.py:
from django.shortcuts import render
from django.http import HttpResponse
from django.conf import settings
from datetime import datetime
from selenium import webdriver

import base64
import os
import urllib.parse as urlparse

DRIVER = “chromedriver”

def get_screenshot(request):
width = 1024
height = 768

if request.method == ‘POST’ and ‘url’ in request.POST:
url = request.POST.get(“url”, “”)
if url is not None and url != ‘’:
params = urlparse.parse_qs(urlparse.urlparse(url).query)
if len(params) > 0:
if ‘w’ in params: width = int(params[‘w’][0])
if ‘h’ in params: height = int(params[‘h’][0])
driver = webdriver.Chrome(DRIVER)
driver.get(url)
driver.set_window_size(width, height)

if ‘save’ in params and params[‘save’][0] == ‘true’:
now = str(datetime.today().timestamp())
img_dir = settings.MEDIA_ROOT
img_name = “”.join([now, ‘_image.png’])
full_img_path = os.path.join(img_dir, img_name)
if not os.path.exists(img_dir):
os.makedirs(img_dir)
driver.save_screenshot(full_img_path)
screenshot = open(full_img_path, “rb”).read()
var_dict = {‘screenshot’:img_name, ‘save’:True}
else:
screenshot = driver.get_screenshot_as_png()
image_64_encode = base64.encodestring(screenshot)
var_dict = {‘screenshot’:image_64_encode}

driver.quit()
return render(request, ‘home.html’, var_dict)
else:
return HttpResponse(“Error”)

Погодите, мы ещё детально не объяснили переменную var_dict и что мы передаём в наш шаблон.
При сохранении скриншота на диск мы передаём название изображения в тег шаблона ‘screenshot’. При получении двоичных данных (в нашем случае там картинка) нам необходимо перекодировать их, чтобы передать в наш словарь в виде строки. Мы не можем передать двоичные данные в метод render.
В Python есть специальный модуль base64, с которым мы легко это сделаем:
import base64

# перекодируем изображение:
screenshot = driver.get_screenshot_as_png()
image_64_encode = base64.encodestring(screenshot)

Для раскодирования создадим файл app_extras.py в папке app/templatetags/ и зарегистрируем шаблонный тег decode_image.
# app_extras.py
from django import template
import base64

register = template.Library

@register.filter()
def decode_image(encoded_image):
return “data:image/png;base64,{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}s” {33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a} encoded_image.decode(“utf8”)

Теперь обновим шаблон home.html для вывода скриншота:

# Финальный home.html
{{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a} load app_extras {33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}}


Enter URL:

{{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a} csrf_token {33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}}


{{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a} if screenshot {33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}}

Screenshot

{{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a} if save {33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}}
Генератор скриншотов сайтов на Python
{{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a} else {33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}}
Генератор скриншотов сайтов на Python
{{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a} endif {33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}}
{{33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a} endif {33d8302486bd10b0fde64d2037652320e6f176a736d71849c0427b0d7398501a}}




Немного пояснений по коду:
Если тег screenshot существует, мы выводим теги HTML. И не будем их выводить если screenshot не задан.
При сохранении скриншота мы показываем его, передавая адрес директории media + наименование изображения как параметр src. Будет что-то подобное:
Генератор скриншотов сайтов на Python
В другом случае мы показываем изображение, прошедшее кодирование и преобразование с помощью шаблонного фильтра, созданного ранее.
Это будет выглядеть примерно так:
Генератор скриншотов сайтов на Python
Запустим наше приложение, чтобы увидеть его в действии:
python manage.py runserver
Для примера я взял адрес сайта Scrapinghub. Я также задал ширину/высоту и сохранение скриншота на диск:
https://www.scrapinghub.com/?w=800&h=600&save=true
Генератор скриншотов сайтов на Python
Исходный код этого проекта вы можете найти на Github.
Надеюсь, этот пример поможет кому-нибудь лучше понять как получать изображения и выводить его на страницу.

Источник

Leave a Comment