Как построить автоматизированную систему мониторинга цен конкурентов 2026
TL;DR
Автоматизированная система мониторинга цен конкурентов состоит из шести небольших связанных этапов: получить страницу конкурента, извлечь цену, нормализовать её в одну форму записи, сохранить, определить, изменилась ли она с последней проверки, и уведомить кого-то, когда это произойдет.
Самый сложный этап — это получение, а не логика вокруг него — обычный HTTP-запрос видит только HTML, который сервер отправляет до выполнения какого-либо JavaScript, и многие витрины отображают цену и наличие товара на стороне клиента или блокируют запросы без реального отпечатка браузера.
Сохранение каждой проверки — включая неудачные извлечения — делает систему надежной. Запуск, который не находит цену, всё равно должен записывать запись с нулевой ценой, а не молча пропускать проверку, иначе пробел в данных будет выглядеть так же, как "цена никогда не менялась".
Полный конвейер этой статьи действительно был запущен, а не просто описан: два запуска против фикстуры (один по цене $129.99, другой по цене $114.99) привели к реальной истории цен и реальному сработавшему оповещению, приведённому ниже дословно.
Обнаружение изменений — это простое SQL-сравнение двух последних сохранённых цен для одного и того же URL — отдельная "служба мониторинга" не нужна для конвейера с одним конкурентом.
Скрейпинг общедоступных страниц цен конкурентов обычно менее рискован, чем скрейпинг данных, ограниченных учетной записью, или персональных данных, но он не лишён риска — соблюдайте опубликованные условия обслуживания и лимиты частоты, и раздел "Ответственная обработка" в этой статье ясно указывает на эти границы, а не пропускает их.
Обзор конвейера
Система, описанная в этой статье, состоит из шести этапов, выполняемых в указанном порядке для каждого отслеживаемого товара конкурента: получить страницу, извлечь цену из полученного ответа, нормализовать её в согласованную запись, сохранить эту запись, определить, отличается ли она от последней сохранённой цены для того же URL, и уведомить, если это так. Каждый этап — это небольшая, независимо тестируемая функция — ни один из них не зависит от фреймворка, хостинга дашбордов или определённой базы данных, за пределами того, что здесь показано (SQLite, выбранный так, чтобы весь конвейер работал как один переносимый скрипт, а не требовал внешней инфраструктуры для поддержки).
Две вещи, которые этот конвейер намеренно не включает, так как это отдельные заботы: пользовательский интерфейс для просмотра истории цен (любой инструмент BI или простой запрос к файлу SQLite справляется с этим) и сопоставление продуктов по различным каталогам конкурентов (сопоставление "вашего продукта" с "их эквивалентным продуктом" — это проблема качества данных, требующая собственного инструмента, а не что-то, что цикл fetch/store этого конвейера тоже должен пытаться решить).
Предварительные требования
Python 3.10 или более поздняя версия (код ниже использует только стандартную библиотеку — sqlite3, re, urllib.request, json, datetime — плюс requests для реального вызова Nstproxy Crawl, показанного в этапе получения).
Ключ API Nstproxy Crawl для реального вызова извлечения в рабочем режиме; проверочный запуск этой статьи заменяет локальную фиктуру по причинам, объясненным в этапе получения ниже, поскольку эта среда не имеет исходящего сетевого доступа к произвольным доменам и не имеет действующего ключа.
Список URL-адресов продуктов конкурентов для отслеживания и для каждого из них — конкретный текстовый шаблон, который используется на его странице для отображения цены (парсер этой статьи ищет Price: $XX.XX; реальная разметка целевого сайта будет нуждаться в собственном шаблоне, проверенном на реальном выводе этой страницы).
Этап 1 — Получение
Обычный вызов fetch() или requests.get() просто получает любой HTML, который сервер отправляет до выполнения какого-либо клиентского JavaScript. Множество реальных витрин отображают цену и доступность после этого момента или возвращают другой ответ на запрос, который не выглядит как браузер. Это этап, на котором обычно ломается самодельный скрейпер в первую очередь, и где слой извлечения с учётом рендеринга и прокси получает свое место. В статье используется конечная точка POST /api/v1/crawl/scrape Nstproxy Crawl (документированная на docs.nstproxy.com/docs/crawl), которая визуализирует страницу и возвращает чистый Markdown вместо необработанного HTML:
Проверка err, а затем success перед доверием к полезной нагрузке имеет значение здесь в частности: документация Nstproxy Crawl явно указывает, что HTTP 200 подтверждает только получение запроса, а не успешное получение целевой страницы — заблокированный или истекший запрос по-прежнему возвращает тело ответа, которое необходимо проверить, а не только код состояния.
Эта среда не имеет доступа к исходящему сетевому соединению с произвольными доменами и не имеет активного ключа API Nstproxy, поэтому исполнение проверки в этой статье заменяет локальный HTTP-фиксатор, возвращающий точно такую же вложенную обертку ответа, которую указывает документация Nstproxy Crawl (внешние code/err/msg/data, внутренние data.success/status, полезная нагрузка страницы в data.data.markdown) вместо реальной конечной точки — раскрыта здесь, а не представлена как реальный вызов. Логика распаковки (err, затем success, затем data.data.markdown) идентична той, которая выполняется для реальной конечной точки; только целевой транспорт изменился для этого теста.
Этап 2 — Извлечение
Этап извлечения возвращает Markdown, а не структурированное поле цены, поэтому извлечение заключается в вы pulling известного шаблона из текста. Реальная целевая страница требует проверки своего собственного шаблона по фактическому отрендеренному тому, что отображается на этой странице; фиктивная страница этой статьи отображает Цена: $129.99, так что извлечение — это одно регулярное выражение:
import re
defparse_price(markdown_text):match= re.search(r"Price:\s*\$([0-9]+\.[0-9]{2})", markdown_text)ifnotmatch:returnNonereturnfloat(match.group(1))
Возврат None при неудачном совпадении — вместо немедленного возбуждения — является преднамеренным: страница, которая временно не может отобразить цену, не должна рушить весь процесс выполнения для всех остальных конкурентов, отслеживаемых в одной партии. То, что происходит с этим None, решается на этапе хранения ниже, а не здесь.
Этап 3 — Нормализация
Каждый источник в конечном итоге должен выглядеть одинаково для этапов хранения и сравнения, независимо от того, от какого конкурента или формата страницы он пришел:
Отслеживание scraped_at для каждой записи, а не только для партии, делает этап обнаружения изменений значимым позже — без временной метки на каждой строке невозможно определить, какая из двух цен для одного и того же URL на самом деле является более свежей.
Преобразовать и сохранить
Хранение каждой проверки — включая неудачное извлечение — и отличает систему мониторинга от скрепера, который время от времени записывает данные в базу данных. Запуск, который не находит цену, все равно записывает строку с price_usd = NULL, так что пробел в охвате виден как явный null в данных, а не как неотличимый от "проверено и без изменений":
Таблица сама по себе — это одна плоская схема — никакие соединения не нужны для такой разветвленной инфраструктуры, используя стандартную библиотеку sqlite3 Python, так что внешняя служба базы данных не требуется для следования за процессом:
definit_db(conn): conn.execute("""
CREATE TABLE IF NOT EXISTS price_history (
id INTEGER PRIMARY KEY AUTOINCREMENT,
competitor TEXT NOT NULL,
url TEXT NOT NULL,
price_usd REAL,
scraped_at TEXT NOT NULL
)
""") conn.commit()
Этап 5 — Обнаружение изменений цены
Обнаружение изменений — это сравнение двух самых последних ненулевых цен, хранящихся для одного и того же конкурента и URL — на этом масштабе не требуется отдельная служба или потоковая инфраструктура:
defdetect_change(conn, competitor_name, url): rows = conn.execute("""
SELECT price_usd, scraped_at FROM price_history
WHERE competitor = ? AND url = ? AND price_usd IS NOT NULL
ORDER BY id DESC LIMIT 2
""",(competitor_name, url),).fetchall()iflen(rows)<2:returnNone latest_price, latest_at = rows[0] previous_price, previous_at = rows[1]if latest_price == previous_price:returnNonereturn{"competitor": competitor_name,"url": url,"previous_price": previous_price,"new_price": latest_price,"delta":round(latest_price - previous_price,2),"previous_at": previous_at,"new_at": latest_at,}
Фильтрация нулевых цен в условии WHERE означает, что одна неудачная выборка не сравнивается с последней реальной ценой и не сообщается как ложное «изменение цены» — она просто пропускается до следующей успешной проверки.
Этап 6 — Оповещение
Этап оповещения — это место, где реальная система вызовет API уведомлений (Slack, электронная почта, веб-хук в внутренний инструмент); в этой статье выводится тот же полезный груз, который был бы отправлен в вызове, так что логика полностью видима:
defalert(change): direction ="dropped"if change["delta"]<0else"rose" message =(f"[price-alert] {change['competitor']}{direction} from "f"${change['previous_price']:.2f} to ${change['new_price']:.2f} "f"({change['delta']:+.2f}) — {change['url']}")print(message)return message
Быстрый обзор
Этап извлечения — это место, где большинство конвейеров мониторинга цен ломается в производстве — Nstproxy Crawl обрабатывает рендеринг JavaScript и доступ через прокси за одним вызовом API, вместо скрейпера, который тихо перестает работать в день, когда целевой сайт добавляет защиту от ботов.
Соединение шести этапов для одного запуска — это одна функция, которая запускает всех отслеживаемых конкурентов через одну и ту же цепочку:
defrun_once(competitors, conn):for competitor in competitors: markdown = fetch_page(competitor["url"]) price = parse_price(markdown) record = normalize(competitor, price, datetime.now(timezone.utc).isoformat()) store(conn, record) change = detect_change(conn, competitor["name"], competitor["url"])if change: alert(change)else:print(f"[price-check] {competitor['name']}: no change detected")
Это было выполнено дважды против страниц фикстур, описанных на Этапе 1 — первый запуск установил базовую цену без чего-либо для сравнения, второй смоделировал реальное снижение цены через день. Захваченный вывод, слово в слово:
--- Запуск 1 ---
[price-check] Trailrunner Co.: $129.99 (нет предыдущей цены для сравнения или без изменений)
--- Запуск 2 (цена изменена) ---
[price-alert] Trailrunner Co. dropped from $129.99 to $114.99 (-15.00) — https://example-shop.test/products/trailrunner-3000
--- Сохраненная история ---
('Trailrunner Co.', 129.99, '2026-08-24T07:18:20.727383+00:00')
('Trailrunner Co.', 114.99, '2026-08-24T07:18:20.730092+00:00')
Первый запуск не имеет предыдущей цены для сравнения, поэтому detect_change правильно возвращает ничего. Хранимая цена второго запуска отличается от первой, поэтому оповещение срабатывает с точным дельтой (-15.00), вычисленной непосредственно из двух сохранённых строк — не заданной заранее или утвержденной, а считанной из той же базы данных SQLite, в которую записывал этап хранения.
Чтобы запустить это по расписанию, а не вручную, стандартной записи в crontab достаточно для конвейера такого размера — никакая оркестрация не требуется для проверки нескольких конкурентов каждые несколько часов:
0 */6 * * * /usr/bin/python3 /path/to/pipeline.py >> /var/log/price-monitor.log 2>&1# запускается каждые 6 часов
Ответственное обращение
Этот конвейер собирает публично видимую информацию о ценах с страниц продуктов конкурентов — не закрытый контент, личные данные или что-либо, требующее аутентификации — что является значительно менее рискованной категорией, чем сбор личных или конфиденциальных данных. В деле hiQ против LinkedIn Девятый округ постановил, что сбор общедоступных веб-данных, как правило, не нарушает Закон США о компьютерном мошенничестве и злоупотреблении, который является наиболее близким к установленному базовому уровню для вопроса "законно ли собирать публичные страницы с ценами" в американском праве — хотя это решение касается одного конкретного статута, а не каждого возможного юридического требования, с которым может столкнуться сайт (включая требования контракта/условий обслуживания). Это не делает его бесприбыльным. Проверьте опубликованные условия обслуживания целевого сайта, прежде чем регулярно собирать с него данные, держите частоту запросов на разумном уровне, а не нагружайте страницу, гораздо чаще смены цены, и не используйте этот шаблон для сбора чего-либо, кроме публичных цен и наличия (без попыток доступа к персонализированным ценам, показанным только учетным записям с логином, и без сбора отзывов клиентов или личных данных, сопутствующих странице). Система мониторинга, созданная для законных целей конкурентной разведки, должна оставаться строго в рамках этого.
Заключение
Автоматизированная система мониторинга цен конкурентов состоит из шести проверяемых этапов, а не одного большого сборщика: получение, извлечение, нормация, хранение, обнаружение изменений и оповещение. Эта статья прошла всю цепочку дважды на реальном (основанном на фикстурах) этапе получения и реальной базе данных SQLite, и снижение цены, о котором она сообщила, произошло в результате фактического сравнения двух сохраненных строк, а не скриптованного примера. Один этап, к которому стоит относиться серьезно в производстве, — это получение. Именно здесь рендеринг JavaScript и защита от ботов действительно нарушают наивную реализацию, и поэтому это единственный этап, который эта статья рекомендует выделить на специальный уровень получения, а не реализовывать вручную. Для фона по этому слою см. пост о запуске Nstproxy Crawl и проверьте цены относительно того, сколько конкурентов и как часто вы планируете проверять их, прежде чем ввязываться в этап получения системы мониторинга для какого-либо размещенного API.
FAQ
В: Как часто должен конвейер проверять цены конкурентов?
Это зависит от того, как часто конкуренты на самом деле меняют цены и насколько чувствительной является эта информация — несколько раз в день достаточно для большинства розничных категорий, в то время как категории с частыми распродажами могут потребовать почасовых проверок. Проверка гораздо чаще, чем на самом деле меняются цены, приводит к лишним вызовам на получение данных без добавления полезного сигнала.
В: Что произойдет, если конкуренты изменят макет своей страницы, и шаблон цены перестанет соответствовать?
Функция parse_price возвращает None, а не вызывает ошибку, так что конвейер продолжает работать для всех остальных отслеживаемых конкурентов; затронутая строка хранится с нулевой ценой, а не с устаревшей или неверной. Система в производстве должна оповещать о повторных нулевых извлечениях для одного и того же URL, так как это сигнализирует о том, что разметка страницы изменилась, и шаблон требует обновления — единственный шаблон регулярного выражения в этой статье является отправной точкой, а не постоянным решением для каждого целевого сайта.
В: Работает ли это для сайтов с динамическими ценами или персонализированными предложениями, показанными только авторизованным пользователям?
Не в том виде, в котором оно построено — этот конвейер получает публичные страницы продуктов, а цены, показанные только аутентифицированной учетной записи, находятся вне рамок секции "Ответственное обращение" выше. Персонализированные или закрытые ценовые предложения составляют принципиально другую (и более чувствительную) категорию сбора данных, чем публичная цена списка.
В: Чем это отличается от простого ручного проверки цен?
Согласованность и история. Человек, проверяющий вручную, как правило, делает это нерегулярно и редко записывает каждую цену, которую он видит, так что нет надежной истории для последующего сравнения; этот конвейер сохраняет каждую проверку — включая неизменившиеся и неудачные — так что этап обнаружения изменений всегда имеет реальное предыдущее значение для сравнения новой цены.
В: Может ли это отслеживать больше, чем просто цену — например, статус запасов или стоимость доставки?
Да — шаблон обобщается напрямую. Добавьте еще один шаблон извлечения на Этапе 2 для каждого дополнительного поля, еще один столбец в таблице price_history, и расширьте detect_change, чтобы сравнивать те поля, которые имеют значение; этапы получения, хранения и оповещения при этом не нуждаются в изменениях.
В: Каковы расходы на запуск этого против десятков или сотен продуктов конкурентов?
Это масштабируется с объемом выборки больше, чем что-либо другое, поскольку извлечение, хранение и сравнение все локальны и фактически бесплатны на этом уровне. Проверьте цены API выборки в сравнении с количеством конкурентов и запланированной частотой перед тем, как привязываться к конкретной частоте, поскольку вызовы выборки обычно являются единственным элементом затрат, который увеличивается с масштабом.
В: Законно ли извлечение цен конкурентов?
Извлечение общедоступной информации о ценах обычно связано с меньшими рисками, чем извлечение личных данных или данных, ограниченных доступом к аккаунту, но "обычно с меньшими рисками" не означает "совершенно безопасно повсюду" — проверьте условия использования целевого сайта, поддерживайте разумную скорость запросов и избегайте сбора любой информации, кроме данных о публичной цене и наличии, на которые ориентирован этот процесс. Это не юридическая консультация; проконсультируйтесь с юристом по конкретному целевому сайту или юрисдикции, если есть реальные сомнения.
Создайте настоящий открытый визуальный конструктор рабочих процессов для агентов ИИ: холст React Flow в сочетании с проверенным движком выполнения топологической сортировки, протестированным от и до с реальным захваченным результатом.
Ivy Lin
Aug. 24th 2026
110M+ реальных IP с 99.9% успешных доступов
Средний отклик ~0.5с для задач высокой конкуренции
Всего от $0.1/GB
Мгновенный доступ к премиальным residential, datacenter, IPv6 и ISP пулам.