Интерактивный ввод капчи

Опубликовал . .  (gaabora) в раздел Обмен - Интеграция с WEB

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

Итак, перед нами некая страница сайта с формой и капчей. В данном примере будет рассмотрена страница проверки полисов РСА, для начала нам понадобится проверить, как именно реализовано "общение" пользователя с сайтом, а именно - какая структура данных отправляется на сервер при отправке формы и что выдает сервер в ответ. Для этого довольно удобно использовать браузер Firefox* с включенным дополнением Firebug* (как установить, можно найти на просторах интернета).

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

Запускаем Firebug нажатием F12, переходим в нем на панель "консоль" и пробуем отправить форму, заполнив необходимые поля случайными данными, капчу вводим правильно
в нашем примере отправка формы асинхронная (без перезагрузки страницы или перехода на другую страницу) и при отправке формы мы видим в консоли строку примерно такого содержания:

+ POST http://dkbm-web.autoins.ru/dkbm-web-1.0/osagovehicle.htm 200 OK 23ms

Разворачиваем, нажав на плюсик, чтобы увидеть подробности отправленного POST - запроса.
Здесь нас интересуют вкладки Post и Ответ:

- на вкладке Post видим:

Параметры         application/x-www-form-urlencoded
answer fd31g (указанный нами текст капчи)
dateRequest 01.01.2017
numberOsago 1111111111
serialOsago ЕЕЕ
...

имена этих параметров понадобятся нам для программной отправки формы.

- на вкладке Ответ видим строку, представляющую из себя JSON-кодированный объект:

{"insurerName":null,"bodyNumber":null,"chassisNumber":null,"licensePlate":null,"policyStatus":null,"vin" :null,"errorMessage":null,"warningMessage":"Сведения о полисе ОСАГО с указанными серией и номером не найдены","errorId":0,"validCaptcha":true}

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

{"insurerName":null,"bodyNumber":null,"chassisNumber":null,"licensePlate":null,"policyStatus":null,"vin" :null,"errorMessage":null,"warningMessage":null,"errorId":0,"validCaptcha":false}

Очевидно, что в отлове можно будет основываться на значение ключа validCaptcha.

Казалось бы вот и все, но нет, чтобы механизм капчи работал, нам нужно узнать как хранится идентификатор нашей сессии, иначе сервер не будет знать какую до этого капчу он нам выдал и, соответственно, не сможет определить, верно ли мы ее ввели.
Переходим в firebug на панель Cookies и берем оттуда все параметры.
в данном примере параметр всего один - "JSESSIONID" с некоторым значением. Значение это нам не понадобятся, т.к. для нашего http-запроса его будет назначать сам сервер сайта.

Ну и конечно же нам нужно получить адрес картинки-капчи: нажимаем по картинке правой кнопкой мыши -> Исследовать элемент с помощью Firebug. На панели HTML автоматически находится и подсвечивается нужный нам кусок кода <img id="captcha" name="captcha" src="simpleCaptcha.png?0.7484971124912407"> - кликаем по нему правой кнопкой - Открыть в новой вкладке. В свежеоткрытой вкладке пробуем убрать из адреса нашей картинки все, что после знака "?", вместе с самим знаком и переходим по новому адресу. В нашем примере капча работает, значит GET-параметры после адреса картинки не обязательны (в данном случае околослучайный GET-параметр нужен для предотвращения кэширования картинки браузерами).

Что мы имеем в итоге:

При заходе на страницу, сервер дает нам идентификатор сессии "JSESSIONID", генерирует некую картинку-капчу "simpleCaptcha.png" из случайного строкового кода расшифровки и ждет от нас форму с этим кодом расшифровки. При получении формы с кодом сервер проверяет его соответствие прошлому сгенерированному, и если они не соответствуют то выдает соответствующую ошибку. Если код верный, далее идут уже проверки другого уровня, например, валидные ли были отправлены данные и т.п.

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

Теперь можно перейти к реализации.

Создадим форму с реквизитами (все строкового типа, неограниченной длины):

  • АдресСервера
  • ПутьДоСтраницыЗапроса
  • ПутьДоКартинкиКапчи
  • ИмяПараметраSESSID
  • ТекстКапчи
  • Результат
  • Капча

а также реквизиты для соответствующих отправляемых параметров

  • СерияПолиса для "serialOsago"
  • НомерПолиса для "numberOsago"
  • ДатаПроверки для "dateRequest"

Разместим реквизиты на форме, при этом вид поля, связанного с реквизитом "Капча" укажем как "Поле картинки"

При открытии формы нам нужно установить соединение с сервером, получить идентификатор сессии и картинку капчи

&НаКлиенте
Процедура ПриОткрытии()
	АдресСервера = "dkbm-web.autoins.ru";
	ПутьДоСтраницыЗапроса = "/dkbm-web-1.0/osagovehicle.htm";
	ПутьДоКартинкиКапчи = "/dkbm-web-1.0/simpleCaptcha.png";
	ИмяПараметраSESSID = "JSESSIONID";
	
	НачатьСессию();
	ПолучитьКапчу();
КонецПроцедуры

Начав сессию, запишем в реквизит формы SESSID значение полученного идентификатора

&НаКлиенте
Процедура НачатьСессию()
	
	Соединение = Новый HTTPСоединение(АдресСервера); 
	
	Ответ = Соединение.GET(Новый HTTPЗапрос(ПутьДоСтраницыЗапроса));
	
	мстр = СтрЗаменить(Ответ.Заголовки["Set-Cookie"], ";",Символы.ПС);
	Для нСтр = 1 по СтрЧислоСтрок(мстр) Цикл
		тСтр = СтрПолучитьСтроку(мстр,нСтр);
		Если Найти(тСтр,ИмяПараметраSESSID) > 0 Тогда
			SESSID = СокрЛП(СтрЗаменить(СтрЗаменить(тСтр,ИмяПараметраSESSID,""),"=",""));
			Прервать;
		КонецЕсли;
	КонецЦикла;
	
КонецПроцедуры

Получим картинку капчи и отобразим ее на форме

