Эта история следует из двух предыдущих сообщений, которые я сделал в связи с созданием Твиттер-бота, чтобы публиковать сообщения, когда срочные паспорта Великобритании Fast Track и услуги Premium доступны онлайн. О них можно узнать здесь:
На момент написания статьи аккаунт ukpassportcheck в Твиттере (https://twitter.com/ukpassportcheck) насчитывает более 11 000 подписчиков. И это помогло тысячам людей записаться на срочные встречи Fast Track и Premium.
Однако я давно хотел что-то придумать. Могу ли я получить доступное количество встреч каждый раз, когда служба выходит в онлайн? Ну, чтобы сделать это, мне нужно что-то более сложное, чем я уже использовал. Причина этого в том, что веб-сайт использует элементы JavaScript, с которыми пользователь взаимодействует, либо щелкая, либо вводя данные.
Возьмите ниже. Это первая страница, когда сервис онлайн. Он спрашивает вас, сколько приложений делает пользователь, а затем есть кнопка отправки (Продолжить). Обычно пользователь щелкает одно из полей, а затем зеленую кнопку «Продолжить». Мне нужен был скрипт, чтобы сделать это для меня.
Для этого я использовал селен для Python. Сначала мне нужно было убедиться, что Chrome установлен на моем компьютере (это было так), а затем я использовал пакет pip chromedriver_autoinstaller
для автоматического использования правильного драйвера Chrome, который был на моем компьютере.
Затем это был случай импорта селена в Python, запуска веб-драйвера и вызова URL-адреса:
from selenium import webdriver chromedriver_autoinstaller.install() this_driver = webdriver.Chrome() this_driver.get(the_url)
После этого я хотел установить некоторые проверки, чтобы убедиться, что веб-страница показывает страницу, которую я хотел (а не страницу с ошибкой). Поэтому я просто проанализировал основной текст, используя:
body = get_body(this_driver) if "message" in body.text: print("Success") ...run code
Затем мне нужно было настроить две основные функции. Один для щелчка по элементам и один для ввода данных в элементы (таких как имена, даты). Для щелчка это:
def click_page_element(the_driver, path_value, wait_time, by_what="xpath"): """ Click the page element :param the_driver: <Selenium.webdriver> The selenium webdriver :param path_value: <string> the path value :param wait_time: <int> the wait time :param by_what: <string> what you want to select """ time.sleep(wait_time) if by_what == "xpath": element = the_driver.find_element(by=By.XPATH, value=path_value) elif by_what == "class": element = the_driver.find_element(by=By.CLASS_NAME, value=path_value) element.click()
Я добавил простой оператор if, чтобы можно было выбрать либо XPATH (предпочтительно), либо CLASS_NAME. Далее идет функция ввода:
def enter_page_element(the_driver, path_value, value_to_enter, wait_time, by_what="xpath"): """ Enter a value on the page :param the_driver: <Selenium.webdriver> The selenium webdriver :param path_value: <string> the path value :param value_to_enter: <string> the value to enter :param wait_time: <int> the wait time :param by_what: <string> what you want to select """ time.sleep(wait_time) if by_what == "xpath": element = the_driver.find_element(by=By.XPATH, value=path_value) elif by_what == "class": element = the_driver.find_element(by=By.CLASS_NAME, value=path_value) element.send_keys(value_to_enter)
Это отправляет значения элементу, которые затем вводятся так, как если бы вы печатали их самостоятельно.
После того, как эти функции будут построены, остается только пройти процесс самостоятельно, записывая XPATH (Inspect > Right Click Element > Copy XPATH
) по мере того, как вы переходите к элементам, которые хотите щелкнуть. Затем вызов вышеперечисленных функций в соответствующем порядке.
После прохождения страниц ввода появляются страницы встреч, где я хочу очистить значения.
Как вы видите выше, есть более одной страницы, и я скоро расскажу, как перемещаться между ними. Но сначала, чтобы очистить вышеуказанные данные, я использовал pandas
:
html = the_driver.page_source table = pd.read_html(html) df = table[0]
Это создает хороший кадр данных pandas на основе значений в таблице HTML. Все, что мне нужно было сделать, это немного почистить его — добавить постоянный индекс для местоположений офисов и просто иметь значения в ячейках DataFrame.
Чтобы перейти к следующей странице, я просто использовал функцию click_page_element
, определенную выше, после нахождения XPATH для кнопки. Затем я объединяю DataFrames с каждой страницы и удаляю дубликаты.
Наконец, нужно еще немного навести порядок, а затем создать полезную таблицу. Здесь я решил использовать seaborn
и тепловую карту, так как у меня были табличные данные, которые я хотел улучшить с помощью цветной полосы из-за различий в количестве встреч в каждом офисе в день. Полученное изображение показано ниже.
Затем я использовал tweepy
для публикации в Твиттере (где authenticate_twitter
— это функция, которую я сделал для предыдущей работы, чтобы авторизоваться в Твиттере с помощью моих токенов доступа):
def post_media(): """ """ api = authenticate_twitter() # Posts status to Twitter media = api.media_upload(filename=filename) api.update_status(status=f'The latest slots', media_ids=[media.media_id]) print("Posted update to Twitter")
И….. поехали!
Да ладно, неужели это было так просто?!
Хорошо, ты меня понял. Итак, были задачи:
XPATH-ошибки
Если вы выбрали неправильный XPATH, это не сработает. Я делал это много.
Элемент не найден (при анализе таблиц назначений)
Поэтому я использовал очень крутую петлю, чтобы продолжать попытки перейти на следующую страницу на странице назначения. Но когда он не мог найти этот элемент «Следующая страница», он зависал. Иногда была только одна следующая страница, иногда много, поэтому я не мог жестко закодировать число в цикле. Вместо этого я просто использовал исключение ошибки:
from selenium.common.exceptions import NoSuchElementException try: the_driver.find_element(by=By.XPATH, value='') click_page_element(the_driver, '', 4, by_what="xpath") except NoSuchElementException: ...go to next step
Автоматизация этого с помощью GitHub Actions
Учетная запись Twitter использует автоматизацию с помощью GitHub Actions, поэтому мне не нужно запускать код. Отлично. Но всякий раз, когда я пробую что-то новое, подобное этому, я сталкиваюсь с множеством ошибок с GitHub Actions.
Проблема заключалась в том, что при локальном тестировании и разработке я использовал Chrome и визуализировал шаги, которые предпринимал селен. Однако в GitHub Actions это должен был быть безголовый браузер. Поэтому я добавил несколько опций в свой код:
options = Options() options.add_argument('--headless') options.add_argument('--disable-gpu') options.add_argument('--no-sandbox') options.add_argument('--window-size=1920,1080') this_driver = webdriver.Chrome(options=options)
Это означало, что он работал с эквивалентным размером окна на моей локальной машине, но также работал в автономном режиме.
Получение селена, работающего над действиями GitHub
Это было не так сложно, как я думал. Я использовал скрипт bash и сохранил его в папке scripts
:
#!/bin/bash set -ex wget https://dl.google.com/linux/direct/google-chrome-stable_current_amd64.deb sudo apt install ./google-chrome-stable_current_amd64.deb
А затем вызвал это в моем действии GitHub, используя:
- name: Install Google Chrome # Using shell script to install Google Chrome run: | chmod +x ./scripts/InstallChrome.sh ./scripts/InstallChrome.sh
Любые вопросы, просто кричите.
Майкл