216 000 евро. Если сумма ниже, европейский муниципалитет при закупке услуг не обязан публиковать тендер на TED, центральном реестре ЕС.
Это значит, что контракты, которые реально может выиграть небольшой подрядчик, находятся в других местах: на портале конкретного муниципалитета, региона, коммунальной службы или министерства.
Задача
Более 250 000 органов власти в ЕС тратят около 2,6 трлн евро в год на работы, поставки и услуги, по оценкам Европейской комиссии. Директивы ЕС регулируют только контракты выше установленных порогов, которые с 1 января 2026 года составляют 140 000 евро для поставок и услуг центральных органов власти, 216 000 евро для местных и региональных органов и 5 404 000 евро для строительных работ. Ниже этих сумм действуют национальные правила, а публикация на TED добровольна.
Именно поэтому мониторинг тендеров вообще требует скрапинга. Если вы строите систему для тендерного агентства или строительной компании, закупки выше порога это простая часть. Все остальное это длинный хвост порталов, которые никто не проектировал для чтения машинами.
Компания BidPrime, продающая данные о госзакупках в Северной Америке, в мае 2026 года назвала версию этой проблемы в США "тысячами рабочих процессов": порталы, ограничивающие автоматический доступ, системы закупок, требующие аккаунт или сессию, вложения, хранящиеся отдельно от извещения, URL, меняющиеся без предупреждения. В этом списке нет ничего специфичного только для США.
Но главный сбой в мониторинге тендеров это не портал, который вернул отказ. Такой сбой виден в логах. Настоящая проблема это запуск, который завершился успешно, но вернул пустой результат.
Портал муниципалитета может ответить на истекшую сессию красивой страницей с кодом HTTP 200. Сообщение о технических работах может отрендериться там, где должна быть таблица результатов. Поисковый запрос, отправленный вчера, может снова вернуть первую страницу, потому что вторая страница существовала только для cookie, созданной при первом запросе. Ваш парсер находит ноль строк и записывает "сегодня новых тендеров нет". В отличие от пропущенной цены, пропущенный тендер вернуть нельзя: к моменту, когда пробел заметят, срок подачи заявок истечет, и извещение потеряет всякую ценность для участника торгов.
Подход: заставить каждый запрос подтверждать полученные данные
Начните с того, что не требует парсинга. API TED предоставляет анонимный доступ к опубликованным извещениям без ключа, поэтому данные выше пороговых значений лучше забирать оттуда. В Великобритании Find a Tender служит центральной платформой, где все государственные заказчики публикуют информацию о закупках с 24 февраля 2025 года, согласно GOV.UK. Проверьте, что уже покрывает центральный реестр, прежде чем писать парсер для портала, который его дублирует.
Для всего остального основную роль играют три правила.
1. Определите реальный ответ, включая пустой. Страница с результатами содержит заголовок столбца ("Closing date", в терминологии самого портала). Реальный день без данных содержит штатную строку пустого состояния портала. Все остальное это сбой, а не ноль. Добавьте обе строки в список validate.data.accept для request, и страница с истекшей сессией приведет к ошибке запроса вместо того, чтобы притвориться днем без публикаций. Наши движки уже не засчитывают распознанную страницу проверки за успешный ответ, но ни один движок не знает, как каждый отдельный совет формулирует таймаут (эту часть нужно указать вам). Проверка выполняется простым поиском подстроки с учетом регистра, поэтому копируйте текст со страницы, а не вводите вручную. Мы разбирали эту механику, когда правила validate стали определять успешность, и ту же проблему со стороны временных рядов в статье Когда блокировка выглядит как точка данных.
2. Сверяйте количество, которое выводит портал. Большинство списков с результатами показывают общее число совпадений ("1-20 из 143"). Распарсите это число и сравните его с количеством сохраненных строк. Если они расходятся, сбор не завершен, независимо от статус-кодов.
3. Найдите маршрут один раз, затем используйте его повторно. Порталы с пагинацией и привязкой к сессиям обычно ожидают запросы следующих страниц с того же адреса и из того же браузера. На такой платформе, как FourA, режим Auto находит рабочий маршрут для первой страницы и возвращает ее сессию: proxy ID, User-Agent и cookies из успешной попытки. Все последующие страницы идут через Single на этом выходе, что и является рабочим путем. Auto служит только для поиска маршрута.
import math, re, requests
API = "https://api.foura.ai/api"
H = {"X-API-Key": "pk_live_..."}
LIST = "https://tenders.example-council.gov/open?page={}"
PER_PAGE = 20
# A real answer is the results table or the portal's own empty-state line.
# An expired session or a maintenance notice served at 200 is neither.
CHECK = {"data": {"accept": ["Closing date", "No open tenders"]}}
def fetch(endpoint, body):
r = requests.post(f"{API}/{endpoint}", headers=H, json=body).json()
if r.get("error"):
raise RuntimeError(r["error"]) # a gap to refill, not a quiet day
return r
# Auto finds a route that works and hands back its session...
first = fetch("auto", {"url": LIST.format(1), "validate": CHECK})
proxy, ua = first["session"]["proxy"], first["session"]["userAgent"]
pages = [first["data"]]
# ...and Single replays it on the same exit for every other page.
m = re.search(r"\d+-\d+ of (\d+)", pages[0])
total = int(m.group(1)) if m else 0
for n in range(2, math.ceil(total / PER_PAGE) + 1):
pages.append(fetch("single", {
"method": "GET", "url": LIST.format(n), "proxy": proxy,
"headers": [["User-Agent", ua]], "validate": CHECK,
})["data"])
Если портал хранит состояние поиска на своем сервере, отправляйте его сессионный cookie в том же списке headers; Single возвращает каждый заголовок Set-Cookie для каждого шага редиректа. Тендерная документация обычно находится за ссылкой на скачивание в виде PDF, а управление редиректами и режим raw buffer в Single возвращают такие файлы в виде байтов, а не поврежденного текста. Еще одна полезная привычка: опрашивать каждый портал по индивидуальному расписанию. Это небольшие серверы небольших муниципалитетов, и залп из двадцати requests выглядит для них гораздо подозрительнее, чем те же двадцать, распределенные на весь день.
Результаты
- Сбор тендеров вырос на 25%. GroupBWT, компания по разработке парсеров, сообщает об этом на примере клиента, отслеживающего 50+ порталов закупок по сравнению с ручным сбором: "не потому, что тендеров стало больше, а потому, что их перестали пропускать".
- Никаких скрейперов для надпороговых закупок. Извещения TED поступают из API, не требующего ключа, поэтому каждый час работы над парсерами уходит на порталы без API.
- Пропуски выявляются, пока закупка еще открыта. Страница, не прошедшая правило accept, вызывает ошибку сразу в день сбора, и задача повторяет попытку вместо сохранения пустого дня.
- Один поиск маршрута на портал, а не на страницу. Поиск Auto выполняется один раз; каждая последующая страница запрашивается через Single по найденному маршруту выхода.
Только первый пункт представляет собой измеренный результат; остальные три описывают архитектурные изменения.
Главный вывод
В мониторинге тендеров самая дорогая запись в базе данных это "сегодня ничего нового", потому что ее нельзя исправить задним числом. Цены можно собрать завтра. Тендер, закрывшийся, пока скрейпер читал страницу с истекшей сессией, уже не вернуть.
Поэтому заставляйте задачу подтверждать нулевой результат. Страница со списком, которая не показывает таблицу, явное сообщение портала об отсутствии записей или совпадающий счетчик, пока ничего вам не сообщила. Принятие такой страницы за валидный ответ приводит к тому, что участник торгов узнает о контракте только из протокола подведения итогов.