Парсинг zakupki.gov.ru: как я выкручиваюсь с их XML

Парсинг zakupki.gov.ru: как я выкручиваюсь с их XML

Что не так с выгрузками

ФТП на ftp.zakupki.gov.ru выглядит просто: регионы, дни, ZIP с XML внутри. На деле всё интереснее. Документация рваная, схемы ссылаются на несуществующие типы, а форматы меняются без предупреждения. Один день тег обязательный, на следующий — уже нет.

В одном архиве спокойно лежат документы разных версий схем. XSD-валидация регулярно падает на реальных данных. ФТП иногда отдаёт пустые файлы или рвёт соединение. Если делать парсер «по книжке» со строгой валидацией — он будет ломаться каждую неделю.

Мой подход: парсер, который прощает

Я сразу решил, что данные будут грязными, а схема — устаревшей. Поэтому парсер должен:

  • вытаскивать максимум даже из кривого файла;
  • не падать на незнакомых тегах;
  • писать в лог расхождения, но не останавливать работу;
  • отправлять непонятный документ в очередь, а не терять.

На практике это значит lxml в режиме recover, несколько XPath-вариантов на каждое поле и отдельный слой проверки уже после парсинга.

Как выглядит код

Вот упрощённый пример, как я читаю извещение:

from lxml import etree
from dataclasses import dataclass, field
from typing import Optional

NS = { ... }

@dataclass
class Notice:
    purchase_number: str
    publish_date: Optional[str] = None
    ...

def parse_notice(xml_bytes: bytes):
    parser = etree.XMLParser(recover=True, huge_tree=True)
    root = etree.fromstring(xml_bytes, parser)
    # дальше ищу поля по списку XPath от конкретного к общему

Главное — recover=True и список путей для каждого поля. Если завтра переименуют тег, меняю только этот список.

Маппинг версий и очередь повторов

Чтобы не плодить if version, держу маппинг в отдельном словаре или YAML. Новую версию добавляю одной строчкой.

Битые файлы не выбрасываю. Кидаю их в таблицу parse_retry_queue. Воркер пробует заново через экспоненциальную задержку. Часто через день-два выходит обновлённая выгрузка и всё парсится само.

Регресс-тесты каждый день

Самая большая опасность — тихая потеря поля. Поэтому держу набор эталонных XML и раз в сутки прогоняю по ним парсер. Плюс мониторю долю документов с пустыми ключевыми полями. Резкий скачок — сигнал, что появилась новая разновидность XML.

Что в итоге

Такой подход работает у меня уже несколько лет без постоянного ручного вмешательства. Данные приходят чистые, можно строить дашборды и модели. Если и вам нужно собирать закупки — берите идею на вооружение, она реально спасает.