В статье пойдет речь о решении визуально привлекательной капчи, решение которой не только немного расслабляет и погружает в транс медитации, но также позволяет немного стряхнуть пыль с фреймворка selenium для python, а также пакета opencv. Именно эти инструменты и будут использоваться на капче, которая относится к так называемому виду капч «с перетаскиванием». Но, для начала, присказка.
Присказка
Посещая ресурсы с открытыми данными для последующего их сбора и анализа, примечателен сайт data.gov.ru. Имеет обширную библиотеку информации на любой вкус и цвет. Но можно найти и зияющие дыры на месте размещения этих самых данных. Дыры(или технолигические отверстия, кому как нравится) в понимании отсутствия этих самых данных либо их значительное устаревание. Причем это присутствует во всех рубриках, обозначенных на сайте.
Чтобы далеко не ходить, откроем, например раздел «Электроника». Самое свежее, что там есть — «Перечень национальных чемпионов» —
Но не тут то было, внутри ничего нет —
Количество наборов данных хотя и выглядит внушительно (23795), но все же маловато, например, на аналогичном ресурсе Индии наборов «немного» больше —
ссылка
В остальном, портал приятен в работе и имеет не менее приятную капчу при отправке письма обратной связи.
Капча
Нас интересует программное решение капчи, которая представлена в форме обратной связи портала. Спортивный интерес, ничего более.
Решение выглядит тривиальным. Необходимо перетащить картинку слева(претенденты) на поле (таргет) справа:
Приступим.
Алгоритм решения будет выглядеть следующим:
- зашли на сайт,
- скачали картинки-претенденты и картинку-таргет,
- обработали картинки, сравнив претендентов с таргетом ,
- перетащили правильного претендента на таргет.
Начнем с импортов пакетов.
import webbrowser,time,os
import cv2
from skimage import measure
from selenium import webdriver
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.action_chains import ActionChains
from selenium.webdriver.firefox.firefox_profile import FirefoxProfile
from selenium.webdriver.firefox.options import Options
options = webdriver.FirefoxOptions()
options.set_preference("general.useragent.override", "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:84.0) Gecko/20100101 Firefox/84.0")
options.set_preference("dom.webdriver.enabled", False)
options.set_preference("browser.helperApps.neverAsk.saveToDisk", 'application/octet-stream')
options.set_preference("browser.download.folderList", 2);
options.set_preference("browser.download.dir", "C:\\5");
Нам понадобится как selenium, который будет выполнять всю работу по открытию браузера, а также перемещениям по web-странице. К selenium идет целый букет опций, например, автоматическое сохранение картинки на диск без выброса окна с вопросом о «сохранить как», директория сохранения — диск с, папка 5.
Также нам понадобится opencv — cv2, а также пакет, который «будет сравнивать» идентичность изображений — measure.
Открываем браузер в разрешении 1200 на 1300.
Задержку прогрузки страниц — 20 сек, заходим на сайт с капчей:
browser = webdriver.Firefox(firefox_binary=r'D:\Program Files (x86)\Mozilla Firefox\firefox.exe',options=options)
browser.set_window_size(1200, 1030)
browser.implicitly_wait(20)
browser.get ('https://data.gov.ru/feedback')
Прокрутим страницу вниз, чтобы увидеть капчу и найдем на ней картинки претенденты и картинку таргет:
act = browser.find_element_by_tag_name('html').send_keys(Keys.DOWN*9)
acts=browser.find_elements_by_class_name('draggable')#нашли 4 элемента капчи
act=browser.find_element_by_class_name('target')#цель, куда перетащить
Теперь мы сделаем такой «фокус» — будем открывать соседнюю вкладку, прогружать в нее картинки и скачивать оттуда. Это нужно для того, чтобы браузер не обновлял капчу:
img_target=act.find_element_by_tag_name('img').get_attribute('src')#картинка-таргет
browser.switch_to.window(browser.window_handles[0])
browser.execute_script("window.open()")
browser.switch_to.window(browser.window_handles[-1])
Теперь скачаем картинки-претенденты и таргет. При скачивании, selenium каждый раз будет закрывать вкладку и переключаться в основной контекст с капчей:
#скачали картинку-таргет
browser.get(str(img_target)+'.png')
with open('target.png', 'wb') as file:
file.write(browser.find_element_by_class_name('transparent').screenshot_as_png)
browser.close()
browser.switch_to.window(browser.window_handles[0])
#скачали картинки-претенденты
for x in range(len(acts)):
with open(str(x)+'.png', 'wb') as f:
f.write(acts[x].screenshot_as_png)
Сравниваем изображения
Итак, в результате работы предыдущего кода, получим 5 картинок на диске: одна картинка-таргет и 4 картинки-претендента.
Нам необходимо сравнить каждого претендента с картинкой-таргетом и далее переместить его на позицию картинки-таргета.
Используем opencv.
Прочитаем картинки и добавим их в список.
#сравниваем изображения
images=[]
imageA = cv2.imread('target.png')
imageB = cv2.imread('0.png')
#так как картинки не совпадают по shape (target имеет другие размеры), приводим их к одинаковым размерам
(H, W, x) = imageB.shape
imageA = cv2.resize(imageA, (W, H))
imageC = cv2.imread('1.png')
imageD = cv2.imread('2.png')
imageE = cv2.imread('3.png')
images.append(imageB)
images.append(imageC)
images.append(imageD)
images.append(imageE)
Здесь примечательны строки:
(H, W, x) = imageB.shape
imageA = cv2.resize(imageA, (W, H))
imageA — это наша таргет картинка и она, как оказалось, немного отличается по размеру, немотря на визуальное сходство. Поэтому мы «подгоняем» ее по размеру с картинками-претендентами.
Переводим картинки в «серые тона».
Но, так как картинка-таргет и здесь из ряда вон, ее дополнительно надо перевести еще и в негатив:
grayA = cv2.cvtColor(imageA, cv2.COLOR_BGR2GRAY)
grayA = 255 - grayA
Теперь, чтобы не дублировать код для каждой картинки, осуществим преобразования в цикле:
scores=[]
for i in images:
b=cv2.cvtColor(i, cv2.COLOR_BGR2GRAY)
s = measure.compare_ssim(grayA, b, full=True)
(score, diff) = measure.compare_ssim(grayA, b, full=True)
diff = (diff * 255).astype("uint8")
print("SSIM: {}".format(score))
scores.append(score)
Здесь каждая картинка-претендент (b) в «сером формате» сравнивается с картинкой-таргетом, далее результаты сравнения формируют список.
Ищем правильную картинку среди претендентов
В списке scores 4 значения, их надо сравнить между собой и найти претендента с максимальным значением. Есть несколько решений по данному вопросу, но мы воспользуемся следующим, который будет эффективен и для иных списков с любым количеством членов:
def my_max(scores):
it = iter(scores)
try:
max_ = next(it)
except StopIteration:
raise ValueError('max() arg has no items')
for item in it:
if max_ < item:
max_ = item
print(max_)
return max_
Вызовем созданную функцию и для удобства поместим результат в переменную —
a=scores.index(my_max(scores))#0 - т.е. первый элемент списка максимальный
Осталось перетащить нужный элемент из числа картинок-претендентов на картинку-цель.
Это делается одной строчкой:
ActionChains(browser).drag_and_drop(acts[a], act).perform()
Проверяем
Как узнать, что все получилось? Конечно же напечатать слово «успех»!
Можно считать цвет фона правильно решенной капчи. Он становится зеленым. Но проще все
решить с помощью того же selenium.
Если капча решена верно, то немного изменяется селектор картинки-таргета.
Поэтому решение будет таким:
act = browser.find_element_by_class_name('targetWrapper').get_attribute('class').split(' ')[-1]
if act=='captchaSuccess':
print('успех')
На этом все, спасибо за внимание.
И да, картинки-претенденты похожи между собой и программа на сердечке-ромбике ошибается, путая их между собой. Так как коэффициенты similarity у них близки.
Полный код программы — здесь.
Спасибо за внимание.
Таксидермия иных капч: