Как ускорить парсинг данных с 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.
Это действительно так?
- Вопрос задан более трёх лет назад
- 7006 просмотров
Комментировать
Решения вопроса 1
Вёбных дел мастер
На 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-й день.

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; > . >
Ускорить 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 серебряных знаков 26 26 бронзовых знаков
измерения показали, что строчка webdriver.Chrome() у вас наибольшее время занимает? Если да, руками service запустите и с помощью Remote к нему подключайтесь.
7 мая 2018 в 22:02
Я просто не знаю, сколько таких сервисов могу поднять? И как лучше всего реализовать «занятость» и «свободность» каждого из сервисов
8 мая 2018 в 11:12
цифры добавьте, которые показывают, что сколько времени у вас занимает и сколько хотите чтобы занимало.
Ускорить работу Selenium Web Driver

Имеется сайт, мне необходимо протестировать его на битые ссылки. Т.е. начиная с главной страницы иду по всем ссылкам со страницы и так далее.
Тест выполняется очень долго (На Chrome Driver заняло полчаса).
Что посоветуете?
HTML UNit Driver будет быстрее?
#2
vmaximv
Отправлено 14 августа 2013 — 10:23
1. Отключить картинки/js/activex/flash/video/css и прочую не нужную хрень.
2. Использовать грид.
#3
tetchenko
Отправлено 14 августа 2013 — 10:38
1. Отключить картинки/js/activex/flash/video/css и прочую не нужную хрень.
2. Использовать грид.
Спасибо, а как можно отключить?
#4
Snap
![]()
Отправлено 14 августа 2013 — 11:29
А почему используете Selenium Webdriver для этих целей?
P.S. голосовал за Html, еще бы знать как в нем вывод на консоль ошибок отключить.
#5
Nwd
Отправлено 19 августа 2013 — 16:26
Голосую за Xenu или что-то в этом роде. Не вижу смысла делать это вебдрайвером
#6
Freiman
Отправлено 20 августа 2013 — 06:26
Можно Xenu, можно вообще не использовать selenium, а просто написать на любом языке программирования.
я подобную штуку писал на python и php.
#7
PavelLobashov
PavelLobashov
Отправлено 20 августа 2013 — 08:07
Можно Xenu, можно вообще не использовать selenium, а просто написать на любом языке программирования.
я подобную штуку писал на python и php.
Да можно хоть на wget+bash. Это задачу можно решить наверно на любом языке, без привлечения тяжелой артиллерии
Количество пользователей, читающих эту тему: 0
0 пользователей, 0 гостей, 0 анонимных
Ответить цитируемым сообщениям Очистить
- Форум тестировщиков
- → Тестирование
- → Автоматизированное тестирование
- → Selenium — Functional Testing
- Политика Конфиденциальности
- Правила форума ·


- Помощь
Ускорить программу написаную с использованием selenium
написал часть программы по парсингу сайта, используя селениум вебдрайвер(нужны были функции клика мыши на элемент «показать» для сбора элементов нужных), часть программы в том смысле, что имею 1 страницу, вторая часть кода будет собирать ссылки на эти страницы. но одна страница при парсинге очень много времени забирает. Можно как-то ускорить все это?
мой код начинается с
from selenium import webdriver
, если это важно.
Лучшие ответы ( 1 )
94731 / 64177 / 26122
Регистрация: 12.04.2006
Сообщений: 116,782
Ответы с готовыми решениями:
необходимо исправить программу написаную на асемблере
нужно сделать в место прямоугольника флажок! Turbo Assembler Version L4.ASM 1 0000 .
Нужно открыть программу, написаную delphi!
Вообщем это несколько программ для управления станционными пультами на учебных пультах техникума.
Как вставить в HTML5 программу, написаную на Паскале?
Как вставить в HTML5 программу, написаную на Паскале?
Обязательно ли устанавливать Framework для того чтобы выполнить программу написаную на C# языке
Допустим я написал программу на языке C# и что бы ее выполнить обязательно должен быть установлен.
Парсер с использованием Selenium, phantomJS
Доброго времени суток. Подскажите, пожалуйста по следующему вопросу: есть сайт.
2740 / 2339 / 620
Регистрация: 19.03.2012
Сообщений: 8,830
Наврятли, очень наврятли. Селениуем медленная штука.
Регистрация: 30.06.2015
Сообщений: 431
alex925, вопрос такой, один человек говорит, что можно открыть те части страницы, которые грузятся js с помощь requests , что-то там в браузере смотреть, запросы и всякое такое, это правда?!
2740 / 2339 / 620
Регистрация: 19.03.2012
Сообщений: 8,830
Сообщение от izuchaju_python 
это правда?
Правда, а что же нет. Ну если быть точным, это частично правда, бывают разные ситуации, иногда так можно иногда нет.
Регистрация: 30.06.2015
Сообщений: 431
alex925, был бы благодарен, если бы вы меня ткнули туда, где расписаны подобные запросы с requests.
Так же вопрос, не знаете, может облачных сервисов, которые позволяют выполнять код пайтон в нем(разные подписки, чем мощнее компьютер , тем дороже) ?
![]()
5890 / 3348 / 1034
Регистрация: 03.11.2009
Сообщений: 9,977
Сообщение от izuchaju_python 
Так же вопрос, не знаете, может облачных сервисов, которые позволяют выполнять код пайтон в нем
repl.it
Ideone.com
pythonfiddle.com
или Вы ищите что-то вроде
pythonanywhere
cloud9
heroku
koding
2740 / 2339 / 620
Регистрация: 19.03.2012
Сообщений: 8,830
Сообщение от izuchaju_python 
alex925, был бы благодарен, если бы вы меня ткнули туда, где расписаны подобные запросы с requests.
Ни где, лично я сам учился по небольшим манам в сети.
298 / 256 / 57
Регистрация: 11.06.2012
Сообщений: 1,557
Сообщение от izuchaju_python 
был бы благодарен, если бы вы меня ткнули туда, где расписаны подобные запросы с requests.
официальная документация для общего понятия, потом если что не получается — гугл. Как правило решение вопроса находится очень быстро, в особенности если поисковый запрос составлять на английском языке.
данный совет подходит для очень большого количества случаев
Регистрация: 30.06.2015
Сообщений: 431
http://olx.ua/obyavlenie/proda. dd2970d79c ребята, парсер написан мною с селениумом, собирает телефоны, вот смотрел я в фаербаги запросы, ответы и прочее. не могу понять, можете помочь разобраться над этим примером?
2740 / 2339 / 620
Регистрация: 19.03.2012
Сообщений: 8,830

Сообщение было отмечено izuchaju_python как решение
Решение
izuchaju_python, на самом деле это элементарно
1 2 3 4 5 6 7 8 9 10 11 12
import requests def get_id(url: str): start, end = url.rfind('ID') + 2, url.rfind('.html') return url[start: end] target = ('http://olx.ua/obyavlenie/prodam-krishku-bagazhnika-na-ford-scorpio-' 'ID82ifl.html') phone_api = 'http://olx.ua/ajax/misc/contact/phone/<>' response = requests.get(phone_api.format(get_id(target))) print(response.json()['value'])
Сообщение от izuchaju_python 
вот смотрел я в фаербаги запросы
ИМХО в 99% случаев не нужное и виснующие дополнение, все, что нужно умеют стандартные инструменты разработчика.
P.S а если хочешь головокружительного ускорения, то используешь aiohttp и все в шоколаде. К примеру у меня есть уведомлялка о событиях, в отслеживание примерно 50 ссылок, скачивание страниц и их парсинг происходит примерно за 3 секунды.
Но если, честно думаю асинхронка пока тебе не по зубам будет, в синхронном коде пока плывешь.
Регистрация: 30.06.2015
Сообщений: 431
А если использовать headless браузер (фантом_ж.с), вместо фаерфокса, это ускорит код по идее?!
2740 / 2339 / 620
Регистрация: 19.03.2012
Сообщений: 8,830
Регистрация: 30.06.2015
Сообщений: 431
alex925,
url: str
— в вашей функции написано. честно, впервые такое, вижу. это просто написано для того, чтобы программа понимала, что переданный аргумент строка?!
ну а так, в целом понятно, получаем вот эту строку
http://olx.ua/ajax/misc/contact/phone/82ifl/
, отправляем requests.get.все понял, спасибо, попробую переписать парсер без селениума.
Пы Сы ide при наводке на строки дает подсказки где об этом почитать в документации. при наводке на последнюю строку получил линк на несуществующую страницуhttps://docs.python.org/3/libr. s.Response
![]()
5890 / 3348 / 1034
Регистрация: 03.11.2009
Сообщений: 9,977
объект ответа
http://docs.python-requests.or. s.Response
2740 / 2339 / 620
Регистрация: 19.03.2012
Сообщений: 8,830
Сообщение от izuchaju_python 
это просто написано для того, чтобы программа понимала, что переданный аргумент строка?!
Да аннотация типов называется.
Регистрация: 30.06.2015
Сообщений: 431
Зашел на работа.юа, посмотрел в инспектор , что происходит , когда нажимаешь на «показать телефон», ничего не возращается. такс. post запрос у нас. пару минут и вышел нужный код.
1 2 3 4 5 6 7
import requests from bs4 import BeautifulSoup params = {'vacancyId':'6104539'} r = requests.post('http://rabota.ua/company3277970/vacancy6104539', data = params) page = BeautifulSoup(r.text, 'html.parser') number = page.find('span', {'class':'opencontact'}) print(number.text)
Спасибо, alex925, благодаря Вам полностью разобрался в этом тонком нюансе.
2740 / 2339 / 620
Регистрация: 19.03.2012
Сообщений: 8,830
Рад, что смог помочь разобраться)
87844 / 49110 / 22898
Регистрация: 17.06.2006
Сообщений: 92,604
Помогаю со студенческими работами здесь
Веб скрапинг с использованием selenium
Есть файл с большим кол-вом сайтов,где с каждой первой страницы с надо скачать все картинки. Как.
Selenium Авторизация и заполнение формы c использованием VPN
Всем привет! Есть код, который должен загружать страницу с использованием VPN: from.
Как ускорить эту часть кода с использованием OpenMp?
я пытаюсь ускорить, но разницы вообще никакой. for (int y = 0; y < src->height -.
Как с использованием selenium открыть страницу любого сайта через Yandex Browser?
У меня стоит browser = webdriver.Chrome() он открывает страницы через Google Chrome, а мне нужно.
Написать программу с использованием поиска с возвратом и программу с использованием рекурсии
Принципы работы механизма поиска с возвратом, с использованием рекурсии (хвостовой и нехвостовой).
Как динамически подключить dll написаную на C#
Есть проект на C#. С помощью LoadLibrary я подгружаю С++ dll-ки, в результате получаю IntPtr на.
