周一至周五 09:00 - 18:00(UTC+08:00) ©2026 NST LABS TECH LTD. 保留所有权利。
如何构建一个自动化竞争对手价格监测系统 (2026)
Marcus Chen Product & Network Architect
如何建立一个自动化竞争对手价格监测系统 2026
TL;DR
一个自动化的竞争对手价格监测系统由六个小阶段串联而成 :获取竞争对手的页面,提取价格,将其标准化为一个记录形状,存储,检测自上次检查以来是否发生变化,以及在发生变化时通知某人。
最困难的阶段是获取,而不是周围的逻辑 — 一个普通的HTTP请求只能看到服务器在任何JavaScript运行之前发送的HTML,许多商店在客户端渲染价格和库存或阻止没有真实浏览器指纹的请求。
存储每次检查——包括失败的提取——使系统值得信赖。 找不到价格的运行仍应写入一个带有null价格的记录,而不是默默跳过检查,因为数据中的空白与“价格从未变化”是相同的。
这篇文章的完整管道实际上已经运行过 ,而不仅仅是描述:对一个固定页面的两次运行(一次为129.99美元,一次为114.99美元)产生了实际的存储价格历史和实际触发的警报,以下是逐字显示。
变更检测是对相同URL的两个最近存储价格的普通SQL比较——对于单一竞争对手管道,不需要单独的“监控服务”。
抓取竞争对手的公共定价页面通常风险较低,但并非没有风险 — 尊重发布的服务条款和速率限制,本文的“负责任的处理”部分明确说明了这一边界,而不是跳过它。
管道概述
本文中的系统有六个阶段,按顺序为每个被跟踪的竞争产品运行:获取页面,从返回的内容中提取价格,将其标准化为一致的记录,存储该记录,检测其是否与相同URL的最后存储价格不同,如果不同则发出警报。每个阶段都是一个小的、可独立测试的函数——它们不依赖于框架、托管仪表板或超出此处所示的特定数据库(SQLite,选择它是为了整个管道作为一个可移植的脚本运行,而不是需要外部基础设施进行跟踪)。
fetch → extract → normalize → store → detect change → alert
该管道故意不包含两件事,因为它们是单独的关注点:浏览价格历史的用户界面(任何BI工具或对SQLite文件的简单查询即可处理),以及跨不同竞争者目录的产品匹配(将“你的产品”与“他们的同类产品”匹配是一个值得专门工具的数据质量问题,而不是本文管道的获取/存储循环应尝试解决的内容)。
先决条件
Python 3.10或更高版本(下面的代码仅使用标准库——sqlite3,re,urllib.request,json,datetime——加上 用于实际的Nstproxy抓取调用,如下所示的获取阶段)。
requests
用于生产中的实际获取调用的Nstproxy Crawl API密钥;本文的验证运行因环境没有向任意域外发出的网络访问和没有有效密钥而用本地固定项替代,原因在于下面的获取阶段中解释。
用于跟踪的竞争产品URL列表,以及对于每个URL,其页面用于显示价格的特定文本模式(本文的解析器查找Price: $XX.XX;实际目标站点的实际标记需要自己的模式,并与该页面的实际输出进行检查)。
阶段 1 — 获取 一个普通的fetch()或requests.get()调用仅接收服务器在任何客户端JavaScript执行之前发送的HTML。许多实际商店在此之后渲染价格和可用性,或对看起来不像浏览器的请求返回完全不同的响应。这是一个手动滚动的抓取器通常第一次崩溃的阶段,也是一个了解渲染的、有代理支持的获取层发挥其作用的地方。本文使用了Nstproxy Crawl的 POST /api/v1/crawl/scrape端点(在docs.nstproxy.com/docs/crawl 中有文档),它渲染页面并返回干净的Markdown,而不是原始的HTML:
import requests
def fetch_page ( url ) :
response = requests . post (
"https://api.nstproxy.com/api/v1/crawl/scrape" ,
headers = { "x-api-key" : NSTPROXY_API_KEY , "Content-Type" : "application/json" } ,
json = { "url" : url , "formats" : [ "markdown" ] , "onlyMainContent" : True } ,
timeout = 60 ,
)
envelope = response . json ( )
if envelope . get ( "err" ) :
raise RuntimeError ( envelope . get ( "msg" , "crawl request failed" ) )
inner = envelope [ "data" ]
if not inner . get ( "success" ) :
raise RuntimeError ( inner . get ( "status" , "crawl did not complete" ) )
return inner [ "data" ] [ "markdown" ]
检查 err 然后 success 之后再信任有效负载在这里特别重要:Nstproxy Crawl 的文档明确说明,HTTP 200 仅确认请求已被接收,而不是目标页面已成功获取——被阻止或超时的获取仍然返回响应体,必须进行检查,而不仅仅是状态代码。
这个环境没有对任意域名的出站网络访问,也没有有效的 Nstproxy API 密钥,因此本文中的验证运行替代了一个本地 HTTP 夹具,返回 Nstproxy Crawl 文档指定的确切嵌套响应信封(外部 code/err/msg/data,内部 data.success/status,页面有效负载在 data.data.markdown)替代真实端点——在这里披露,而不是以实时调用的方式呈现。被测试的展开逻辑(err,然后 success,然后 data.data.markdown)与实际端点运行的逻辑完全相同;只是传输目标为此测试改变。
第 2 阶段 — 提取 获取阶段返回的是 Markdown,而不是结构化的价格字段,因此提取是从文本中提取已知模式的问题。真实目标页面需要检查其自己的模式与该页面的实际呈现输出;本文的夹具页面呈现 Price: $129.99,因此提取是一个简单的正则表达式:
import re
def parse_price ( markdown_text ) :
match = re . search ( r"Price:\s*\$([0-9]+\.[0-9]{2})" , markdown_text )
if not match :
return None
return float ( match . group ( 1 ) )
在匹配失败时返回 None——而不是立即引发异常——是故意的:暂时未能呈现价格的页面不应使整个管道运行崩溃,尤其是对同一批次中跟踪的其他竞争者。该 None 的后续处理将在下面的存储阶段决定,而不是在这里。
第 3 阶段 — 标准化 每个数据源最终都需要在存储和比较阶段看起来相同,无论它来自于哪个竞争者或页面格式:
from datetime import datetime , timezone
def normalize ( competitor , price , scraped_at ) :
return {
"competitor" : competitor [ "name" ] ,
"url" : competitor [ "url" ] ,
"price_usd" : price ,
"scraped_at" : scraped_at ,
}
每条记录跟踪 scraped_at,而不仅仅是每个批次,这使得后面的变更检测阶段更有意义——没有每行的时间戳,就无法判断同一 URL 的两个价格中哪个实际上是最近的。
转换和存储 存储每次检查——包括提取失败——是监控系统与偶尔向数据库写入的抓取器之间的区别。发现没有价格的运行仍然会写入一行,price_usd = NULL,因此覆盖中的间隙在数据中可显式显示为空值,而不是与“已检查且未更改”不可分辨:
def store ( conn , record ) :
conn . execute (
"INSERT INTO price_history (competitor, url, price_usd, scraped_at) VALUES (?, ?, ?, ?)" ,
( record [ "competitor" ] , record [ "url" ] , record [ "price_usd" ] , record [ "scraped_at" ] ) ,
)
conn . commit ( )
该表本身是一个单一的平面架构——对于这个规模的管道不需要连接,使用 Python 的 标准库 sqlite3 模块 ,因此不需要外部数据库服务来进行后续操作:
def init_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 的两个最近非空价格之间的比较——在这个规模下不需要单独的服务或流管道:
def detect_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 ( )
if len ( rows ) < 2 :
return None
latest_price , latest_at = rows [ 0 ]
previous_price , previous_at = rows [ 1 ]
if latest_price == previous_price :
return None
return {
"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、电子邮件、内部工具的 webhook)的地方;本文打印出该调用将发送的相同有效载荷,因此逻辑完全可见:
def alert ( change ) :
direction = "dropped" if change [ "delta" ] < 0 else "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 调用,而不是一个在目标网站添加反机器人保护的那天就安静停止工作的爬虫。
组装管道 将六个阶段连接在一起以进行一次运行是一个将每个跟踪的竞争对手通过相同链条运行的单一函数:
def run_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. 从 $129.99 降至 $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抓取启动帖子 ,并在承诺任何托管API的监测系统的获取阶段之前,查看定价 ,以了解您计划检查多少竞争对手以及多频繁。
常见问题解答 这取决于竞争对手实际更改价格的频率以及该信息的时效性——对于大多数零售类别,每天几次就足够了,而易闪销的类别可能需要每小时检查。检查的频率远高于价格实际变化的频率会浪费获取调用,而没有提供有用的信号。
问:如果竞争对手更改其页面布局,价格模式停止匹配,会发生什么?
parse_price函数返回None而不是引发异常,因此管道仍然可以继续运行,跟踪其他每个竞争对手;受影响的行将以空价格而不是过时或错误的价格存储。生产系统应在同一URL上重复返回null的提取时发出警报,因为这表明页面的标记已更改,模式需要更新——本文的单模式正则表达式是一个起点,而不是针对每个目标网站的永久解决方案。
问:这是否适用于具有动态定价或仅向登录用户显示的个性化报价的网站?
按照目前的构建不适用——该管道抓取公共产品页面,而仅向经过身份验证的帐户显示的定价超出了上述“负责任处理”部分的范围。个性化或需账户访问的定价是一种实质上不同(且更敏感)的数据收集类别,而不是公开列出的价格。
一致性和历史记录。手动检查的人往往笨拙检查,并且很少记录下他们看到的每个价格,因此没有可靠的历史记录以便后续比较;该管道存储每次检查的结果——包括未更改和失败的检查——因此变化检测阶段总是有一个真实的先前值可以与新的价格进行比较。
问:这可以追踪价格以外的更多内容吗——例如库存状态或运费?
可以——该模式可以直接推广。在第2阶段为每个附加字段添加另一个提取模式,在price_history表中添加另一列,并扩展detect_change以比较重要字段;获取、存储和警报阶段完全不需要更改。
问:针对数十个或数百个竞争对手产品运行此项的成本是多少?
那比其他任何东西都与提取量相关,因为提取、存储和比较在这个规模下都是本地进行的,实际上是免费的。在承诺特定的节奏之前,检查一下提取API的 定价 与竞争对手的数量以及计划的检查频率,因为提取调用通常是随着规模增长的唯一项目。
抓取公开可见的定价信息通常风险较低于抓取个人或账户限制的数据,但“通常风险较低”并不等同于“在任何地方都没有风险”——检查目标网站的服务条款,保持请求速率合理,并避免收集超出该管道范围的公开价格和可用性数据。这不是法律建议;如果有真正的不确定,请咨询针对特定目标网站或司法管辖区的法律顾问。
立即访问住宅、数据中心、IPv6 与 ISP 高质量代理池。 创建免费账号并立即试用 ->