Как ускорить работу selenium python
Перейти к содержимому

Как ускорить работу selenium python

  • автор:

Ускорить selenium webdriver

В моём проекте необходимо делать скриншот области страницы, я нашёл selenium wd, но он очень долго запускается, что может быть проблемой при использовании. Я думаю, что дело в том, что он создаёт при запуске инстанс хедлесс хрома, можно ли это делать до использования метода get()? Стоит учесть и то, что это всё должно работать при нескольких параллельных запросах. Я думаю, что можно создать пул из chromedriver , например, из 5и, а потом запускать их по-очереди. Но я не знаю, как это сделать Код:

def get(): chrome_options = Options() chrome_options.add_argument("--headless") chrome_options.add_argument('--no-sandbox') chrome_driver = "/mnt/e/pibot/old/chromedriver" brw = webdriver.Chrome(chrome_options=chrome_options, executable_path=chrome_driver) try: brw.get('https://ru.wikipedia.org/wiki/%D0%A1%D0%BB%D1%83%D0%B6%D0%B5%D0%B1%D0%BD%D0%B0%D1%8F:%D0%A1%D0%BB%D1%83%D1%87%D0%B0%D0%B9%D0%BD%D0%B0%D1%8F_%D1%81%D1%82%D1%80%D0%B0%D0%BD%D0%B8%D1%86%D0%B0') brw.set_window_size(1280, 5000) # правильная обрезка скрина try: location_toc = brw.find_element_by_id('toc').location print(brw.find_element_by_id('toc').text) except: location_toc = print(location_toc) location_content = brw.find_element_by_id('content').location size_content = brw.find_element_by_id('content').size brw.save_screenshot('/mnt/e/pibot/old/'+r+'a.png') im = Image.open('/mnt/e/pibot/old/'+r+'a.png') im = im.crop((location_content['x'], location_content['y'], location_content['x'] + size_content['width'], location_content['y'] + location_toc['y']-85)) im.save('/mnt/e/pibot/old/'+r+'a.png') print('Готово, выход') print(brw.current_url) except: raise Exception finally: brw.close() 

Отслеживать
задан 6 мая 2018 в 16:07
695 1 1 золотой знак 8 8 серебряных знаков 28 28 бронзовых знаков

измерения показали, что строчка webdriver.Chrome() у вас наибольшее время занимает? Если да, руками service запустите и с помощью Remote к нему подключайтесь.

7 мая 2018 в 22:02

Я просто не знаю, сколько таких сервисов могу поднять? И как лучше всего реализовать «занятость» и «свободность» каждого из сервисов

8 мая 2018 в 11:12

цифры добавьте, которые показывают, что сколько времени у вас занимает и сколько хотите чтобы занимало.

Ускоряем Selenium-тесты

Все, кто использует Selenium тесты в своём билде, знают, что это достаточно дорогое удовольствие, потому что очень медленно. Из-за этого многие не запускают билд полностью перед коммитами или, вообще, запускают билд только по ночам. Итак, делаем наши тесты быстрее.

Отключаем native events

Примерная суть native events такова: когда тест натыкается на строку

driver.findElement(By.id(“someId”)).sendKeys(“hello”);

то операционке отправлятся некое событие типа “Введи в поле вооон с теми координатами слово ‘hello’”. Координаты расчитываются как-то сами собой и всё выглядит так, будто пользователь медленно вводит текст с клавиатуры. Проблема в том, что это затрачивает колоссальное количество времени, а нам это не надо.
Чтобы отключить эту фишку надо запускать Firefox с предварительно настроенным профайлом:

 private WebDriver firefox()

Вот так мы получили быстрый драйвер.
Если у Вас есть уже готовый профайл для запуска тестов, то можно создать в нём файл user.js:

user_pref("webdriver_enable_native_events", false); 

Либо немного поменять метод создания драйвера

 private WebDriver firefox(final String profileName)
Отключаем анимацию

Анимация на сайте — это, конечно, вещь, но во время тестов — это зло. Приходится постоянно ждать, когда эта анимация закончится, вставлять какие-то костыли и т.д. И да, анимация сама по-себе съедает часть времени теста. Вообщем, отключаем анимацию на примере jQuery

 private WebDriver initDriver() < final WebDriver wrappedDriver = firefox(); final EventFiringWebDriver driver = new EventFiringWebDriver(wrappedDriver); driver.register(new AbstractWebDriverEventListener() < @Override public void afterNavigateTo(final String url, final WebDriver driver) < ((JavascriptExecutor) driver).executeScript("jQuery.fx.off = true;"); >// скорее всего переопределения одного метода не хватит // так что переопределяем тут всякие beforeClickOn, afterNavigateBack и т.д. > ); return driver; > 

Дальше работаем с инстансом класса EventFiringWebDriver. В примере я переопределил только метод afterNavigateTo(), который вызывается автоматически после каждого вызова driver.get(someUrl). В реальной жизни этого явно недостаточно. Например, тест жмёт на кнопку и происходит автоматичекий редирект на другую страницу. В этом случае вызова afterNavigateTo() не произойдёт, поэтому приходится переопределять дополнительно beforeClickOn(), чтобы отключить анимацию при первом клике на какой-нибудь элемент после загрузки новой страницы.

Запускаем тесты в параллельном режиме

Изначально, я посматривал в сторону Selenium Grid, но у меня даже дэмку не получилось запустить, да и вообще grid показался мне не очень удобным: нужно запускать хаб и сервер. Поэтому запускаем тесты в двух и более броузерах средствами Maven и TestNG.
Добавляем в pom.xml

 org.apache.maven.plugins maven-surefire-plugin 2.8.1  run-integration-tests integration-test test  integration classes 2     

Если Вы инжектируете в тесты потоконебезопасные инстансы со scope=«singleton», то необходимо поменять на scope=«prototype». В лучшем случае на данном этапе можно отделаться только конфигурацией конфигов (например Spring’a). Если каждый ваш тест использует уникальное имя пользователя для логина, то всё должно заработать с пол-пинка.

Итоги

Раньше в проекте, над которым я работаю, исполнение Selenium-тестов занимало примерно 5 минут, теперь же — 1,5 минуты.

Для справки

Все вышеописанные действия были проведены на свяке Java+Selenium 2.8.0+Maven+TestNG+Firefox 7.0.1+Spring

Как ускорить парсинг данных с Python/Selenium?

В текущем варианте парсинг осуществляется с chromedriver. Практически имею около 100.000 ссылок, по которым находятся таблицы. У каждой таблицы имеется кнопка «Подробнее», которую сейчас нажимает парсер, копирует содержимое попапа, закрывает его и т.д.
В общем чтобы пропарсить наверное миллион таких строк у меня уйдет месяц непрерывной работы селениума. Ищу способ как-то ускорить это.

Проблема, установил небольшие задержки, которые нужны в аккурат дать подгрузиться попапу и дать ему закрыться, иначе возникают ошибки element is not found.

В общем, спасайте. Подскажите как это реально делается, чтобы ускорить работу хотя бы в 10 раз. (за пол часа он прошел около 400 страниц, спарсив около 2000 строк). Это как пройтись мне самому, нажать на каждую ссылку «Подробнее», но копирование отдать скрипту. Это вряд ли можно назвать полной автоматизацией. тем более с такими объемами (не оцениваю их как большие).

Существуют ли «реальные» бустеры таких операций? Я понимаю, что селениум сделан для тестирования или хотя бы для парсинга страниц, где нет кучи попапов, которые все надо прокликать.

upd: после постинга продолжил гуглить и в одном обсуждении нашел следующее:

javascript tables is exactly why I went with selenium for some sites. However, rather than parsing directly with selenium, I was passing driver.page_source (raw html containing whatever javascript generated) to bs4 and parsing with bs4. I was shocked to find out that this round about method was faster than using selenium.find_element_by_XXXXX methods without ever invoking bs4.

Это действительно так?

  • Вопрос задан более трёх лет назад
  • 7184 просмотра

Комментировать
Решения вопроса 1

alekciy

Вёбных дел мастер

На 100к ссылок, особенно если требуется их обходить достаточно часто (или на сервере ресурсов мало), есть уже смысл задумать о более кастомных (читай, напилить руками низкоуровневый механизм), но более быстрых механизмах. Как-то запросы на получение AJAX данных через curl. Или если данные получаться в рамтайме на клиенте через замудренный JS, то применить SpiderMonkey, V8 либо другие серверных движки.

В общем чтобы пропарсить наверное миллион таких строк у меня уйдет месяц непрерывной работы селениума

Делал на кластере из PhantomJS парсер который должен был за 15 минут обходит чуть больше 1к страниц и парсить из них разные хитрые таблички. Требовалось что-то около десяти инстансов PhantomJS, 20 Гб ОЗУ и 16 ядер ЦПУ. На таком кластере 100к за сутки переварит реально.
Когда требование по времени ужесточилось до 5 минут, напилил на SpiderMonkey.

element is not found

Нужно использовать wait(). Тогда дальше код будет выполняться когда на странице появиться нужный элемент.

где нет кучи попапов, которые все надо прокликать

Наличие/отсутствие попапов не играет роли. Все, что появляется в DOM, все можно отработать. Регулярно тягаю данные с яндекс ворстата. Много там разных хитрых обработчиков. Но все силами PhantomJS-а через webdriver решается рано или поздно.

Это действительно так?

Возможно. Но так ли это в вашем контексте ни кто кроме эксперимента не скажет. Т.е. берем данное утверждение и проверяем в своей задаче парсинга.

Ответ написан более трёх лет назад
Нравится 2 3 комментария
Bjornie @Bjornie Автор вопроса

«Требовалось что-то около десяти инстансов PhantomJS»
Можете показать кусок кода (или направить на реализацию подобного решения), о котором вы говорите? Если я запускаю «тупо» 2 одинаковых скрипта (естественно на разный список ссылок), то я вижу, что первый работает нормально, а второй «плетется», иногда подтормаживая, или вообще останавливаясь. Не знаю точно в чем проблема: соединение, настройки удаленного сервера, или какие-то другие факторы.

«Нужно использовать wait().»
Расставил везде где нужно по time.sleep(1) или wait.until. Запинаний не было.

«Наличие/отсутствие попапов не играет роли. Все, что появляется в DOM, все можно отработать.». Это понятно, что все появляется в DOM. Сейчас в моем примере сервер отдает целый шаблон с html-тегами (а не просто массив данных), который при открытии появляется или наоборот удаляется. Все это ведь надо прокликать, так или иначе. Иначе как дать появится данным в дереве?

«Возможно. Но так ли это в вашем контексте ни кто кроме эксперимента не скажет.»
В общем я сделал прокликивание ссылок через селениум, а парсинг данных через bs4. Работает, как и обещали — быстрее. В 2 с небольшим раза (т.е. не 5 часов, а 2.7 где-то). Это уже хорошо.

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

alekciy

Bjornie: >Можете показать кусок кода (или направить на реализацию подобного решения), о котором вы говорите?

У меня PHP. На python-е думаю отличий будет не сильно много. Архитектура такая. Есть класс который запускает заданное количество PhantomJS. Поскольку последний может из коробки работать через webdriver, то интансы запускаются в фоном режиме, при этом каждый из них слушает строго свой заданный локальный порт. Кроме того каждый из них запускается строго через прокси (что бы были заходы с разных IP + на случай бана), у каждого своя прокся. После чего приложение когда нужно соединяется с этим фантомами и отправляет в них требуемые задания. Задачи на загрузку складываются в очередь redis, скрипт который заполняет очередь запускается строго в одном экземпляре (гарантируется через семафоры) и заполняет очередь только если она пустая (тогда задачи не дублируются). Это скрипт запускается кроном. Если другой скрипт (назовем его воркер). Он так же пускается по крону каждую минуту. Он забирает из очереди redis одно задание, отправляет его фантому, парсит страницу, складывает результат в базу, завершает работу. Кусок кода (кластер стартует через startWebDriverCluster):

basePath . '/lib/php-webdriver/lib/__init__.php'); // [1- Инициализация окружения $webdriver_host = '127.0.1.1'; $capabilities = array( WebDriverCapabilityType::BROWSER_NAME => 'phantomjs', 'phantomjs.page.settings.userAgent' => 'Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:25.0) Gecko/20100101 Firefox/25.0', ); try < $driver = RemoteWebDriver::create(":", $capabilities, 10000); > catch (Exception $e) < return false; >// Задаем размер экрана по умолчанию $window = new WebDriverDimension(1024, 768); $driver->manage()->window()->setSize($window); // -1] return $driver; > /** * По идентификатору драйвера вернет running если связанный с ним браузер запущен, * либо stopped если он остановлен. Данные получаются на основании обращения к прослущиваемому сокету, * поэтому отражают реальную ситуацию. */ public function getWebDriverStatus($id) < if ( !array_key_exists($id, $this->_proxy_list) ) < throw new \Exception("Incorrect webdriver id #$id\n"); >$proxy = $this->_proxy_list[$id]; $webdriver_host = '127.0.1.1'; $errno = 0; $errstr = ''; $socket = @fsockopen($webdriver_host, $proxy['wd_port'], $errno, $errstr, 5); return is_resource($socket) ? 'running' : 'stopped'; > /** * Запускает группу webdriver (phamtomjs). Каждый из них ассоциирован с определенным * прокси. Висят как демоны и слушают каждый свой локальный порт. */ public function startWebDriverCluster($ttl = 3600) < $webdriver_host = '127.0.1.1'; foreach ($this->_proxy_list as $key => $proxy) < // Не запускаем неактивные webdrive if ( !$proxy['is_active'] ) < continue; >// Если порт занят, то не стартуем такой webdrive (т.к. скорее всего он был запущен в прошлый вызов команды) $errno = 0; $errstr = ''; $socket = @fsockopen($webdriver_host, $proxy['wd_port'], $errno, $errstr, 5); if ( is_resource($socket) ) < continue; >// Если все впорядке, стартуем webdrive $cookie_dir = Yii::app()->basePath . '/runtime/cookie/'; if ( !file_exists($cookie_dir) ) < mkdir($cookie_dir); chmod($cookie_dir, 0775); >$cookie_file = $cookie_dir . $key . '.txt'; // Костыль - часть загрузок фейлится, возможно из-за кук, поэтому тупо грохаем файл с куками // хотя это и противоречит первоначальной задумке if ( file_exists($cookie_file) ) < unlink($cookie_file); >$cmd = "phantomjs --load-images=false --proxy=: --proxy-auth=: --ignore-ssl-errors=true --cookies-file= --webdriver=127.0.1.1: > /dev/null 2>&1 &"; exec($cmd); // Даем время phantomjs-у запуститься $is_run = false; for ($i = 0; $i < 20; ++$i) < usleep(500000); $socket = @fsockopen($webdriver_host, $proxy['wd_port'], $errno, $errstr, 1); if ( is_resource($socket) ) < $is_run = true; break; >> // Отмечаем как работающий $cache_key = 'proxy.' . $key; $proxy_info = Yii::app()->redis->get($cache_key); // Если данных по прокси в кэше нет, то считаем прокси доступным if ( empty($proxy_info) ) < $proxy_info = $proxy; $proxy_info['is_active'] = $is_run; $proxy_info['wd_port'] = $key + $this->start_port_num; // не используем системные порты $proxy_info['last_req_time'] = 0; // время последнего запроса $proxy_info['req_count'] = 0; // счетчик удачных попыток $proxy_info['status'] = $is_run ? 'running' : 'stopped'; > else < $proxy_info['status'] = $is_run ? 'running' : 'stopped'; >Yii::app()->redis->set($cache_key, $proxy_info); > $cache_key = 'cluster.status'; Yii::app()->redis->set($cache_key, 'run', $ttl); > /** * + * Останавливает все демонты webdrive кластера если вызван без параметров. * Остановит только конкретный драйвер по его $id. * * @param int $id Идентификатор прокси которую нужно выгрузить. */ public function stopWebDriverCluster($id = null) < $webdriver_host = '127.0.1.1'; foreach ($this->_proxy_list as $key => $proxy) < if (!is_null($id) && $id != $key ) < continue; >$errno = 0; $errstr = ''; $socket = @fsockopen($webdriver_host, $proxy['wd_port'], $errno, $errstr, 5); if ( is_resource($socket) ) < $cmd = 'kill -15 `ps ax -o pid,args | grep -v grep | grep phantom | grep \'' . $key . '.txt\' | awk \'\'`'; exec($cmd); > > if ( is_null($id) ) < $cache_key = 'cluster.status'; Yii::app()->redis->delete($cache_key); > > /** * Вернет webdrive который можно использовать для запросов. При заданном $id прокси * вернет связанный с ней webdrive (проверка на факт активности прокси не выполняется). * В случае ошибок (свободных webdrive нет, заданный webdrive деактивирован/выключен) * вернет null. */ public function getDriver($id = null) < $proxy = is_null($id) ? $this->_getActiveProxyInfo($id) : $this->getProxyInfoById($id); // Убеждаемся, что заданный драйвер запущен и работает, если нужно, пытается его запустить $status = $this->getWebDriverStatus($id); if ('stopped' == $status) < $is_run = $this->activateProxy($id); if ( !$is_run ) < return null; >> if ( !empty($proxy) ) < $driver = $this->_getDriver($proxy); if ( !empty($driver) ) < return array($id =>$driver); > > return null; > . >

