Как правильно скачать PDF-файл с веб-сайта в 1С, если вместо него приходит HTML?

Программист 1С v8.3 (Управляемые формы)
← К списку

При автоматизации взаимодействия с внешними веб-сервисами, особенно когда требуется загрузить отчеты или документы в формате PDF, разработчики 1С часто сталкиваются с проблемой: вместо ожидаемого PDF-файла сохраняется обычная HTML-страница, иногда даже упакованная в архив. Давайте вместе разберемся, почему это происходит и как эффективно решить эту задачу средствами 1С.

Наш кейс основан на попытке загрузить список лекарственных средств с сайта Росздравнадзора в формате PDF. Изначальная попытка, как это часто бывает, привела к получению HTML-страницы вместо PDF.

Решение 1: Оптимизированный HTTP-запрос в 1С

Рассмотрим основной подход, который позволил успешно загрузить PDF-файл в одной из рабочих конфигураций 1С. Мы проанализируем, какие параметры и заголовки оказались ключевыми.

  1. Инициализация соединения и запроса:

    Начнем с создания объектов для установки защищенного HTTP-соединения и формирования запроса. Обратите внимание на использование ЗащищенноеСоединениеOpenSSL() для работы с HTTPS.

    
    Адрес = "roszdravnadzor.gov.ru";
    HTTPСоединение = Новый HTTPСоединение(Адрес,443,,,,,Новый ЗащищенноеСоединениеOpenSSL(),Ложь);
    HTTPЗапрос = Новый HTTPЗапрос("services/turnover");
    

    Здесь мы указываем адрес хоста и порт 443 для HTTPS. Объект HTTPЗапрос инициализируется с относительным путем к ресурсу, который обрабатывает запрос на сервере.

  2. Формирование параметров запроса:

    Ключевым моментом является правильная передача параметров, которые сервер ожидает для генерации PDF. В нашем случае, это строка параметров, аналогичная той, что отправляется браузером при нажатии кнопки "Скачать PDF".

    
    Параметры="q_label=244857651&dt_from=01.01.2025&dt_to=&q_type_ls=&q_org=&q_dt_ru_from=&q_dt_ru_to=&q_no_ru=&q_tn=&q_mnn=&q_series=&q_producer=&q_country=&pdf=1";
    HTTPЗапрос.УстановитьТелоИзСтроки(Параметры, "UTF-8");
    

    Здесь особенно важен параметр pdf=1, который явно указывает серверу, что нам нужен именно PDF-файл. Метод УстановитьТелоИзСтроки() используется для передачи параметров в теле POST-запроса с кодировкой UTF-8. Это соответствует стандартному типу содержимого application/x-www-form-urlencoded.

  3. Настройка HTTP-заголовков:

    Заголовки играют критическую роль в том, как сервер обрабатывает запрос. Мы должны максимально точно эмулировать заголовки, которые отправляет браузер при успешной загрузке.

    
    HTTPЗапрос.Заголовки.Вставить("accept-encoding", "gzip, deflate, br, zstd");
    HTTPЗапрос.Заголовки.Вставить("accept", "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7");
    HTTPЗапрос.Заголовки.Вставить("content-type", "application/x-www-form-urlencoded");
    
    • accept-encoding: Сообщает серверу, какие методы сжатия поддерживает клиент.
    • accept: Указывает, какие типы содержимого клиент готов принять. Включение application/pdf или */* (любой тип) здесь очень важно.
    • content-type: Информирует сервер о формате данных, передаваемых в теле запроса. Для наших параметров это application/x-www-form-urlencoded.

    В первоначальной попытке было много заголовков, включая User-Agent, Referer, Origin, Cookie и другие. Иногда их избыток или неточность могут мешать. В данном случае, сокращенный набор заголовков оказался достаточным.

  4. Отправка запроса и сохранение ответа:

    Наконец, отправляем сформированный запрос и сохраняем полученный ответ в файл.

    
    HTTPОтвет = HTTPСоединение.ОтправитьДляОбработки(HTTPЗапрос,"F:\data\123.pdf");
    

    Метод ОтправитьДляОбработки() выполняет запрос и сохраняет тело ответа сервера в указанный файл. Если сервер действительно вернул PDF, то файл 123.pdf будет корректным PDF-документом.

Важно отметить: Этот код был успешно протестирован на платформе 1С 8.3.25.1546 в файловой базе, управляемом приложении (УТ11). Это указывает на то, что для современных версий платформы данный подход является рабочим.

Решение 2: Глубокий анализ проблемы и её обходные пути

Если базовое решение не сработало, или вы столкнулись с более сложными сценариями, давайте углубимся в детали и рассмотрим дополнительные аспекты.

1. Проверка типа содержимого ответа (Content-Type)

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

Мы настоятельно рекомендуем после получения ответа от сервера проверять заголовок Content-Type объекта HTTPОтвет.

  1. Получение заголовка:

    
    Ответ = Соединение.ОтправитьДляОбработки(Запрос, ПолныйПутьФайла);
    ТипСодержимого = Ответ.Заголовки.Получить("Content-Type");
    
  2. Анализ типа:

    Если ТипСодержимого равно application/pdf, то файл, скорее всего, является PDF. Если же это text/html или что-то иное, значит, сервер вернул HTML-страницу. В этом случае, сохранение файла с расширением `.pdf` не сделает его PDF-документом.

    
    Если Ответ.КодСостояния = 200 Тогда
        ТипСодержимого = Ответ.Заголовки.Получить("Content-Type");
        Если Найти(ТипСодержимого, "application/pdf") > 0 Тогда
            // Файл успешно скачан и это PDF
            Сообщить("PDF-файл успешно загружен.");
        Иначе
            // Получен HTML или другой тип контента
            Сообщить("Внимание! Вместо PDF получен файл типа: " + ТипСодержимого + ". Возможно, это HTML-страница.");
            // Можно сохранить с другим расширением, например, .html
            // Ответ.ПолучитьТелоКакСтроку() или обработать другим способом
        КонецЕсли;
    Иначе
        Сообщить("Ошибка при загрузке файла. Код состояния: " + Ответ.КодСостояния);
    КонецЕсли;
    

2. Обработка перенаправлений (Redirects)

Некоторые веб-сервисы, особенно при генерации отчетов, сначала возвращают HTTP-ответ с кодом состояния перенаправления (например, 302 Found) и заголовком Location, который указывает на URL, откуда можно скачать сгенерированный файл. Браузеры автоматически следуют этим перенаправлениям, но в 1С это нужно обрабатывать явно.

Разберем по шагам, как это сделать:

  1. Проверка кода состояния: После первого запроса проверьте Ответ.КодСостояния. Если оно равно 301, 302, 303 или 307, то это перенаправление.

  2. Извлечение нового URL: Получите URL для перенаправления из заголовка Location.

    
    Если Ответ.КодСостояния = 302 Тогда
        НовыйURL = Ответ.Заголовки.Получить("Location");
        Если Не ПустаяСтрока(НовыйURL) Тогда
            // Выполняем новый GET-запрос по НовомуURL
            // ... (создаем новый HTTPЗапрос и Соединение, если нужно)
            // ... (отправляем GET-запрос)
        КонецЕсли;
    КонецЕсли;
    
  3. Выполнение второго запроса: Обычно, для скачивания файла по новому URL достаточно выполнить простой GET-запрос. Убедитесь, что вы правильно формируете новое HTTPСоединение и HTTPЗапрос для этого URL.

3. Влияние режима совместимости 1С

Мы выяснили, что код успешно работает в УТ11, но не работает в УТ10 с режимом совместимости "Версия 8.2.13". Это критичный момент. Режим совместимости ограничивает функциональность конфигурации до уровня указанной версии платформы. Это может проявляться в:

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

4. Использование WinHttps для старых версий 1С

Если обновление платформы или изменение режима совместимости невозможно, распространенным решением для обхода ограничений встроенных HTTP-объектов 1С в старых версиях является использование WinHttps. Это COM-объект, предоставляемый операционной системой Windows, который дает более низкоуровневый и гибкий контроль над HTTP/HTTPS запросами.

Давайте проанализируем преимущества WinHttps:

Использование WinHttps требует создания COM-объекта (например, Новый COMОбъект("WinHttp.WinHttpRequest.5.1")) и работы с его методами (Open, SetRequestHeader, Send, ResponseText, ResponseBody и т.д.). Это более сложный подход, но он предоставляет мощные инструменты для решения проблем совместимости.

5. Комплексность HTTP-заголовков

Как мы уже упоминали, HTTP-заголовки играют важную роль. Рассмотрим подробнее некоторые из них:

Совет: Используйте инструменты разработчика в браузере (например, F12 в Chrome/Firefox) для анализа HTTP-запросов, которые ваш браузер отправляет при успешной загрузке PDF. Постарайтесь максимально точно эмулировать эти заголовки в своем коде 1С.

6. Улучшенная обработка ошибок

Для надежной работы всегда включайте в код проверку КодСостояния ответа сервера (200 для успеха, 4xx для клиентских ошибок, 5xx для серверных ошибок) и обрабатывайте возможные исключения, которые могут возникнуть при выполнении HTTP-запросов (например, проблемы с сетью, таймауты).

Например:


Попытка
    Ответ = Соединение.ОтправитьДляОбработки(Запрос, ПолныйПутьФайла);
    Если Ответ.КодСостояния = 200 Тогда
        Сообщить("Файл успешно загружен.");
    Иначе
        Сообщить("Ошибка загрузки. Код состояния: " + Ответ.КодСостояния + 
                 " " + Ответ.Заголовки.Получить("Status-Text"));
    КонецЕсли;
Исключение
    Сообщить("Произошла ошибка при выполнении HTTP-запроса: " + ОписаниеОшибки());
КонецПопытки;

Дополнительный пример: Решение на Python (для понимания логики)

Для более глубокого понимания принципов работы с веб-сервисами, давайте посмотрим на аналогичное решение, реализованное на языке Python. Хотя это не код 1С, он демонстрирует те же универсальные принципы и подход к решению проблемы, подтверждая наши выводы относительно заголовков и проверки типа содержимого.


import requests
import logging
import os
from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry 

# --- Параметры препарата (указываем здесь!) ---
drug_name = "гелофузин"
series_number = "244857651"
mnn = ""

# --- Константы ---
save_folder = r"C:\Users\Иван\Downloads"
safe_drug_name = drug_name.replace(" ", "")
save_filename = f"Выписка_{safe_drug_name}.pdf"
save_path = os.path.join(save_folder, save_filename)

# --- Логирование ---
logging.basicConfig(
    level=logging.INFO,
    format="%(asctime)s - %(levelname)s - %(message)s",
    handlers=[
        logging.FileHandler("download_log.txt", encoding="utf-8"),
        logging.StreamHandler()
    ])

def download_pdf():
    url = "https://roszdravnadzor.gov.ru/services/turnover"
    headers = {
        "Accept": "text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,/;q=0.8,application/signed-exchange;v=b3;q=0.7",
        "Accept-Encoding": "gzip, deflate, br, zstd",
        "Accept-Language": "ru,en;q=0.9,en-GB;q=0.8,en-US;q=0.7",
        "Cache-Control": "max-age=0",
        "Connection": "keep-alive",
        "Content-Type": "application/x-www-form-urlencoded",
        "Origin": "https://roszdravnadzor.gov.ru" ,
        "Referer": "https://roszdravnadzor.gov.ru/" ,
        "Sec-Fetch-Dest": "document",
        "Sec-Fetch-Mode": "navigate",
        "Sec-Fetch-Site": "same-origin",
        "Sec-Fetch-User": "?1",
        "Upgrade-Insecure-Requests": "1",
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/136.0.0.0 Safari/537.36 Edg/136.0.0.0",
        "Cookie": "uid=434360480391640124; sp_test=1; sputnik_session=1746516644741|3",
    }

    payload = {
        "q_label": drug_name,
        "dt_from": "",
        "dt_to": "",
        "q_type_ls": "",
        "q_org": "",
        "q_dt_ru_from": "",
        "q_dt_ru_to": "",
        "q_no_ru": "",
        "q_tn": "",
        "q_mnn": mnn,
        "q_series": series_number,
        "q_producer": "",
        "q_country": "",
        "pdf": "1",
    }

    # Сессия с повторами
    session = requests.Session()
    retries = Retry(
        total=3,
        backoff_factor=2,
        status_forcelist=[429, 500, 502, 503, 504],
        allowed_methods=["POST"]
    )
    adapter = HTTPAdapter(max_retries=retries)
    session.mount("https://", adapter)
    session.mount("http://", adapter)

    try:
        logging.info("Отправка POST-запроса на %s", url)
        response = session.post(url, headers=headers, data=payload, timeout=(10, 60))

        if response.status_code == 200:
            if response.headers.get('Content-Type') == 'application/pdf':
                with open(save_path, "wb") as f:
                    f.write(response.content)
                logging.info("Файл успешно сохранён: %s", save_path)
            else:
                logging.error("Ожидался PDF, но пришло: %s", response.headers.get('Content-Type'))
        else:
            logging.error("Ошибка при скачивании файла: статус %s", response.status_code)

    except requests.exceptions.RequestException as e:
        logging.exception("Ошибка при выполнении запроса: %s", str(e))

download_pdf()

Что мы можем увидеть в этом коде, применительно к 1С:

В заключение, успешная загрузка PDF-файлов с веб-сайтов в 1С требует внимательного анализа HTTP-запросов, точной настройки параметров и заголовков, а также грамотной обработки ответов сервера. Надеемся, что этот подробный разбор поможет вам решить подобные задачи в ваших проектах 1С.

← К списку