Делаем бизнес прозрачным или еще один пример распознавания капчи
Не для кого не секрет, что капча является популярным средством, чтобы снизить нагрузку на сайт и предотвратить скачивание информации роботами. Сегодня, когда капча применяется практически на каждом сайте, рассмотрим кейс с ее обходом на сервисе «Прозрачный бизнес».
Что такое «Прозрачный бизнес»?
Сервис содержит комплексную информацию о финансовых и правовых параметрах юридических лиц (подробнее тут). Данная информация полезна как для самих организаций с целью проверки контрагентов, так и для специалистов по данным, например, с целью сбора статистики и построения инфографиков по определенному региону или стране в целом.
Прежде чем начать, допустим, что у нас уже есть список ИНН, по которым надо опросить Сервис. Если нет, их можно вытащить из открытых данных ФНС или тут.
На старт
Начнем с 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': ''}
Однако если слишком часто дергать Сервис получим красивую картинку,
которая будет доступна по ссылке https://pb.nalog.ru/static/captcha.bin?r=1664389287469&a=B19F70E11E1ED39188D369F4F698A07A3EF963834C354814A1500080C8EA265EE3109E11270E79BDD6E154DCB897E1B5&version=2
.
Ключевым здесь является параметр version=2
. Попробуем заменить на version=3
и получаем:
Уже лучше, верно?
Затем делаем предварительные преобразования: убираем фон, чистим от шума и приводим к монохромному виду. Получается вот так:
Код метода
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
На этом все!