Делаем бизнес прозрачным или еще один пример распознавания капчи

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

Что такое «Прозрачный бизнес»?

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

Прежде чем начать, допустим, что у нас уже есть список ИНН, по которым надо опросить Сервис. Если нет, их можно вытащить из открытых данных ФНС или тут.

На старт

Начнем с POST запроса на https://pb.nalog.ru/search-proc.json. Тело запроса будет таким:

payload = {'page': '1', 'pageSize': '10', 'pbCaptchaToken': '', 'token': '', 'mode': 'search-ul', 'queryAll': '',
           'queryUl': 'ИНН ДЛЯ ЗАПРОСА', 'okvedUl': '', 'statusUl': '', 'regionUl': '', 'isMspUl': '', 'queryIp': '', 'okvedIp': '', 'statusIp': '',
           'regionIp': '', 'isMspIp': '', 'mspIp1': '1', 'mspIp2': '2', 'mspIp3': '3', 'queryUpr': '', 'uprType1': '1', 'uprType0': '1',
           'queryRdl': '', 'dateRdl': '', 'queryAddr': '', 'regionAddr': '', 'queryOgr': '', 'ogrFl': '1', 'ogrUl': '1', 'npTypeDoc': '1',
           'ogrnUlDoc': '', 'ogrnIpDoc': '', 'nameUlDoc': '', 'nameIpDoc': '', 'formUlDoc': '', 'formIpDoc': '', 'ifnsDoc': '',
           'dateFromDoc': '', 'dateToDoc': ''}

Однако если слишком часто дергать Сервис получим красивую картинку,

060fa367e96b2593ba9a0ca24493b68b.jfif

которая будет доступна по ссылке https://pb.nalog.ru/static/captcha.bin?r=1664389287469&a=B19F70E11E1ED39188D369F4F698A07A3EF963834C354814A1500080C8EA265EE3109E11270E79BDD6E154DCB897E1B5&version=2.

Ключевым здесь является параметр version=2. Попробуем заменить на version=3 и получаем:

b52f593342a08edfabb0645388abf12f.jfif

Уже лучше, верно?

Затем делаем предварительные преобразования: убираем фон, чистим от шума и приводим к монохромному виду. Получается вот так:

7624fd907dfb6c422264ad3c2a623d55.jpgКод метода

    def clean_image(self):
        for iy, y in enumerate(self.img_a):
            for ix, x in enumerate(y):
                pass
                if self.img_a[iy][ix][0] > 100 and self.img_a[iy][ix][1] > 100 and self.img_a[iy][ix][2] > 100:
                    self.img_a[iy][ix][0], self.img_a[iy][ix][1], self.img_a[iy][ix][2] = 255, 255, 255
                # Чистим полосы
                if self.img_a[iy][ix][0] >= 27 and self.img_a[iy][ix][0] <= 97 and \
                        self.img_a[iy][ix][1] >= 52 and self.img_a[iy][ix][1] <= 104 and \
                        self.img_a[iy][ix][2] >= 48 and self.img_a[iy][ix][2] <= 117:
                    self.img_a[iy][ix][0], self.img_a[iy][ix][1], self.img_a[iy][ix][2] = 255, 255, 255
                # Все, что осталось, делаем одного цвета
                if not (self.img_a[iy][ix][0] == 255 and self.img_a[iy][ix][1] == 255 and self.img_a[iy][ix][2] == 255):
                    self.img_a[iy][ix][0], self.img_a[iy][ix][1], self.img_a[iy][ix][2] = 0, 0, 0

Теперь надо нарезать картинку на цифры. Идея такая: сканируем картинку слева на право, центр первой встреченной вертикальной полосы из 5 подряд черных пикселей будет точкой входа. Рекурсивно находим все прилежащие черные пиксели. Границы получившегося прямоугольника и есть границы нашей цифры. Повторив процедуру шесть (по количеству цифр) раз, получаем шесть мини-картинок (массивов) с цифрами.

Иногда (скорее редко) получается, что в один массив попадают 2 цифры. Детектируем такие случаи по раздутой ширине прямоугольника (больше 35 пикселей), делим его (прямоугольник) пополам и надеемся, что нам повезет.

Из практики известно, что средний размер картинки с цифрой — 24 на 44 пикселя. По этому средствами библиотеки pillow трансформируем ее до этих значений.

Обучаем нейросеть

Мы научились получать капчу и резать ее на вменяемые цифры. Но как сформировать датасет для обучения нейронки? И здесь Сервис (pb.nalog.ru) идет нам на помощь. Все просто: каждый раз при скачивании капчи мы получаем новое изображение с одинаковыми цифрами. Другими словами, если скачать 10 000 капч, у нас будет 10 000 вариантов, например, цифры 8. Такой оборот избавляет нас от ручной разметки данных, и это хорошо!

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

model = keras.Sequential([
    keras.layers.Flatten(input_shape=(44, 24)),
    keras.layers.Dense(528, activation='tanh'),
    keras.layers.Dense(264, activation='tanh'),
    keras.layers.Dense(132, activation='tanh'),
    keras.layers.Dense(66, activation='tanh'),
    keras.layers.Dense(33, activation='tanh'),
    keras.layers.Dense(10,  activation='softmax')
])

model.compile(optimizer = 'adam', loss='categorical_crossentropy', 
              metrics = ['accuracy'])

Обученная модель также доступна на GitHub.

Получаем данные

Итак, когда мы можем преобразовывать картинку в шестизначный код, получаем токен капчи через POST запрос https://pb.nalog.ru/captcha-proc.json

payload = {'captcha': РЕЗУЛЬТАТ РАСПОЗНОВАНИЯ КАПЧИ}

Затем возвращаемся к запросу https://pb.nalog.ru/search-proc.json (с которого все началось) только теперь в поле pbCaptchaToken пробрасываем полученный токен капчи.

В ответ приходит токен организации. Его и передаем в теле запроса по url https://pb.nalog.ru/company-proc.json.

 payload = {'token': ТОКЕН ОРГАНИЗАЦИИ, 'method': 'get-request'}

Здесь Сервис, скорее всего, покажет еще одну капчу. Да, чтобы добраться до данных, капчу придется решить дважды. Но алгоритм действий уже известен! Решаем капчу, токен капчи передаем с полем pbCaptchaToken, и в ответ получаем новый токен и id.

Снова отправляем запрос на https://pb.nalog.ru/company-proc.json только теперь метод будет get-response.

payload = {'token': НОВЫЙ ТОКЕН, 'id': id, 'method': 'get-response'}

Наконец, если все прошло хорошо, получим json с данными об организации. Что с ними делать — решайте сами.

Ссылка на GitHub

На этом все!

© Habrahabr.ru