Перед нами стояла задача: автоматизировать процесс снятия скриншотов со страничек web-приложения - некоторого сайта. Требовалось:
- Получать стартовый URL.
- Искать на страничке, полученной по стартовому URL все ссылки на другие странички.
- Переходить по всем найденным ссылкам и делать скриншоты со всех страничек.
- Ограничивать глубину вложенности при прохождении найденных ссылок.
Решить задачу можно было различными способами. Так как объектом исследования является web-приложение (работающее в браузерах), было решено следующее.
Для этого была написана небольшая программа "скрин-шутер".- Воспользоваться средствами Selenium WebDriver, который позволяет контролировать работу web-приложения, в том числе делать скриношоты содержимого окна браузеров.
- Поиск ссылок реализовать из CSS, используя библиотеку-парсер lxml, и по xPath, используя xPath-парсер Selenium WebDriver.
- Ограничить переход по "дереву ссылок" через написание рекурсивных функций, с уменьшающимся аргументом для "глубины" поиска ссылок.
1. Краткое описание.
Screen shooter - консольная программа, написанная на Python 3.2., предназначенная для автоматизации снятия скриншотов сайта.
Перед использованием программы необходимо установить и настроить Selenium WebDriver для Python 3.2.:
Программа ищет все ссылки на стартовой страничке, задаваемой ключом -u, перемещается по ним и делает скриншоты.
Глубина прохождения ссылок ограничивается ключом -d и берется из промежутка [0, +oo), по умолчанию 1: снимать скриншоты со стартовой странички и всех страничках по найденным на ней ссылкам.
Программа ищет все ссылки на стартовой страничке, задаваемой ключом -u, перемещается по ним и делает скриншоты.
Глубина прохождения ссылок ограничивается ключом -d и берется из промежутка [0, +oo), по умолчанию 1: снимать скриншоты со стартовой странички и всех страничках по найденным на ней ссылкам.
В программе реализован древовидный поиск ссылок. При этом стартовая страничка имеет наивысший уровень глубины, является верхним узлом дерева ссылок, а найденные на ней ссылки на другие странички, являются узлами-потомками с уровнем глубины на единицу меньше. Таким образом, отсчет уровней идет сверху вниз до нуля.
Используя ключ -m можно указать 2 метода поиска ссылок на страничках: CSS (по умолчанию) - более медленный (использует библиотеку lxml), xPath - более быстрый (использует библиотеку WebDriver).
Ключом -b можно указать открываемые браузеры (*firefox, *chrome, *ie), *firefox по умолчанию. Использование приложенных к программе драйверов для Chrome и IE возможно только в Windows.
Скриншоты кладутся в каталог <корень_проекта>/screens/<дата_время>/ в виде файлов depth_<глубина>_index_<номер>.png.
Программа хорошо применима для сайтов с простой древовидной структурой страничек, желательно с абсолютными путями и со ссылками без зацикливания. На данный момент программа не может определять тип http-авторизации (такая задача не стояла), поэтому в случае запроса на авторизацию, она остановится, ожидая действия пользователя.
2. Программный код.
Программный код доступен в zip-архиве по ссылке:https://docs.google.com/open?id=0B-1rf8K04ZS5UmQ5dWNzZWMtWXc
Код на GitHub:
https://github.com/Tim55667757/ScreenShooter
Структура проекта
ScreenShooter - корень проекта.
/browser_drivers - каталог с Windows-драйверы для chrome и ie.
/ff_profile - каталог для профиля мозиллы (пустой по умолчанию).
screen_shooter.py - реализация программы.
readme.txt - актуальная информация по проекту.
Содержимое модуля screen_shooter.py
Глобальные переменные:
startURL = 'http://some_test_site/' # Стартовый URL.
depth = 1 # Глубина прохождения по ссылкам.
method = 'CSS' # Метод поиска ссылок, ('CSS' или 'xPath').
linksTree = {} # Словарь для дерева ссылок.
workDir = os.path.abspath(os.curdir) # Рабочая директория проекта, относительно которой строятся другие пути.
resultPath = workDir + '/screens' # Путь к каталогу со скриншотами, относительно рабочей директории.
browser = None # Переменная для браузера открываемого Selenium WebDriver.
browserString = '*firefox' # Строка для запуска браузера ('*firefox', '*ie', '*chrome'). '*firefox' по умолчанию.
startURL = 'http://some_test_site/' # Стартовый URL.
depth = 1 # Глубина прохождения по ссылкам.
method = 'CSS' # Метод поиска ссылок, ('CSS' или 'xPath').
linksTree = {} # Словарь для дерева ссылок.
workDir = os.path.abspath(os.curdir) # Рабочая директория проекта, относительно которой строятся другие пути.
resultPath = workDir + '/screens' # Путь к каталогу со скриншотами, относительно рабочей директории.
browser = None # Переменная для браузера открываемого Selenium WebDriver.
browserString = '*firefox' # Строка для запуска браузера ('*firefox', '*ie', '*chrome'). '*firefox' по умолчанию.
Функции:
ParseArgs() # Парсер аргументов командной строки.
def ParseArgs():
"""
Function get and parse command line keys.
"""
parser = argparse.ArgumentParser()
parser.add_argument("-u", "--URL", type=str,
help="Start URL for screen-shooter. For example: http://forworktests.blogspot.com/")
parser.add_argument("-d", "--depth", type=int,
help="Depth for link's tree from interval [0..+оо]. Start URL has the highest depth.")
parser.add_argument("-m", "--method", type=str,
help="Method = CSS: use lxml CSS finder, Method = xPath: use WebDriver xPath finder.")
parser.add_argument("-b", "--browser", type=str,
help="Browser for testing (*firefox, *ie, *chrome). *firefox by default.")
args = parser.parse_args()
global startURL
global depth
global method
global browserString
if args.URL != None:
startURL = args.URL
if (args.depth != None) and (args.depth >= 0):
depth = args.depth
else:
depth = 1
if (args.method != None) and ((args.method == 'CSS') or (args.method == 'xPath')):
method = args.method
else:
method = 'CSS'
if (args.browser == '*chrome') or (args.browser == '*ie'):
browserString = args.browser
else:
browserString = '*firefox'
LinksCrawlerByXPath(startURL, depth, tree) # Поиск ссылок по xPath.
def LinksCrawlerByXPath(startURL='http://forworktests.blogspot.com/', depth=1, tree={}):
'''
Function LinksCrawlerByXPath(startURL, depth, tree) looks for all links by xPath, starting with an initial URL
and then passing on these links looks for other links.
initial_url URL for crawler start. For example: 'http://forworktests.blogspot.com/'
initial_depth Scan depth
'''
global browser
if (depth <= 0) or (browser == None):
tree[startURL] = None
return 0
try:
browser.get(startURL)
tree[startURL] = {}
except:
tree[startURL] = 'Connect error'
return 1
try:
# get links by xPath
allLinks = browser.find_elements_by_xpath("//a")
hrefs = []
hrefs = [x.get_attribute('href') for x in allLinks if x.get_attribute('href') not in hrefs]
for href in hrefs:
if not href.startswith('http'):
href = startURL + href
LinksCrawlerByXPath(href, depth - 1, tree[startURL])
return 0
except:
tree[startURL] = 'Parse error'
return 1
LinksCrawlerByCSS(startURL, depth, tree) # Поиск ссылок в CSS.
def LinksCrawlerByCSS(startURL='http://forworktests.blogspot.com/', depth=1, tree={}):
'''
Function LinksCrawlerByCSS(startURL, depth, tree) looks for all links by CSS, starting with an initial URL
and then passing on these links looks for other links.
initial_url URL for crawler start. For example: 'http://forworktests.blogspot.com/'
initial_depth Scan depth
'''
if (depth <= 0):
tree[startURL] = None
return 0
try:
raw_page = urllib.request.urlopen(startURL).read()
tree[startURL] = {}
except:
tree[startURL] = 'Connect error'
return 1
try:
page = lxml.html.document_fromstring(raw_page)
# get links from css
hrefs = []
hrefs = [elem.get('href') for elem in page.cssselect('a') if
(((elem.get('href') != None) or (elem.get('href') == '')) and
(elem.get('href') not in hrefs))]
for href in hrefs:
if not href.startswith('http'):
href = startURL + href
LinksCrawlerByCSS(href, depth - 1, tree[startURL])
return 0
except:
tree[startURL] = 'Parse error'
return 1
GetScreen(nameOfScreen) # Снятие скриншота и сохранение в указанный файл.
def GetScreen(nameOfScreen='screen.png'):
"""
Function gets screenshot from browser and create file by format: depth_NUM_index_NUM.png
Screens put into dir: project_root/screens/date_time/
"""
global resultPath
global browser
try:
browser.get_screenshot_as_file(nameOfScreen)
print('Screenshot created: %s' % nameOfScreen)
return 0
except:
print('Can not create screenshot-file!')
return 1
MakeAllScreens(screenDir, depth, tree) # Функция для прохождения по всему дереву ссылок и снятие скриншота страничек.
def MakeAllScreens(screenDir='screens/1', depth=1, tree={}):
"""
Function gets screenshot from all pages from link's tree.
"""
global resultPath
global browser
if (depth < 0) or tree == {}:
return 0
try:
index = 0
for href in tree:
browser.get(href)
screenFile = screenDir + '/' + 'depth_' + str(depth) + '_index_' + str(index)
if screenFile[-4:] != '.png':
screenFile = screenFile + '.png'
GetScreen(screenFile)
MakeAllScreens(screenDir, depth - 1, tree[href])
index += 1
except:
print('Error while capturing process!')
return 1
OpenBrowser(opTimeout, browserString, ffProfile) # Открытие браузера с указанным таймаутом для операций.
def OpenBrowser(opTimeout=10, browserString='*firefox', ffProfile=None):
"""
Commands for opening WebDriver browser.
"""
global browser
try:
# Get new browser instance and put it into browser array. One browser for one thread.
if browserString == '*chrome':
chromeOptions = webdriver.ChromeOptions()
chromeOptions.add_argument('--start-maximized')
chromeOptions.add_argument('--log-path=' + workDir + '/browser_drivers/chromedriver.log')
browser = webdriver.Chrome(executable_path=workDir + '/browser_drivers/chromedriver.exe',
chrome_options=chromeOptions)
elif browserString == '*ie':
browser = webdriver.Ie(executable_path=workDir + '/browser_drivers/IEDriverServer.exe',
log_file=workDir + '/browser_drivers/iedriver.log')
browser.maximize_window()
else:
ffp = webdriver.FirefoxProfile(ffProfile)
browser = webdriver.Firefox(firefox_profile=ffp, timeout=opTimeout)
browser.maximize_window()
print('command: OpenBrowser, status: oK')
return 0
except Exception as e:
print('command: OpenBrowser, status: error')
return 1
CloseBrowser() # Закрытие браузера.
def CloseBrowser():
"""
Try to close WebDriver browser.
"""
global browser
if browser != None:
try:
browser.close()
browser = None
print('command: CloseBrowser, status: oK')
return 0
except Exception as e:
print('command: CloseBrowser, status: error')
return 1
Main() # Открытие браузера, переход на стартовый URL, поиск ссылок, хождение по ссылкам и снятие скриншотов.
def Main():
"""
Goto URL, find all links in a tag, and then goes to those links and makes screenshot of pages.
"""
global startURL
global depth
global method
global linksTree
global browser
global browserString
status = 0
startTime = datetime.now()
print('Start time: ' + str(startTime))
try:
# Open browser (only for Selenium WebDriver) and find all links in every page.
status += OpenBrowser(10, browserString, None)
if method == 'CSS':
status += LinksCrawlerByCSS(startURL, depth, linksTree)
elif method == 'xPath':
status += LinksCrawlerByXPath(startURL, depth, linksTree)
else:
pass
# Going by every link and capture screenshot to screenDir
screenDir = resultPath
if not (os.path.exists(screenDir)):
os.mkdir(screenDir)
screenDir = screenDir + '/' + datetime.now().strftime('%d_%m_%Y_%H_%M_%S')
if not (os.path.exists(screenDir)):
os.mkdir(screenDir)
if status == 0:
MakeAllScreens(screenDir, depth, linksTree)
except:
print('It were errors during the program execution.')
sys.exit(1)
finally:
CloseBrowser()
finishTime = datetime.now()
print('Finish time: ' + str(finishTime))
print('Duration: ' + str(finishTime - startTime))
sys.exit(status)
3. Запуск программы.
Параметры для консольного запуска представлены ключами:
| Ключ | Слово | Описание |
|---|---|---|
-h | --help | Показать подсказку по опциям. |
| -u | --URL | Стартовый URL для скрин-шутера. |
| -d | --depth | Глубина спуска по ссылкам [0..+оо). Стартовый URL имеет наивысший индекс, спуск идет до нуля. |
-m | --method | 'CSS' для использования lxml CSS finder (медленнее), 'xPath' для использования WebDriver XPath finder (быстрее). |
-b | --browser | Строка браузера (*firefox, *chrome, *ie), показывающая, какой браузер запустить. |
Если ключи не указаны, используются настройки переменных в программе по умолчанию. Ключи, указанные при запуске в консоли имеют больший приоритет, чем настройки по умолчанию!
Находясь в корне проекта для запуска программы можно использовать команды:
python screen_shooter.py --URL=http://forworktests.blogspot.com/ # Запуск скрин-шутера на указанный URL. Последний слеш обязателен. Браузер Mozilla по умолчанию. python screen_shooter.py -u http://forworktests.blogspot.com/ -d 0 # Сделать скриншот только целевого узла. По умолчанию depth = 1 - делаются скриншоты всех страничек по ссылкам. python screen_shooter.py -u http://forworktests.blogspot.com/ -m xPath -b *ie # Использовать поиск ссылок по xPath (по умолчанию CSS), запустить браузер IE. python screen_shooter.py -u http://forworktests.blogspot.com/ --method=xPath --browser=*chrome # Использовать поиск ссылок по xPath, запустить браузер Chrome.
В результате выполнения одной команды из примеров:
python screen_shooter.py -u http://forworktests.blogspot.com/ -d 0
получим скриншот одной странички данного блога, сделанной в браузере firefox, следующего вида:

