Русский
Русский
English
Статистика
Реклама

Captcha

Перевод Как обойти капчу-слайдер с помощью JS и Puppeteer

23.06.2020 10:15:03 | Автор: admin

Друзья, предлагаю вашему вниманию перевод статьи https://medium.com/@filipvitas/how-to-bypass-slider-captcha-with-js-and-puppeteer-cd5e28105e3c


Для владельца сайта спам огромная проблема. С другой стороны, капчи сводят меня с ума и ухудшают пользовательский опыт.


Капчи отстой. Скажем прямо. Есть много способов прохождения верификации и каждый из них плох.


В последние годы боты умнеют с каждым днем, и защитить от них сайт становится непросто. При достаточном количестве времени и ресурсов можно обойти почти любую капчу. К Puppeteer есть плагины для прохождения Recaptcha. Некоторые компании предоставляют сервисы специально для этого. Например, 2Capcha. Вот как можно использовать Puppeteer и 2Captcha.


Есть сайты, где в качестве альтернативы используется капча со слайдером (сдвиньте ползунок для верификации). Но зачем кому-то вообще использовать настолько простую капчу с ползунком?


Причины следующие:


  • Большинство ботов работают не на JS, поэтому вы можете их остановить
  • Слайдер более дружелюбен к пользователю
  • Для пользователей мобильных устройств свайпнуть слайдер привычное действие

Так что для людей слайдеры понятны и просты.


Давайте попробуем обойти кое-какие капчи со слайдером.


Передвиньте ползунок для отправки


jQuery используется в веб-формах, где есть элемент передвиньте ползунок для отправки. Это альтернатива капче, предотвращающая спам при заполнении веб-форм.


image

Сперва мы заполняем поля ввода. Чтобы передвинуть этот слайдер, нам нужно:


  • навести курсор на центр ползунка
  • нажать кнопку мыши
  • передвинуть мышь
  • отпустить кнопку мыши

image

Готово. Это было просто.


Слайдер регистрации Dipbit


Dipbit сайт для обмена цифровых валют. Обе страницы и ввод логина, и регистрация содержат элемент передвиньте ползунок для верификации.


image

Dipbit пытается быть немного умным, поэтому нам нужно добавить код для сокрытия работы Puppeteer.


image
image

Taobao


Taobao китайский сайт онлайн-покупок, принадлежащий Alibaba. Их слайдер регистрации похож на аналогичный у Dipbit. Единственное отличие регистрационная форма находится внутри
image
image

Капча-слайдер с головоломкой


Я наткнулся на компонент передвиньте полузнок фреймворка Vue, который должен быть простым для людей и сложным для ботов.


Этот способ верификации выводит изображение, создает два canvas-слоя и один слайдер. Он обрабатывает исходное изображение как части паззла. Пользователю нужно двигать слайдер так, чтобы части подошли. Как только части подошли, пользователь отпускает слайдер, и верификация пройдена.


Эта капча рандомизирует положения частей паззла, чтобы запутать ботов.


image

Я не собирался делать здесь что-то особенное, вроде ML или OCR, поэтому просто буду понемногу двигать слайдер, сравнивая конечное и исходное изображения.


Для сравнения изображений я использую rembrandt.js library. Как только найдется минимально отличающееся, я передвину слайдер в наилучшее положение и отпущу кнопку мыши.



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


image
image

Все примеры кода доступны здесь: github repo, копируйте на здоровье всё, что захотите.


Заключение


Это всегда дилемма: нужно ли сайтам быть более дружелюбными и использовать простые для прохождения капчи, или они должны агрессивно защищать себя от ботов, жертвуя удобством пользования. Война между сайтами и ботами не заканчивается никогда. Какой бы метод верификации ни использовался, это лишь вопрос времени, прежде чем кто-то придумает, как его обойти.


Несмотря на всё это, информация предоставлена исключительно в образовательных целях, используйте Puppeteer осмотрительно.

Подробнее..
Категории: Javascript , Puppet , Puppeteer , Captcha

Перевод Обход капч с помощью Headless Chrome

26.06.2020 10:22:09 | Автор: admin

Использование 2Captcha and Puppeteer для автоматического прохождения капч



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


image

Amazons CAPTCHA fallback


КАПЧА (от CAPTCHA англ. полностью автоматизированный и общедоступный тест Тьюринга для различения компьютеров и людей) задумана как врата, которые пропускают людей и отсеивают роботов (программ). Волнистые линии и слова сейчас уже не так распространены их заменила вторая версия реКАПЧИ от Google. Эта капча выдает вам зеленую галочку, если ваш коэффициент человечности оценивается как достаточно высокий.


image

Если же вы не дотянули до заданного порога человечности, то реКАПЧА прибегает к использованию подобной паззлу картинки, которая на удивление эффективна скорее в том, чтобы еще сильнее действовать на нервы, по сравнению с расшифровкой пары слов.


image

При том, что людей КАПЧИ раздражают, их можно было бы и потерпеть, если бы они хотя бы выполняли свою работу, однако, зачастую гораздо проще автоматизировать их распознавание, чем постоянно доказывать, что вы человек.


Как работает 2Captcha


2Captcha решает много разных типов капчи. Для взаимодействия с 2Captcha используются два адреса: на in.php присылается задача и в ответ Вы получаете captcha_id, на res.php вы по captcha_id получаете ответ, когда он будет готов.


2Captcha решает капчи в нескольких различных стилях с помощью двух, по большей части одних и тех же, конечных точек обработки запросов. Первый запрос передает данные, необходимые для решения капчи, и возвращает ID запроса. В случае капчи на основе изображения, данные будут изображением самой капчи, закодированным в 64base.


image

После получения ID запроса, вам нужно будет повторно отправлять его на сервер 2капчи, пока решение не будет готово.


image

С Recaptcha V2 история немного другая. Вы по-прежнему имеете дело с двухступенчатым процессом, что и представленный выше, но отправляете другие данные. Теперь вам нужно отправлять публичный ключ рекапчи (reCAPTCHA sitekey), который можно найти среди атрибутов элемента-контейнера капчи, независимо от того, был ли загружен
image

Получаемый ответ это токен, который нужно отправить вместе с формой и ввести в скрытое текстовое поле с идентификатором g-recaptcha-response. Скриншот ниже показывает, где оно находится, и я специально отключил CSS-свойство display: none, чтобы показать его на странице. Имея возможность редактировать это поле, вам будет проще вручную протестировать ответ 2Captcha, чтобы сократить количество переменных при тестовой интеграции.


image

Для капч, основанных на изображениях, результат доступен почти мгновенно. Для реКАПЧ версии 2 может потребоваться от 15 до 30 секунд.


Автоматизация через Puppeteer


Прежде чем беспокоиться о капче, нужно разобраться со всем прочим, но прежде чем мы сможем это сделать, нужно определиться с нашими инструментами. 3 причины, по которым в данном посте мы будем использовать Googles Chrome:


  1. В нем все очень легко автоматизируется через Puppeteer API.
  2. На нем можно работать как в headless-режиме, так и с графическим интерфейсом, что очень удобно и практично.
  3. Это самый распространенный в мире браузер, поэтому все имеющиеся на сайтах приемы анти-автоматизации имеют меньший шанс срабатывания (например, блокировка Selenium или PhantomJS)

Использование Puppeteer


Необязательно устанавливать хром, если не хочется, в Puppeteer есть все необходимое, в том числе Chromium. Но можете использовать и локально установленный Chrome, решать вам.


$ npm install puppeteer

Убедитесь, что все настроено, устроив предварительный тест. Для этого упражнения мы будем автоматизировать страницу регистрации Reddit, просто потому что это была первая страница с рекапчей, которая мне попалась.


const puppeteer = require('puppeteer');const chromeOptions = {  headless:false,  defaultViewport: null};(async function main() {  const browser = await puppeteer.launch(chromeOptions);  const page = await browser.newPage();  await page.goto('https://old.reddit.com/login');})()

В этом коде мы указываем два свойства конфигурации при запуске: headless: false, чтобы видеть то, что мы делаем, и defaultViewport: null, чтобы учесть неприятный визуальный глитч, при котором область просмотра не заполняет окно. Ни тот, ни другой не важны для работы браузера в headless-режиме, просто с ними удобней видеть и, что самое важное, делать скриншоты. Например, такой:


image

Это было просто! Теперь мы готовы перейти к следующему шагу автоматизировать регистрацию так, будто капчи никогда и не было. Вот где возможность включать/ выключать headless-режим будет полезна, ведь мы можем управлять браузером от лица человека, когда нужно. Сначала следует понять, как получить доступ к элементам на странице, которые нужно поменять. Запустите браузер и просмотрите открытую страницу через инструменты разработчика Google Chrome (горячая клавиша F12). Далее, найдите текстовые поля, которые нужно будет изменить. (горячие клавиши: +Shift+C на Mac и Ctrl+Shift+C на Windows) В случае с Reddit, нам нужно получить прямой доступ к полю ввода логина, двум полям ввода пароля и кнопке. Электронная почта необязательна, так что можем ее проигнорировать. Печать в текстовых полях через API библиотеки Puppeteer почти до смешного проста: вы просто передаете селектор, который идентифицирует элемент, и желаемую строку с помощью метода .type().


await page.type('#user_reg', 'some_username');await page.type('#passwd_reg', 'SuperStrongP@ssw0rd');await page.type('#passwd2_reg', 'SuperStrongP@ssw0rd');

Производить операции с кнопкой так же удобно, разве что у кнопки на странице Reddit нет связанного с ней идентификатора, так что нам нужен более сложный селектор. Если вы не знакомы с селекторами CSS, то для краткого ознакомления посмотрите Mozilla Developer Network.


await page.click('#register-form button[type=submit]');

Вот и всё! Протестируйте скрипт, чтобы удостовериться, что отправка логина работает. Из-за КАПЧИ, конечно, ничего не получится, но зато мы можем протестировать работу хуков.


image

Подождите-ка! Мы ведь даже не видим капчу, и консоль JavaScript жалуется на ошибки. Что здесь происходит? Во время автоматизации веб-страниц, помимо капч, есть множество других препятствий, которые могут встать у вас на пути. И одна из них настолько высокая скорость выполнения операций, что страница ломается. Когда браузеры автоматизированы, они работают во много-много раз быстрее, чем на то способен обычный человек, и зачастую это приводит к выполнению кода в таком порядке, в каком разработчики его не тестировали (это называется состоянием гонки или неопределённость параллелизма).


Страница Reddit страдает от состояния гонки, тогда как Google выводит реКАПЧУ после фокусировки на втором поле ввода пароля. То есть наш скрипт выполняется c такой скоростью, что смена фокуса происходит до того, как готов скрипт реКАПЧИ. Есть много решений этой проблемы, но самое простое добавить минимальную задержку, необходимую, чтобы миновать это состояние гонки. Мы могли бы добавить хуки и обработчики событий, чтобы убедиться, что мы действуем только после того, как рекапча загрузилась, но, похоже, самих разработчиков Reddit состояние гонки устраивает, так что нам нет необходимости умничать. Есть много способов организовать задержку, но в опциях Puppeteer, связанных с запуском браузера, есть параметр slowMo, который глобально задерживает все действия на заданное значение. Это очень грубый подход, так как замедляются вообще все действия Puppeteer, но начать с этого можно.


const chromeOptions = {  headless:false,  defaultViewport: null,  slowMo:10,};

После добавления этой опции мы видим, что капча снова работает. Ради забавного эксперимента, можно попробовать пройти капчу прямо сейчас и посмотреть что будет. Поскольку мы используем вариант Chromium с настройками по умолчанию, который запускается библиотекой Puppeteer, и мы управляем им через средства автоматизации, то реКАПЧА будет изо всех сил пытаться доказать, что мы не человек. Вам, вероятно, придется пройти несколько уровней проверки, даже если вы разберетесь со всеми картинками. Когда я тестировал это, мне пришлось пройти через 10 различных повторений, прежде чем я получил зеленую галочку.


К счастью, все это можно сделать намного легче.


Настройка 2Captcha


2Captcha требует API ключ, который выдается при регистрации. Вам также потребуется какое-то количество средств, потому что, не всё в жизни бесплатно. И просто ради смеха, конечно же, вам понадобиться пройти КАПЧУ.


`http://2captcha.com/res.php?key=${apiKey}&action=get&id=${reqId}`;

2Captchas API работает через двухступенчатый процесс, где вы отправляете данные КАПЧИ и получаете результат после возврата ID запроса. Поскольку мы имеем дело с reCaptcha v2, нам нужно будет отправить публичный ключ Reddit, о котором говорилось ранее. Нам также нужно убедиться, что мы выбрали метод userrecaptcha и отправляем URL страницы, на которой расположена реКАПЧА.


