CoderCastrov logo
CoderCastrov
Парсер

Как использовать Python и Selenium для парсинга веб-страниц

Как использовать Python и Selenium для парсинга веб-страниц
просмотров
9 мин чтение
#Парсер
Python and Selenium Web Scraping

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

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

Традиционный парсинг данных

Поскольку большинство веб-сайтов создают страницы для чтения людьми, а не для автоматического чтения, парсинг веб-страниц в основном состоял из программного анализа данных разметки веб-страницы (подумайте о правом клике и выборе "Просмотреть исходный код"), а затем обнаружения статических шаблонов в этих данных, которые позволяли программе "читать" различные фрагменты информации и сохранять их в файл или базу данных.

Если данные отчета можно было найти, часто к ним можно было получить доступ, передавая переменные формы или параметры с URL-адресом. Например:

https://www.myreportdata.com?month=12&year=2004&clientid=24823

Python стал одним из самых популярных языков для парсинга веб-страниц, в частности благодаря различным библиотекам, созданным для этого языка. При парсинге веб-страниц с использованием Python популярная библиотека Beautiful Soup предназначена для извлечения данных из файлов HTML и XML путем поиска, навигации и модификации тегов (т.е. дерева разбора).

Парсинг в браузере

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

Три основных проблемы помешали мне использовать стандартные методы парсинга:

  1. Веб-страница, с которой я хотел получить данные, была динамической и содержала большое количество элементов, которые генерировались с помощью JavaScript. Традиционные методы парсинга не смогли обработать такую страницу.

  2. Некоторые данные, которые мне было нужно получить, были доступны только после выполнения определенных действий на странице, таких как заполнение формы или нажатие кнопки. Традиционные методы парсинга не позволяли мне взаимодействовать с веб-страницей.

  3. Некоторые данные были защищены от парсинга с помощью CAPTCHA или других методов проверки, которые были сложны для автоматического обхода.

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

Парсинг веб-страниц с использованием Selenium

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

Мой выбор для парсинга веб-страниц - Python, так как в нем есть хорошо интегрированные библиотеки, которые обычно могут обеспечить всю необходимую функциональность. И, конечно же, существует библиотека Selenium для Python. Это позволит мне создать "браузер" - Chrome, Firefox, IE и т. д. - а затем притвориться, что я сам использую браузер, чтобы получить доступ к нужным мне данным. И если мне не нужно, чтобы браузер фактически отображался, я могу создать его в "безголовом" режиме, что сделает его невидимым для любого пользователя.

Настройка проекта

Для начала экспериментов с парсером веб-страниц на Python мне потребовалось настроить проект и получить все необходимое. Я использовал компьютер с операционной системой Windows 10 и убедился, что у меня установлена достаточно новая версия Python (в моем случае это была версия 3.7.3). Я создал пустой скрипт на Python, а затем загрузил библиотеки, которые, как я думал, могут понадобиться, используя PIP (установщик пакетов для Python), если эти библиотеки еще не были установлены. Вот основные библиотеки, с которыми я начал:

import argparse
import requests
from bs4 import BeautifulSoup

Также я добавил некоторые параметры вызова в скрипт (с использованием библиотеки argparse), чтобы я мог играться с различными наборами данных, вызывая скрипт из командной строки с разными параметрами. Эти параметры включали идентификатор клиента, начальную и конечную даты в формате месяц/год.

Проблема 1 — Сертификат

Первым выбором, который мне нужно было сделать, был выбор браузера, который я собираюсь использовать с Selenium. Так как я обычно использую Chrome, который основан на открытом проекте Chromium (также используется браузерами Edge, Opera и Amazon Silk), я решил попробовать его первым.

Я смог запустить Chrome в скрипте, добавив необходимые библиотечные компоненты и выполнив несколько простых команд:

# Загрузка компонентов Selenium
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait, Select
from selenium.webdriver.support import expected_conditions as EC
from selenium.common.exceptions import TimeoutException

# Установка драйвера Chrome и переход по URL сайта отчета
url = "https://reportdata.mytestsite.com/transactionSearch.jsp"
driver = webdriver.Chrome()
driver.get(url)

Так как я не запускал браузер в режиме без графического интерфейса, браузер фактически появлялся, и я мог видеть, что он делает. Он сразу же попросил меня выбрать сертификат (который я установил ранее).

Первая проблема, с которой нужно было справиться, была связана с сертификатом. Как выбрать правильный сертификат и принять его, чтобы получить доступ к веб-сайту? В моем первом тесте скрипта у меня появилось такое предупреждение:

Это было нехорошо. Я не хотел каждый раз вручную нажимать кнопку "ОК", когда запускал свой скрипт.

Оказалось, что я смог найти обходной путь для этой проблемы — без программирования. Хотя я надеялся, что Chrome имеет возможность передать имя сертификата при запуске, такой функции не существовало. Однако Chrome имеет возможность автоматически выбирать сертификат, если в вашем реестре Windows есть определенная запись. Вы можете настроить его на выбор первого обнаруженного сертификата или быть более конкретным. Поскольку у меня был загружен только один сертификат, я использовал общий формат.

Таким образом, с учетом этого настройки, когда я сказал Selenium запустить Chrome и появилось окно выбора сертификата, Chrome автоматически выбирал сертификат и продолжал работу.


Проблема 2 — Iframes

Хорошо, теперь я находился на сайте, и появилась форма, в которой требовалось ввести идентификатор клиента и диапазон дат для отчета.

Изучив форму в инструментах разработчика (F12), я заметил, что форма представлена внутри iframe. Поэтому, прежде чем я мог начать заполнять форму, мне нужно было "переключиться" на соответствующий iframe, где находилась форма. Для этого я использовал функцию переключения Selenium, вот так:

