Анализ раздела "Участники регионального тура" сайта thebest.its.1c.ru
Для того, чтобы брать информацию со страницы, нам надо изучить ее. Посмотрев исходный код страницы, можно выделить следующие фрагменты:
<select name="partner" id="partner" class="form-control">
<option value="e3506426-dd64-11e4-9573-e41f13bdf6c4" selected="selected">г. Абакан, ООО "Хакасия.ру"</option>
<option value="e3a39c36-dd64-11e4-9573-e41f13bdf6c4">г. Апатиты, ООО "КОЛА ДИГЕСТА"</option>
...
<option value="f391e760-dd64-11e4-9573-e41f13bdf6c4">г. Ярославль, ООО "Ярософт"</option>
</select>
В списке выбора партнера можем увидеть, что каждый элемент списка имеет атрибут value. Данное значение используется для получение информации об участниках выбраного партнера. Например, для получения информации об участниках в центре проведения г. Апатиты, ООО "Кола Дигеста" можно по ссылке http://thebest.its.1c.ru/public/reg_tour?partner=e3a39c36-dd64-11e4-9573-e41f13bdf6c4 либо http://thebest.its.1c.ru/public/reg_tour/e3a39c36-dd64-11e4-9573-e41f13bdf6c4. Если мы получим все значения атрибута value, то мы сможем получить доступ ко всем страницам партнеров с участниками.
Теперь необходимо разобраться, как получать информацию о самих участниках. Опять же глянем исходный код страницы:
<tbody>
<tr>
<td class="regional_tour__fio">...</td>
<td class="regional_tour__nomination_td"><span class="nomination k" title="Кадровый учет и трудовое право"></span></td>
<td title="Тест: 84, акция Вконтакте: 15, акция от партнера: 15" class="text-center">114</td>
<td>2015-03-ххххх</td>
</tr>
...
</tbody>
В таблице каждая строка представлена 4 ячейками:
- Ячейка с классом regional_tour_fio. В данной ячейке размещается фамилия, имя и отчество участника
- Ячейка с классом regional_tour__nomination_td. В данной ячейке размещается элемент span, класс которого зависит от номинации, в которой участвовал пользователь. Таких номинаций три:
- Бухгалтерский учет и налогообложение (класс nomination b)
- Кадровый учет и трудовое право (класс nomination k)
- Платформа 1С:Предприятие 8 – разработка и администрирование (класс nomination it)
- Ячейка с классом text-center. В данной ячейке размещается количество баллов, набранных за отборочный тур. Также здесь можно увидеть атрибут title, в котором хранится калькуляция баллов. Например "Тест: 84, акция Вконтакте: 15, акция от партнера: 15". Здесь хотелось бы отметить, что обязательно указывается только "Тест", остальные значения могут не указываться, если участник не принимал участия в акциях партнера или проводимых Вконтакте.
- Ячейка без опозновательных знаков :) В ней находится номер сертификата Профессионал 1С:ИТС. Ее будем определять как ячейку без атрибута class.
Создание справочников
Создадим пустую конфигурацию и добавим справочники Участники и Партнеры. На рисунке ниже показаны реквизиты справочников
В справочнике Участники реквизиты Фамилия, Имя, Отчество, Сертификат имеют тип Строка, БаллыЗаТест, БаллыВК, БаллыПартнер, ОбщиеБаллы имеют тип число и реквизит Партнер имеет тип СправочникСсылка.Партнеры. В справочнике Партнеры все реквизиты имеют тип строка.
Теперь создадим общую форму, с помощью которой будем обновлять информацию в справочниках. В данной форме создадим две команды: ОбновитьИнформацию и ЗакрытьФорму.
Разбор адреса
Начнем писать код в модуле этой формы. Для удобства работы напишем сперва функцию СтруктураАдреса, которая будет разбирать наш адрес по частям и возвращать структуру. Согласно RFC1738, адрес страницы строится следующим образом:
<scheme>://<user>:<password>@<host>:<port>/<url-path>,
где
- scheme - протокол подключения http, https, ftp и т. д; обязательный;
- user - имя пользователя; необязательный;
- password - пароль пользователя; необязательный;
- host - имя сервера; обязательный;
- port - порт соединения; необязательный;
- url-path - путь к странице; необязательный.
&НаКлиенте
Функция СтруктураАдреса(Знач Адрес) Экспорт
Адрес = СокрЛП(Адрес);
// схема
Схема = "";
Позиция = Найти(Адрес, "://");
Если Позиция > 0 Тогда
Схема = Лев(Адрес, Позиция - 1);
Адрес = Сред(Адрес, Позиция + 3);
КонецЕсли;
// строка соединения и путь на сервере
СтрокаСоединения = Адрес;
ПутьНаСервере = "";
Позиция = Найти(СтрокаСоединения, "/");
Если Позиция > 0 Тогда
ПутьНаСервере = Сред(СтрокаСоединения, Позиция + 1);
СтрокаСоединения = Лев(СтрокаСоединения, Позиция - 1);
КонецЕсли;
// информация пользователя и имя сервера
МассивРазбора = СтрРазделить(СтрокаСоединения,"@");
ИмяПользователя = ?(МассивРазбора.Количество() = 1,Неопределено,МассивРазбора [0]);
ИмяСервера = ?(ИмяПользователя = Неопределено, СтрокаСоединения, МассивРазбора [1]);
// пользователь и пароль
Если ИмяПользователя <> Неопределено Тогда
МассивРазбора = СтрРазделить(ИмяПользователя,":");
ИмяПользователя = МассивРазбора [0];
ПарольПользователя = ?(МассивРазбора.Количество() = 1,Неопределено,МассивРазбора [1]);
КонецЕсли;
// имя сервера и пароль
МассивРазбора = СтрРазделить(ИмяСервера,":");
ИмяСервера = МассивРазбора [0];
Порт = ?(МассивРазбора.Количество() = 1,Неопределено, Число(МассивРазбора [1]));
Результат = Новый Структура;
Результат.Вставить("Схема", Схема);
Результат.Вставить("Логин", ИмяПользователя);
Результат.Вставить("Пароль", ПарольПользователя);
Результат.Вставить("ИмяСервера", ИмяСервера);
Результат.Вставить("Порт", Порт);
Результат.Вставить("ПутьНаСервере", ПутьНаСервере);
Возврат Результат;
КонецФункции
Получение DOM-объектов с веб-страницы
Для работы с элементами (тегами) нам надо получить исходный текст страницы и преобразовать его в DOM-объекты. Для этого напишем функцию ПолучитьСтраницуВОбъектDOM(Адрес). Алгоритм функции следующий: мы создаем переменную типа HTTPСоединение, в конструкторе которого указываем имя сервера и порт. Используя данное соединение, мы создаем HTTPзапрос к необходимой нам страницы и пытаемся прочесть результат. Если получить результат не удалось, то выводим сообщение об ошибке, иначе получаем исходный код в кодировке UTF8.
&НаКлиенте
Функция ПолучитьСтраницуВОбъектDOM(Знач Адрес)
Адрес = СтруктураАдреса(Адрес);
Соединение = Новый HTTPСоединение(Адрес.ИмяСервера, Адрес.Порт);
ВебЗапрос = Новый HTTPЗапрос(Адрес.ПутьНаСервере);
Попытка
Результат = Соединение.Получить(ВебЗапрос);
Исключение
Сообщить("Ошибка соединения");
ВызватьИсключение;
КонецПопытки;
Ответ = Результат.ПолучитьТелоКакСтроку(КодировкаТекста.UTF8);
Чтение = Новый ЧтениеHTML;
Чтение.УстановитьСтроку(Ответ);
ОбъектыDOM = Новый ПостроительDOM;
Возврат ОбъектыDOM.Прочитать(Чтение);
КонецФункции
Получение и обработка данных из DOM-объектов
Чтобы получить необходимую информацию, нам необходимо загрузить страницу, где она расположена. Но у нас таких страниц несколько, и для этого нам необходимо обработать элементы списка партнеров. Чтобы получить список партнеров и адреса страниц, нам надо просмотреть все элементы option и получить значение атрибута value, из которого получим адреса страниц и сам текст этого элемента. Поиск элемента в объектах DOM будем осуществлять с помощью метода построителя DOM - ПолучитьЭлементыПоИмени(). В результате работы этого метода мы получим переменную с типом СписокЭлементовDOM(), в котором будут содержаться все элементы option и его содержимое. Для получения атрибута используем метод ПолучитьАтрибут(), а для получения содержимого элемента используем метод ТекстовоеСодержимое() Получить. Эти данные мы сохраним в структуре Партнер("Город", "НомерНаСайте", "Партнер"), которую в свою очередь добавим в массив. Вот тут у меня вопрос к читателям: как лучше сделать - передавать по одной структуре на сервер и там сохранять в справочнике или собрать все структуры в массив и передать массив на сервер? Я попробовал оба варианта, и по мне лучше второй. Далее мы подгружаем все страницы с информацией о участниках поочередно. Для получения информации об участниках необходимо найти таблицу без шапки (элемент tbody) и в нем получить все строки (элемент tr). После этого просматриваем все ячейки строки (элемент td) на соответствие атрибута class одному из условий. Все условия мы с вами рассмотрели ранее. Когда получаем элементы ячеек, нам необходимо выполнить некоторую обработку:
- В ячейке "Номинация" необходимо найти элемент span и на основании атрибута class данного элемента получить номинацию;
- Разобрать атрибут title в ячейке с количеством баллов, чтобы получить отдельно баллы за тест, за акцию ВК и баллы от партнеров.
Все полученные данные сохраняем в структуру Участник("ФИО", "Номинация", "БаллыТест", "БаллыВК", "БаллыПарт", "ОбщиеБаллы", "Сертификат", "Партнер"). Как и в случае с партнерами, данную структуру добавляем в массив. Полученные массивы отправляем на сервер для сохранения. В итоге функция получилась следующая:
&НаКлиенте
Процедура ОбновляемИнформацию(Знач Адрес)
Страница = ПолучитьСтраницуВОбъектDOM(Адрес);
ОбъектыСписка = Страница.ПолучитьЭлементыПоИмени("option");
Партнер = Новый Структура;
МассивПартнеров = Новый Массив;
МассивУчастников = Новый Массив;
Для каждого Объект Из ОбъектыСписка Цикл
ПартнерГород = СтрРазделить(Объект.ТекстовоеСодержимое,",");
Номер = Объект.ПолучитьАтрибут("value");
Партнер.Вставить("Город",СокрЛП( ПартнерГород[0]));
Партнер.Вставить("НомерНаСайте",Номер);
Партнер.Вставить("Партнер",СокрЛП(ПартнерГород[1]));
МассивПартнеров.Добавить(Новый Структура ("Город,НомерНаСайте,Партнер",Партнер ["Город"], Партнер ["НомерНаСайте"], Партнер ["Партнер"]) );
СтраницаПартнер = ПолучитьСтраницуВОбъектDOM("http://thebest.its.1c.ru/public/reg_tour/"+Партнер ["НомерНаСайте"]);
Участник = Новый Структура;
Таблица = СтраницаПартнер.ПолучитьЭлементыПоИмени("tbody");
СтрокиТаблицы = Таблица[0].ПолучитьЭлементыПоИмени("tr");
Для каждого Строка Из СтрокиТаблицы Цикл
ПоляТаблицы = Строка.ПолучитьЭлементыПоИмени("td");
Для каждого Поле Из ПоляТаблицы Цикл
Если Поле.ПолучитьАтрибут("class") = "regional_tour__fio" Тогда
Участник.Вставить("ФИО",Поле.ТекстовоеСодержимое);
КонецЕсли;
Если Поле.ПолучитьАтрибут("class") = "regional_tour__nomination_td" Тогда
Номинации = Поле.ПолучитьЭлементыПоИмени("span");
Номинация = "";
Номинация = ?(Номинации[0].ПолучитьАтрибут("class")="nomination it","Информационные технологии",Номинация);
Номинация = ?(Номинации[0].ПолучитьАтрибут("class")="nomination k","Кадры",Номинация);
Номинация = ?(Номинации[0].ПолучитьАтрибут("class")="nomination b","Бухгалтерия",Номинация);
Участник.Вставить("Номинация",Номинация);
КонецЕсли;
Если Поле.ПолучитьАтрибут("class") = "text-center" Тогда
МассивБаллов = СтрРазделить(Поле.ПолучитьАтрибут("title"),",");
БаллыТест = 0; БаллыВК = 0; БаллыПарт = 0;
Для каждого Элемент Из массивБаллов Цикл
Баллы = СтрРазделить(Элемент,":");
Если СокрЛП(Баллы [0]) = "Тест" Тогда
БаллыТест=СокрЛП(Баллы [1]);
ИначеЕсли СокрЛП(Баллы [0]) = "акция Вконтакте" Тогда
БаллыВК=СокрЛП(Баллы [1]);
ИначеЕсли СокрЛП(Баллы [0]) = "акция от партнера" Тогда
БаллыПарт=СокрЛП(Баллы [1]);
КонецЕсли;
Участник.Вставить("БаллыТест", БаллыТест);
Участник.Вставить("БаллыВК", БаллыВК);
Участник.Вставить("БаллыПарт", БаллыПарт);
КонецЦикла;
Участник.Вставить("ОбщиеБаллы", Поле.ТекстовоеСодержимое);
КонецЕсли;
Если Поле.ПолучитьАтрибут("class") = Неопределено Тогда
Участник.Вставить("Сертификат",Поле.ТекстовоеСодержимое);
КонецЕсли;
КонецЦикла;
МассивУчастников.Добавить(Новый Структура ("ФИО,Номинация,БаллыТест,БаллыВК,БаллыПарт,ОбщиеБаллы,Сертификат,Партнер", Участник ["ФИО"],Участник ["Номинация"],Участник ["БаллыТест"],Участник ["БаллыВК"],Участник ["БаллыПарт"],Участник ["ОбщиеБаллы"],Участник ["Сертификат"],Партнер ["НомерНаСайте"] ));
КонецЦикла;
КонецЦикла;
ОбновитьПартнеров(МассивПартнеров);
ОбновитьУчастников(МассивУчастников);
КонецПроцедуры
Сохранение данных в справочниках
Для сохранения данных в справочниках мы создадим функции ОбновитьУчастников(МассивУчастников) и ОбновитьПартнеров(МассивПартнеров):
&НаСервере
Процедура ОбновитьПартнеров(Знач МассивПартнеров)
Для каждого Партнер Из МассивПартнеров Цикл
ТекущийПартнер = Справочники.Партнеры.НайтиПоРеквизиту("НомерНаСайте", Партнер ["НомерНаСайте"]);
Если ТекущийПартнер = Справочники.Партнеры.ПустаяСсылка() Тогда
ТекущийПартнер = Справочники.Партнеры.СоздатьЭлемент();
Иначе
ТекущийПартнер = ТекущийПартнер.ПолучитьОбъект();
КонецЕсли;
ТекущийПартнер.НомерНаСайте = Партнер ["НомерНаСайте"];
ТекущийПартнер.Город = Партнер ["Город"];
ТекущийПартнер.Партнер = Партнер ["Партнер"];
ТекущийПартнер.Наименование = Партнер ["Город"] + ", " + Партнер ["Партнер"];
ТекущийПартнер.Записать();
КонецЦикла;
КонецПроцедуры
&НаСервере
Процедура ОбновитьУчастников(Знач МассивУчастников)
Для каждого Участник Из МассивУчастников Цикл
ТекущийУчастник = Справочники.Участники.НайтиПоНаименованию(Участник["ФИО"]);
Если ТекущийУчастник = Справочники.Участники.ПустаяСсылка() Тогда
ТекущийУчастник = Справочники.Участники.СоздатьЭлемент();
Иначе
ТекущийУчастник = ТекущийУчастник.ПолучитьОбъект();
КонецЕсли;
ФИО = СтрРазделить(Участник ["ФИО"], " ");
ТекущийУчастник.Фамилия = ФИО [0];
ТекущийУчастник.Имя = ФИО [1];
ТекущийУчастник.Отчество = ФИО [2];
ТекущийПартнер = Справочники.Партнеры.НайтиПоРеквизиту("НомерНаСайте", Участник ["Партнер"] );
ТекущийУчастник.Партнер = ТекущийПартнер;
ТекущийУчастник.БаллыЗаТест = Участник ["БаллыТест"];
ТекущийУчастник.БаллыВК = Участник ["БаллыВК"];
ТекущийУчастник.БаллыПартнер = Участник ["БаллыПарт"];
ТекущийУчастник.ОбщиеБаллы = Участник ["ОбщиеБаллы"];
ТекущийУчастник.Наименование = Участник ["ФИО" ];
ТекущийУчастник.Сертификат = Участник ["Сертификат" ];
ТекущийУчастник.Записать();
КонецЦикла;
КонецПроцедуры
Осталось только сделать приятные мелочи по оформлению, но каждый делает на свой вкус и цвет. Я вывел форму списка справочника Участники единственной на рабочую облать начальной страницы, и вместо вывода номера сертификата я применил условное форматирование (строка выделяется цветом, если человек получил сертификат). В итоге получилось следующее: