Selenium – это инструмент, изначально разработанный для автоматизации тестирования веб-приложений, доступный для нескольких языков программирования. Хотя это не его основное назначение, Selenium также используется в Python для веб-скрапинга (парсинга или сбора данных) из-за его способности работать с содержимым, генерируемым JavaScript-кодом, что обычным инструментам скрапинга, таким как BeautifulSoup, не под силу.
Еще один случай использования Selenium для скрапинга возникает, когда необходимо взаимодействовать с веб-страницей перед сбором данных, например, кликать по кнопкам или заполнять поля. Этот случай использования и будет рассмотрен в данной статье. В качестве примера мы будем парсить investing.com для извлечения исторических данных о курсах обмена доллара на одну или несколько валют.
Скачивайте книги ТОЛЬКО на русском языке у нас в телеграм канале: PythonBooksRU
В Интернете можно найти API и пакеты Python, которые значительно упрощают задачу сбора финансовых данных по сравнению с их ручным сбором. Однако в данном случае речь идет о том, как Selenium может быть полезен для извлечения данных общего характера, финансовые данные – лишь пример.
Скрапинг
Для начала необходимо понять, что это за сайт. Следующий URL ведет к историческим данным по курсу доллара по отношению к евро:
https://investing.com/currencies/usd-eur-historical-data
На этой странице можно увидеть таблицу с данными и возможность задать нужный нам диапазон дат. Этим мы и воспользуемся. Чтобы посмотреть данные по другим валютам по отношению к доллару, просто замените в URL “eur” на код другой валюты.
Кроме того, в данном случае предполагается, что вам нужен только курс валюты по отношению к доллару. Если это не так, просто замените “usd” в URL.
Код
Начнем, конечно, с импорта, благо его не так много. Импортируем несколько полезных элементов из Selenium, функцию sleep
для вставки пауз в код и Pandas для манипуляций с датой, когда это необходимо.
from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from time import sleep import pandas as pd
Напишем функцию для извлечения данных. Функция будет принимать следующие параметры:
- Список кодов валют
- Дату начала
- Дату окончания
- Булево значение, указывающее, хотим ли мы экспортировать данные в csv-файл. По умолчанию будем использовать значение False.
Кроме того, поскольку идея состоит в том, чтобы создать парсер, способный собирать данные о нескольких валютах, мы также инициализируем пустой список для хранения данных о каждой валюте.
def get_currencies(currencies, start, end, export_csv=False): frames = []
Поскольку теперь функция имеет список валют, мы будем выполнять итерации по этому списку и получать данные о каждой валюте по очереди.
Поэтому для каждой валюты в списке мы сгенерируем URL, инициализируем драйвер браузера и используем его для получения страницы. Затем мы будем использовать окно браузера, но оно будет заметно только в том случае, если вы установите опцию option.headless
в значение False, иначе Selenium выполнит всю работу, ничего не показав вам.
for currency in currencies: my_url = f'https://br.investing.com/currencies/usd-{currency.lower()}-historical-data' option = Options() option.headless = False driver = webdriver.Chrome(options=option) driver.get(my_url) driver.maximize_window()
На данном этапе мы уже получаем информацию об исторических данных, и мы могли бы просто получить таблицу с этими данными. Однако по умолчанию мы видим данные только за последние 20 дней. Нам нужны данные за определенный период времени, который мы указали, и для этого мы будем использовать некоторые интересные функции Selenium для взаимодействия с сайтом. Вот тут-то Selenium и раскрывает себя во всей красе!
Здесь нам нужно щелкнуть на поля даты, заполнить Start Date и End Date нужными нам датами и нажать кнопку Apply. Для этого мы используем WebDriverWait
, ExpectedConditions
и By
, чтобы убедиться, что веб-драйвер будет ждать, пока элементы, с которыми мы хотим взаимодействовать, станут доступными для нажатия. Это важно, поскольку, если дайвер попытается взаимодействовать с каким-либо элементом до того, как он станет кликабельным, будет вызвано исключение.
Время ожидания будет равно двадцати секундам, но вы можете установить его по своему усмотрению. Сначала выделим кнопку даты по ее Xpath и щелкнем на ней.
date_button = WebDriverWait(driver, 20).until( EC.element_to_be_clickable((By.XPATH, "/html/body/div[5]/section/div[8]/div[3]/div/div[2]/span"))) date_button.click()
Теперь необходимо заполнить поле Start Date. Сначала выделим его, затем с помощью clear
удалим дату по умолчанию, а с помощью send_keys
заполним его нужной нам датой.
start_bar = WebDriverWait(driver, 20).until( EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[1]/input[1]"))) start_bar.clear() start_bar.send_keys(start)
А теперь повторим процесс для поля End Date.
end_bar = WebDriverWait(driver, 20).until( EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[1]/input[2]"))) end_bar.clear() end_bar.send_keys(end)
После этого выделим кнопку Apply и щелкнем на ней. Затем с помощью функции sleep
мы приостановим выполнение кода на несколько секунд и убедимся, что новая страница полностью загружена.
apply = WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[5]/a"))) apply.click() sleep(5)
Если параметр option.headless
был установлен в значение False, то весь процесс будет происходить на ваших глазах, как будто кто-то действительно нажимает на страницу. Когда Selenium нажмет на кнопку Apply, вы увидите, как таблица перезагружается, чтобы показать данные за указанный вами период времени.
Теперь мы используем функцию pandas.read_html
для выбора всех таблиц на странице. Эта функция получит исходный код страницы. Наконец, мы можем выйти из драйвера.
dataframes = pd.read_html(driver.page_source) driver.quit() print(f'{currency} scraped.')
Обработка исключений
Несмотря на то, что процесс сбора данных завершен, необходимо учитывать, что Selenium иногда может быть немного нестабильным и в конечном итоге может не загрузить страницу в какой-то момент выполнения кода.
Чтобы предотвратить неожиданное завершение программы, мы поместим весь код в блок try
, который будет находиться внутри бесконечного цикла. После того как Selenium выполнит все описанные выше процессы сбора данных, цикл будет прерван, но каждый раз, когда будет происходить ошибка парсинга, будет выполняться блок except
. В этом сценарии код будет выполнять следующие действия:
- Завершение драйвера. Это всегда важно сделать, чтобы в итоге не оказалось, что запущены десятки веб-драйверов, занимающих много памяти.
- Вывод сообщения об ошибке.
- Пауза перед следующей итерацией.
- Переход к следующей итерации цикла.
Этот процесс будет повторяться до тех пор, пока не будут правильно собраны данные по каждой валюте. А вот код, в котором мы все это реализовали:
for currency in currencies: while True: try: # Opening the connection and grabbing the page my_url = f'https://br.investing.com/currencies/usd-{currency.lower()}-historical-data' option = Options() option.headless = False driver = webdriver.Chrome(options=option) driver.get(my_url) driver.maximize_window() # Clicking on the date button date_button = WebDriverWait(driver, 20).until( EC.element_to_be_clickable((By.XPATH, "/html/body/div[5]/section/div[8]/div[3]/div/div[2]/span"))) date_button.click() # Sending the start date start_bar = WebDriverWait(driver, 20).until( EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[1]/input[1]"))) start_bar.clear() start_bar.send_keys(start) # Sending the end date end_bar = WebDriverWait(driver, 20).until( EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[1]/input[2]"))) end_bar.clear() end_bar.send_keys(end) # Clicking on the apply button apply = WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[5]/a"))) apply.click() sleep(5) # Getting the tables on the page and quiting dataframes = pd.read_html(driver.page_source) driver.quit() print(f'{currency} scraped.') break except: driver.quit() print(f'Failed to scrape {currency}. Trying again in 30 seconds.') sleep(30) continue
Осталось сделать последний шаг. Если вы помните, на данный момент мы имеем список, содержащий все таблицы на странице, сохраненные как датафреймы. Нам нужно выбрать ту таблицу, которая содержит нужные нам исторические данные.
Для каждого DataFrame в списке dataframes
мы будем проверять, соответствует ли название его столбцов нашим ожиданиям. Если они совпадают, то это наш фрейм и мы прерываем цикл. И теперь мы, наконец, готовы добавить этот DataFrame в список, который был инициализирован в начале.
for dataframe in dataframes: if dataframe.columns.tolist() == ['Date', 'Price', 'Open', 'High', 'Low', 'Change%']: df = dataframe break frames.append(df)
И да, если параметр в export_csv
было передано значение True, то нам необходимо экспортировать csv-файл. Но это далеко не проблема, поскольку метод DataFrame.to_csv()
легко справится с этой задачей. А затем мы можем просто завершить эту функцию, вернув список DataFrame
. Этот последний шаг выполняется после завершения цикла по списку валют.
# Inside the loop if export_csv: df.to_csv('currency.csv', index=False) print(f'{currency}.csv exported.') # Outside of the loop return frames
Вот и все! Вот полный код для всего, что мы только что сделали:
from selenium import webdriver from selenium.webdriver.chrome.options import Options from selenium.webdriver.support.ui import WebDriverWait from selenium.webdriver.support import expected_conditions as EC from selenium.webdriver.common.by import By from time import sleep import pandas as pd def get_currencies(currencies, start, end, export_csv=False): frames = [] for currency in currencies: while True: try: # Opening the connection and grabbing the page my_url = f'https://br.investing.com/currencies/usd-{currency.lower()}-historical-data' option = Options() option.headless = False driver = webdriver.Chrome(options=option) driver.get(my_url) driver.maximize_window() # Clicking on the date button date_button = WebDriverWait(driver, 20).until( EC.element_to_be_clickable((By.XPATH, "/html/body/div[5]/section/div[8]/div[3]/div/div[2]/span"))) date_button.click() # Sending the start date start_bar = WebDriverWait(driver, 20).until( EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[1]/input[1]"))) start_bar.clear() start_bar.send_keys(start) # Sending the end date end_bar = WebDriverWait(driver, 20).until( EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[1]/input[2]"))) end_bar.clear() end_bar.send_keys(end) # Clicking on the apply button apply = WebDriverWait(driver, 20).until(EC.element_to_be_clickable((By.XPATH, "/html/body/div[7]/div[5]/a"))) apply.click() sleep(5) # Getting the tables on the page and quiting dataframes = pd.read_html(driver.page_source) driver.quit() print(f'{currency} scraped.') break except: driver.quit() print(f'Failed to scrape {currency}. Trying again in 30 seconds.') sleep(30) continue # Selecting the correct table for dataframe in dataframes: if dataframe.columns.tolist() == ['Date', 'Price', 'Open', 'High', 'Low', 'Change%']: df = dataframe break frames.append(df) # Exporting the .csv file if export_csv: df.to_csv('currency.csv', index=False) print(f'{currency}.csv exported.') return frames
Следующие шаги и подведение итогов
На данный момент мы написали код, который позволяет получить исторические данные о курсе обмена валют по отношению к доллару и возвращает список датафреймов или несколько csv-файлов по вашему запросу.
Но всегда есть место для улучшений. С помощью нескольких дополнительных строк кода несложно сделать так, чтобы функция возвращала и экспортировала единственный DataFrame, содержащий данные для каждой валюты из списка. Также можно написать функцию обновления, использующую те же функции Selenium, которая будет принимать существующий DataFrame и обновлять исторические данные до текущей даты.
Кроме того, точно такая же логика, используемая для сканирования валют, может быть использована для сканирования акций, индексов, товаров, фьючерсов и многого другого. Буквально сотни страниц можно перелопатить.
Однако если вы решили заняться именно этим, то прежде чем запускать свой код, необходимо принять некоторые меры безопасности. Это связано с тем, что чем больше запросов вы посылаете сайту в автоматическом режиме, тем больше вы перегружаете его сервер, и вероятность того, что он будет заблокирован, возрастает.
В конечном итоге, использование Selenium, как показано в этой статье, может быть полезным во многих других ситуациях. Например, для входа на сайты, заполнения форм, выбора элементов в раскрывающемся списке и многого другого. Конечно, это не единственное решение для таких проблем, но оно определенно может быть полезным в зависимости от конкретной ситуации.
Надеюсь, статья вам понравилась, и, возможно, окажется полезной. Если у вас есть вопросы или предложения, не стесняйтесь, обращайтесь.
Перевод статьи «Writing a Scraping Bot with Python and Selenium».
Пингбэк: Библиотека urllib в Python
Пингбэк: Как заработать на Python: превращаем код в золото