# Переключиться на iframe, где находится форма
frame_ref = driver.find_elements_by_tag_name("iframe")[0]
iframe = driver.switch_to.frame(frame_ref)

Хорошо, теперь я нахожусь в правильном фрейме и могу определить компоненты, заполнить поле идентификатора клиента и выбрать выпадающие списки с датами:

# Найти поле идентификатора клиента и заполнить его
element = driver.find_element_by_name("custId")
element.send_keys(custId)  # отправить тестовый идентификатор

# Найти и выбрать выпадающие списки с датами
select = Select(driver.find_element_by_name("fromMonth"))
select.select_by_visible_text(from_month)
select = Select(driver.find_element_by_name("fromYear"))
select.select_by_visible_text(from_year)
select = Select(driver.find_element_by_name("toMonth"))
select.select_by_visible_text(to_month)
select = Select(driver.find_element_by_name("toYear"))
select.select_by_visible_text(to_year)

Задача 3 — JavaScript

Единственное, что оставалось на форме, это "нажать" кнопку "Найти", чтобы начать поиск. Это было немного сложно, так как кнопка "Найти" казалась управляемой JavaScript и не была обычной кнопкой типа "Отправить". Исследуя ее в инструментах разработчика, я нашел изображение кнопки и смог получить ее XPath, щелкнув правой кнопкой мыши.

Затем, вооружившись этой информацией, я нашел элемент на странице и нажал на него.

# Найти кнопку "Найти" и нажать на нее
driver.find_element_by_xpath("/html/body/table/tbody/tr[2]/td[1]/table[3]/tbody/tr[2]/td[2]/input").click()

И вуаля, форма была отправлена и данные появились! Теперь я могу просто распарсить все данные на странице с результатами и сохранить их, как требуется. Или могу ли я?

Получение данных

Сначала мне нужно было обработать случай, когда поиск ничего не нашел. Это было довольно просто. Было бы отображено сообщение на форме поиска без ее закрытия, например, "Записи не найдены". Я просто искал эту строку и останавливался, если находил ее.

Но если результаты все же были найдены, данные представлялись в виде div-элементов с плюсом (+), чтобы открыть транзакцию и показать все ее детали. Открытая транзакция показывала минус (-), который при нажатии закрывал div-элемент. При нажатии на плюс вызывался URL для открытия соответствующего div-элемента и закрытия любого открытого.

Таким образом, было необходимо найти все плюсы на странице, собрать URL-адрес рядом с каждым из них, а затем пройтись по каждому URL-адресу, чтобы получить все данные для каждой транзакции.

# Проход по транзакциям и подсчет
links = driver.find_elements_by_tag_name('a')
link_urls = [link.get_attribute('href') for link in links]
thisCount = 0
isFirst = 1
for url in link_urls:
if (url.find("GetXas.do?processId") >= 0):  # URL для перехода к транзакциям
       	if isFirst == 1:  # уже развернуты +
              	isFirst = 0
else:
       	driver.get(url)  # свернуты +, поэтому развернуть
# Найти ближайший элемент к URL-элементу с правильным классом, чтобы получить тип транзакции                            tran_type=driver.find_element_by_xpath("//*[contains(@href,'/retail/transaction/results/GetXas.do?processId=-1')]/following::td[@class='txt_75b_lmnw_T1R10B1']").text
              # Получить статус транзакции
              status = driver.find_element_by_class_name('txt_70b_lmnw_t1r10b1').text
              # Добавить к счетчику, если транзакция найдена
              if (tran_type in ['Перемещение','Перемещение внутри','Переключение']) and 
(status == "Завершено"):
                    thisCount += 1

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

Для этого проекта по парсингу веб-страниц на Python счетчик возвращался обратно в вызывающее приложение. Однако его и другие полученные данные также можно было сохранить в плоский файл или базу данных.

Дополнительные возможные преграды и решения

Во время парсинга современных веб-сайтов с использованием собственного браузера могут возникнуть и другие проблемы, но большинство из них можно решить. Вот несколько примеров:

Попытка найти что-то до того, как оно появится

  • Как часто во время просмотра веб-сайтов вы ожидаете, пока страница загрузится, иногда несколько секунд? То же самое может произойти при программном навигировании. Вы ищете класс или другой элемент - и его там нет!
  • К счастью, Selenium может ожидать появления определенного элемента и прерваться, если элемент не появится, например, так:
element = WebDriverWait(driver, 10). until(EC.presence_of_element_located((By.ID, "theFirstLabel")))

Преодоление Captcha

Некоторые сайты используют Captcha или аналогичные средства для предотвращения нежелательных роботов (которыми они могут считать вас). Это может затруднить парсинг веб-сайтов и замедлить его работу.

Для простых запросов (например, "сколько будет 2 + 3?") их можно обычно легко прочитать и решить. Однако для более сложных преград существуют библиотеки, которые могут помочь в их разгадывании. Некоторые примеры таких библиотек: 2Captcha, Death by Captcha и Bypass Captcha.

Изменения в структуре веб-сайта

Веб-сайты предназначены для изменений - и они часто меняются. Поэтому при написании парсингового скрипта лучше иметь это в виду. Следует обдумать, какие методы будут использоваться для поиска данных, а какие нет. Рассмотрите методы частичного сопоставления, а не попытку полного совпадения фразы. Например, веб-сайт может изменить сообщение с "Записи не найдены" на "Записи не обнаружены", но если ваше сопоставление основано на "Записи не", то все должно быть в порядке. Также рассмотрите, сопоставлять ли по XPATH, ID, имени, тексту ссылки, тегу или классу, или CSS-селектору - и какой из них наименее вероятно изменится.

Резюме: Python и Selenium

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

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

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