При интеграции 1С с внешними веб-сервисами, парсинге сайтов (например, государственные реестры, каталоги товаров или курсы валют) разработчики часто сталкиваются с необходимостью программно разобрать HTML-код страницы. В отличие от современных веб-технологий, где поиск по CSS-селекторам является стандартом де-факто, встроенные механизмы платформы 1С:Предприятие накладывают определенные ограничения. Особенности работы зависят от того, где выполняется код — на клиенте или на сервере, а также под какой операционной системой (Windows или Linux) запущен сервер 1С.
В этой статье мы подробно разберем все возможные способы поиска HTML-узлов по селекторам и XPath, проанализируем ограничения каждого подхода и найдем оптимальные решения для высоконагруженных и кроссплатформенных систем.
Если задача выполняется в интерактивном режиме на клиенте (в форме обработки или управляемого приложения), мы имеем полный доступ к стандартному объектному движку рендеринга. Поле HTML-документа на форме инициализирует полноценный браузерный контекст, в котором доступны все привычные методы JS.
Разберем пример, когда нам нужно найти первый же элемент ввода input на форме документа. Для этого мы можем напрямую обратиться к свойству Документ элемента управления:
&НаКлиенте
Процедура НайтиЭлементНаФорме()
// Получаем DOM-документ из элемента формы
ДокументHTML = Элементы.ТвоеПолеHTMLДокумента.Документ;
// Используем стандартный JS-метод querySelector для поиска по CSS-селектору
ПервыйВвод = ДокументHTML.querySelector("input");
Если ПервыйВвод <> Неопределено Тогда
Сообщить("Найден элемент с типом: " + ПервыйВвод.type);
// Можем получить значение или атрибуты
ЗначениеПоля = ПервыйВвод.value;
КонецЕсли;
КонецПроцедуры
Этот метод чрезвычайно прост и эффективен, однако он имеет критическое ограничение: его невозможно использовать в фоновых заданиях, регламентных операциях или в серверных модулях, так как там отсутствует пользовательский интерфейс и элементы формы недоступны.
Когда парсинг необходимо выполнить на стороне сервера, но при этом сервер работает под управлением операционной системы Windows, мы можем прибегнуть к использованию системных COM-объектов. Наиболее популярным решением является создание объекта HtmlFile, который под капотом использует библиотеки Internet Explorer.
Рассмотрим пример загрузки HTML-кода из файла и поиска ссылки по CSS-селектору:
&НаСервере
Функция ПолучитьСсылкуИзФайлаHTML(ПолноеИмяФайла, СтрокаВыражения)
// Считываем сырой HTML-код из файла
ТекстовыйДокумент = Новый ТекстовыйДокумент;
ТекстовыйДокумент.Прочитать(ПолноеИмяФайла, КодировкаТекста.UTF8);
ТекстHTML = ТекстовыйДокумент.ПолучитьТекст();
// Создаем COM-объект HtmlFile
ДокументHTML = Новый COMОбъект("HtmlFile");
ДокументHTML.open("text/html");
ДокументHTML.write(ТекстHTML);
ДокументHTML.close(); // Обязательно закрываем поток записи
Попытка
// Находим элемент по селектору (например, "a.download-link")
НайденныйЭлемент = ДокументHTML.querySelector(СтрокаВыражения);
Если НайденныйЭлемент <> Неопределено Тогда
Возврат НайденныйЭлемент.href;
КонецЕсли;
Исключение
ЗаписьЖурналаРегистрации("Парсинг HTML", УровеньЖурналаРегистрации.Ошибка,,, ОписаниеОшибки());
Возврат "";
КонецПопытки;
Возврат "";
КонецФункции
Преимущества метода:
Недостатки метода:
Для создания универсального решения, которое будет стабильно работать и на Windows, и на Linux, нам необходимо использовать исключительно встроенные объекты платформы 1С. Платформа предоставляет класс ПостроительDOM, который умеет читать HTML и XML.
Однако есть архитектурный нюанс: объект ДокументHTML, получаемый после чтения через ЧтениеHTML, не поддерживает язык запросов XPath и не имеет методов querySelector. В то же время объект ДокументDOM имеет полноценный метод ВычислитьВыражениеXPath(), но инициализировать его напрямую из «грязного» веб-HTML зачастую не получается из-за строгих требований XML-стандарта.
Рассмотрим обходной путь решения этой проблемы. Мы загружаем HTML-документ, записываем его тело в XML-формате, а затем считываем этот поток как полноценный ДокументDOM. Разберем этот алгоритм по шагам:
&НаСервере
Функция НайтиУзлыЧерезXPath(ПутьКФайлу, ВыражениеXPath) Экспорт
// Step 1: Читаем исходный файл как HTML
ЧтениеHTML = Новый ЧтениеHTML;
ЧтениеHTML.ОткрытьФайл(ПутьКФайлу, "UTF-8");
ПостроительDOM = Новый ПостроительDOM;
ДокументHTML = ПостроительDOM.Прочитать(ЧтениеHTML);
ЧтениеHTML.Закрыть();
// Step 2: Конвертируем HTML-структуру в строго валидный XML-формат
ЗаписьXML = Новый ЗаписьXML;
ЗаписьXML.УстановитьСтроку("UTF-8");
ЗаписьDOM = Новый ЗаписьDOM;
// Записываем только тело документа (Body), чтобы избежать проблем с заголовками мета-данных
ЗаписьDOM.Записать(ДокументHTML.Тело, ЗаписьXML);
СтрокаXML = ЗаписьXML.Закрыть();
// Step 3: Читаем полученную XML строку в ДокументDOM
ЧтениеXML = Новый ЧтениеXML;
ЧтениеXML.УстановитьСтроку(СтрокаXML);
ДокументDOM = ПостроительDOM.Прочитать(ЧтениеXML);
ЧтениеXML.Закрыть();
// Step 4: Выполняем XPath запрос к DOM-модели
Разыменователь = Новый РазыменовательПространствИменDOM(ДокументDOM);
РезультатXPath = ДокументDOM.ВычислитьВыражениеXPath(ВыражениеXPath, ДокументDOM, Разыменователь);
МассивРезультатов = Новый Массив;
ТекущийЭлемент = РезультатXPath.ПолучитьСледующий();
Пока ТекущийЭлемент <> Неопределено Цикл
МассивРезультатов.Добавить(ТекущийЭлемент);
ТекущийЭлемент = РезультатXPath.ПолучитьСледующий();
КонецЦикла;
Возврат МассивРезультатов;
КонецФункции
Проблема «Недопустимое имя XML» и пути ее решения
При использовании описанного выше метода на реальных сайтах (например, при парсинге реестров на сайте ФНС) вы можете столкнуться с ошибкой: Недопустимое имя XML во время выполнения метода ЗаписьDOM.Записать().
Причина кроется в том, что современный стандарт разметки HTML5 допускает вольности, которые абсолютно недопустимы в строгом XML (XHTML):
<script defer> или <input disabled>. В XML обязательно должно быть явное присвоение значения, например: defer="defer".<meta>, <br>, <link>).Для борьбы с этой проблемой мы рекомендуем перед передачей HTML-текста парсеру выполнять его предварительную очистку с помощью функций работы со строками или регулярных выражений. В частности, удаление секций скриптов и стилей часто полностью решает проблему валидации:
// Пример простой очистки HTML перед парсингом
Функция ОчиститьHTMLОтСкриптов(ИсходныйТекст)
// Удаляем все блоки <script>...</script>
Результат = ИсходныйТекст;
Пока Истина Цикл
ПозНачала = Найти(НРег(Результат), "<script");
Если ПозНачала = 0 Тогда Прервать; КонецЕсли;
ПозКонца = Найти(НРег(Результат), "</script>");
Если ПозКонца = 0 Тогда Прервать; КонецЕсли;
Результат = Лев(Результат, ПозНачала - 1) + Сред(Результат, ПозКонца + 9);
КонецЦикла;
Возврат Результат;
КонецФункции
Если нам необходимо гарантированно избежать ошибок валидации XML на сервере под Linux и при этом не хочется использовать регулярные выражения, мы можем реализовать ручной обход дерева элементов HTML с помощью объекта ОбходДереваDOM. Этот метод работает медленнее, чем нативный XPath, но он полностью безопасен и не зависит от валидности XML-структуры.
Проанализируем пример функции, которая обходит все узлы документа и ищет элементы по заданному имени тега и классу CSS:
&НаСервере
Функция НайтиУзлыПоТегуИКлассу(ПутьКФайлу, ИскомыйТег, ИскомыйКласс) Экспорт
ЧтениеHTML = Новый ЧтениеHTML;
ЧтениеHTML.ОткрытьФайл(ПутьКФайлу, "UTF-8");
ПостроительDOM = Новый ПостроительDOM;
ДокументHTML = ПостроительDOM.Прочитать(ЧтениеHTML);
ЧтениеHTML.Закрыть();
СписокНайденныхУзлов = Новый Массив;
// Создаем стандартный объект обхода дерева DOM 1С
Фильтр = Новый ОпределенныйКлассФильтраDOM(); // Если требуется кастомная фильтрация
Обходчик = ДокументHTML.СоздатьОбходДерева(ДокументHTML.Тело, ПолучитьПоказательПолнотыОбхода(), Фильтр);
ТекущийУзел = Обходчик.СледующийУзел();
Пока ТекущийУзел <> Неопределено Цикл
// Проверяем, является ли узел элементом HTML
Если ТекущийУзел.ТипУзла = ТипУзлаDOM.Элемент Тогда
// Сверяем имя тега (приводим к нижнему регистру для надежности)
Если НРег(ТекущийУзел.ИмяУзла) = НРег(ИскомыйТег) Тогда
// Проверяем наличие нужного класса в атрибутах
АтрибутClass = ТекущийУзел.ПолучитьАтрибут("class");
Если АтрибутClass <> Неопределено И Найти(АтрибутClass, ИскомыйКласс) > 0 Тогда
СписокНайденныхУзлов.Добавить(ТекущийУзел);
КонецЕсли;
КонецЕсли;
КонецЕсли;
ТекущийУзел = Обходчик.СледующийУзел();
КонецЦикла;
Возврат СписокНайденныхУзлов;
КонецФункции
&НаСервере
Функция ПолучитьПоказательПолнотыОбхода()
// Возвращаем битовую маску для обхода только элементов (Element Nodes)
Возврат 1;
КонецФункции
Используя этот подход, мы получаем полный контроль над процессом разбора структуры веб-страницы. Нам не страшны отсутствующие закрывающие теги или неэкранированные спецсимволы в атрибутах — платформа 1С самостоятельно нормализует дерево при чтении HTML.
Для крупных проектов, где критически важна скорость парсинга больших объемов данных, а также стабильность работы под Linux, отличным решением будет предварительная обработка скачанной страницы через консольную утилиту HTML Tidy.
Эта утилита доступна для установки во всех дистрибутивах Linux (через apt-get install tidy) и на Windows. Она за доли секунды преобразует любой «битый» HTML в идеальный, валидный XHTML, который без единой ошибки прочитается построителем DOM 1С.
Давайте разберем, как вызвать эту утилиту из кода 1С:
&НаСервере
Функция ОчиститьHTMLЧерезTidy(ПутьКФайлуИсточнику, ПутьКВыходномуФайлу)
// Формируем параметры запуска для tidy, заставляя утилиту преобразовать код в XHTML
ПараметрыЗапуска = " -asxhtml --numeric-entities yes --output-xhtml yes --quiet yes -o """
+ ПутьКВыходномуФайлу + """ """ + ПутьКФайлуИсточнику + """";
// Запускаем консольное приложение в зависимости от ОС сервера
СистемнаяИнформация = Новый СистемнаяИнформация;
Если Найти(ВРег(СистемнаяИнформация.ОперационнаяСистема), "LINUX") > 0 Тогда
ИмяПриложения = "tidy";
Иначе
ИмяПриложения = "tidy.exe";
КонецЕсли;
КодВозврата = Неопределено;
ЗапуститьПриложение(ИмяПриложения + ПараметрыЗапуска, , Истина, КодВозврата);
// Код возврата 0 или 1 означает успешное исправление разметки
Возврат (КодВозврата = 0 Или КодВозврата = 1);
КонецФункции
После обработки исходного файла этой функцией вы можете гарантированно использовать быстрый кроссплатформенный парсинг через XPath (описанный в Варианте 3) без малейшего риска падения системы по ошибкам несовместимости XML.
Подводя итоги нашего анализа, дадим практические рекомендации по выбору архитектурного подхода для вашей задачи: