€216,000. Под тази граница европейски общински съвет, купуващ услуги, не е задължен да публикува търга си в TED, централния регистър на ЕС.
Което означава, че договорите, които малък участник реално може да спечели, се намират другаде: в портала на съответната община, регион, комунално дружество или министерство, което извършва покупката.
Предизвикателството
Над 250 000 публични органа в ЕС харчат около €2,6 трилиона годишно за строителство, доставки и услуги, според данните на Европейската комисия. Директивите на ЕС уреждат само договори над праговете, които от 1 януари 2026 г. са €140,000 за доставки и услуги на централното правителство, €216,000 за местни и регионални органи и €5,404,000 за строителство. Под тях се прилагат националните правила и публикуването в TED е доброволно.
Ето защо мониторингът на търгове изобщо е задача за scraping. Ако изграждате такова решение за консултантска компания за оферти или строителна фирма, частта над праговете е лесната. Останалата част е дълга опашка от портали, които никой не е проектирал за четене от машини.
BidPrime, която продава данни за търгове в публичния сектор в Северна Америка, нарече американската версия на това "хиляди работни процеси" през май 2026 г.: портали, които ограничават автоматизирания достъп, системи за обществени поръчки, изискващи акаунт или сесия, прикачени файлове, съхранявани отделно от обявлението, URL адреси, които се променят без предупреждение. Нищо в този списък не е специфично само за САЩ.
Но проблемът, който наистина вреди при мониторинга на търгове, не е порталът, който ви отказва достъп. Той се появява в логовете ви. Проблемът е изпълнението, което се връща със статус успех, но без никакви данни.
Общински портал може да отговори на изтекла сесия с приятелска страница при HTTP 200. Съобщение за техническа поддръжка може да се зареди на мястото, където трябва да бъде таблицата с резултати. Търсене, което сте изпратили вчера, може да ви върне отново първа страница, защото втора страница е съществувала само за бисквитката, която я е поискала. Вашият parser намира нула реда и записва "няма нови търгове днес". И за разлика от пропусната цена, пропуснатият търг не се връща: докато някой забележи пропуска, крайният срок за подаване е изтекъл и обявлението няма никаква стойност за кандидата.
Подходът: Всеки fetch трябва да докаже какво е видял
Започнете с това, което няма нужда от скрапване. API на TED предоставя анонимен достъп до публикувани обявления, без да се изисква ключ, така че извличайте обявленията над прага оттам. В Обединеното кралство Find a Tender е централната платформа, където всички възложители публикуват информация за обществени поръчки от 24 февруари 2025 г., според GOV.UK. Проверете какво вече покрива централният регистър, преди да пишете парсер за портал, който само го копира.
За всичко останало три правила вършат основната работа.
1. Дефинирайте реален отговор, включително празния. Страница с резултати съдържа своето заглавие на колона ("Closing date", по думите на самия портал). Реално празен ден съдържа съобщението за празно състояние на самия портал. Всичко останало е пропуск, а не нула. Поставете и двата низа в списъка validate.data.accept на заявката, и страница с изтекла сесия ще провали заявката, вместо да мине като ден без данни. Нашите енджини вече отказват да броят за успех страница за верификация, която разпознават, но никой енджин не знае как всеки отделен съвет формулира страницата си за изтекло време (тази част трябва да дойде от вас). Съвпадението е обикновен подниз, чувствителен към регистъра (case-sensitive), така че копирайте текста от страницата, вместо да го пренаписвате. Разгледахме механиката, когато правилата за validate започнаха да определят успеха, и същия проблем от гледна точка на времевите редове в Когато блокирането изглежда като точка от данни.
2. Проверявайте бройката, която порталът показва. Повечето списъци с резултати показват колко обявления отговарят на търсенето ("1-20 of 143"). Парснете това число и го сравнете с редовете, които сте запазили. Ако се различават, изпълнението не е приключило, независимо какво показват статус кодовете.
3. Намерете маршрута веднъж, след което го преизползвайте. Портали с пагинация и обвързани със сесия обикновено очакват следващите страници от същия адрес и същия браузър. С платформа като FourA, Auto намира маршрут, който работи за първа страница, и връща неговата сесия: proxy ID, User-Agent и бисквитките от успешния опит. Всяка следваща страница минава през 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"])
Ако порталът пази състоянието на вашето търсене на своя сървър, изпратете неговото session cookie в същия headers списък; Single връща всеки Set-Cookie header за всеки redirect hop. Тръжните документи обикновено се намират зад линк за изтегляне като PDF файлове, а контролът на пренасочванията и режимът на суров буфер на Single ги връща като байтове вместо повреден текст. Още един полезен навик: правете poll на всеки портал по негов собствен график. Това са малки сървъри, поддържани от малки общини, и двадесет requests в един burst изглеждат много по-зле за тях, отколкото двадесет, разпределени в рамките на един следобед.
Резултати
- 25% повече уловени търгове. GroupBWT, фирма за услуги по scraping, докладва това за клиент, наблюдаващ над 50 портала за обществени поръчки спрямо ръчна база, "не защото съществуваха повече търгове, а защото спряха да ги пропускат."
- Без scraper за частта над праговете. Обявленията в TED идват от API, което не изисква ключ, така че всеки час работа по parser-а отива за портали, които нямат API.
- Пропуските излизат наяве, докато обявлението е все още отворено. Страница, която не покрива правилото за приемане, генерира грешка в деня на възникване и задачата я опитва отново, вместо да записва празен ден.
- Едно откриване на маршрут за портал, а не за всяка страница. Търсенето на Auto се извършва веднъж; всяка следваща страница е Single извикване през намерения exit.
Само първата точка е измерен резултат; останалите три описват какво променя тази конфигурация.
Основен извод
При мониторинга на търгове най-скъпият запис, който можете да съхраните, е "няма нищо ново днес", защото това е единственият, който не можете да коригирате по-късно. Цените се връщат утре. Търг, който е приключил, докато вашият scraper е чел страница с изтекла сесия, не се връща.
Затова накарайте задачата да си заслужи нулите. Страница със списък, която не може да ви покаже таблицата, собствения ред на портала за празно състояние или съответстващ брой, все още не ви е казала нищо, а приемането ѝ за валиден отговор е начинът, по който кандидатът разбира за даден договор чак от обявлението за неговото възлагане.