Парсинг сайта с помощью PYTHON + SELENIUM

Kate

Administrator
Команда форума
В этой статье, на примере моей задачи, рассмотрим, как можно извлечь большой объем данных с сайта ГИББД и с помощью какого инструмента. Это может быть полезно для финансовых компаний, которые принимают автомобили в качестве залога. Итак, мне необходимо было получить информацию о владельцах и периодах владения автомобилями, чтобы определить были ли изменения в конкретном периоде. Данная информация есть на официальном сайте ГИБДД.рф. На входе было дано 70 тысяч VIN номеров автомобилей, по которым и возможно было сделать эту выгрузку.

50bf5195979e375d602bc3d1e78e7d32.png

Поскольку я ранее не занимался парсингом, то решил проанализировать различные Интернет-ресурсы для поиска необходимого алгоритма, однако, для поставленной мне задачи отсутствовало готовое решение, в связи с чем мне пришлось делать всё самому.
В ходе анализа я обнаружил библиотеку «requests» для передачи POST запросов, но понял, что она не подходит, поскольку на сайте «ГИБДД.РФ» есть элементы JS, а значит, что VIN номер не передать через адресную строку.
В ходе дальнейшего поиска решения для поставленной задачи я обнаружил библиотеку «Selenium», которая позволяет имитировать действия пользователя на сайте, в том числе с элементами JS.
Для ее установки используется команда:
-pip install selenium.
При использовании браузера «Google Сhrome» для работы с вышеуказанной библиотекой необходима имеющаяся в свободном доступе программа «Webdriver Сhrome».
Проблемы:
На мой взгляд, особую сложность при парсинге подобных сайтов вызывает изучение вариантов различного поведения самого сайта при отправке запросов. В моём случае – это появление видеорекламы (которое можно закрыть спустя определенное время), либо фоторекламы (которое проходит само), то есть своеобразный аналог капчи. Проблематика заключалась в необходимости анализа периодичности появления рекламы, ее разновидности и способах обхода вышеуказанной рекламы.
Алгоритм действий:

Импортируем все необходимые библиотеки:

from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.common.keys import Keys
import pandas as pd
import time
from bs4 import BeautifulSoup
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
Задаём опции для webdriver, запускаем его и переходим на нужную нам страницу проверки авто:

option = Options()
option.add_argument("--disable-infobars")
browser = webdriver.Chrome('C:\webdr\chromedriver.exe',chrome_options=option)
# 'https://xn--90adear.xn--p1ai/check/auto' – ГИБДД.РФ
browser.get('https://xn--90adear.xn--p1ai/check/auto')
7beba9bb935beadf5e606f46d669fed1.png

Открывается новое окно webdriver, после чего запускается сайт со следующим содержанием:

Затем осуществляем поиск нужных нам элементов через код страницы (правой кнопкой мыши – посмотреть код). В нашем случае это элемент с вводом VIN номера и кнопка с запросом.

d7121d1215bb17f56cec73bc6cf8255a.png

Находим идентификатор элемента id=”checkAutoVIN” и имя class =”checker”.Осуществляем поиск данных элементов, затем вставляем нужный VIN в соответствующую строку:

elem = browser.find_element(By.ID, 'checkAutoVIN' )
elem.send_keys('5GRGXXXXXXX129289' + Keys.RETURN)
и нажимаем на кнопку:

share = browser.find_element(By.CLASS_NAME, 'checker' )
share.click()
Первая проблема – появляется видеореклама, при просмотре которой через 4 секунды появляется крестик справа.

f7e3b2127d86ebc0a590eaf1ad839661.png

Находим код этого крестика на странице

c450951417e495d876fcd196b4f53cea.png

С помощью функции “WebDriverWait” ждём появления этого крестика и нажимаем на него, если это видеореклама.

WebDriverWait(browser, 10).until(EC.element_to_be_clickable((By.CLASS_NAME, "close_modal_window"))).click()
В случае появления фоторекламы вышеуказанный код выдаст ошибку, которую я решил с помощью конструкции try – except (она пригодится ещё много раз).В случае появления фоторекламы необходимо дождаться ее самопроизвольного закрытия:

b876dc858743f40feb8c685920c4cc13.png
time.sleep(4)
В дальнейшем для парсинга страницы необходимо воспользоваться библиотекой “ BeautifulSoup”.

0b9a907ee5178cd5e7444a492bda9542.png
elements = soup.find_all(attrs={"class":{"ownershipPeriods-from", "field vehicle-model", "field vehicle-year", "ownershipPeriods-to", "simplePersonType"}})
elements1=soup.find_all(attrs={"class":{"ownershipPeriods-from"}})
elements2=soup.find_all(attrs={"class":{"ownershipPeriods-to"}})
elements3=soup.find_all(attrs={"class":{"simplePersonType"}})
elements4=soup.find_all(attrs={"class":{"field vehicle-model"}})
elements5=soup.find_all(attrs={"class":{"field vehicle-year"}})
Для заполнения полученной информацией создадим пустую таблицу:

df = pd.DataFrame({'VIN': [], 'С': [], 'По': [], 'Владелец': [], 'Марка или модель':[], 'Год выпуска':[]})
Учитываем, что периодов владений автомобилем может быть несколько. В таком случае в каждом из найденных элементов будет несколько записей (элементы сохраняются в виде списка). Каждый новый период добавим с тем же VIN номером в новую строку:

for j in range(len(elements1)):
df1 = df1.append({'VIN': '5GRGXXXXXXX29289', 'Марка или модель':elements4[j].text, 'Год выпуска':elements5[j].text, 'С': elements1[j].text, 'По': elements2[j].text,
'Владелец':elements3[j].text}, ignore_index=True)
На первый взгляд кажется, что для решения поставленной задачи необходимо только запустить цикл по всем VIN номерам, однако обнаружились следующие проблемы. После проверки 5 VIN номеров сайт начинает сильно затормаживаться. Чтобы это избежать, я установил счетчик и когда он достигает значение 5 – обнуляем его и инициализируем webdriver заново. В ходе данной работы я установил, что для корректной работы цикла важно не закрывать предыдущий webdriver (“browser.quit()”), поскольку при его закрытии в следующем проходе цикла возникает ошибка.

Также счётчик помог мне понять, что реклама появляется при первом запросе, при условии, что VIN номер есть в базе ГИБДД.
Учитываем указанную информацию и вводим новую переменную «ermes=»», и при парсинге добавляем условие, что, если эта переменная равна ‘По указанному VIN не найдена информация о регистрации транспортного средства.’, то проделываем операцию с пережиданием или закрытием рекламы.

elementserr=soup.find_all(attrs={"class":{"check-space check-message"}})
if elementserr[0].text=='По указанному VIN не найдена информация о регистрации транспортного средства.':
ermes=elementserr[0].text
Следующая проблема – иногда запрос выполняется с ошибкой. В таком случае необходимо нажимать кнопку запроса информации до тех пор, пока запрос выполнится без ошибки:

while elementserr[0].text=='При получении ответа сервера произошла ошибка.':
share.click()
time.sleep(4)
Для успешного завершения поставленной задачи остается лишь взять все VIN номера и сделать по ним цикл:

vin=pd.read_excel(r'C:\Users\grvla\Desktop\Парс.xlsx')
Таким образом, разработанный мной парсер работает стабильно, прерывания возможны, но крайне редко (оставлял на пару дней). В случае прерывания, будем запускать цикл заново, пропуская те значения, которые есть в итоговой таблице:

sh=0
ermes=''
for i in vin.vins:
if i in df.VIN.unique():
continue
В качестве ключевых выводов хочу отметить:

⦁ Разработанный парсинг не могут забанить по ip, поскольку демонстрируется реклама. Мы же просто имитируем деятельность человека на сайте. Его можно запустить сразу на многих компьютерах, что ускорит результат работы. Количество полученных VIN-номеров в день – 7-8 тысяч. То есть на 10 компьютерах 70 тысяч VIN -номеров можно пропарсить за один день.

⦁ Библиотека Selenium позволяет имитировать действия пользователя в браузере. Это помогает автоматизировать сбор данных практически с любого сайта, в котором нет капчи. С её помощью возможна работа с сайтами, в которых есть элементы javascript, с чем не справляются другие библиотеки для парсинга. Достаточно простой для написания код.

 
Сверху