По горячим следам

Evidence

В этой заметке первоначально речь шла о котиках. Сегодня текст был полностью переписан, но никто этого не докажет... если только не успел вовремя сделать скриншот. К скриншотам нынче принято относиться как к фактам или уликам. Тестировщики веб-приложений прилагают к своим отчетам скриншоты страниц, обводя ошибки красным. Блоггеры публикуют картинки, где запечатлены смешные ляпы в новостных изданиях. А кому-то просто лень набирать тексты и он публикует в фейсбучной ленте картинку понравившейся шутки.


Все умеют делать скриншоты руками. Можно ли научить этому паука — чтобы он, наткнувшись на какой-то интересный ресурс, сохранил его скриншот? ...Есть два популярных способа.

  1. Среди компонентов GUI-библиотеки Qt есть браузер WebKit. Питоновские порты PySide и PyQt дают возможность использовать его возможности из Python-а.
  2. При помощи Selenium веб-драйвера, позволяющего программе управлять разными браузерами. Для наших целей удобнее всего использовать невидимый браузер PhantomJs.

Если машина, с которой веб-краулер совершает свои набеги, представляет собой виртуальный хост, доступный программисту через консоль, то графическая библиотека Qt вряд ли еще для чего-нибудь пригодится. А весит она немало. А вот Selenium — инструмент во всех отношениях полезный, не только для создания скриншотов. Встречаются сайты, чье содержание невозможно получить, не выполнив JavaScript. Selenium подключается к браузеру и позволяет программе совершать все те же операции, которые человек делает руками.

Визит в параллельную Вселенную

Для достижения цели придется выйти за рамки привычной питоновской экосистемы и посетить "параллельную Вселенную" NodeJS.

  • NodeJS лучше всего установить стандартным для ОС способом (на OSX это может быть brew, на Linux — apt или подобное).
  • npm, менеджер пакетов NodeJS.
  • PhantomJs — оффлайн браузер. На Маке система пакетов brew устанавливает полноценную версию. На Linux-е стандартная версия может оказаться усеченной, поэтому лучше сразу скачать последнюю версию с официального сайта и установить ее.

Selenium лучше установить не через менеджер пакетов, а при помощи pip, чтобы получить более позднюю версию:

$ pip install selenium

Рекомендую, как и в других случаях, virtualenvironment.

Программа make_screenshot.py

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import os
import hashlib
import logging
import argparse

from selenium import webdriver
from selenium.webdriver.common.desired_capabilities import DesiredCapabilities
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By

SCREENSHOT_DIR = '.' # path for screenshots
PHANTOMJS_DIR = '/usr/local/bin/phantomjs' # path to PhantomJS executable
USER_AGENT = 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10.8; rv:28.0) Gecko/20100101 Firefox/28.0'
WAIT_TIMEOUT = 10

# set custom User-Agent header to PhantomJS
caps = DesiredCapabilities.PHANTOMJS
caps["phantomjs.page.settings.userAgent"] = USER_AGENT


def url2filename(url):
    '''Convert URL to filesystem-friendly string.
    '''
    m = hashlib.md5()
    m.update(url)
    return '%s.png' % m.hexdigest()

def main(url):
    driver = webdriver.PhantomJS(executable_path=PHANTOMJS_DIR)
    try:
        driver.get(url)
        logging.info('Waiting for page to be loaded...')
        WebDriverWait(driver, WAIT_TIMEOUT).until(
            EC.presence_of_element_located((By.TAG_NAME, 'body')))
        fname = url2filename(url)
        logging.info('Saving screenshot as %s...' % fname)
        driver.save_screenshot(os.path.join(SCREENSHOT_DIR, fname))
    finally:
        driver.close()

if __name__ == '__main__':
    parser = argparse.ArgumentParser(description='Make screenshot of a web page')
    parser.add_argument('url', type=str, help='URL to load')
    parser.add_argument('-v', '--verbose', action='store_true', default=False, help='increase output verbosity')
    args = parser.parse_args()

    logging.basicConfig(
        level=logging.DEBUG if args.verbose else logging.WARNING,
        datefmt='%Y-%m-%d %H:%M:%S',
        format='%(asctime)s - %(levelname)-8s - %(message)s', 
    )

    main(args.url)

Пример запуска:

$ python make_screenshot.py http://www.example.com

Результатом будет файл 5acd2aba1ca1e129349d1df2ba906106.png в текущей директории.

Ключ --help покажет доступные опции.

Пояснения

  • В строках 15-19 настройки, которые в "боевой" программе, конечно же, стоит держать в конфигурационном файле.
  • Строка 22: здесь браузер-невидимка PhantomJS притворяется безобидным Firefox-ом.
  • Строки 25 - 30: функция url2filename преобразует URL в строку, которая содержит только ascii-символы и не совпадает для разных URL-ов. По-хорошему, каждый URL следовало бы еще нормализовать: привести к нижнему регистру, убрать лишнее... Но этой задаче будет посвящена отдельная заметка. Здесь мы самонадеянно предполагаем, что пользователь вводит нормализованные адреса.
  • Строка 33: Инициализация драйвера. Здесь мы указываем, что в качестве браузера будет использоваться PhantomJS.
  • Строка 35: Загрузка страницы.
  • Строки 37-38: Здесь демонстрируется техника работы с динамическими страницами — т.е. таких, которые не загружаются в готовом виде, а формируются JavaScript-ом. Драйвер будет дожидаться появления тега BODY. На практике дожидаются появления какого-то текста или всплывающего окна или кнопки — зависит от сайта.
  • Строка 41: Наконец, страница сохраняется.

social

Яндекс.Метрика