&НаКлиенте
Процедура ПолучитьКапчу()
	
	Соединение = Новый HTTPСоединение(АдресСервера);
	
	Заголовки = Новый Соответствие();
	Заголовки.Вставить("Accept","application/json");
	Заголовки.Вставить("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
	Заголовки.Вставить("Connection","keep-alive");
	Заголовки.Вставить("Host",АдресСервера);
	Заголовки.Вставить("Referer",АдресСервера+ПутьДоСтраницыЗапроса);
	Заголовки.Вставить("Cookie",ИмяПараметраSESSID+"="+SESSID);

	Ответ = Соединение.GET(Новый HTTPЗапрос(ПутьДоКартинкиКапчи,Заголовки));
	
	Капча = ПоместитьВоВременноеХранилище(Ответ.ПолучитьТелоКакДвоичныеДанные(), УникальныйИдентификатор);

КонецПроцедуры

Далее конечному пользователю остается только ввести данные и проверить результат

&НаКлиенте
Процедура Проверить(Команда)
	Соединение = Новый HTTPСоединение(АдресСервера);
	
	Заголовки = Новый Соответствие();
	Заголовки.Вставить("Accept","application/json");
	Заголовки.Вставить("Content-Type","application/x-www-form-urlencoded; charset=UTF-8");
	Заголовки.Вставить("Connection","keep-alive");
	Заголовки.Вставить("Host",АдресСервера);
	Заголовки.Вставить("Referer",АдресСервера+ПутьДоСтраницыЗапроса);
	Заголовки.Вставить("Cookie",ИмяПараметраSESSID+"="+SESSID);
	
	ПараметрыЗапроса = Новый Структура();
	
	ПараметрыЗапроса.Вставить("serialOsago",СерияПолиса);
	ПараметрыЗапроса.Вставить("numberOsago",НомерПолиса);
	ПараметрыЗапроса.Вставить("dateRequest",ДатаПроверки);
	ПараметрыЗапроса.Вставить("answer",ТекстКапчи);
	
	СодержимоеЗапроса = Новый HTTPЗапрос(ПутьДоСтраницыЗапроса, Заголовки);
	СодержимоеЗапроса.УстановитьТелоИзСтроки(СтрСоединитьСтруктуру(ПараметрыЗапроса,"&","="));
	
	Ответ = Соединение.POST(СодержимоеЗапроса);
	
	Результат = Ответ.ПолучитьТелоКакСтроку();
	
	ТекстКапчи = "";
	
	ПолучитьКапчу();
	
КонецПроцедуры
Функция СтрСоединитьСтруктуру(Структура, РазделительЭлементов="", РазделительКлючЗначение="", ВставлятьРазделительКлючаЗначенияПриПустомЗначении=Истина) Экспорт
	Строка = "";
	Если ТипЗнч(Структура) = Тип("Структура") ИЛИ ТипЗнч(Структура) = ТИП("Соответствие") Тогда
		Для Каждого Элемент Из Структура Цикл
			Строка = "" + Строка + Элемент.Ключ
				+ ?(ЗначениеЗаполнено(Элемент.Значение) ИЛИ ВставлятьРазделительКлючаЗначенияПриПустомЗначении
					,РазделительКлючЗначение + Элемент.Значение
					,""
				)
				+ РазделительЭлементов;
		КонецЦикла;
	Иначе
		Возврат Ложь;
	КонецЕсли;
	Возврат Лев(Строка, СтрДлина(Строка)-СтрДлина(РазделительЭлементов));
КонецФункции

- в поле результат отобразится ответ в виде строки JSON-кодированного объекта, который можно распарсить соответствующими средствами 1С.

Для удобства работы с JSON создадим еще пару функций-прослоек

Функция json_decode(Строка,ИменаСвойствСоЗначениемДата=Неопределено) Экспорт	
	ЧтениеJSON = Новый ЧтениеJSON();
	ЧтениеJSON.УстановитьСтроку(Строка);
	Результат = ПрочитатьJSON(ЧтениеJSON,,ИменаСвойствСоЗначениемДата);
	ЧтениеJSON.Закрыть();
	Возврат Результат;
КонецФункции

Функция json_encode(Знач Данные) Экспорт
	ЗаписьJSON = Новый ЗаписьJSON;
	ЗаписьJSON.УстановитьСтроку(Новый ПараметрыЗаписиJSON(,,,ЭкранированиеСимволовJSON.СимволыВнеASCII,Истина,Истина,Истина,Истина));	
	ЗаписатьJSON(ЗаписьJSON,Данные,,"ВСтроку",ЭтаФорма);
	Возврат ЗаписьJSON.Закрыть();
КонецФункции

Функция ВСтроку(Свойство,Значение,ДополнительныеПараметры,Отказ) Экспорт
	Возврат ""+Значение;
КонецФункции

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

Попытка
	СтруктураРезультата = json_encode(Результат);
	
	Сообщения = СтруктураРезультата.errorMessage+СтруктураРезультата.warningMessage;
	Если ЗначениеЗаполнено(Сообщения) Тогда
		Сообщить(Сообщения);
	КонецЕсли;
	
	Если НЕ СтруктураРезультата.validCaptcha Тогда
		Сообщить("Капча введена не правильно");
	Иначе
		Сообщить(СтруктураРезультата.policyStatus);
	КонецЕсли;
Исключение
	Сообщить(ОписаниеОшибки());
КонецПопытки;

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

См. также

Комментарии
1. Виталий Чебан (VitaliyCeban) 209 23.02.17 11:36 Сейчас в теме
Осталось прикрутить антигейт (лёгкий вариант, но медленный, в среднем 15 секунд на распознавание, неприемлемо) или искусственную нейронную сеть, для автораспознавания, благо капча относительно легкая.
unmensch; +1 Ответить