Как-то раз возникла необходимость выкачать с одного сайта информацию про компании из списка ИНН. Но на сайте стоит капча, в которой «зашит» ещё и пример. Что-то типа: «Ответь 95 плюс 5?» Руками перебирать 1000 ИНН конечно же не хочется — это не наш способ. Нужно как-то обойти капчу. Первым делом стал искать модель распознавания. Попробовав парочку, стало понятно, что без дообучения работают они так себе. Если знаете хорошие модельки, прям чтобы огонь и из коробки, напишите пожалуйста в комментариях ;) И вот чтобы не заниматься ML и как-то по-быстрому решить задачу взялся эмулировать работу юзера на сайте с помощью пакета pyautogui…
Алгоритм простой:
- берём ИНН из таблицы,
- вставляем в форму на сайте,
- скачиваем картинку капчи на локаль,
- переходим на сайт распознавания картинок (типа того: cardscanner.co),
- загружаем капчу в сервис,
- копируем результат в буфер обмена,
- переходим на сайт huggingface.co в любой рабочий интерфейс LLM модели где есть поле для ввода. Я выбрал Mistral-Nemo-Instruct-2407,
- вставляем распознанный текст и получаем ответ от LLM,
- возвращаемся на сайт с формой капчи,
- вставляем ответ, обрабатываем варианты ответа формы.
Всё :)
Алгоритм работает с ошибками, поэтому проработку вариантов нужно заложить в код. Но, в целом, оставив на ночь комп работать, утром можно получить результат.
Нам понадобится компьютер, Jupyter Notebook, браузер с тремя вкладками: сайт с капчей, сервис распознавания картинок, бесплатная LLM (например на huggingface.co) и готовая таблица с данными для формы.
Открываем наш Jupyter Notebook, пишем...
Импортируем нужные пакеты
pip install pynput --quiet
pip install pyperclip --quiet
pip install pyautogui --quiet
Параметр --quiet для того чтобы не выводить на экран простыню технической информации по установке.
Чтобы узнать координаты куда двигать курсор мышки именно на своём мониторе, можно использовать небольшой скрипт, который возвращает координаты щелчка мыши.
# Слушаем мышку
from pynput import mouse
def on_click(x, y, button, pressed):
if pressed:
print(f'Координаты клика: {x}, {y}')
# Создаем слушатель
with mouse.Listener(on_click=on_click) as listener:
listener.join()
Устанавливаем нужные библиотеки
import pandas as pd
import pyautogui
import pyperclip
import os
import time
Загружаем нашу исходную таблицу. Тут есть один момент... Если для вставки в форму на сайте использовать числа, в которых в начале могут стоять нули, как у ИНН. То всего скорее pandas распознает такие числа как тип данных Integer или Float и все нули в начале отбросит. Тогда в форме возникнет ошибка. Чтобы такого не произошло, нужно при загрузке данных указать загружаемый тип как строка String.
df = pd.read_excel('data.xlsx', dtype={'ИНН':str})
Устанавливаем координаты для элементов интерфейса и вводные данные
Координаты на сайте с формой поиска
coords_input = (-1408, 642) # Форма поиска
coords_captcha = (-1251, 760) # CAPTCHA
coords_save_as = (-1205, 773) # Меню "Сохранить изображение как..."
coords_captcha_answer = (-1300, 833) # Мой ответ на CAPTCHA
coords_button_up = (-1396, 869) # Кнопка "ОТПРАВИТЬ", верхняя позиция
coords_button_down = (-1373, 949) # Кнопка "ОТПРАВИТЬ", нижняя позиция
coords_isanswer = (-1256, 881) # Место вердикта от CAPTCHA
coords_new_captcha = (-1405, 797) # Кнопка обновления капчи
coords_isna_inf = (-996, 945) # Ответ формы сайта
Координаты на сервисе распознавания cardscanner.co
coords_tools = (-668, 102) # Меню Tools
coords_menu_png_to_txt = (-782, 413) # Пункт меню
coords_upload = (-1036, 354) # Кнопка Upload File
coords_convert = (-787, 467) # Кнопка Convert
coords_answer_png_txt = (-1039, 320) # Поле ответа сервиса
Координаты LLM на huggingface.co
coords_mistral_ask = (-701, 911) # Поле ввода вопроса
coords_mistral_answer = (-773,852) # Ответ LLM
Необходимые флаги проверок
wrong_captcha = 'Неправильный код проверки.'
for_match_inf = 'По указанным параметрам запроса сведений не найдено.'
right_captcha = True
pyautogui.PAUSE = 1 # Прауза между действиями
Перебор всех ИНН в датафрейме
counter = 1 # Счётчик
for index, row in df.iterrows():
wrong_captcha_str = wrong_captcha # Начальное условие на капчу
inn = str(row['ИНН']) # Приводим к строке
print(f'{counter}. ИНН: {inn}')
Перебор всех значений для формы
pyautogui.moveTo(coords_input, duration=1) # Двигаем курсор к полю с ИНН
pyautogui.tripleClick(interval=0.1) # Кликаем 3 раза ЛКМ
pyautogui.press('delete') # Стираем предыдущее значение
pyautogui.write(inn) # Вставляем ИНН
Блок с капчей
while wrong_captcha_str == wrong_captcha:
pyautogui.moveTo(coords_captcha, duration=1) # Мышку на CAPTCHA
pyautogui.click(button='right') # Жмём ПКМ
pyautogui.moveTo(coords_save_as, duration=0.5) # Сохр.изображение как...
pyautogui.click(button='left') # Жмём ЛКМ
pyautogui.press('1') # Задать название файла как "1.png"
pyautogui.press('enter') # Подтвердить ввод
pyautogui.press('left') # Если папка содержит файл "1.png" жмём ←
pyautogui.press('enter') # И подтверждаем замену
pyautogui.hotkey('ctrl', 'tab') # Переходим на след.вкладку
Сервис распознавания картинок
pyautogui.press('f5') # Обновляем страницу
time.sleep(2) # Ждём 2 сек. пока загрузится страница
pyautogui.scroll(130) # Скролим в самый верх сайта
pyautogui.moveTo(coords_tools, duration=1) # Курсор на меню Tools
pyautogui.moveTo(coords_menu_png_to_txt, duration=0.5) # Курсор на пункт меню
pyautogui.click(button='left') # Жмём ЛКМ
pyautogui.moveTo(coords_upload, duration=1) # Жмём кнопку загрузки файла
pyautogui.click(button='left') # Жмём ЛКМ
# Набираем название файла:
pyautogui.press('1')
pyautogui.press('.')
pyautogui.press('p')
pyautogui.press('n')
pyautogui.press('g')
pyautogui.press('enter') # Жмём Enter
time.sleep(2) # Ждём 2 сек. пока загрузится страница
pyautogui.moveTo(coords_convert, duration=1) # Кнопка Convert
pyautogui.click(button='left') # Жмём ЛКМ
pyautogui.moveTo(coords_answer_png_txt, duration=1) # Курсор в поле ответа
time.sleep(9) # Ждём 9 сек.
pyautogui.click(button='left') # Жмём ЛКМ
pyautogui.hotkey('ctrl', 'a') # Выделяем весь ответ
pyautogui.hotkey('ctrl', 'c') # Копируем ответ в буфер обмена
pyautogui.hotkey('ctrl', 'tab') # Переходим на след.вкладку
Дополняем вопрос капчи для LLM
copied_text = pyperclip.paste() # Вставляем из буфера в переменную
copied_text = copied_text.strip() # Удаляем пробелы в начале и в конце
# Дополнение капчи
substring_to_insert = "Выведи ответ на следующий вопрос только одним
числом. Вопрос: "
# Вставляем подстроку на нужную позицию
modified_text = substring_to_insert + copied_text
# Записываем обновленный текст обратно в буфер обмена
pyperclip.copy(modified_text)
Блок для LLM
pyautogui.moveTo(coords_mistral_ask, duration=0.5) # Курсор в поле вопроса
pyautogui.click(button='left') # Жмём ЛКМ
pyautogui.hotkey('ctrlleft', 'v') # Вставляем распознанную капчу
pyautogui.press('enter') # Жмём Enter
time.sleep(5) # Ждём 5 сек.
pyautogui.moveTo(coords_mistral_answer, duration=0.5) # На ответ от LLM
pyautogui.tripleClick(interval=0.1) # Кликаем 3 раза на ответ от Mistral
pyautogui.hotkey('ctrlleft', 'c') # Копируем ответ в буфер обмена
pyautogui.hotkey('ctrl', 'tab') # Переходим на вкладку с капчей
Ответ на капчу
pyautogui.moveTo(coords_captcha_answer, duration=0.5) # На ответ для капчи
pyautogui.tripleClick(interval=0.1) # Жмём три раза ЛКМ
pyautogui.hotkey('ctrlleft', 'v') # Вставляем ответ на капчу
Если кнопка «ОТПРАВИТЬ» ниже/выше
if right_captcha:
pyautogui.moveTo(coords_button_up, duration=0.5) # Кнопка ОТПРАВИТЬ
выше
else:
pyautogui.moveTo(coords_button_down, duration=0.5) # Кнопка ОТПРАВИТЬ
ниже
pyautogui.click(button='left') # Жмём ОТПРАВИТЬ
time.sleep(1) # Ждём 1 сек.
pyautogui.moveTo(coords_isanswer, duration=0.5) # К месту ответа, если
ответ на капчу неправильный
pyperclip.copy('Заглушка') # Костыль для нормальной работы
pyautogui.tripleClick(interval=0.1) # Кликаем 3 раза по ответу на капчу
pyautogui.hotkey('ctrlleft', 'c') # Копируем ответ в буфер обмена
wrong_captcha_str = pyperclip.paste() # Вставить из буфера в переменную
wrong_captcha_str = wrong_captcha_str.strip() # Удаляем пробелы в начале и в
конце
Проверка ответа на капчу. Что делаем дальше
if wrong_captcha_str == wrong_captcha: # Если равно, ответ неверный
right_captcha = False # Флаг правильности капчи
pyautogui.moveTo(coords_new_captcha, duration=0.5) # "Новая капча"
pyautogui.click(button='left') # Жмём ЛКМ
else:
right_captcha = True # Флаг правильности капчи
pyautogui.moveTo(coords_isna_inf, duration=0.5) # Курсор на ответ формы
pyautogui.tripleClick(interval=0.1) # Жмём ЛКМ 3 раза
pyautogui.hotkey('ctrlleft', 'c') # Копируем ответ в буфер обмена
no_inf_str = pyperclip.paste() # Из буфера в переменную
no_inf_str = no_inf_str.strip() # Удаляем пробелы в начале и в конце
if no_inf_str == for_match_inf: # Обработка ответа формы
df.loc[index, 'res_fsrar'] = 'no_inf'
counter += 1 # Счётчик
else:
pyautogui.hotkey('ctrlleft', 's') # Сохранение страницы
pyautogui.write(inn) # Название файла по ИНН
pyautogui.press('enter') # И подтверждаем замену
pyautogui.press('left') # Если папка содержит файл жмём ←
pyautogui.press('enter') # И подтверждаем замену
df.loc[index, 'res_fsrar'] = 'loaded' # Пометка
counter += 1 # Счётчик
Сохранение результатов
df.to_excel('result.xlsx', index=False, engine='xlsxwriter')
При прохождении капчи, можно далее сохранить полученную страницу и потом спокойно распарсить данные, как я это и сделал.
Метод не самый хороший, а местами совсем нехороший :) Но работает. И лучше конечно же обучить свою модель на распознавание капчи, найти какую-нибудь LLM (может даже квантованную) для решения задачи из капчи, а потом запустить всё вместе. Для многотысячных таблиц, так и сделаю. А пока это работает.