Перед нами стояла задача: автоматизировать процесс снятия скриншотов со страничек 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, следующего вида: