Эта статья написана для начинающих пользователей, которые хотят разобраться в работе сервиса отдачи информации zakupki.gov.ru. Мы шаг за шагом разберем, как получить токен для физического лица, как выглядит XML-документ для запроса и как написать простую программу на Python для взаимодействия с сервисом. Это не руководство от профессионала, а скорее дневник выживания: как не сойти с ума, пока пытаешься подружиться с сервисом zakupki.gov.ru

С 1 января 2025 года доступ к FTP-серверу zakupki.gov.ru будет закрыт, и вся информация станет доступна только через сервисы отдачи данных. До этой даты физические лица могут получать общедоступную информацию через сервис https://int44.zakupki.gov.ru/eis-integration/services/getDocsLE2 без необходимости использовать токены или электронную цифровую подпись (ЭЦП). Однако с нового года работа с сервисом потребует токенов или настройки инфраструктуры для подписания запросов ЭЦП.

Открытые данные закупок — это мощный инструмент для анализа, прозрачности государственного управления и создания полезных сервисов, как коммерческих, так и общественных. Например, на их основе можно анализировать расходы государства, выявлять нарушения или автоматизировать поиск тендеров. Ранее доступ был через FTP-сервер, сейчас данную возможность закрывают, а вместо него предлагается получать данные по протоколу SOAP. У меня нет технического образования и складывается такое ощущение, что государство хочет таким ненавязчивым способом восполнить пробелы в моих знаниях.

SOAP (Simple Object Access Protocol) — это протокол обмена данными, при котором запросы и ответы представляют собой строго структурированные XML-документы. Для многих разработчиков (включая меня) это может быть первое знакомство с этим протоколом. Более того, практическая информация по работе с SOAP и конкретно с сервисом закупок zakupki.gov.ru представлена в сети крайне ограниченно.

Структура статьи:

  1. Получение токена для работы с сервисом

  2. Структура xml-документа

  3. Пример программы на python

Получение токена для работы с сервисом

Получить токен можно следуя инструкции "ИНСТРУКЦИЯ ПО ИСПОЛЬЗОВАНИЮ СЕРВИСОВ ОТДАЧИ ИНФОРМАЦИИ ЕИС ДЛЯ ЮРИДИЧЕСКИХ И ФИЗИЧЕСКИХ ЛИЦ", размещенном на zakupki.gov.ru. Но для того, чтобы не открывать лишних окон, вкратце опишу процесс здесь:

  1. Нужно перейти по адресу: https://zakupki.gov.ru/pmd/auth/welcome (естественно через поддерживаемый браузер).

  2. Проходим авторизацию через Госуслуги, выбираем "Регистрация нового потребителя машиночитаемых данных", далее "Физическое лицо, индивидуальный предприниматель", заполняем все сведения, отправляем запрос и происходит перенаправление на новую страницу с токеном.

  3. Готово! Сохраняем токен в надежное место

Пример токена: 5d035886-82af-4f98-8a74-278bf72ff457 (был указан в инструкции, не работающий)
Пример токена: 5d035886-82af-4f98-8a74-278bf72ff457 (был указан в инструкции, не работающий)

Структура xml-документа

Формирование правильного XML-документа — одна из главных сложностей при работе с сервисом. Любая ошибка, например, нарушение порядка тегов или отсутствие обязательного параметра, может привести к некорректному ответу сервера. Сервер либо возвращает сообщение об ошибке («Ошибка валидации полученного запроса по интеграционной схеме»), либо повторяет отправленный запрос без обработки.

Основные требования к XML-документам

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

  2. Каждый запрос должен содержать токен. Его необходимо указывать в элементе внутри.

  3. Использование метода. Название метода запроса указывается в внутри.

  4. Обязательные параметры для каждого метода. Список обязательных параметров для каждого метода можно найти в интеграционной схеме https://int44.zakupki.gov.ru/eis-integration/services/getDocsIP?xsd=getDocsIP-ws-api.xsd

Создание идеального XML — это как готовить торт: ингредиенты строго по списку, порядок добавления важен, а итоговая структура должна быть идеальной