const formData = {  method: 'userrecaptcha',  key: apiKey, // your 2Captcha API Key  googlekey: '6LeTnxkTAAAAAN9QEuDZRpn90WwKk_R1TRW_g-JC',  pageurl: 'https://old.reddit.com/login',  json: 1};const response = await request.post('http://2captcha.com/in.php', {form: formData});const requestId = JSON.parse(response).request;

После того как вы сделали этот запрос и получили обратно идентификатор запроса, вам нужно запросить URL res.php, используя ваши ключ API и идентификатор запроса, чтобы получить ответ.


<a href="`http://2captcha.com/res.php?key=${apiKey}&action=get&id=${reqId}`;"></a>

Если ваша капча не готова, вы получите ответ CAPTCHA_NOT_READY, что указывает на то, что нужно попробовать ещё раз через секунду или две. Когда капча готова, ответом будут данные, соответствующие выбранному методу отправки. Для капчи, основанной на изображении это будет решение. Для reCaptcha V2 данные, которые нужно отправить в полях формы.


Для reCaptcha V2 время нахождения решения может различаться минимум 15 секунд, максимум 45. Ниже показан пример механизма опроса, но в этот раз простого запроса по URL, который может быть интегрирован в ваше приложение так, как вам будет удобно.


async function pollForRequestResults(  key,   id,   retries = 30,   interval = 1500,   delay = 15000) {  await timeout(delay);  return poll({    taskFn: requestCaptchaResults(key, id),    interval,    retries  });}function requestCaptchaResults(apiKey, requestId) {  const url = `http://2captcha.com/res.php?key=${apiKey}&action=get&id=${requestId}&json=1`;  return async function() {    return new Promise(async function(resolve, reject){      const rawResponse = await request.get(url);      const resp = JSON.parse(rawResponse);      if (resp.status === 0) return reject(resp.request);      resolve(resp.request);    });  }}const timeout = millis => new Promise(resolve => setTimeout(resolve, millis))

Как только у вас есть данные ответа, вам нужно вставить результат в скрытое текстовое поле g-recaptcha-response в форме регистрации Reddit. Это не так просто, как использование метода .type() библиотеки Puppeteer, так как элемент невидим и не может получить фокус ввода. Можно сделать его видимым и затем использовать .type(), или же использовать JavaScript, чтобы внедрить значение на страницу. Для добавления JavaScript на страницу, при использовании Puppeteer, у нас есть метод .evaluate(), который принимает либо функцию, либо строку (при передаче функции она просто преобразуется в строку с помощью метода .toString()), и запускает ее в контексте страницы.


const response = await pollForRequestResults(apiKey, requestId);const js = `document.getElementById("g-recaptcha-response").innerHTML="${response}";`await page.evaluate(js);

После внедрения этого значения у нас все готово для завершения регистрации. Это действительно так просто.


Полный скрипт доступен ниже, если вам захочется поэкспериментировать с Puppeteer и/или 2Captcha.


Что вы теперь можете сделать?


Этот пост был написан ради двух вещей:


  1. Показать вам, насколько капчи нехорошая вещь

и


  1. Показать вам, что капчам нет необходимости блокировать вас

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

Подробнее..

Распознавание символов

10.02.2021 12:04:06 | Автор: admin

Работа с изображениями одна из самых распространенных задач в машинном обучении. Мы покажем пример обработки изображения, получение матриц (тензоров) чисел, подготовку данных обучающего множества, пример архитектуры нейронной сети.

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

Постановка задачи: имеются цветные изображения букв и цифр (CAPTCHA). Необходимо распознать буквы и цифры, находящиеся на изображениях. Последовательность решения задачи:

анализ изображений;

подготовка данных;

генерация данных;

тренировка нейронной сети, предсказание ответов.

Рис.1 пример изображений (CAPTCHA)Рис.1 пример изображений (CAPTCHA)

Проводим предварительный анализ 100 изображений в формате .png. Изображения состоят из 29 уникальных символов 12345789абвгдежзиклмнопрстуфя. Символы могут быть повернуты на определенный угол (от -1 +15), могут быть смещены по горизонтали и вертикали, символы могут накладываться друг на друга. Символы представлены в цвете, все три символа могут иметь различные цвета. На фоне имеются более мелкие символы в светлых оттенках (шум на изображении). Для анализа, вывода и обработки изображений понадобятся библиотеки языка python 3 opencv, matplotlib, pillow. Определим положение символов на изображении путем подбора разделяющих линий:

import cv2 # импортируем библиотеку для раб.с графикойimage = cv2.imread('.\Captcha.png') # читаем изоб. Результат numpy array# рисуем линию (img, (x1, y1), (x2, y2), (255, 255, 255), 4)      # изображение на котором рисуем, точка начала линии, точка конца линии,     # цвет линии цветовая модель BGR, толщина линии.image = cv2.line(image, (14, 0), (14, 50), (0, 0, 255), 1)    # для вывода можно задать функцию, параметры прочитанное иозбр., имя окнаdef view_image(image, name_wind='default'):    cv2.namedWindow(name_wind, cv2.WINDOW_NORMAL) # # создаем окно вывода    cv2.imshow(name_wind, image) # # в окно передаем изображение image    cv2.waitKey(0) # # ждем нажатия любой клавиши, 0 нет таймера.    cv2.destroyAllWindows() # # уничтожение (закрытие) всех оконview_image(image)  # # вызов функции вывода с передачей прочитанного файла
Рис. 2 пример определение диапазона символаРис. 2 пример определение диапазона символа

Так же для вывода удобно использовать matplotlib, особенность библиотеки в работе с моделью RGB: передав данные модели BGR вывод будет отличаться, интенсивный красный станет интенсивным синим и наоборот, зеленый не изменится. Для корректного вывода в matplotlib (если цвета имеют значения) необходимо поменять местами матрицы цветов синего и красного.

Перебором изображений с расстановкой разделяющих линий, определим место положение символов. На изображениях каждый символ находится в своем диапазоне. Для всех символов верхняя граница находится на 3 строке, нижняя на 47. Вертикальные границы (за которые символы не выходят): для первого символа 1444 колонка, для второго символа: 3262 колонка, для третьего символа: 48 72. Изображение при чтении библиотекой opencv представляется в виде тензора numpy array, размерностью (50, 100, 3). Изображение представлено в 3 массивах, состоящих из 50 строк и 100 столбцов. Каждый из трех массивов отвечает за свой цвет BGR (blue синий, green зеленый, red красный), каждый из 3-х массивов находится в диапазоне от 0-255.

Рис.3 Цветовая модель RGBРис.3 Цветовая модель RGB

Данные такого вида не совсем удобны для дальнейшей обработки цветов. Так как символ имеет не четкий цвет, а сумму цветов, к краям более светлый тон, сумма цветов меняет свои значения. Для выделения символа определенного цвета необходимо будет указывать диапазоны для трех цветов B(n-m) G(k-l) R(y-z). Вместо этого проще представить изображение в другой цветовой модели HSV (Hue, Saturation, Value тон, насыщенность, яркость). В библиотеке opencv единицы измерения Heu 0 179, S 0 255, V 0 255. При данной цветовой модели достаточно указать сектор цвета Heu и для всех символов указать постоянные значения S 10 255, V 0 234, отсекая тем самым фон и шумовые изображения, представленные в более светлых тонах.

Рис.4 Цветовые модели RGB (BGR) и HSVРис.4 Цветовые модели RGB (BGR) и HSV
# # преобразование из BGR цветовой модели в HSVimage = cv2.imread('.\captcha_png')hsv = cv2.cvtColor(image, cv2.COLOR_BGR2HSV)

HSV представляет собой тензор (50, 100, 3) (3 матрицы numpy array размерностью (50, 100), 50 одномерных массивов, содержащие 100 значений). Индексы матриц [:, :, 0] Hue, [:, :, 1] Saturation, [:, :, 2] Value.

Исходное изображениеИсходное изображение

Отобразим матрицы в градиентах серого (0 черный 255 белый).

[:,:, 0] Тон, отсутствует белый т.к. диапазон до 179, приблизительный разброс цветов 160 179 и 0~30 красный цвет, 60 ~ 100 зеленый, 110 ~ 150 синий. На первом изображении цифра 9 более светлая т.к. оттенок красного попал в диапазон от 160, на втором изображении буква м черная т.к. оттенок попал в диапазон 0~30

[:,:, 1] Насыщенность, фон и шум на изображении представлен в светлых, пастельных тонах значения 0~10, символы имеют более выраженный цвет >10

[:,:, 2] Яркость, изображение светлое следовательно яркость фона 240 ~ 255, символы более темнее имеют значения < 240.

На основании матриц S и V (насыщенность и яркость), можно получить фильтры для отделения символов от фона и от шума в матрице Hue (тон).

mask_S = image[:, :, 1]&lt; 10; mask_V = image[:, :, 1] > 240

Результат: две матрицы булевых значений (50, 100) [[True, False, ..,], , [..]]. Значению истина соответствует фон и шум. Если применить эти фильтры к матрице тон (Hue) и придать значениям, соответствующим истина 255, матрица будет содержать значения 0-179, 255 удобно для дальнейшей фильтрации (дальнейшая работа только с матрицей тон, Hue).

# значение 255 не попадает в диапазон матрицы 0 - 179image[:, :, 0][mask_S] = 255 ; image[:, :, 0][mask_V] = 255
Рис.5 Результат фон и часть шума имеют значения 255Рис.5 Результат фон и часть шума имеют значения 255

Следующий этап отделение символов друг от друга. Задаем диапазон, в котором находится символ.

Рис.6 Разделение символов по определенном диапазонамРис.6 Разделение символов по определенном диапазонам
img_char1 = image[3: 47, 14: 44, 0].copy()img_char2 = image[3: 47, 32: 62, 0].copy()img_char3 = image[3: 47, 48: 78, 0].copy()

Для выделения каждого символа и удаления части соседнего символа, отсекаем область нахождения соседнего символа, находим количество значений соответствующих тонам в матрице (максимальное количество значений имеет число 255 фон (500 800 значений в матрице), следующее по количеству значений будет основной тон символа, единичные значения будет иметь оставшийся не отфильтрованный шум). На основании основного тона символа находим диапазон оттенков N -10, N + 10.

Рис.7 Определение областей 1 и 3 символа, где нет данных 2-го символаРис.7 Определение областей 1 и 3 символа, где нет данных 2-го символа

Оставляем часть 1 и 3 символа не попадающее в область 2 символа. Получаем матрицы, отвечающие за тон.

# преобразуем матрицу в массив значений, посчитаем количество значенийval_count_1 = img_char1[3: 47, 14: 32, 0].copy().reshape(-1) val_color_hue_1 = pd.Series(val_count_1).value_counts()# val_color_hue_1 ->255  741, 106  11, 104  11, 20  1, 99  1.val_color_hue_1 = pd.Series(val_count_1).value_counts().index[1] # числа тонов являются индексами, устанавливаем сектор тона Hue -10, +10.val_color_char_hue_1_min = val_base_hue_1  10 = 106 - 10 = 96val_color_char_hue_1_max = val_base_hue_1 + 10 = 106+ 10 = 116

На матрицы Hue для символов 1, 3 накладываем фильтр, значения где тон попадает в указанный диапазон равны 0, иначе 255.

mask_char1 = (img_char1> 96) &amp; (img_char1&lt;116)img_char1[~mask_char1] = 255 # где не символ (полная область определения) img_char1[mask_char1] = 0 # где символ
Рис.8 Отображение результата в виде pandas dataframeРис.8 Отображение результата в виде pandas dataframe

Необходимо привести значения к 0 и 1 и инвертировать матрицу.

img_char1[img_char1 == 0] = 1; img_char1[img_char1 == 255] = 0

Из 2-го символа, находящегося в центре изображения, удаляем части 1 и 3 символа путем определения частей матриц, пересекающих 2-й символ и приравнивании к 255 тех значений матрицы 2, где матрицы 1 и 3 не равны нулю.

Рис.9 Удаление из 2-го символа данных 1 и 3-го символовРис.9 Удаление из 2-го символа данных 1 и 3-го символов

Проводим аналогичные преобразования матрицы символа 2. Результат матрицы символов 1, 2, 3 со значениями 0, 1. При выводе изображения имеют пропуски, остатки шума. Проводим фильтрацию, дополнительное преобразование инструментами opencv, указываем ядро (окно матрицы) которое проходит по матрице символов и при нахождении рядом заполненных пикселей и пропусков, заполняет пропуски

kernel = np.ones((3, 3), np.uint8)closing = cv2.morphologyEx(np_matrix, cv2.MORPH_CLOSE, kernel)
Рис.10 Корректировка данных, заполнение пропусковРис.10 Корректировка данных, заполнение пропусков

Определяем центры символов, перемещаем символы в центр массива добавляя и удаляя строки, столбцы, заполненные нулями.

Рис.11 Расположение символов в средине матрицыРис.11 Расположение символов в средине матрицы

Разработан алгоритм выделения символов из изображения. Для дальнейшего определения значения символа построим и обучим нейронную сеть. Для обучения нейронной сети скачиваем ~100 экземпляров картинок выделяем из них символы, размечаем картинки вручную. Получаем 300 экземпляров размеченных данных (массивы 4430 содержащие числа 0 и 1). Этого количества данных недостаточно. Определяем и скачиваем шрифт, которым отображаются символы на картинках. Воспользовавшись библиотекой pillow языка python, размещаем символы шрифта на изображении 4430, задаем смещение и поворот символов случайным выбором из заданных значений, преобразуем в массив nympy array. Формируем выборку данных из сгенерированных данных и данных размеченных вручную.

shift_x = [1, 1, -1, -1, -2, 2, 0, 0, 0]shift_y = [1, 1, -1, -1, -2, 2, 0, 0, 0]rotor_char = [15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0, -1]char = '12345789абвгдежзиклмнопрстуфя'# в цикле генерируем данные  ~10_000  60_000shift _x_r = random.choice(shift_x)shift _y_r = random.choice(shift_y)rotor_r = random.choice(rotor_char)char_r = random.choice(char)
Рис.13 Пример подобранного текста и размещение символов в матрицахРис.13 Пример подобранного текста и размещение символов в матрицах
train_x = []train_x.append(char)train_x = np.array(train_x)train_x = train_x.reshape(train_x.shape[0], train_x[1], train_x[2], 1)

Тренировочный набор данных представляет собой тензор размерностью (50000, 44, 30, 1), дополнительная размерность (1) для нейронной сети.

Каждому экземпляру тренировочной выборки соответствует экземпляр из массива ответов: char_y = [0, 4, , 29] 50_000 (цифры 0-29 ключи словаря символов

char = '12345789абвгдежзиклмнопрстуфя' # 29 позицийdict_char = {char[i]: i for i in range(len(char))}dict_char_reverse = {i[1]: i[0] for i in dict_char.items()}

Приведем данные предсказания к виду унитарный код (one-hot encoding). Преобразуем численные значения в массив, имеющий длину 29. Массив состоит из нулей и одной единицы. Позиция единицы соответствует кодируемому символу. Например, буква а будет иметь вид 000000000100000000000000000000.

Img_y = utils.to_categorical(Img_y)# пример 1 -> (array( [1, 0, 0, 0, , 0, 0],  dtype=float32)# пример 2 -> (array( [0, 1, 0, 0, , 0, 0],  dtype=float32)

Разбиваем полученные данные на тренировочный набор и тестовый.

x_train, x_test, y_train, y_test = sklearn.train_test_split(                             out_train_x_rsh, out_train_y_sh,                              test_size=0.1, shuffle=True)

Для распознавания символов необходимо построить нейронную сеть, так как данные похожи на учебный набор mnist (рукописные цифры 2828) на ресурсе kaggle находим примеры архитектуры нейронной сети дающие хороший результат. Архитектура нейронной сети:

# Определим простую модельImport tensorflow as tfdef model_detection():    model=tf.keras.models.Sequential([        tf.keras.layers.Conv2D(input_shape=(44,30, 1), filters=32,                 kernel_size=(5, 5), padding='same', activation='relu'),        tf.keras.layers.Conv2D( filters=32, kernel_size=(5, 5),                                padding='same', activation='relu'),        tf.keras.layers.MaxPool2D(pool_size=(2, 2)),        tf.keras.layers.Dropout(0.2),        tf.keras.layers.Conv2D( filters=64, kernel_size=(3, 3),                 padding='same', activation='relu'),        tf.keras.layers.Conv2D( filters=64, kernel_size=(3, 3),                 padding='same', activation='relu'),        tf.keras.layers.MaxPool2D(pool_size=(2, 2), strides=(2, 2)),        tf.keras.layers.Dropout(0.2),        tf.keras.layers.Flatten(),        tf.keras.layers.Dense(256, activation='relu'),        tf.keras.layers.Dropout(0.2),        tf.keras.layers.Dense(29, activation=tf.nn.softmax)])    model.compile(optimizer='adam', loss='categorical_crossentropy',                   metrics=['accuracy'])    returnmodel# создадим экземпляр моделиmodel = model_detection()

Зададим параметр, при улучшении которого модель сохраняется (valaccuracy).

checkpoint = ModelCheckpoint('captcha_1.hdf5', monitor='val_accuracy',                                        save_best_only=True, verbose=1)model.fit(x_train, y_train, epochs=5, validation_data=(x_test, y_test),           verbose=1, callbacks=[checkpoint])

После тренировки сохраняются веса модели с наилучшими показателями valaccuracy, доли верных ответов. Дальнейшая работа программы: на вход подается изображение, которое обрабатывается ранее описанным алгоритмом. Результат обработки numpy array (массив). Далее инициализируется модель, загружаются веса ранее обученной модели. На вход модели подают данные (1, 2, 3 символ). В результате модель выдает вероятность того или иного символа. Приняв за верный ответ позицию с наибольшей вероятностью из словаря значение символ получаем символьное предсказание модели.

model2 = model_detection() # инициализируемmodel2.load_weights('captcha_1.hdf5') # загружаем весаprediction_ch_1 = model2.predict(char_1) # массив 29 значений вероятностей# позиция на которой наибольшая вероятность, ключ к словарю ответовprediction_ch_1 = np.argmax(prediction_ch_1, axis=1)# из словаря ключ число, значение цифра или буква получаем ответdict_char_reverse[prediction_ch_1]

Данный алгоритм обрабатывает цветные изображения, на которых находятся символы букв и цифр, результат распознавания символов нейронной сетью 95% (точность), распознавание каптчи 82% (точность). На примере разбора алгоритма распознавания символов можно заметить, что основную долю разработки занимает подготовка, обработка и генерация данных. Выбор архитектуры и обучение нейронной сети является важней частью задачи, но не самой затратной по времени. Вариантов решения задачи распознавания цифр, букв, изображений предметов и т.п. множество, в данной статье приведен лишь один из примеров решения, показаны этапы решения, трудности, с которыми можно столкнуться в результате работы и примеры их преодоления. А как Вы работаете с каптчами?

Подробнее..

Перевод Однажды Microsoft забанила всю мою страну за читерство

19.03.2021 12:22:16 | Автор: admin

Об игре


Club Bing это набор игр, в которые можно было играть в 2007-2012 годах. Все игры были связаны со словами, в них нужно было играть онлайн, чтобы зарабатывать очки, которые можно было тратить в онлайн-магазине для покупки призов. Одна из игр называлась Chicktionary. Цель игры: использовать 7 слов, чтобы составить как можно больше слов.


Буквы, которые можно использовать, указаны внизу, а слова, которые нужно составить это маленькие яйца сверху. Всегда есть одно слово из семи букв.

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

  • 123 Main St. Apt #1, Anywhere, YZ, USA
  • 123 Main St. Apt #2, Anywhere, YZ, USA
  • 123 Main St. Apt #3, Anywhere, YZ, USA

Очевидно, наилучшим соотношением стоимости к очкам обладал контроллер XBox, поэтому можно было оставить компьютер набирать очки на нескольких аккаунтах, а затем тратить все очки на покупку контроллеров. На одном форуме был пост с фотографией парня, получившего почтой примерно 100 контроллеров за один день. Он сразу же выложил их на Ebay и продал. Другими призами были:

  • Телескопы
  • Проигрыватель
  • Xbox
  • Наручные часы Michael Kors
  • Одежда North Face
  • Надувной каяк

На всех призах была маркировка Bing.

Microsoft выпустила эту игру для рекламы своего поискового движка Bing. Каждый раз, когда ты вводил ответ, браузер выполнял его поиск в отдельном фрейме. Вероятно, это позволяло убедить людей больше пользоваться Bing, а также увеличивало количество пользователей, якобы использующих Bing. А этот показатель позволял Microsoft требовать больше денег у рекламодателей, желавших появляться в результатах поиска Bing. Я подсчитал, что все играющие в Chicktionary скриптеры составляли 2-4% от всех поисковых запросов Bing. К тому же я провёл небольшие вычисления, сравнив доход и ежемесячное количество поисковых запросов Google и Bing, и выяснил, что Microsoft получала от игры довольно неплохую прибыль. Иллюзия того, что Bing стал популярнее, вероятно, принесла больше рекламных долларов, чем стоили призы.

Скриптинг


Существовало несколько скриптов для автоматической игры, часто в них использовался AutoHotKey. Я написал собственный скрипт на VB.Net, у него был встроенный браузер. Я назвал его Chicken в честь Chicktionary, а ещё потому, что в моей стране был забавный телевизионный скетч о курице.


Так как игра была написана на Flash, было не очень легко взаимодействовать с элементами в DOM, поэтому игровой процесс состоял из сочетания:

  • Взаимодействий DOM
  • browser.Go(<URL>)
  • Скриншотов и поиска пикселей
  • Windows API для имитации ввода и нажатий мыши

В Microsoft работают не дураки. Они знали о существовании хакеров. Они отлично справлялись с выявлением пользователей, слишком быстро решающих головоломки, и обнуляли их призы. Кроме того, они усовершенствовали распознавание дубликатов адресов доставки. И, разумеется, они использовали капчу.

Captcha


Знаете, такие кривые буквы, которые нужно вводить, чтобы доказать, что ты человек:


Можно подумать, что они бы полностью препятствовали автоматизации, но это не так. Существует больше десятка онлайн-сервисов решения captcha. Их цена составляет примерно 1 доллар за 500 решённых капч. Club Bing отображал капчу через каждые четыре игры. За каждую игру ты получал 20 билетов (если ввести все слова правильно), а Xbox стоил 55000 билетов.

(55000 билетов / Xbox) * (1 игра / 20 билетов) * (1 капча/ 4 игры) * (1 доллар / 500 капч) = 1,375 доллара за Xbox

Довольно выгодная сделка. Кроме того, для этого понадобится 55 дней, потому что в день можно зарабатывать только по 1000 билетов. Но можно завести несколько разных аккаунтов, чтобы через 55 дней получить несколько консолей Xbox. С более дешёвыми призами всё было ещё быстрее: видеоигра стоила 5000 билетов.

Существование сервисов для решения капч объясняет, почему читерство было настолько популярным: эти сервисы имеют партнёрские программы. Скрипт, выполнявший решение, отправляет вместе с запросом партнёрский код, благодаря чему когда пользователь платит, партнёр получает часть денег. Поэтому у скриптописателей была мотивация распространять свои скрипты и писать самые качественные.

Капча с котами


Примерно в 2010 году Microsoft заменила алгоритм капчи с кривых букв на Asirra. Asirra выглядит вот так:


На каждом из этих изображений есть кошка или собака. Чтобы решить задачу, нужно правильно определить 12 из 12 кошек или 11 из 12 дважды подряд.

Когда Club Bing перешёл на эту капчу, всё сообщество читеров Club Bing приостановило свою деятельность. Я приступил к работе.

Первым делом я попробовал отправлять сервису решения капч кошек и собак. В 2010 году эти сервисы не обладали мощными средствами распознавания изображений. Это были просто люди из Бангладеша, отвечавшие на ваши запросы. Я попытался прикинуть, сколько они зарабатывали:

(2000 рабочих часов / год) * (1 капча/ 5 секунд) * (1 доллар / 500 капч) = 2880 долларов / год при полной занятости.

Вероятно, половина оставалась у владельца веб-сайта, потому что, знаете ли, капитализм, то есть работники зарабатывали примерно столько же, сколько администратор на ресепшене, если верить случайно выбранному сайту.

Я захотел узнать, смогут ли они решать Asirra. Вот изображение, которое я отправил:


Используемый мной сервис de-captcher вернул такой ответ:

cat or dog?

Строго говоря, он был верным, но меня не устраивал. Я отправил картинку ещё несколько раз:

imageimage

Чтобы получить полезный ответ, потребовалось четыре попытки:

dog

Это не предвещало ничего хорошего. Во-первых, мне нужно было 12 правильных ответов. Если предположить, что каждый раз на поиск работника в Бангладеше, способного сделать всё правильно, потребуется 4 попытки, то это составит 48 запроса. Цена Xbox только увеличилась до 66 долларов! А если они ошибутся хотя бы один раз, мне придётся удвоить эту сумму. Немыслимо! Мне нужно было решение получше.

Почему бы просто не использовать глубокое обучение?


Не забывайте, что дело происходило в 2010 году. Глубокое обучение (deep learning) тогда было развито совсем не так хорошо, как сейчас. В этой статье Стэнфордского университета говорится, что авторам удавалось использовать машинное обучение для правильного решения головоломок всего примерно в 10% случаев. Совершенно неприемлемо! (В основном они просто замечали зелёный цвет, потому что собаки лежат на лужайке с большей вероятностью, чем кошки.)

Также Microsoft использовала схему token-bucket, которая временно блокировала пользователя, если он слишком много раз подряд ошибается. Хотя token-bucket не выполнялась на тестовом сервера Asirra, её применяли в Club Bing.

Harvest: новая надежда


Исследовательский отдел Microsoft создал веб-сайт для демонстрации новой технологии Asirra.
На нём была тестовая площадка, на которой можно было проверить работу сервиса, она сообщала, если вы решали всё правильно. Снова взглянем на изображение:


Видите эту маленькую кнопку Adopt me (Приюти меня)? Она там есть, потому что Asirra была результатом партнёрства с petfinder.com. Petfinder это сервис поиска новых хозяев для домашних питомцев, у которого есть большие списки животных. При нажатии на кнопку Adopt me браузер переходит к профилю этого животного. Разумеется, в профиле указан его вид: кошка или собака.

Повторюсь, в Microsoft работают не дураки. Они знали, что кто-то попробует нажимать на Adopt me под каждым изображением и получить правильный ответ. Поэтому они делали недействительными саму задачу капчи и все ссылки на профили после первого нажатия на кнопку Adopt me. То есть можно было получить только один ответ.

Я решил написать программу, которая сделает это много раз и сопоставит каждое изображение с числом:

  • 0: неизвестно
  • 1: собака
  • 2: кошка

Я назвал её Harvest (уборка урожая), чтобы продолжить тему куриц и сельского хозяйства.

Каждая попытка занимала довольно мало времени, но я не знал, сколько животных мне нужно узнать. На веб-сайте Asirra утверждалось, что их 3,1 миллиона. Но на самом ли деле это так?

Обратный парадокс дней рождения


Многим людям известен парадокс дней рождения: несмотря на то, что в году 365 дней, достаточно собрать в одной комнате всего 22 человека, чтобы получить вероятность 50 на 50 того, что у двух из них день рождения совпадает. Если d=365, а n=22, то вероятность можно посчитать так:


Обратность заключается в том, что если мы знаем, что 22 человека в комнате дают вероятность 50 на 50 нахождения двух людей с одинаковым днём рождения, то можно перевернуть уравнение и вычислить, сколько дней в году.

Аналогично если я буду запрашивать серверы Asirra и отслеживать каждое встреченное изображение, то как долго мне надо будет ждать повтора? Я провёл примерно такой эксперимент (но на VB.Net, а не на псевдокоде python):

def trial():   images_seen = []   while True:       p = fetch_puzzle()       for image in p.images():           if image in images_seen:               break           images_seen.append(image)   return len(images_seen)

В каждой попытке я запрашивал задачи, пока не получу в попытке повтор кошки или собаки. Я проводил множество попыток и отслеживал, сколько изображений получалось до первого повтора. Затем я брал медиану всех этих попыток, что позволяло мне определить, сколько животных нужно увидеть, прежде чем вероятность повтора составит 50 на 50. Далее я вычислил в обратном порядке представленное выше уравнение, чтобы получить количество изображений. И в самом деле, мой ответ оказался достаточно близок к 3,1 миллиона.

Распределённая уборка урожая


Я записал свой скрипт на USB-флэшки и раздал их друзьям. Также я написал программу слияния для объединения баз данных. Через каждые один-два дня мои друзья возвращали мне свои флэшки, я запускал Combine (комбайн, снова сельскохозяйственная тема) и записывал базу на все флэшки, чтобы мои уборщики урожая не повторяли свою работу. Уборщики нажимали Adopt me только на неизвестных изображениях, поэтому поддержание актуальности распределённых баз данных позволяло избегать повторной работы.

Можно ли ускориться?


Спустя 2-3 недели я собрал примерно 1,5 миллиона изображений. Я приближался к этапу, когда задачу иногда можно было почти решить из базы данных. Однако в базе данных присутствовали никогда не заполняемые дыры, потому что ссылка по кнопке Adopt me была сломана. Возможно, это животное уже забрали? Я добавил в базу данных ещё один результат:

  • 0: неизвестно
  • 1: кошка
  • 2: собака
  • 3: нерабочая ссылка

Но есть и другой способ получить правильный ответ: отгадать!

Asirra давала знать, если пользователь решил задачу правильно. Я погонял код какое-то время и замерил следующие показания:

  • adopt_time: сколько времени нужно для нажатия на ссылку Adopt me, загрузку petfinder.com и получить ответ кошка/собака.
  • adopt_success_rate: вероятность того, что при нажатии на Adopt me мы получим ответ, а не сломанную ссылку.
  • guess_time: сколько времени нужно на то, чтобы отправить случайную догадку и узнать, правильно ли решена задача. (На это требовалось меньше времени, чем на загрузку petfinder.com.)

Предположив, что соотношение кошек к собакам 50 на 50 (на самом деле оно было ближе к 40 на 60, ну да ладно), я мог вывести уравнение того, сколько животных я узнаю за секунду при помощи adopt me:

adopt_learning_rate = 1 / adopt_time * adopt_success_rate

Также можно было вычислить скорость узнавания при догадках. Если количество неизвестных животных равно n, то вероятность угадать правильно равна 1 к 2n. Но если я угадаю их правильно, то узнаю все n животных:

guess_learning_rate = n / guess_time * (1 / 2**n)

Приравняв эти два значения и вычислив n, я смог выяснить, что если знаю больше, чем 7 из 12 животных, то смогу просто угадывать остальных, и это будет эффективнее, чем adopt me. Я поместил этот код в уборщик урожая, и пару недель спустя у меня и друзей была достаточная для работы база данных.

Cats Be Gone: сервер решений


Microsoft Research проделал хорошую работу, но допустил пару ошибок в протоколе. Во-первых, они не ограничили частоту запросов к сервису. Благодаря этому и стала возможной уборка урожая. Во-вторых, они неправильно реализовали proofs-of-work.

В процессе решения капчи есть три стороны:

  1. Поставщик captcha (например, Microsoft Asirra или Google reCaptcha)
  2. Сервер captcha (например, Club Bing или другой веб-сервер)
  3. Пользователь captcha (например, игроки в Club Bing)

Один из способов возможной реализации этой схемы: сервер запрашивает у поставщика задачу и ответ. Затем сервер отправляет задачу пользователю, пользователь решает её, и сервер подтверждает решение.


Эта идея плоха по нескольким причинам:

  1. Теперь вся обработка производится на сервере. Что если сервер реализует её неправильно?
  2. Если Asirra когда-нибудь захочет изменить протокол, то каждому серверу придётся обновлять своей веб-сайт.
  3. Сервер-имитатор может стать самым эффективным уборщиком урожая.

Вот как это работало на самом деле (на этот раз для удобства чтения Asirra размещён посередине):


Теперь серверу не нужно знать подробностей работы системы. У него даже нет ответа! Но здесь Microsoft совершила ошибку:

  • В Club Bing было ограничение по частоте, поэтому нельзя было совершать слишком много ошибок подряд, но такого ограничения не было у Asirra.
  • Отсутствовала проверка токена по IP-адресу.

Поэтому я с лёгкостью смог создать веб-сайт cats-be-gone.kicks-ass.org, передававший действительные токены по HTTP. Вот так:


(Хотя IP-адреса токенов не проверялись, проверялась их метка времени. Токены были действительны только примерно один час. Сервер Cats Be Gone (Кошки, брысь) на самом деле генерировал их заранее, и всегда имел наготове 20 токенов, чтобы они были под рукой, когда потребуются.)

Мы с друзьями успешно попользовались какое-то время этим сервером, и в процессе сбора сервером новых ответов ситуация становилась всё лучше.

Превращаем эту систему в бизнес (доход за всё время: 0 долларов)


Поговорив с друзьями, я подумал: Стоит открыть сервер для всех и сделать из него бизнес! Люди уже привыкли к сервисам платы за капчи, поэтому я решил, что они будут вместо этого платить мне. Я решил брать по 1 доллару за 200 решений, это больше, чем стандартная ставка 1 доллар/500, но у меня не было конкурентов. Я выложил клиент на один из самых популярных форумов для подобных вещей и открыл Google Store для приёма платежей.

На форумах много болтали о том, что это какой-то способ обхода, а не солвер, поэтому когда дойдёт время до покупки приза, полученные билеты будут деактивированы. В прошлом уже были проблемы с программами обхода капчи. Люди имели полное право на подозрения. Поэтому я на какое-то время сделал сервис бесплатным. А после того, как он начал доказывать свою действенность при отсутствии альтернатив, у меня появились клиенты и 50 долларов на потенциальных продажах. Ого, да я теперь богач! Ага, точно! Даже бангладешцы выгоднее тратили своё время.

Я обещал, что не буду обрабатывать оплату, пока не будет потрачено 10% платежа, и что верну деньги неудовлетворённым покупателям. Однако всего спустя неделю я отменил все заказы, потому что защита Microsoft полностью уничтожила мою идею.

Империя Microsoft наносит ответный удар


Всё это время Microsoft пыталась использовать различные способы, чтобы победить моё читерство. Первым делом они попробовали переименовать все изображения. Это оказалось полной катастрофой и мне пришлось начинать всё сначала, потому что единственная привязка к кошке или собаке была по имени файла!

Да не, шучу, конечно. Я уже скачал все изображения. 3,1 миллиона изображений по 1 МБ каждый это всего 3,1 терабайта. Даже в те времена 3 терабайта были вполне доступны. Это на меня никак не повлияло. Я знал, что они могут попробовать что-то подобное, поэтому написал уборщик-скачиватель.

Ещё они попытались изменять изображения. Случайным образом выбирались 10-20 пикселей изображения и менялся их цвет. Этого было бы более чем достаточно для поломки любого криптографического хэша, который бы я мог использовать, например, сопоставление SHA1(image), -> cat/dog. Но я его не использовал. Я пользовался MinHash.

Хэширование изображений, v1: MinHash


MinHash очень прост: выбираем десять пикселей изображения и выполняем конкатенацию их значений. Вот и всё. Пока вы выбираете одни и те же десять пикселей, результат будет постоянным.


Если Microsoft изменит пару пикселей, то это особо ни на что не повлияет. Каковы шансы, что они совпадут? А если и совпадут, то это, вероятно, будет всего 1 из 12 изображений, поэтому я могу просто попробовать угадать ответ для него. В худшем случае придётся получить ещё одну задачу и попробовать снова.

И это сработало. Я также сделал так, чтобы сервер обновлялся самостоятельно при правильной догадке и база данных со временем пополнялась.

Microsoft побеждает Cats Be Gone


Microsoft наконец ограничил частоту запросов Asirra, поэтому один сервер Cats-Be-Gone больше не мог создавать токены для всех. И они начали сопоставлять токены с IP-адресами, поэтому токены сервера Cats-Be-Gone потеряли свою ценность. Хуже всего то, что они удалили 3,1 миллиона изображений с petfinder.com и создали совершенно новый набор.

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

Краудсорсинг


Я знал, что некоторые из пользователей захотят отвечать на капчи самостоятельно, поэтому настроил клиент таким образом, чтобы он запрашивал у сервера ответы, а затем просил пользователя просто ответить на неизвестные картинки. Затем клиент должен был отправлять результаты на сервер для обновления базы данных.

Чтобы усложнить злоумышленникам задачу заполнения моей базы данных мусором, я зашифровал всю передачу данных жёстко прописанным ключом и пропускал релизы клиента через обфускатор .Net, чтобы его было сложнее найти. Это бы помешало устраивать хаос в базе данных только обычным пользователям, но такой защиты было вполне достаточно. Единственными, кто бы разобрался, как получить доступ к базе данных при помощи реверс-инжиниринга, были те, кто хотел бы скачать всю базу целиком. Я решил, что меня это не волнует и пусть будет так.

Кроме того, поскольку у меня не было изображений, всё хэширование выполнялось в коде клиента, потому что не хотел отправлять 12 изображений на сервер при каждом запросе. И я знал, что MinHash недостаточно надёжен, поэтому решил перейти на pHash.

Хэширование изображений, v2: pHash


pHash великолепен. Его принцип примерно таков:

  1. Преобразуем изображение в оттенки серого.
  2. Применяем гауссово размытие.
  3. Ужимаем до одинакового квадратного размера.
  4. Применяем к нему дискретное косинусное преобразование.
  5. Сохраняем только 64 наиболее значимые значения.
  6. Для каждого значения записываем 1, если оно больше медианы, в противном случае 0.
  7. Теперь у нас есть 64-битное число!

Для этого у pHash есть библиотека, и естественно, она написана не на VB.Net, поэтому я реализовал её самостоятельно. Сегодня можно было бы просто использовать другой язык, в котором есть библиотека, но я объясню, как работает pHash, потому что это довольно круто.

Преобразование в оттенки серого


Тут всё довольно просто, достаточно преобразовать RGB-значение каждого пикселя в яркость. Существует несколько способов сделать это, вот тот, который описан в Википедии:

Y = 0.2126 * R + 0.7152 * G + 0.0722 * B

Вот как это сделать на python:

from PIL import Imageimage = Image.open('dog.jpg')image.show()for x in range(image.size[0]):    for y in range(image.size[1]):        (r, g, b) = image.getpixel((x,y))        brightness = 0.2126 * r + 0.7152 * r + 0.0722 * b        new_pixel = tuple([int(brightness)] * 3)        image.putpixel((x,y), new_pixel)image.show()


Довольно просто.

Гауссово размытие


Заменяем каждый пиксель взвешенной суммой окружающих его пикселей. Вот пример размытия с радиусом 8.


(Я пропущу этап сжатия до квадратного размера, потому что это довольно скучно и очевидно.)

Дискретное косинусное преобразование


Дискретное косинусное преобразование (Discrete cosine transform, DCT) похоже на преобразование Фурье тем, что можно преобразовать последовательность чисел из одной формы в другую, а также обратить преобразование. inverse_dct(dct(image)) == image. Вам необязательно знать всё о частотном анализе. Достаточно знать, что можно взять матрицу чисел, например, изображение в оттенках серого, и преобразовать её в ещё одну матрицу чисел. И обратное преобразование тоже возможно.

В отличие от преобразования Фурье, DCT состоит из косинусов, а не из степеней e, поэтому все результаты вещественны, без мнимых чисел. Этот способ используется в JPEG для сжатия изображений.

Вот код, который я нашёл онлайн, выполняющий эту задачу на Python. Я немного его изменил. Вот самая важная часть:

im = rgb2gray(imread('dog.jpg'))   #read in the image to grayscaleimF = dct2(im)                     #DCTfraction = 1for y in range(len(im)):    for x in range(len(im[y])):        if x > len(im[y])//fraction or y > len(im)//fraction:            im[y][x] = 0           # blacken pixels that aren't in the top left corner            imF[y][x] = 0          # blacken pixels that aren't in the top left cornerim1 = idct2(imF)                   # inverse DCT


Мы считываем изображение и выполняем с ним DCT. Затем мы зачерняем часть исходного изображения, а также ту же часть преобразованного изображения. А затем инвертируем преобразование. Вот как это выглядит без зачернения:


Здесь нет ничего неожиданного. DCT обратим, поэтому логично, что выходные данные эквивалентны входящим. Немного странно, что изображение DCT только чёрное; вскоре мы к этому вернёмся!

Давайте посмотрим, что произойдёт, если мы отбросим три четверти изображения:


Преобразованное DCT изображение по-прежнему выглядит довольно неплохо, несмотря на потерю трёх четвертей информации. Давайте зачерним его ещё больше.


Оригинал потерял кучу данных, но изображение DCT по-прежнему выглядит приемлемо! В этом и заключается сила DCT: все важные биты находятся в одном углу, а всё остальное можно отбросить.

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


Это левый верхний кусок изображения DCT. Мы видим, что все значимые биты находились в углу. Вот почему DCT сработал, даже несмотря на то, что мы отбросили столько много бит: мы отбросили те биты, которые были неважны. Такой подход работает только с фотографиями, но именно с ними мы и хотим работать.

Чтобы закодировать это в число, мы используем упомянутый выше способ: взяв только 64 числа из левого верхнего угла, закодируем 1, если значение выше медианы, в противном случае закодируем 0. Результатом будет 64-битное число с половиной нулей. Существует более 1018 таких чисел, что намного больше, чем 3,1 миллиона изображений, которые нам нужно закодировать, так что вероятность коллизии мала.

Теперь нам просто нужно найти эффективный способ хранения всех этих чисел для поиска.

VP-деревья


Vantage point tree это своего рода двоичное дерево, работающее следующим образом: для каждого узла мы задаём центр и радиус. Если точка, которую мы ищем, находится внутри радиуса, движемся влево. Если снаружи, то движемся вправо. Продолжаем двигаться так, пока не найдём ответ.


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

                    01101010111001                    10100010010010отличающиеся биты:  ^^  ^   ^ ^ ^^Расстояние Хэмминга равно 7

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

Chicken и pHash


Теперь, когда мы решили полностью положиться на краудсорсинг, мне показалось несправедливым зарабатывать на этом деньги. Но я продолжил хостить сервер, чтобы кто угодно мог делиться ответами на задачи. Я выдавал результаты Asirra бесплатно и собирал новые ответы, когда о них сообщали. Через каждую пару дней я повторно генерировал дерево на основе последних данных и перезапускал сервер. Максимум я получал примерно 10 запросов в секунду на моём самодельном, написанном на VB.Net HTTP-сервере. Всего у меня было около 2000 уникальных пользователей. Я вычислил примерное количество очков, полученных пользователями с помощью Chicken и среднюю стоимость очка на основании продаваемых на ebay товаров. Похоже, благодаря Chicken пользователи в сумме получили призов примерно на полмиллиона долларов.

Позже Microsoft начала передавать случайным образом пикселизированные и повёрнутые изображения. Они оказались настолько искажёнными, что приводили pHash в растерянность. Компания препятствовала читерству и другими способами. Например, в Club Bing забанили мою страну целиком. В 2012 году работа Club Bing прекратилась.

Я так и не получил Xbox, а мой надувной каяк до меня не добрался. В основном я отправлял призы в качестве неожиданных подарков друзьям и членам семьи. Единственное, что я оставил себе это дешёвый телескоп и куртку, которая мне понравилась.



На правах рекламы


Надёжный и недорогой VDS от VDSina позволит разместить любой проект, на любой операционной системе Linux или Windows. Всё будет работать без сбоев и с высоким uptime!

Подробнее..

Из песочницы Как задача из классического сбора данных, перешла в решение простенькой задачи MNIST. Или как я спарсил сайт ЦИК

02.10.2020 14:18:50 | Автор: admin
В один из будничных дней, под вечер, от моего начальника прилетела интересная задачка. Прилетает ссылка с текстом: хочу отсюда получить все, но есть нюанс. Через 2 часа расскажешь, какие есть мысли по решению задачи. Время 16:00.

Как раз об этом нюансе и будет эта статья.

Я как обычно запускаю selenium, и после первого перехода по ссылке, где лежит искомая таблица с результатами выборов Республики Татарстан, вылетает оно

image

Как вы поняли, нюанс заключается в том, что после каждого перехода по ссылке появляется капча.

Проанализировав структуру сайта, было выяснено, что количество ссылок достигает порядка 30 тысяч.

Мне ничего не оставалось делать, как поискать на просторах интернета способы распознавания капчи. Нашел один сервис

+ Капчу распознают 100%, так же, как человек
Среднее время распознавания 9 сек, что очень долго, так как у нас порядка 30 тысяч различных ссылок, по которым нам надо перейти и распознать капчу.

Я сразу же отказался от этой идеи. После нескольких попыток получить капчу, заметил, что она особо не меняется, все те же черные цифры на зеленом фоне.

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

На часах уже было 17:00, и я начал искать предобученные модели по распознаванию чисел. После проверки их на данной капче точность меня не удовлетворила ну что ж, пора собирать картинки и обучать свою нейросетку.

Для начала нужно собрать обучающую выборку.

Открываю вебдрайвер Хрома и скриню 1000 капчей себе в папку.

from selenium import webdriveri = 1000driver = webdriver.Chrome('/Users/aleksejkudrasov/Downloads/chromedriver')while i>0:    driver.get('http://www.vybory.izbirkom.ru/region/izbirkom?action=show&vrn=4274007421995&region=27&prver=0&pronetvd=0')    time.sleep(0.5)    with open(str(i)+'.png', 'wb') as file:        file.write(driver.find_element_by_xpath('//*[@id="captchaImg"]').screenshot_as_png)    i = i - 1


Так как у нас всего два цвета преобразовал наши капчи в чб:

from operator import itemgetter, attrgetterfrom PIL import Imageimport globlist_img = glob.glob('path/*.png')for img in list_img:    im = Image.open(img)    im = im.convert("P")    im2 = Image.new("P",im.size,255)    im = im.convert("P")    temp = {}# Бежим по картинке и переводим её в чб    for x in range(im.size[1]):        for y in range(im.size[0]):            pix = im.getpixel((y,x))            temp[pix] = pix            if pix != 0:                 im2.putpixel((y,x),0)    im2.save(img)

20761


Теперь нам надо нарезать наши капчи на цифры и преобразовать в единый размер 10*10.
Сначала мы разрезаем капчу на цифры, затем, так как капча смещается по оси OY, нам нужно обрезать все лишнее и повернуть картинку на 90.

def crop(im2):    inletter = False    foundletter = False    start = 0    end = 0    count = 0    letters = []    for y in range(im2.size[0]):         for x in range(im2.size[1]):             pix = im2.getpixel((y,x))            if pix != 255:                inletter = True#ищем первый черный пиксель цифры по оси OX        if foundletter == False and inletter == True:             foundletter = True            start = y#ищем последний черный пиксель цифры по оси OX         if foundletter == True and inletter == False:             foundletter = False            end = y            letters.append((start,end))        inletter = False    for letter in letters:#разрезаем картинку на цифры        im3 = im2.crop(( letter[0] , 0, letter[1],im2.size[1] )) #поворачиваем на 90        im3 = im3.transpose(Image.ROTATE_90)         letters1 = []#Повторяем операцию выше        for y in range(im3.size[0]): # slice across            for x in range(im3.size[1]): # slice down                pix = im3.getpixel((y,x))                if pix != 255:                    inletter = True            if foundletter == False and inletter == True:                foundletter = True                start = y            if foundletter == True and inletter == False:                foundletter = False                end = y                letters1.append((start,end))            inletter=False        for letter in letters1:#обрезаем белые куски            im4 = im3.crop(( letter[0] , 0, letter[1],im3.size[1] )) #разворачиваем картинку в исходное положение         im4 = im4.transpose(Image.ROTATE_270)         resized_img = im4.resize((10, 10), Image.ANTIALIAS)        resized_img.save(img)

Время уже, 18:00 пора заканчивать с этой задачкой, подумал я, попутно раскидывая цифры по папкам с их номерами.

Объявляем простенькую модель, которая на вход принимает развернутую матрицу нашей картинки.

Для этого создаем входной слой из 100 нейронов, так как размер картинки 10*10. В качестве выходного слоя 10 нейронов каждый из которых соответствует цифре от 0 до 9.

from tensorflow.keras import Sequentialfrom tensorflow.keras.layers import Conv2D, MaxPooling2D, Flatten, Dense, Dropout, Activation, BatchNormalization, AveragePooling2Dfrom tensorflow.keras.optimizers import SGD, RMSprop, Adamdef mnist_make_model(10, 10):    # Neural network model    model = Sequential()    model.add(Dense(100, activation='relu', input_shape=(10*10)))    model.add(Dense(10, activation='softmax'))    model.compile(loss='categorical_crossentropy', optimizer=RMSprop(), metrics=['accuracy'])    return model

Разбиваем наши данные на обучающую и тестовую выборку:

list_folder = ['0','1','2','3','4','5','6','7','8','9']X_Digit = []y_digit = []for folder in list_folder:    for name in glob.glob('path'+folder+'/*.png'):        im2 = Image.open(name)        X_Digit.append(np.array(im2))        y_digit.append(folder)

Разбиваем на обучающую и тестовую выборку:

from sklearn.model_selection import train_test_splitX_train, X_test, y_train, y_test = train_test_split(X_Digit, y_digit, test_size=0.15, random_state=42)train_data = X_train.reshape(X_train.shape[0], 10*10) #Преобразуем матрицу векторов размерностью 100test_data = X_test.reshape(X_test.shape[0], 10*10) #Преобразуем матрицу векторов размерностью 100#преобразуем номер класса в вектор размерностью 10num_classes = 10train_labels_cat = keras.utils.to_categorical(y_train, num_classes)test_labels_cat = keras.utils.to_categorical(y_test, num_classes)

Обучаем модель.

Эмпирическим путем подбираем параметры количество эпох и размер бэтча:

model = mnist_make_model(10,10)model.fit(train_data, train_labels_cat, epochs=20, batch_size=32, verbose=1, validation_data=(test_data, test_labels_cat))


Сохраняем веса:

model.save_weights("model.h5")

Точность на 11 эпохе получилась отличная: accuracy = 1.0000. Довольный, в 19:00 иду домой отдыхать, завтра еще нужно будет написать парсер для сбора информации с сайта ЦИКа.

Утро следующего дня.

Дело осталось за малым, осталось обойти все страницы на сайте ЦИКа и забрать данные:

Загружаем веса обученной модели:

model = mnist_make_model(10,10)model.load_weights('model.h5')

Пишем функцию для сохранения капчи:

def get_captcha(driver):    with open('snt.png', 'wb') as file:        file.write(driver.find_element_by_xpath('//*[@id="captchaImg"]').screenshot_as_png)    im2 = Image.open('path/snt.png')    return im2

Пишем функцию для предсказания капчи:

def crop(im):    list_cap = []    im = im.convert("P")    im2 = Image.new("P",im.size,255)    im = im.convert("P")    temp = {}    for x in range(im.size[1]):        for y in range(im.size[0]):            pix = im.getpixel((y,x))            temp[pix] = pix            if pix != 0:                im2.putpixel((y,x),0)        inletter = False    foundletter=False    start = 0    end = 0    count = 0    letters = []    for y in range(im2.size[0]):         for x in range(im2.size[1]):             pix = im2.getpixel((y,x))            if pix != 255:                inletter = True        if foundletter == False and inletter == True:            foundletter = True            start = y        if foundletter == True and inletter == False:            foundletter = False            end = y            letters.append((start,end))        inletter=False    for letter in letters:        im3 = im2.crop(( letter[0] , 0, letter[1],im2.size[1] ))        im3 = im3.transpose(Image.ROTATE_90)        letters1 = []        for y in range(im3.size[0]):            for x in range(im3.size[1]):                pix = im3.getpixel((y,x))                if pix != 255:                    inletter = True            if foundletter == False and inletter == True:                foundletter = True                start = y            if foundletter == True and inletter == False:                foundletter = False                end = y                letters1.append((start,end))            inletter=False        for letter in letters1:            im4 = im3.crop(( letter[0] , 0, letter[1],im3.size[1] ))        im4 = im4.transpose(Image.ROTATE_270)        resized_img = im4.resize((10, 10), Image.ANTIALIAS)        img_arr = np.array(resized_img)/255        img_arr = img_arr.reshape((1, 10*10))        list_cap.append(model.predict_classes([img_arr])[0])    return ''.join([str(elem) for elem in list_cap])

Добавляем функцию, которая скачивает таблицу:

def get_table(driver):    html = driver.page_source #Получаем код страницы     soup = BeautifulSoup(html, 'html.parser') #Оборачиваем в "красивый суп"    table_result = [] #Объявляем лист в котором будет лежать финальная таблица    tbody = soup.find_all('tbody') #Ищем таблицу на странице    list_tr = tbody[1].find_all('tr') #Собираем все строки таблицы    ful_name = list_tr[0].text #Записываем название выборов    for table in list_tr[3].find_all('table'): #Бежим по всем таблицам        if len(table.find_all('tr'))>5: #Проверяем размер таблицы            for tr in table.find_all('tr'): #Собираем все строки таблицы                snt_tr = []#Объявляем временную строку                for td in tr.find_all('td'):                    snt_tr.append(td.text.strip())#Собираем все стоблцы в строку                table_result.append(snt_tr)#Формируем таблицу    return (ful_name, pd.DataFrame(table_result, columns = ['index', 'name','count']))

Собираем все линки за 13 сентября:

df_table = []driver.get('http://www.vybory.izbirkom.ru')driver.find_element_by_xpath('/html/body/table[2]/tbody/tr[2]/td/center/table/tbody/tr[2]/td/div/table/tbody/tr[3]/td[3]').click()html = driver.page_sourcesoup = BeautifulSoup(html, 'html.parser')list_a = soup.find_all('table')[1].find_all('a')for a in list_a:    name = a.text    link = a['href']    df_table.append([name,link])df_table = pd.DataFrame(df_table, columns = ['name','link'])

К 13:00 я дописываю код с обходом всех страниц:

result_df = []for index, line in df_table.iterrows():#Бежим по строкам таблицы с ссылками    driver.get(line['link'])#Загружаем ссылку    time.sleep(0.6)    try:#Разгадываем капчу если она вылетает        captcha = crop(get_captcha(driver))        driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(captcha)        driver.find_element_by_xpath('//*[@id="send"]').click()        time.sleep(0.6)        true_cap(driver)    except NoSuchElementException:#Отлавливаем ошибку если капче не появилась        pass    html = driver.page_source    soup = BeautifulSoup(html, 'html.parser')    if soup.find('select') is None:#Проверяем есть ли выпадающий список на странице        time.sleep(0.6)        html = driver.page_source        soup = BeautifulSoup(html, 'html.parser')                  for i in range(len(soup.find_all('tr'))):#Ищем ссылку на результат выборов            if '\nРЕЗУЛЬТАТ ВБОРОВ\n' == soup.find_all('tr')[i].text:#Ищем фразу, следующая за этой фразой наша ссылка на таблицу с выборами                rez_link = soup.find_all('tr')[i+1].find('a')['href']        driver.get(rez_link)        time.sleep(0.6)        try:            captcha = crop(get_captcha(driver))            driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(captcha)            driver.find_element_by_xpath('//*[@id="send"]').click()            time.sleep(0.6)            true_cap(driver)        except NoSuchElementException:            pass        ful_name , table = get_table(driver)#Получаем таблицу        head_name = line['name']        child_name = ''        result_df.append([line['name'],line['link'],rez_link,head_name,child_name,ful_name,table])    else:#Если выпадающий список присутствует, обходим все ссылки        options = soup.find('select').find_all('option')        for option in options:            if option.text == '---':#Пропускаем первую строку из выпадающего списка                continue            else:                link = option['value']                head_name = option.text                driver.get(link)                try:                    time.sleep(0.6)                    captcha = crop(get_captcha(driver))                    driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(captcha)                    driver.find_element_by_xpath('//*[@id="send"]').click()                    time.sleep(0.6)                    true_cap(driver)                except NoSuchElementException:                    pass                html2 = driver.page_source                second_soup = BeautifulSoup(html2, 'html.parser')                for i in range(len(second_soup.find_all('tr'))):                    if '\nРЕЗУЛЬТАТ ВБОРОВ\n' == second_soup.find_all('tr')[i].text:                        rez_link = second_soup.find_all('tr')[i+1].find('a')['href']                driver.get(rez_link)                try:                    time.sleep(0.6)                    captcha = crop(get_captcha(driver))                    driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(captcha)                    driver.find_element_by_xpath('//*[@id="send"]').click()                    time.sleep(0.6)                    true_cap(driver)                except NoSuchElementException:                    pass                ful_name , table = get_table(driver)                child_name = ''                result_df.append([line['name'],line['link'],rez_link,head_name,child_name,ful_name,table])                if second_soup.find('select') is None:                    continue                else:                    options_2 = second_soup.find('select').find_all('option')                    for option_2 in options_2:                        if option_2.text == '---':                            continue                        else:                            link_2 = option_2['value']                            child_name = option_2.text                            driver.get(link_2)                            try:                                time.sleep(0.6)                                captcha = crop(get_captcha(driver))                                driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(captcha)                                driver.find_element_by_xpath('//*[@id="send"]').click()                                time.sleep(0.6)                                true_cap(driver)                            except NoSuchElementException:                                pass                            html3 = driver.page_source                            thrid_soup = BeautifulSoup(html3, 'html.parser')                            for i in range(len(thrid_soup.find_all('tr'))):                                if '\nРЕЗУЛЬТАТ ВБОРОВ\n' == thrid_soup.find_all('tr')[i].text:                                    rez_link = thrid_soup.find_all('tr')[i+1].find('a')['href']                            driver.get(rez_link)                            try:                                time.sleep(0.6)                                captcha = crop(get_captcha(driver))                                driver.find_element_by_xpath('//*[@id="captcha"]').send_keys(captcha)                                driver.find_element_by_xpath('//*[@id="send"]').click()                                time.sleep(0.6)                                true_cap(driver)                            except NoSuchElementException:                                pass                            ful_name , table = get_table(driver)                            result_df.append([line['name'],line['link'],rez_link,head_name,child_name,ful_name,table])

А после приходит твит, который изменил мою жизнь

2020-09-29-12-11-31
Подробнее..

Как обойти капчу Гугл

22.03.2021 16:16:43 | Автор: admin
Ранее нам попадались относительно простые капчи:
Как обойти капчу, используя распознавание звука
Как обойти капчу: нейросеть на Tensorflow,Keras,python v числовая зашумленная капча
В этот раз поработаем над чем-то более серьезным и давно знакомым:

Итак задача: обойти капчу, желательно с первого раза.

Воспользуемся возможностями сверточных нейросетей, а именно vgg16.
Джентельменский набор, который используется:
python 3.6.4
tensorflow 2.0.0, keras 2.2.1
opencv 4.1.2

В качестве полигона для тестов выберем какой-нибудь сайт с формой обратной связи, защищенной капчей гугл. Например, этот https://captcha.guru/ru/feedback/ (*искренне не знаю кто это такие, сайт выбран случайно).
Беглый анализ капч подобного вида показывает, что капча попадается в двух базовых вариантах:
на 9 картинок (приведена в начале поста) и
на 16:


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


Поэтому, нейросеть была обучена только на усеченном количестве категорий, и будет работать с категориями капч, которые наиболее вероятны, остальные будет пропускать.

Общий алгоритм работы будет выглядеть так:
  • зашли на сайт с капчей, нажали Я не робот;
  • сделали скрин капчи с экрана, если она совпадает с определенными категориями объектов;
  • разрезали капчу на части;
  • скормили каждый кусок капчи нейросети;
  • понажимали на картинки, где объект распознан нейросетью;
  • обработали ошибки, и возможно, прошлись по 2-му, 3-му кругу капчи.


Итак, как говорится, ближе к коду.

Зашли на сайт с капчей, нажали Я не робот.


Здесь воспользуемся фреймворком selenium в python.

import webbrowser,time,os,pyautoguifrom selenium import webdriverfrom selenium.webdriver.common.keys import Keysimport randomimport osbrowser = webdriver.Firefox()browser.implicitly_wait(5)browser.get ('https://captcha.guru/ru/feedback/')time.sleep(5)iframe = browser.find_elements_by_tag_name('iframe')[0]browser.switch_to.frame(iframe)act = browser.find_element_by_css_selector('.recaptcha-checkbox-border')act.click()

В коде видно, что капча появляется в отдельном так называемом фрейме. Это необходимо учитывать при переключениях между основным контентом и фреймами капчи.

После выполнения кода результат будет примерно следующий:
картинка



Теперь необходимо:
получить категорию объекта капчи (здесь мосты);
сохранить картинку в нужных пропорциях, если она в нужной категории объектов;
разрезать картинку на 9 частей.

Сделали скрин капчи с экрана, если она совпадает с определенными категориями объектов


t=random.uniform(1, 4) #пауза между скачиваниями случайнаbrowser.switch_to.default_content()iframe = browser.find_elements_by_tag_name('iframe')[3]browser.switch_to.frame(iframe)time.sleep(3)act = browser.find_element_by_xpath('/html/body/div/div/div[2]/div[1]/div[1]/div/strong')print(act.text)

Здесь время t для случайной паузы, чтобы гугл, не слишком сразу определил нас как робота. Данную t мы применим позднее.
Этот код выведет категорию объекта, изображенного на капче (здесь мосты).
Задаем категории, с которыми будем работать, не пропуская:
a=['велосипеды','пешеходные переходы','гидрантами','автомобили','автобус']

Остальные категории отсекаются, так как они встречаются значительно реже, либо в капче 16-ть картинок вместо 9-ти.

Сделали скрин капчи с экрана, если она совпадает с определенными категориями объектов.


Рассмотрим следующий фрагмент:
if act.text not in a:                #обновили картинку с капчи                        act = browser.find_element_by_xpath('//*[@id="recaptcha-reload-button"]')                act.click()                time.sleep(t)                browser.switch_to.default_content()                iframe = browser.find_elements_by_tag_name('iframe')[3] #узнаем категорию капчи:автобусы,гидранты...                browser.switch_to.frame(iframe)                time.sleep(2)                act = browser.find_element_by_xpath('/html/body/div/div/div[2]/div[1]/div[1]/div/strong')                print(act.text)        if act.text in a:                      #сохраняем картинку                        os.chdir('C:\\1\\')                im=pyautogui.screenshot(imageFilename=str(0)+'.jpg',region=(509,411,495,495))                #нарезаем картинку                img = Image.open('0.jpg')                area1=(0,0,163,163) #спереди,сверху,справа,снизу)                  img1 = img.crop(area1)                area2=(163,0,326,163)                 img2 = img.crop(area2)                area3=(326,0,489,163)                img3 = img.crop(area3)                    area4=(0,163,163,326)                 img4 = img.crop(area4)                area5=(163,163,326,326)                  img5 = img.crop(area5)                area6=(326,163,489,326)                 img6 = img.crop(area6)                    area7=(0,326,163,489)                img7 = img.crop(area7)                area8=(163,326,326,489)                img8 = img.crop(area8)                    area9=(326,326,489,489)                img9 = img.crop(area9)                    img1.save("1"+".png")                img2.save("2"+".png")                img3.save("3"+".png")                img4.save("4"+".png")                img5.save("5"+".png")                img6.save("6"+".png")                img7.save("7"+".png")                img8.save("8"+".png")                img9.save("9"+".png")

Здесь вначале происходит проверка категории объекта. Если объект из категории велосипеды,пешеходные переходы,гидранты,автомобили либо автобус, то программа работает далее. В противном случае, обновляет картинку капчи.
Далее картинка сохраняется по пути C:\1\vgg-net\0.jpg (в windows).
И нарезается с сохранением 9-ти файлов .png в этой же директории.

Скормили каждый кусок капчи нейросети.


Понадобится предобученная модель нейросети, в которую для анализа будут поступать нарезанные картинки.

from keras.models import load_modelimport argparseimport pickleimport cv2def prescript(file):    # функция нейросети                ap = argparse.ArgumentParser()        ap.add_argument("-i", "--image",type=str, default=file,help="path to input image we are going to classify")        ap.add_argument("-m", "--model",type=str,default="simple_nn.model",help="path to trained Keras model")        ap.add_argument("-l", "--label-bin",type=str,default="simple_nn_lb.pickle",help="path to label binarizer")        ap.add_argument("-w", "--width", type=int, default=32, help="target spatial dimension width")        ap.add_argument("-e", "--height", type=int, default=32, help="target spatial dimension height")        ap.add_argument("-f", "--flatten", type=int, default=1, help="whether or not we should flatten the image")        args = vars(ap.parse_args())                image = cv2.imread(file)        output = image.copy()        image = cv2.resize(image, (args["width"], args["height"]))        image = image.astype("float") / 255.0                if args["flatten"] > 0:                image = image.flatten()                image = image.reshape((1, image.shape[0]))        else:                image = image.reshape((1, image.shape[0], image.shape[1], image.shape[2]))                model = load_model(args["model"])        lb = pickle.loads(open(args["label_bin"], "rb").read())                preds = model.predict(image)        i = preds.argmax(axis=1)[0]        label = lb.classes_[i]                text = "{}: {:.2f}%".format(label, preds[0][i] * 100)        print(text[0]) # 1-предмет есть на картинке, 0 - предмета нет        global result        result = text[0]

Нейросеть помещена в функцию, которая отдает либо 1 ('объект есть на картинке') либо 0 ('нет объекта').
Еще одна функция, с помощью которой будем кликать по картинкам, если нейросеть вернула '1' (наличие объекта):
def clicks(x,y):        if result=='1': # если предмет есть на картинке, нажимаем на картинку                act = browser.find_element_by_xpath('/html/body/div/div/div[2]/div[2]/div/table/tbody/tr['+str(x)+']/td['+str(y)+']')                act.click()

Ну и собственно, функция, которая будет вызывать 9-ть раз (картинок 9 штук) функцию нейросети и функцию нажимания на картинки:
def predict():        prescript("1"+".png")        clicks(1,1)               prescript("2"+".png")        clicks(1,2)        prescript("3"+".png")        clicks(1,3)        prescript("4"+".png")        clicks(2,1)        prescript("5"+".png")        clicks(2,2)        prescript("6"+".png")        clicks(2,3)        prescript("7"+".png")        clicks(3,1)        prescript("8"+".png")        clicks(3,2)                prescript("9"+".png")        clicks(3,3)                act = browser.find_element_by_css_selector('#recaptcha-verify-button')        act.click()        time.sleep(1)predict()


Обработали ошибки, и возможно, прошлись по 2-му, 3-му кругу капчи.


Иногда, после даже после нажатий на правильные картинки капчи, предлагается заново ее пройти с фразами: Попробуйте еще раз, Вы слишком стары для этого и т.п.
Поэтому добавим код для учета ситуаций:
try:        act = browser.find_element_by_css_selector('.rc-imageselect-error-dynamic-more') #Посмотрите также новые изображения.        captcha() # заново сохраняем картинки        predict() # заново распознаем картинки       except:        try:                act = browser.find_element_by_css_selector('.rc-imageselect-incorrect-response')#Повторите попытку.                captcha() # заново сохраняем картинки                predict() # заново распознаем картинки        except:                pass


О минусах реализации:
работает не со всеми категориями картинок (это сделано намеренно, чтобы облегчить размер модели);
ошибается (все-таки обучающий набор был не размера imagenet, а google неохотно отдавал экземпляры для обучения);
работает неспеша, так как последовательно обрабатывается каждая из 9-ти картинок;
не работает с 16-сегментными картинками.

*Статья носит научно-познавательный характер, не направлена на нарушение действующего законодательства и не содержит призывы к данному нарушению.

Программы для скачивания (программа и модель) скачать.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru