Скрипт проверки наличия свободных дат в посольстве

в 8:37, , рубрики: python, selenium-webdriver, автоматизация, КодоБред, ненормальное программирование, скрипт, метки: , , ,
Предисловие:

В Беларуси стоит острая проблема с получением виз в Еврозону (т.е. Шенген). Все из-за того, что Польское посольство предоставляет так называемые мульти-визы за покупками (т.е. многократные). Регистрация производится на сайте посольства онлайн. Но вся проблема состоит в том, что свободных дат не словить. Единственный вариант — круглосуточно чекать страницу, и если появится дата — быстро «ловить» ее и заканчивать регистрацию. Т.к. свободного времени для круглосуточного чека нет, было принято решение об автоматизации данного процесса.
Сразу оговорюсь, что существуют различные скрипты, которые вылавливают свободные даты и за которые люди получают деньги. Мой скрипт не претендует на их место по быстроте, качеству и т.д. Данный скрипт был сделан только для себя, никакой коммерческой и иной выгоды я не преследовал.

Постановка задачи и входные данные:

Для начала необходимо было изучить то, как проходит процесс регистрации.
Линк на сайт посольства: by.e-konsulat.gov.pl/
На главной странице видим два селекта, с выбором страны и города. Выбрав необходимые параметры нас редиректит на by.e-konsulat.gov.pl/Informacyjne/Placowka.aspx?IDPlacowki=94.
Потом выбираем из меню «Национальная Виза — Зарегистрируйте бланк» и переходим на by.e-konsulat.gov.pl/Uslugi/RejestracjaTerminu.aspx?IDUSLUGI=1&IDPlacowki=94 — данный урл я и брал за входную точку, т.к. нет смысла в автоматизации предыдущих страниц (конечно, перед этим я проверил возможность входа по данному урлу с чистыми куками)
Далее мы видим капчу. Введя ее, нам дается результат — Отсутствие свободных дат.
Исходя из этих данных, мы можем сделать набросок плана нашего будущего скрипта:
image

Выбор инструмента

После того, как я определился с тем, что необходимо делать — стал вопрос о подходящем инструменте. Сразу хочу оговориться, я не являюсь программистом, я тестироващик. Но некоторые знания языков присутствуют.
В самом начале я хотел автоматизировать данный процесс на TestComplete. После автоматизации я столкнулся с некоторыми проблемами, основная из которых была скорость отработки скрипта, да и плюс ко всему я юзал старую версию тесткомплита 7.5, которая работает максимум с браузером Mozzila 3.5. Сами понимаете, что в таком старом браузере отображение элементов хромает, да и верстка местами едет. Поэтому на данный инструмент я забил и присмотрелся к Selenium WebDriver.
Языком написания скрипта был выбран Python. Выбор пал на него по одной только причине, я был немного знаком с данным скриптовым языком, а лезть в Java, например и изучать его не было ни времени, ни желания.

Работа с капчей

На самом деле автоматизировать данные действия не составляет особого труда, но все портит ненавистная капча. Вся проблема заключалась в том, что капчи с периодичность раз в один-два месяца менялись и поэтому не было смысла продумывать технологию разгадывания капчи (создания шаблонов, масок и т.д.). По этой причине я решил заюзать antigate.
Зарегистрировавшись там и закинув 3 доллара, я получил ресурсов на 3000 капч.
Но теперь необходимо было продумать алгоритм обработки данной капчи, отправки ее на антигейт и получения значения капчи. Выглядело это примерно так:
image
Для работы с антигейтом я использовал API данного сервиса. Пришлось развернуть на локальной машине PHP server, не заморачиваясь выбор пал на Denwer. Создал локальный сайт test1.ru и закинул туда php страницу для работы с API сервиса.
Листинг данной страницы

<?php
function recognize(
    $filename,
    $apikey,
    $is_verbose = true,
    $sendhost = "antigate.com",
    $rtimeout = 10,
    $is_phrase = 0,
    $is_regsense = 1,
    $is_numeric = 0,
    $min_len = 4,
    $max_len = 4,
    $is_russian = 1)
{
    if (!file_exists($filename))
    {
        if ($is_verbose) echo "<b>file $filename not found</b>";
        return false;
    }
    $fp=fopen($filename,"r");
    if ($fp!=false)
    {
        $body="";
        while (!feof($fp)) $body.=fgets($fp,1024);
        fclose($fp);
        $ext=strtolower(substr($filename,strpos($filename,".")+1));
    }
    else
    {
        if ($is_verbose) echo "<b>could not read file $filename<b>";
        return false;
    }

    if ($ext=="jpg") $conttype="image/pjpeg";
    if ($ext=="gif") $conttype="image/gif";
    if ($ext=="png") $conttype="image/png";


    $boundary="---------FGf4Fh3fdjGQ148fdh";

    $content="--$boundaryrn";
    $content.="Content-Disposition: form-data; name="method"rn";
    $content.="rn";
    $content.="postrn";
    $content.="--$boundaryrn";
    $content.="Content-Disposition: form-data; name="key"rn";
    $content.="rn";
    $content.="$apikeyrn";
    $content.="--$boundaryrn";
    $content.="Content-Disposition: form-data; name="phrase"rn";
    $content.="rn";
    $content.="$is_phrasern";
    $content.="--$boundaryrn";
    $content.="Content-Disposition: form-data; name="regsense"rn";
    $content.="rn";
    $content.="$is_regsensern";
    $content.="--$boundaryrn";
    $content.="Content-Disposition: form-data; name="numeric"rn";
    $content.="rn";
    $content.="$is_numericrn";
    $content.="--$boundaryrn";
    $content.="Content-Disposition: form-data; name="min_len"rn";
    $content.="rn";
    $content.="$min_lenrn";
    $content.="--$boundaryrn";
    $content.="Content-Disposition: form-data; name="max_len"rn";
    $content.="rn";
    $content.="$max_lenrn";
    $content.="--$boundaryrn";
    $content.="Content-Disposition: form-data; name="is_russian"rn";
    $content.="rn";
    $content.="$is_russianrn";
    $content.="--$boundaryrn";
    $content.="Content-Disposition: form-data; name="file"; filename="capcha.$ext"rn";
    $content.="Content-Type: $conttypern";
    $content.="rn";
    $content.=$body."rn";
    $content.="--$boundary--";


    $poststr="POST http://$sendhost/in.php HTTP/1.0rn";
    $poststr.="Content-Type: multipart/form-data; boundary=$boundaryrn";
    $poststr.="Host: $sendhostrn";
    $poststr.="Content-Length: ".strlen($content)."rnrn";
    $poststr.=$content;


    $fp=fsockopen($sendhost,80,$errno,$errstr,30);
    if ($fp!=false)
    {
        fputs($fp,$poststr);
        $resp="";
        while (!feof($fp)) $resp.=fgets($fp,1024);
        fclose($fp);
        $result=substr($resp,strpos($resp,"rnrn")+4);
    }
    else
    {
        if ($is_verbose) echo "<b>could not connect to anti-captcha</b>";
        if ($is_verbose) echo "<b>socket error: $errno ( $errstr )</b>";
        return false;
    }

    if (strpos($result, "ERROR")!==false or strpos($result, "<HTML>")!==false)
    {
        if ($is_verbose) echo "<b>server returned error: $result</b>";
        return false;
    }
    else
    {
        $ex = explode("|", $result);
        $captcha_id = $ex[1];
        if ($is_verbose) echo "<b>$captcha_id</b>";
    }
}

$text=recognize("captcha.png","Здесь должен быть ключ для работы с сервисом",true,"antigate.com");
?>

Я не стал досконально разбираться, что к чему, но единственное, что я выставил — это следующие настройки:

    $is_phrase = 0, //является ли ваша капча фразой
    $is_regsense = 1, //регистро зависимая или нет?
    $is_numeric = 0, //Состоит из цифр?
    $min_len = 4, //минимальная длинна
    $max_len = 4, //максимальная длинна
    $is_russian = 1 //есть ли русские символы

В итоге нам необходимо поместить изображение captcha.png в директорию, где находится index.php и перейти по урлу test1.ru
В итоге капча полетит на сервис, когда она разгадается нам придет ее id, обрамленный в тег b, либо придет какая-нибудь ошибка, которая отобразиться.
Останется дело за малым, только забрать значение капчи со страницы по ее id.

Создание скрипта

Т.к. все предварительные подготовки сделаны, то можем приступать непосредственно к написанию скрипта.
Работать мы будем с двумя открытыми окнами Firefox. Т.к. в одном окне у нас будет происходить чек дат, а во втором все работы относительно капчи. Для отображения капчи в новом окне, мы просто будем находить сам элемент на странице по id и считывать урл текущей капчи. При обращении на данный урл, мы получим только изображение капчи, без лишних элементов.
image

Теперь листинг скрипта, с комментариями:

from selenium import webdriver
from selenium.webdriver.support.ui import WebDriverWait
import time

driver = webdriver.Firefox() #запускаем первое окно (основное)
add_driver = webdriver.Firefox() #запускаем дополнительное окно для работы с капчей
driver.get("https://by.e-konsulat.gov.pl/Uslugi/RejestracjaTerminu.aspx?IDUSLUGI=1&IDPlacowki=94") #переходим на наш урл
captcha_url = driver.find_element_by_id('c_uslugi_rejestracjaterminu_ctl00_cp_botdetectcaptcha_CaptchaImage').get_attribute('src') #Находим элемент капчи по его id и считываем урл, по которому будет доступно изображение

add_driver.get(captcha_url) #во втором окошке открываем нашу капчу
add_driver.set_window_size(50,200) #делаем ресайз окна браузера, чтобы сделать скриншот именно капчи, без лишних серых полей
add_driver.get_screenshot_as_file('captcha.png') #делаем скриншот окна, в итоге на нашем скриншоте будет только капча и сохраняем его в директорию локального сайта test1.ru, т.к. у меня скрипт лежит тамже, то путь не писал
add_driver.get(http://test1.ru) #переходим на урл нашей странички, для работы с антигейтом
captcha_id = add_driver.find_element_by_xpatch('//b') #находим элемент, который обрамлен в тег b, подразумевая ,что там хранится значение id капчи
count = false
while (count = false)
    add_driver.get('http://antigate.com/res.php?key=Ключ для работы с антигейтом&action=get&id=" + captcha_id)
    captcha_complete = add_driver.find_element_by_xpatch('//pre').text # находим наше значение (на антигейте оно обрамлено в тег pre)
    if (captcha_complete.find('ERROR') >= 0) #проверяем, выскочила ли ошибка
        time.sleep(5) #спим 5 секунд
    else
        count = true #выходим из цикла проверки
# теперь значение нашей капчи содержится в переменной  captcha_complete, его и вводим в инпут

driver.find_element_by_id('ctl00_cp_BotDetectCaptchaCodeTextBox').send_keys(captcha_complete) #находим наш инпут и вводим значение капчи
driver.find_element_by_id('ctl00_cp_btnDalej').click() #находим кнопку далее и кликаем на нее
result = driver.find_element_by_id('ctl00_cp_lblBrakTerminow').text
if (result.find('Отсутствие') >= 0)
    print('Нет даты')
else
    print('Дата есть')
Будущие улучшения

Основа готова, наш скрипт переходит на страницу, получает капчу, распознает ее через сервис распознавания, вводит капчу, кликает далее и проверяет наличие даты. Для себя я сделал следующее — загнал все это действие в цикл while (true) и чекал сайт, пока не словилась дата (также я добавил отправку письма на мыло, в случае положительного результата). Доработок по скрипту конечно же можно произвести много, например:
1) поставить проверку на ошибки и исходя из ошибок предпринимать различные действия
2) поставить проверку на неправильную капчу и отправку репорта на антигейт (пожаловаться на плохого работника)
3) дописать авторегистратор, а не просто чекер даты
и т.д.

Послесловие

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

Автор: dgu_minsk

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js