Веб-скрапинг динамических сайтов с помощью Python

В этом руководстве вы узнаете, как использовать пакет Selenium Python для веб-скрапинга данных из различных HTML-элементов на YouTube и Hacker News.

8 min read
Davis David

Большая часть доступных для веб-скрапинга данных находится на таких динамических сайтах, как Amazon и YouTube. Они предлагают интерактивный и отзывчивый пользовательский опыт, основанный на пользовательских интересах. Например, когда вы заходите в свой аккаунт на YouTube, представленный на нем видеоконтент подстраивается под ваш поиск. В результате веб-парсинг динамических сайтов может оказаться более сложной задачей, поскольку данные постоянно изменяются в зависимости от взаимодействия с пользователем.

Для того чтобы выполнить веб-скрапинг данных с динамических сайтов, необходимо использовать передовые методы, которые позволяют имитировать взаимодействие реального пользователя с сайтом, перемещаться, выбирать генерируемый JavaScript контент и обрабатывать асинхронные запросы JavaScript и XML (AJAX).

В этом руководстве вы узнаете, как выполнять веб-скрапинг данных с динамического сайта с помощью Selenium — библиотеки Python с открытым исходным кодом.

Сбор данных с динамического сайта с помощью Selenium

Перед тем, как приступать к сбору данных с динамического сайта, необходимо разобраться с пакетом Python, который вы будете использовать, под названием Selenium.

Что представляет собой Selenium?

Selenium — пакет Python с открытым исходным кодом, представляющий собой систему автоматизированного тестирования. Она позволяет выполнять различные операции на динамических сайтах. В список доступных для нее задач входит открытие и закрытие диалоговых окон, поиск определенных запросов на YouTube или заполнение форм. И все это в предпочитаемом вами браузере.

При использовании Selenium с Python вы можете управлять браузером и автоматически извлекать данные с динамических сайтов, написав всего несколько строк кода.

Теперь, когда вы знаете, как работает Selenium, давайте приступим.

Создайте новый проект Python

Первое, что вам нужно сделать, это создать новый проект Python. Создайте каталог с именем data_scraping_project , в котором будут храниться все собранные данные и файлы исходного кода. В этом каталоге будет два подкаталога:

  1. scripts будет содержать все сценарии Python, которые извлекают и собирают данные с динамического веб-сайта.
  2. data — место, где будут храниться все данные, извлеченные из динамического сайта.

Установка пакетов Python

После создания каталога data_scraping_project необходимо установить следующие пакеты Python, которые помогут вам искать, собирать и сохранять данные с динамического сайта:

  • Selenium
  • Webdriver Manager будет управлять бинарными драйверами для различных браузеров. Webdriver предоставляет вам набор API, позволяющий запускать различные команды для взаимодействия с сайтами, облегчая разбор, загрузку и изменение содержимого.
  • pandas сохранит извлеченные с динамического сайта данные в простой CSV-файл.

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

pip install selenium 

Selenium будет использовать бинарный драйвер для управления выбранным вами браузером. Этот пакет Python предоставляет драйверы для следующих браузеров: Chrome, Chromium, Brave, Firefox, IE, Edge и Opera.

Затем выполните в терминале следующую команду pip для установки webdriver-manager :

pip install webdriver-manager 

Чтобы установить pandas, выполните следующую команду pip :

pip install pandas 

Что вы будете парсить

В этой статье мы извлечем данные из двух разных мест — YouTube-канала Programming with Mosh и Hacker News:

Программирование с YouTube-каналом Mosh

С YouTube-канала Programming with Mosh можно получить следующую информацию:

  • Название видеоролика.
  • Ссылка на видео или его URL.
  • Ссылка на изображение или его URL.
  • Количество просмотров для конкретного видео.
  • Время публикации видео.
  • Комментарии с определенного URL видеоролика на YouTube.

Из Hacker News вы получите следующие данные:

Скриншот Hacker News

  • Название статьи.
  • Ссылка на статью.

Теперь, когда вы знаете, что будете парсить, давайте создадим новый Python-скрипт (data_scraping_project/scripts/youtube_videos_list.py).

Импорт пакетов Python

Во-первых, вам понадобится импортировать пакеты Python, которые вы будете использовать для поиска, сбора и хранения данных в CSV-файл:

 # import libraries from selenium import webdriver from selenium.webdriver.chrome.service import Service from selenium.webdriver.common.by import By from webdriver_manager.chrome import ChromeDriverManager from selenium.webdriver.common.keys import Keys from selenium.webdriver.common.action_chains import ActionChains from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC import time import pandas as pd 

Инициализация Webdriver

Для инициализации Webdriver необходимо выбрать браузер, который будет использовать Selenium (в данном случае Chrome), а затем установить соответствующий бинарный драйвер.

В Chrome есть инструменты разработчика для отображения HTML-кода страницы. Они помогут определиться с элементами для сканирования и сбора данных. Чтобы отобразить HTML-код, необходимо щелкнуть правой кнопкой мыши страницу в браузере Chrome и выбрать Inspect Element.

Чтобы установить бинарный драйвер для Chrome, запустите следующий код:

driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) 

Бинарный драйвер для Chrome будет установлен на вашей машине и автоматически инициализирует Webdriver.

Парсите данные с помощью Selenium

Чтобы выполнить веб-скрапинг данных с помощью Selenium, необходимо определить URL YouTube в простой переменной Python (url) . Из этой ссылки вы собираете все данные, о которых говорилось ранее, за исключением комментариев с конкретного URL в YouTube:

# Define the URL url = "https://www.youtube.com/@programmingwithmosh/videos" # load the web page driver.get(url) # set maximum time to load the web page in seconds driver.implicitly_wait(10) 

Selenium автоматически загружает ссылку на YouTube в браузере Chrome. Кроме того, задается временной интервал (10 секунд), чтобы убедиться, что страница полностью загружена (включая все HTML-элементы). Это поможет вам выполнить веб-скрапинг данных, отображаемых с помощью JavaScript.

Парсите данные, используя теги и id

Одним из преимуществ Selenium является то, что он может извлекать данные, используя самые разные представленные на странице элементы, включая id и теги.

Например, для веб-скрапинга данных можно использовать либо id ( post-title ), либо теги ( h1 и p ):

Introduction to data scrapping using Python

You can use selenium python package to collect data from any dynamic website

Или, если вы хотите получить данные по ссылке на YouTube, необходимо использовать, представленный на странице идентификатор. Откройте URL YouTube в браузере, затем щелкните правой кнопкой мыши и выберите Inspect, чтобы определить id. Затем с помощью мыши просмотрите страницу и определите id, который содержит список представленных на канале видео:

Используйте Webdriver для веб-скрапинга данных, которые находятся в пределах определенного id. Чтобы найти HTML-элемент по id-атрибуту, вызовите метод find_element() Selenium и передайте By.ID в качестве первого аргумента, а id — в качестве второго аргумента.

Чтобы собрать для каждого ролика его заголовок и ссылку на видео, необходимо использовать id-атрибут video-title-link . Поскольку вы собираетесь собрать несколько HTML-элементов с этим «айдишником», следует использовать метод find_elements() :

# collect data that are withing the id of contents contents = driver.find_element(By.ID, "contents") #1 Get all the by video tite link using id video-title-link video_elements = contents.find_elements(By.ID, "video-title-link") #2 collect title and link for each youtube video titles = [] links = [] for video in video_elements: #3 Extract the video title video_title = video.get_attribute("title") #4 append the video title titles.append(video_title) #5 Extract the video link video_link = video.get_attribute("href") #6 append the video link links.append(video_link) 

Этот код выполняет следующие задачи:

  • Он собирает данные, которые находятся внутри id-атрибута объекта contents .
  • Он собирает все HTML-элементы с id-атрибутом video-title-link из объекта WebElement под названием contents .
  • Он создает два списка для добавления заголовков и ссылок.
  • Он извлекает заголовок ( title ) видео с помощью метода get_attribute() и передает его.
  • Он добавляет название видео в список с заголовками.
  • Он извлекает ссылку на видео с помощью метода get_atribute() и передает href в качестве аргумента.
  • Он добавляет ссылку на видео в соответствующий список для URL.

На этом этапе все названия видео и ссылки будут находиться в двух списках Python: titles and links .

Далее необходимо извлечь ссылку на размещенное на странице изображение до того, как вы нажмете на ссылку видео YouTube для просмотра ролика. Чтобы получить ссылку на изображение, необходимо найти все элементы HTML, вызвав метод find_elements() и передав By.TAG_NAME в качестве первого аргумента, а название тега в качестве второго аргумента:

#1 Get all the by Tag img_elements = contents.find_elements(By.TAG_NAME, "img") #2 collect img link and link for each youtube video img_links = [] for img in img_elements: #3 Extract the img link img_link = img.get_attribute("src") if img_link: #4 append the img link img_links.append(img_link) 

Этот код собирает все HTML-элементы с тегом img из объекта WebElement под названием contents . Он также создает список для добавления ссылок на изображения и извлекает его с помощью метода get_attribute() , передавая src в качестве аргумента. Наконец, он добавляет ссылку на изображение в список img_links .

Вы также можете использовать id и название тега, чтобы извлечь максимальное количество данных, связанных с видеороликом на YouTube. Перейдя по URL на YouTube, можно увидеть количество просмотров и время публикации для каждого видео. Чтобы извлечь эту информацию, необходимо собрать все HTML-элементы, имеющие идентификатор metadata-line , а затем собрать данные из HTML-элементов тега span :

#1 find the element with the specific ID you want to scrape meta_data_elements = contents.find_elements(By.ID, 'metadata-line') #2 collect data from span tag meta_data = [] for element in meta_data_elements: #3 collect span HTML element span_tags = element.find_elements(By.TAG_NAME, 'span') #4 collect span data span_data = [] for span in span_tags: #5 extract data for each span HMTL element. span_data.append(span.text) #6 append span data to the list meta_data.append(span_data) # print out the scraped data. print(meta_data) 

Этот блок кода собирает все HTML-элементы, имеющие id-атрибут metadata-line , из объекта WebElemen под названием contents и создает список для добавления данных из тега span , который будет содержать количество просмотров и время публикации.

Он также собирает все HTML-элементы с тегом span из объекта WebElement под названием meta_data_elements и создает список с их данными. Затем он извлекает текстовые данные из HTML-элемента span и добавляет их в список span_data . И наконец, он добавляет данные из списка span_data в meta_data .

Данные, извлеченные из HTML-элемента span, будут выглядеть следующим образом:

Далее необходимо создать два списка Python и сохранить отдельно количество просмотров и время публикации:

#1 Iterate over the list of lists and collect the first and second item of each sublist views_list = [] published_list = [] for sublist in meta_data: #2 append number of views in the views_list views_list.append(sublist[0]) #3 append time published in the published_list published_list.append(sublist[1]) 

Здесь вы создаете два списка Python, которые извлекают данные из meta_data , и добавляете количество просмотров в views_list , а время публикации — в published_list .

На данном этапе вы извлекли название видео, URL страницы с видео, URL изображения, количество просмотров и время публикации ролика. Эти данные можно сохранить в DataFrame с помощью пакета Python под названием pandas . Используйте следующий код для сохранения данных из titles , links , img_links , views_list , и published_list в DataFrame (pandas):

# save in pandas dataFrame data = pd.DataFrame( list(zip(titles, links, img_links, views_list, published_list)), columns=['Title', 'Link', 'Img_Link', 'Views', 'Published'] ) # show the top 10 rows data.head(10) # export data into a csv file. data.to_csv("../data/youtube_data.csv",index=False) driver.quit() 

Вот как должны выглядеть извлеченные данные в DataFrame (pandas):

Эти сохраненные данные экспортируются из pandas в CSV-файл под названием youtube_data.csv с помощью функции to_csv() .

Теперь вы можете запустить youtube_videos_list.py и убедиться, что все работает правильно.

Веб-скрапинг данных с помощью CSS селектора

Selenium также может извлекать данные на основе определенных шаблонов в HTML-элементах с помощью CSS-селектора. Селектор используется для выделения определенных элементов по их id, тегу, классу и другим атрибутам.

Например, здесь, в HTML-коде страницы присутствует сразу несколько элементов div, один из которых имеет класс «inline-code» :

  

Hello World!

Learn Data Scraping
data scraping with Python code
Saving

Вы можете использовать CSS-селектор для поиска на странице тега div с классом “‘inline-code”`. Этот же подход можно применить и для извлечения комментариев под видеороликом в YouTube.

Теперь давайте воспользуемся CSS-селектором, чтобы собрать комментарии, опубликованные к этому видео на YouTube.

Раздел комментариев на YouTube доступен под следующим тегом и названием класса:

Создадим новый скрипт (data_scraping_project/scripts/youtube_video_ comments.py). Импортируйте все необходимые пакеты, аналогично показанному ранее примеру, и добавьте следующий код для автоматического запуска браузера Chrome, просмотра URL-адреса видеоролика в YouTube и веб-скрапинга комментариев с помощью CSS-селектора:

#1 instantiate chrome driver driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) #2 Define the URL url = "https://www.youtube.com/watch?v=hZB5bHDCmeY" #3 Load the webpage driver.get(url) #4 define the CSS selector comment_section = 'ytd-comment-thread-renderer.ytd-item-section-renderer’ #5 wait until element matching the given criteria to be found try: element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CSS_SELECTOR, comment_section)) ) except: driver.quit() #6. collect HTML elements within the CSS selector comment_blocks = driver.find_elements(By.CSS_SELECTOR,comment_section) 

Этот код инициализирует драйвер для Chrome и определяет ссылку на видео в YouTube, чтобы спарсить опубликованные комментарии. Затем он загружает страницу в браузере и ожидает 10 секунд, пока не появятся HTML-элементы, соответствующие нужному CSS-селектору.

Затем он собирает все HTML-элементы comment с помощью CSS-селектора ytd-comment-thread-renderer.ytd-item-section-renderer и сохраняет их в объекте WebElement comment_blocks .

Затем вы можете извлечь имя каждого автора с помощью «айдишника» author-text и текст с помощью еще одного id — content-text , из каждого комментария в объекте WebElement под названием comment_blocks :

#1 specify the id attribute for author and comment author_id = 'author-text' comment_id = 'content-text' #2 Extract the text value for each comment and author in the list comments = [] authors = [] for comment_element in comment_blocks: #3 collect author for each comment author = comment_element.find_element(By.ID, author_id) #4 append author name authors.append(author.text) #5 collect comments comment = comment_element.find_element(By.ID, comment_id) #6 append comment text comments.append(comment.text) #7 save in pandas dataFrame comments_df = pd.DataFrame(list(zip(authors, comments)), columns=['Author', 'Comment']) #8 export data into a CSV file. comments_df.to_csv("../data/youtube_comments_data.csv",index=False) driver.quit() 

Этот код задает id для автора и комментария. Затем он создает два списка для добавления имени и текста. Он собирает каждый HTML-элемент, имеющий указанные «айдишники», из объекта WebElement и добавляет данные в соответствующие списки.

Наконец, он сохраняет собранные данные в DataFrame (pandas) и экспортирует их в CSV-файл под названием youtube_comments_data.csv .

Вот как будут выглядеть авторы и комментарии из первых десяти строк в DataFrame (pandas):

Веб-скрапинг данных с помощью класса

В дополнение к извлечению данных с помощью CSS-селектора, вы также можете парсить информацию, используя для целеуказания определенный класс. Чтобы найти с помощью Selenium HTML-элемент по имени класса, необходимо вызвать метод find_element() и передать By.CLASS_NAME в качестве первого аргумента, а в качестве второго аргумента — название класса.

В этом разделе вы будете использовать имя класса для извлечения заголовков и ссылок на статьи, опубликованные на Hacker News. На этой странице HTML-элемент, содержащий заголовок и ссылку на статью, имеет класс titleline :

The Mullvad Browser (mullvad.net)  302 points by Foxboron 2 hours ago | hide | 119 comments  

Создайте новый Python-скрипт (data_scraping_project/scripts/hacker_news.py), импортируйте все необходимые пакеты и добавьте следующий код для поиска заголовков и ссылок статей, опубликованных на Hacker News:

#1 define url hacker_news_url = 'https://news.ycombinator.com/' #2 instantiate chrome driver driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) #3 load the web page driver.get(hacker_news_url) #4 wait until element matching the given criteria to be found try: element = WebDriverWait(driver, 10).until( EC.presence_of_element_located((By.CLASS_NAME, 'titleline')) ) except: driver.quit() #5 Extract the text value for each title and link in the list titles= [] links = [] #6 Find all the articles on the web page story_elements = driver.find_elements(By.CLASS_NAME, 'titleline') #7 Extract title and link for each article for story_element in story_elements: #8 append title to the titles list titles.append(story_element.text) #9 extract the URL of the article link = story_element.find_element(By.TAG_NAME, "a") #10 appen link to the links list links.append(link.get_attribute("href")) driver.quit() 

Этот код определяет URL страницы, автоматически запускает браузер Chrome, а затем переходит по URL новости на Hacker News. При этом он подождет 10 секунд, пока не появятся HTML-элементы, соответствующие нужному CLASS NAME .

Затем он создает два списка для добавления заголовков и ссылок статей. Он также собирает все HTML-элементы, содержащие класс titleline , и добавляет извлеченные заголовки и ссылки в объект WebElement под названием story_elements .

Наконец, код добавляет название статьи в список с заголовками и собирает HTML-элементы с тегом a из объекта story_element . Он извлекает ссылку с помощью метода get_attribute() и добавляет ее в список links.

Далее необходимо использовать метод to_csv() из библиотеки pandas для экспорта извлеченных данных. Вы будете экспортировать заголовки и ссылки в CSV-файл hacker_news_data.csv и сохранять данные в каталоге:

# save in pandas dataFrame hacker_news = pd.DataFrame(list(zip(titles, links)),columns=['Title', 'Link']) # export data into a csv file. hacker_news.to_csv("../data/hacker_news_data.csv",index=False) 

Вот как заголовки и ссылки из первых пяти строк появляются в DataFrame (pandas):

Как обрабатывать бесконечное прокручивание

Некоторые динамические страницы при прокрутке страницы вниз подгружают дополнительный контент. В этом случае, если вы не переходите в нижнюю часть страницы, Selenium способен извлечь только ту часть данных, которая располагается в видимой части экрана.

Чтобы в этом случае получить больше данных, необходимо дать Selenium команду прокрутить страницу до самого низа, дождаться загрузки нового контента, а затем автоматически спарсить нужные данные. Например, следующий сценарий на языке Python прокрутит первые сорок результатов поиска книг по Python и спарсит ссылки на них:

#1 import packages from selenium.webdriver.chrome.service import Service from webdriver_manager.chrome import ChromeDriverManager from selenium import webdriver from selenium.webdriver.common.by import By import time #2 Instantiate a Chrome webdriver driver = webdriver.Chrome(service=Service(ChromeDriverManager().install())) #3 Navigate to the webpage driver.get("https://example.com/results?search_query=python+books") #4 instantiate a list to keep links books_list = [] #5 Get the height of the current webpage last_height = driver.execute_script("return document.body.scrollHeight") #6 set target count books_count = 40 #7 Keep scrolling down on the web page while books_count > len(books_list): driver.execute_script("window.scrollTo(0, document.body.scrollHeight);") #8 Wait for the page to load time.sleep(5) #9 Calculate the new height of the page new_height = driver.execute_script("return document.body.scrollHeight") #10 Check if you have reached the bottom of the page if new_height == last_height: break last_height = new_height #11 Extract the data links = driver.find_elements(By.TAG_NAME, "a") for link in links: #12 append extracted data books_list.append(link.get_attribute("href")) #13 Close the webdriver driver.quit() 

Этот код импортирует пакеты Python, которые планируются для использования, а затем инициализирует и запускает Chrome. После этого он переходит на страницу и создает список для добавления ссылок.

Он получает высоту текущей страницы с помощью скрипта return document.body.scrollHeight и устанавливает количество ссылок, которые вы хотите собрать. Затем он продолжает прокручивать страницу вниз до тех пор, пока значение переменной book_count не станет больше длины списка в book_list . Далее наступает 5-секундное ожидание загрузки страницы.

Он вычисляет новую высоту, выполняя скрипт return document.body.scrollHeight , и проверяет, достигнута ли нижняя часть страницы. Если да, то цикл завершается. В противном случае он обновляет значение last_height и продолжает прокрутку вниз. Наконец, он собирает HTML-элементы с именем тега a из объекта WebElement, а также извлекает и добавляет ссылки в соответствующий список. По завершению их сбора он закрывает Webdriver.

Обратите внимание: чтобы ваш скрипт завершился в определенный момент, необходимо задать количество элементов, которые должны быть извлечены, если страница имеет бесконечную прокрутку. Если вы этого не сделаете, ваш код продолжит выполняться в бесконечном режиме.

Веб-скрапинг с помощью инструментов Bright Data

Несмотря на возможность веб-скрапинга данных с помощью таких программ с открытым исходным кодом, как, например, Selenium, они, как правило, не имеют какой-либо сопроводительной техподдержки. Кроме того, процесс парсинга с их помощью может оказаться достаточно сложным и длительным. Поэтому если вы ищете мощное и надежное решение для веб-скрапинга, стоит обратить свое внимание на предложение компании Bright Data.

Bright Data — онлайн-платформа, которая облегчает задачу по веб-скрапингу информации, находящейся в публичном доступе, предоставляя собственные инструменты. Среди них есть эффективные решения для веб-скрапинга, прокси и предварительно собранные наборы данных. Вы даже можете использовать Web Scraper IDE для создания собственных скраперов в среде разработки JavaScript.

Web Scraper IDE также имеет готовые функции скрапинга и шаблоны кода для различных популярных динамических сайтов, включая Indeed scraper и Walmart scraper. Это означает, что с его помощью можно легко ускорить разработку и быстро масштабировать систему по парсингу.

Bright Data предлагает целый ряд опций для форматирования данных, включая JSON, NDJSON, CSV и Microsoft Excel. Он также интегрирован с различными платформами, что позволяет легко обмениваться собранными данными.

Заключение

Сбор данных с динамических сайтов требует усилий и планирования. С помощью Selenium вы сможете в автоматическом режиме собирать данные с любого динамического сайта.

Хотя вполне реально извлекать данные с помощью Selenium, такой способ потребует от вас много времени и больших трудозатрат. Именно поэтому мы рекомендуем использовать для парсинга динамических сайтов Web Scraper IDE. Благодаря готовым функциям и шаблонам кода с его помощью вы можете сразу же приступить к извлечению данных.

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *