Как быстро получить заголовок HTML-страницы (тег title) по ссылке в 1С

Программист 1С v8.3 (Управляемые формы) IT и автоматизация бизнеса
← На главную

В процессе интеграции 1С с интернет-магазинами, прайс-листами и внешними веб-ресурсами программисты часто сталкиваются с необходимостью парсинга веб-страниц. Типичная задача — обойти список ссылок на товары и получить их наименования, которые обычно содержатся в заголовке страницы (теге title). Однако стандартный последовательный обход ссылок работает крайне медленно: обработка всего 60 страниц может занимать до 3-4 минут. В этой статье мы подробно разберем, почему возникает эта проблема, проанализируем частые ошибки разработчиков и реализуем максимально быстрое и эффективное решение на платформе 1С:Предприятие.

Разбор типичной ошибки: путаница между HTTP-заголовками и HTML-тегами

Проанализируем пример кода, который часто пытаются использовать начинающие разработчики:


Если ЗначениеЗаполнено(АдресСтраницы) И Найти(АдресСтраницы, "http") > 0 Тогда
    Соединение = Новый HTTPСоединение(АдресСайта);
    Запрос = Новый HTTPЗапрос(АдресСтраницы);
    Ответ = Соединение.Получить(Запрос);
    Если Ответ.КодСостояния = 200 Тогда 
        Значение = Ответ.Заголовки; // Ошибка здесь
    КонецЕсли; 
КонецЕсли;

В данном примере разработчик пытается получить заголовок страницы через свойство Ответ.Заголовки. Выясним причину, почему этот код не возвращает название страницы и работает медленно:

  1. Свойство "Заголовки" (HTTP Headers) содержит служебную информацию веб-сервера (например, тип контента Content-Type, кодировку, параметры кэширования, длину ответа и cookies). К названию страницы в формате HTML (тегу title) эти данные не имеют никакого отношения.
  2. Скачивание страницы целиком. Метод Получить() без дополнительных параметров скачивает абсолютно все тело страницы (HTML-код, внутренние стили, скрипты). Если страница весит несколько мегабайт, процесс загрузки 60 страниц превращается в долгое ожидание.

Для решения этой проблемы нам необходимо решить две задачи: минимизировать объем скачиваемых данных и обеспечить сверхбыстрое извлечение тега title из полученного текста страницы.

Метод 1. Ограничение объема загрузки с помощью HTTP-заголовка Range

Зачем скачивать всю веб-страницу целиком, если тег title всегда находится в самом начале документа, внутри контейнера head? Мы можем запросить у сервера только первые несколько килобайт данных. Для этого используется стандартный HTTP-заголовок Range.

Рассмотрим подробнее, как применить этот метод в 1С:


// Укажем адрес сайта и параметры подключения
Сервер = "example.com";
Ресурс = "/index.php?productID=3803";

Соединение = Новый HTTPСоединение(Сервер, , , , , 10); // Таймаут 10 секунд
Запрос = Новый HTTPЗапрос(Ресурс);

// Запрашиваем только первые 4 килобайта (4096 байт) страницы
Запрос.Заголовки.Вставить("Range", "bytes=0-4096");

Ответ = Соединение.Получить(Запрос);

// Большинство современных серверов при получении заголовка Range возвращают статус 206 (Partial Content)
Если Ответ.КодСостояния = 200 Или Ответ.КодСостояния = 206 Тогда
    ТекстНачалаСтраницы = Ответ.ПолучитьТелоКакСтроку();
    // Переходим к парсингу полученного фрагмента
КонецЕсли;

Важное примечание: Использование заголовка Range позволяет сэкономить до 95% входящего трафика и сокращает время ожидания ответа сервера в разы.

Метод 2. Быстрый парсинг тега title с помощью регулярных выражений

Использование тяжелых объектов платформы, таких как ЧтениеHTML или ПостроительDOM, оправдано только тогда, когда нужно полностью распарсить сложный документ. Если нам требуется вытащить всего одну строку, построение DOM-модели в памяти будет избыточным и медленным.

В современных версиях платформы 1С:Предприятие (начиная с версии 8.3.23) появилась нативная поддержка регулярных выражений. Разберем по шагам, как извлечь заголовок с их помощью:


// Используем регулярное выражение для поиска содержимого тега title (без учета регистра)
ШаблонТега = "(?i)<title\b[^>]*>(.*?)</title>";
РегулярноеВыражение = Новый РегулярноеВыражение(ШаблонТега);

РезультатСопоставления = РегулярноеВыражение.Сопоставить(ТекстНачалаСтраницы);
Если РезультатСопоставления.Количество() > 0 Тогда
    // Получаем значение первой группы захвата (текст внутри тегов)
    ЗаголовокСтраницы = РезультатСопоставления[0].Группы[1].Значение;
    ЗаголовокСтраницы = СокрЛП(ЗаголовокСтраницы);
    Сообщить("Заголовок страницы: " + ЗаголовокСтраницы);
Иначе
    Сообщить("Тег title не найден в полученном фрагменте.");
КонецЕсли;

Если ваша база работает на более старой версии платформы, регулярные выражения можно вызвать через COM-объект или применить быстрое вырезание подстроки стандартными функциями 1С. Посмотрим на пример реализации классического строкового парсинга:


// Быстрый поиск границ тегов без использования регулярных выражений
ТекстВВерхнемРегистре = ВРег(ТекстНачалаСтраницы);
ПозОткрывающегоТега = СтрНайти(ТекстВВерхнемРегистре, "<TITLE>");

Если ПозОткрывающегоТега > 0 Тогда
    ПозЗакрывающегоТега = СтрНайти(ТекстВВерхнемРегистре, "</TITLE>", , ПозОткрывающегоТега);
    Если ПозЗакрывающегоТега > 0 Тогда
        НачалоТекста = ПозОткрывающегоТега + 7; // Длина тега "<title>"
        ДлинаТекста = ПозЗакрывающегоТега - НачалоТекста;
        ЗаголовокСтраницы = Сред(ТекстНачалаСтраницы, НачалоТекста, ДлинаТекста);
        ЗаголовокСтраницы = СокрЛП(ЗаголовокСтраницы);
    КонецЕсли;
КонецЕсли;

Метод 3. Распараллеливание запросов (Многопоточность в 1С)

Даже если каждый отдельный запрос выполняется за 0.1 секунды, при последовательном обходе 1000 ссылок общее время ожидания все равно будет значительным. Основное время тратится на сетевое ожидание ответа от удаленного сервера. Чтобы решить эту проблему, применим распараллеливание с помощью фоновых заданий.

Проанализируем архитектуру параллельного парсинга:

  1. Исходный список ссылок разбивается на пакеты (например, по 10 ссылок в каждом).
  2. Для каждого пакета программно запускается фоновое задание.
  3. Каждое фоновое задание выполняет сетевые запросы независимо от других и записывает результат во временное хранилище или специальный регистр сведений.
  4. Основной поток ожидает завершения всех фоновых заданий и собирает воедино полученные заголовки.

Посмотрим на пример кода для запуска параллельной обработки:


// Подготовим массив пакетов со ссылками
МассивПакетов = Новый Массив; // Массив массивов со ссылками
// ... код заполнения пакетов по 10 ссылок ...

МассивЗаданий = Новый Массив;
Для Каждого ПакетСсылок Из МассивПакетов Цикл
    ПараметрыЗапуска = Новый Массив;
    ПараметрыЗапуска.Добавить(ПакетСсылок);
    
    // Запуск фонового задания, выполняющего скачивание
    Задание = ФоновыеЗадания.Выполнить("ПарсерСервер.ПолучитьЗаголовкиПакетом", ПараметрыЗапуска);
    МассивЗаданий.Добавить(Задание);
КонецЦикла;

// Ожидаем завершения всех потоков (максимум 60 секунд)
Попытка
    ФоновыеЗадания.ОжидатьЗавершения(МассивЗаданий, 60);
Исключение
    Сообщить("Время ожидания выполнения потоков истекло.");
КонецПопытки;

Что делать, если заголовок формируется динамически через JavaScript?

Если целевой сайт представляет собой современное SPA-приложение (React, Angular, Vue), классическое HTTPСоединение вернет вам пустой каркас страницы, где тег title может быть пустым или содержать статичную фразу вроде "Загрузка...". Настоящий заголовок формируется на стороне клиента встроенным скриптом.

Для таких случаев у нас есть два пути решения:

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

← На главную