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

Разработка /
Недавно я столкнулся с довольно простой задачей: создать генератор скриншотов сайтов. Идея такая: вводим любой URL в поле ввода и показываем под ним сгенерированый скриншот страницы.
Для разработки этого приложения я использовал / и 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 требует драйвера для взаимодействия с браузером и я использую 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<path>.*)$’, 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
<h1>Enter URL:</h1>
<form action=”/get_screenshot/” method=”post”>
	{% csrf_token %}
	<input type=”url” name=”url” size=”40" placeholder=”Ex.  https://www.netflix.com" required>

	<input type=”submit” value=”Generate screenshot”>
</form> 


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

Разработка: home template
Перед созданием 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,%s” % encoded_image.decode(“utf8”) 

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

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

…
<body>
<div align=”center”>
<h1>Enter URL:</h1>
<form action=”/get_screenshot/” method=”post”>
{% csrf_token %}
<input type=”url” name=”url” size=”40" placeholder=”Ex. https://www.netflix.com" required>

<input type=”submit” value=”Generate screenshot”>
</form>
 
{% if screenshot %}
    <h2>Screenshot</h2>
    {% if save %}
        <img src=”{{ MEDIA_URL }}{{ screenshot }}” alt="Разработка: Генератор скриншотов сайтов на Python" />
    {% else %}
        <img src=”{{ screenshot|decode_image }}” alt="Разработка: Генератор скриншотов сайтов на Python" /> 
    {% endif %}
{% endif %}
</div>
</body>
… 

Немного пояснений по коду:
Если тег screenshot существует, мы выводим теги HTML. И не будем их выводить если screenshot не задан.
При сохранении скриншота мы показываем его, передавая адрес директории media + наименование изображения как параметр src. Будет что-то подобное:
<img src=”/media/1484738370.392261_image.png” alt="Разработка: Генератор скриншотов сайтов на Python" />

В другом случае мы показываем изображение, прошедшее кодирование и преобразование с помощью шаблонного фильтра, созданного ранее.
Это будет выглядеть примерно так:
<img src=”data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAABAAAAAJPCAIAAABtoTFXAAAgAElEQVR4nOS9W6xtzXIe9FV1jzHn
… alt="Разработка: Генератор скриншотов сайтов на Python" />

Запустим наше приложение, чтобы увидеть его в действии:
python manage.py runserver

Для примера я взял адрес сайта Scrapinghub. Я также задал ширину/высоту и сохранение скриншота на диск:
www.scrapinghub.com/?w=800&h=600&save=true
Разработка: example
Исходный код этого проекта вы можете найти на Github.
Надеюсь, этот пример поможет кому-нибудь лучше понять как получать изображения и выводить его на страницу.

Источник
0 комментариев
Только зарегистрированные и авторизованные пользователи могут оставлять комментарии.