Поддерживаемые методы:

  • getDocsByReestrNumberRequest – запрос формирования в ХД архивов с документами по реестровому номеру

  • getDocsByOrgRegionRequest – запрос формирования в ХД архивов с документами по региону заказчика и типу документа;

  • getDocSignaturesByUrlRequest – запрос формирования в ХД архивов с подписями документов;

  • getNsiRequest – запрос в хранилище документов (ХД) данных справочника

Далее я приведу пример работающего шаблона xml документа. Все взаимодействие с сервисом происходит через url https://int44.zakupki.gov.ru/eis-integration/services/getDocsIP. Для работы с этим сервисом каждый xml файл в soapenv:Header должен содержать элемент individualPerson_token со значение вашего токена. Пример:

<soapenv:Header>
  <individualPerson_token>5d035886-82af-4f98-8a74-278bf72ff457</individualPerson_token>
</soapenv:Header>

В основной части направляется название метода и структурные элементы, которые ему соответствуют. Для каждого запроса должен быть index, в котором есть id, дата создания и режим работы сервиса (TEST или PROD, но лучше сразу PROD указывать). Пример:

<index>
    <id>3f227a50-418d-47ea-a3f7-e9a85f3af6e7</id>
    <createDateTime>2024-12-25T16:34:29</createDateTime>
    <mode>PROD</mode>
</index>

И затем в selectionParams надо указать параметры отбора файлов.
Если мы используем getDocsByReestrNumberRequest, то надо указать тип подсистемы и реестровый номер. Пример:

<selectionParams>
    <subsystemType>PRIZ</subsystemType>
    <reestrNumber>0888200000224000038</reestrNumber>
</selectionParams>

В целом файл для метода getDocsByOrgRegionRequest будет выглядеть следующим образом:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://zakupki.gov.ru/fz44/get-docs-ip/ws">
   <soapenv:Header>
      <individualPerson_token>5d035886-82af-4f98-8a74-278bf72ff457</individualPerson_token> <!-- Здесь должен быть указан ваш токен --> 
   </soapenv:Header>
   <soapenv:Body>
      <ws:getDocsByOrgRegionRequest> <!-- Здесь указывается метод --> 
         <index> <!-- index принцип формирования был приведен ранее --> 
            <id>3f227a50-418d-47ea-a3f7-e9a85f3af6e7</id>
            <createDateTime>2024-12-25T16:34:29</createDateTime>
            <mode>PROD</mode>
         </index>
         <selectionParams> <!-- обратите внимание, для каждого метода будут свои параметры отбора, перечисленные в интеграционной схеме. Сохранение порядка параметров обязательно! --> 
		    <subsystemType>PRIZ</subsystemType>
		    <reestrNumber>0888200000224000038</reestrNumber>
		</selectionParams>
      </ws:getDocsByOrgRegionRequest>
   </soapenv:Body>
</soapenv:Envelope>

Для каждого метода существуют свои обязательные поля. Подробнее посмотреть обязательные параметры можно тут: https://int44.zakupki.gov.ru/eis-integration/services/getDocsIP?xsd=getDocsIP-ws-api.xsd. Порядок параметров важен!

Например, для метода getDocsByOrgRegionRequest я перепутал порядок и сначала у меня стоял subsystemType, а затем orgRegion и сервер выдавал ошибку несоответствия запросу интеграционной схеме. Пример правильного xml файла для этого метода:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://zakupki.gov.ru/fz44/get-docs-ip/ws">
   <soapenv:Header>
      <individualPerson_token>5d035886-82af-4f98-8a74-278bf72ff457</individualPerson_token>
   </soapenv:Header>
   <soapenv:Body>
      <ws:getDocsByOrgRegionRequest> 
         <index>
            <id>3f227a50-418d-47ea-a3f7-e9a85f3af6e8</id>
            <createDateTime>2024-12-25T16:38:45</createDateTime>
            <mode>PROD</mode>
         </index>
         <selectionParams>
			<orgRegion>72</orgRegion>
			<subsystemType>PRIZ</subsystemType>
			<documentType44>epNotificationEF2020</documentType44>
			<periodInfo>
				<exactDate>2024-12-24</exactDate>
			</periodInfo>
        </selectionParams>
      </ws:getDocsByOrgRegionRequest>
   </soapenv:Body>
</soapenv:Envelope>

Пример программы на Python

Для отправки запроса и получения результата мы будем использовать python.
Для начала импортируем библиотеки, которые пригодятся в будущем:

import uuid
import datetime
import os
import requests
import xmltodict

Для автоматизированной отправки запросов для метода , сформируем шаблон xml файла, в котором будут автоматически из кода подставляться токен, ID и время запроса и назовем его test.xml:

<soapenv:Envelope xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/" xmlns:ws="http://zakupki.gov.ru/fz44/get-docs-le/ws">
   <soapenv:Header>
      <individualPerson_token>{{ token }}</individualPerson_token>
   </soapenv:Header>
   <soapenv:Body>
      <ws:getDocsByReestrNumberRequest>
         <index>
            <id>{{ UUID }}</id>
            <createDateTime>{{ created_time }}</createDateTime>
            <mode>PROD</mode>
         </index>
         <selectionParams>
            <subsystemType>PRIZ</subsystemType>
            <reestrNumber>0888200000224000038</reestrNumber>
         </selectionParams>
      </ws:getDocsByReestrNumberRequest>
   </soapenv:Body>
</soapenv:Envelope>

Теперь нам надо получить заполненный шаблон:

file_path = 'test.xml' # расположение файла-шаблона
token = '5d035886-82af-4f98-8a74-278bf72ff457' # вставьте сюда ваше токен

with open(file_path, 'r', encoding='utf-8') as file:
    xml_content = file.read()
    generated_uuid = str(uuid.uuid4()) # уникальный id для запроса
    generated_datetime = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%dT%H:%M:%S') # текущая дата
    xml_data = xml_content.replace("{{ UUID }}", generated_uuid).replace("{{ created_time }}", generated_datetime).replace("{{ token }}", token) # заполнение шаблона

Далее нам остается только отправить данные на сервер:

url = 'https://int44.zakupki.gov.ru/eis-integration/services/getDocsIP'
headers = {
    'Content-Type': 'text/xml; charset=utf-8', 
}
response = requests.post(url, data=xml_data, headers=headers, timeout=30)

В ответ мы получаем xml файл, в котором есть ссылка на архив. Пример ответа:

<?xml version="1.0" encoding="UTF-8"?>
<soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/">   
  <soap:Header/>   
  <soap:Body>      
    <ns2:getDocsByOrgRegionResponse xmlns:ns2="http://zakupki.gov.ru/fz44/get-docs-ip/ws">         
      <index>            
        <id>8e5e2386-c1e9-499c-88e8-f23313667472</id>            
        <refId>e4858318-62c6-41c0-99a5-879cdc103b39</refId>            
        <createDateTime>2024-12-25T14:42:25.180</createDateTime>            
        <mode>PROD</mode>         
      </index>         
      <dataInfo>            
        <archiveUrl>ССЫЛКА</archiveUrl>         
      </dataInfo>      
    </ns2:getDocsByOrgRegionResponse>   
  </soap:Body></soap:Envelope>

Для парсинга xml воспользуемся библиотекой xmltodict и получим url архива:

response_content = xmltodict.parse(response.content)
url_archive = response_content['soap:Envelope']['soap:Body']['ns2:getDocsByOrgRegionResponse']['dataInfo']['archiveUrl']

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

подчеркивание - это моя работа :)
подчеркивание - это моя работа :)

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

headers = {
    'individualPerson_token': token
}
response_get_archive = requests.get(url_archive, headers=headers, timeout=120)

И далее нам остается только сохранить архив:

with open('archive.zip', 'wb') as f:
    f.write(response_get_archive.content)

Полный код программы:

import uuid
import datetime
import os
import requests
import xmltodict

file_path = 'test.xml'
token = '5d035886-82af-4f98-8a74-278bf72ff457' # вставьте сюда ваше токен

with open(file_path, 'r', encoding='utf-8') as file:
    xml_content = file.read()
    generated_uuid = str(uuid.uuid4())
    generated_datetime = datetime.datetime.strftime(datetime.datetime.now(), '%Y-%m-%dT%H:%M:%S')
    xml_data = xml_content.replace("{{ UUID }}", generated_uuid).replace("{{ created_time }}", generated_datetime).replace("{{ token }}", token)


url = 'https://int44.zakupki.gov.ru/eis-integration/services/getDocsIP'
headers = {
    'Content-Type': 'text/xml; charset=utf-8', 
}
response = requests.post(url, data=xml_data, headers=headers, timeout=30)

response_content = xmltodict.parse(response.content)
url_archive = response_content['soap:Envelope']['soap:Body']['ns2:getDocsByOrgRegionResponse']['dataInfo']['archiveUrl']
headers = {
    'individualPerson_token': token
}
response_get_archive = requests.get(url_archive, headers=headers, timeout=120)

with open('archive.zip', 'wb') as f:
    f.write(response_get_archive.content)

Надеюсь данная статья послужит толчком для изучения протокола работы с SOAP и поможет разобраться с сервисом отдачи информации и документов zakupki.gov.ru

Комментарии (7)


  1. exLH
    26.12.2024 10:20

    А почему не взять Zeep (как вариант), чтобы xml руками не собирать? https://docs.python-zeep.org/en/master/


    1. Pashasyr Автор
      26.12.2024 10:20

      Дельное замечание, но в целом для целей статьи подойдет и ручная сборка, как мне кажется)


  1. kreatiffchik
    26.12.2024 10:20

    Спасибо!

    Сразу несколько общих уточнений, наблюдений и новшеств:

    1. Битые архивы. Примерно 1 из 1000 архивов отдаётся битым. Кто автоматизирует дальше обработку - учтите. Пока не могу выловить, почему так происходит. Битый архив собирается именно с серверов ЕИС.
    2. ЕИС реализована доработка, обеспечивающая запросы методом getPublicDocsByOrgRegionRequest - ежечасных предподготовленных архивов по информации и документам. Это вместо запросов информации и документов за произвольный период времени todayInfo.
    3. Как и боялись - наблюдается падение скорости работы сервиса, когда просыпается Москва.
    4. Если кому критично - сервера отдачи информации работают по времени UTC+0.

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


    1. Pashasyr Автор
      26.12.2024 10:20

      Да, пока странноватая система если честно. Например, данные по РНП можно получить по реестровому номеру, а если пытаться получить архив РНП по региону с датой (метод getPublicDocsByOrgRegionRequest), то он всегда возвращает NoData. Понятно почему так происходит - на ftp сервере данные по РНП были в целом по всей стране, не по регионам. Но интеграционная схема позволяет делать запросы по региону и дате по РНП и получается не очень логично: схема позволяет, ошибок не выдает, данные в целом есть, но возвращает всегда NoData.


  1. Andrey_Solomatin
    26.12.2024 10:20

    Последний раз про SOAP вспоминал в этом ролике.
    https://www.youtube.com/watch?v=gR1PujzQ53Q


  1. Andrey_Solomatin
    26.12.2024 10:20

    Например, для метода getDocsByOrgRegionRequest я перепутал порядок и сначала у меня стоял subsystemType, а затем orgRegion и сервер выдавал ошибку несоответствия запросу интеграционной схеме. Пример правильного xml файла для этого метода:

    У вас есть схема, значит в процессе разработки можно включить валидацию по схеме, чтобы не было сюрпризов в ответах. А еще лучше сразу тест на это накидать. Это экономит время на стадии разработки.


  1. Andrey_Solomatin
    26.12.2024 10:20

    with open('archive.zip', 'wb') as f: f.write(response_get_archive.content)

    Из стрима в файл переливать лучше по кусочкам. А вдруг файл больше чем память?
    гляньте на https://docs.python.org/3/library/shutil.html#shutil.copyfileobj