缓存时间:
2026/08/25 19:59
# 全球燃油危机期间追踪Costco汽油价格
来源:https://www.jack.bio/blog/costco-gas-tracking
所有发布的观点和内容均为个人观点,不代表任何现任或前任雇主的立场。本站为个人维护的技术分享平台,技术内容仅供教育交流用途。
Costco实时汽油价格看板
**摘要:** 在霍尔木兹海峡燃料危机期间,我追踪了600个Costco加油站的汽油价格波动,并绘制了价格变化地图。
查看实时看板(https://www.jack.bio/costcogas)
四月时,我恰好住在两个Costco仓库之间。多年来每次需要加油时,我都会在心里进行同样的计算:这两个仓库哪个本周实际更优惠。
遗憾的是,Costco从未集中公布汽油价格。官方网站将价格分散在各个仓库页面中,而我附近两个仓库的标价往往不同。众所周知,Costco的汽油业务属于引流亏本商品,通常比周边加油站便宜15至20美分。这个差价通常足以让绕路加油值得考虑,但不足以让我在两个仓库间做出明确选择。
随着那个春天油价攀升,作为坦帕市的通勤者——这座几乎没有公共交通的扩张型城市——我对加油地点变得愈发敏感。我开始特别好奇Costco的定价策略:他们的油价是否与其他加油站同步上涨?如果油价持续攀升,Costco还能继续将汽油作为引流商品吗?
当时缺乏可靠的历史趋势观察工具。因此,我自然而然地决定构建一个追踪系统。
## 大规模采集汽油数据
遗憾的是,数据采集可能是整个项目中最简单的环节。虽然我很想分享类似华夫饼屋故事(https://www.jack.bio/blog/wafflehouse)那样的酷炫逆向工程方法,但Costco在数据采集方面几乎没有设置障碍。
初步挖掘Costco网站时,我发现了`AjaxGetGasPricesService`这个端点。它接受以`ID1_ID2_ID3_ID4`格式输入的仓库ID——用下划线作为数组分隔符是个有趣的设计!这个端点很有用,但并非完全符合需求:我还需要每个仓库的位置数据,以便进行地图标注并计算实际驾车距离。
进一步挖掘后,我找到了完全符合需求的接口:`AjaxWarehouseBrowseLookupView`。正如其名,该函数通过输入经纬度参数检索仓库信息。更妙的是,`populateWarehouseDetails`参数能在响应中包含几乎所有Costco相关信息:地址、营业时间、服务项目、美食广场可用性(!),以及最重要的汽油价格。
```
GET /AjaxWarehouseBrowseLookupView
?latitude=27.95
&longitude=-82.45
&hasGas=true
&populateWarehouseDetails=true
&countryCode=US
```
`populateWarehouseDetails`唯一的缺点是返回的响应体数据量*极其庞大*。虽然我很想一次性获取全美所有Costco数据,但该API每次请求最多返回50个仓库,且按距离排序。为了实现全国覆盖,我需要对地图进行系统性扫描。
### 全国价格扫描策略
实施方案相当简单:在全国范围内布置坐标网格,逐点扫描,并对返回的仓库ID进行去重。考虑到约600个Costco网点,每个响应返回50个,使用3度间隔的网格覆盖美国本土,再加上阿拉斯加和夏威夷的几个手动选取点,重叠范围已足够捕获所有仓库。
```python
def grid_points(step: int = 3) -> list[tuple[float, float]]:
points = []
for lat in range(25, 50, step):
for lng in range(-125, -65, step):
points.append((float(lat), float(lng)))
points.extend([
(61.2, -149.9), # 安克雷奇
(64.8, -147.7), # 费尔班克斯
(21.3, -157.8), # 檀香山
(20.9, -156.5), # 卡胡鲁伊
])
return points
```
我限制了并发请求数量以避免冲击服务器,并为极少数失败请求添加了自动重试机制。
```python
async def fetch_all_costcos() -> list[CostcoStation]:
points = grid_points()
seen: dict[int, CostcoStation] = {}
semaphore = asyncio.Semaphore(CONCURRENCY)
async with httpx.AsyncClient(headers=HEADERS, timeout=30) as client:
tasks = [fetch_point(client, lat, lng, semaphore, ts)
for lat, lng in points]
results = await asyncio.gather(*tasks)
for result in results:
for station in result:
seen.setdefault(station.id, station)
return list(seen.values())
```
整个扫描过程耗时不到60秒,一次性获取了全美所有Costco加油站的当前价格、地址和坐标!
## 数据存储方案
扫描系统运行成功后,需要确定数据存储位置。由于项目核心是追踪价格随时间的变化,这本质上是时间序列问题:相同约600个仓库被持续反复采样,且需保留所有历史记录以便回溯分析每个网点的变化趋势。
幸运的是,TimescaleDB是理想的解决方案。作为PostgreSQL的扩展,它既保留了我熟悉的查询语言和工具链,又叠加了时间序列特性。更重要的是它免费且易于部署(感谢TigerData!),这对个人技术项目而言正是我们需要的基础设施特性。
数据存储解决后,需要构建展示界面。没有采用复杂方案:基于Next.js快速构建应用,使用Tailwind CSS美化界面避免电子表格观感,经历与Cloudflare Workers部署斗争的些许挫败午后,最终形成了可搜索的Costco汽油价格实时数据库。
但独自享用成果毫无趣味。当系统初具雏形时,我为每个追踪的加油站创建独立页面,并允许搜索引擎收录所有页面。这样下次有人站在Costco停车场犹豫该去哪个仓库时,就无需像我当初那样逆向工程API,直接搜索即可获取信息。
## 最终分析:油价真的在下降吗?
项目启动于霍尔木兹海峡危机引发石油市场动荡不久。这个狭窄的咽喉要道对全球油轮运输至关重要,仅是关闭威胁就足以引发油价飙升。
当然,我需要这些数据满足个人需求,但内心总有个声音驱使我去探寻超越个人钱包的答案。
在持续追踪的155天中(实时数据!),Costco共执行了38,101次独立价格调整,日均246次变动,覆盖约600个仓库。总体来看,降价(Cuts)更胜一筹:20,535次降价对比17,566次涨价,降价比涨价多约2,969次。
**每日价格变动**
涨价次数 vs 降价次数
点击或拖动探索数据
**关键事件标注**
2026年6月17日:美伊协议签署
2026年7月2日:协议破裂
2026年7月23日:油轮再遭袭击
2026年8月13日:胡塞武装袭击油轮
那么,我究竟得出了什么结论?即便是实力雄厚的Costco也无法完全摆脱霍尔木兹海峡局势的影响。这个将汽油业务视为“四舍五入误差”的企业,最终仍需顺应市场波动……只是时间问题而已。
我以理性的方式回应了这个发现——在花费数月完善这套每箱油节省几美分的系统后,我将本田CR-V换成了特斯拉Model Y,现在每月支付的车贷比追逐两个仓库间15美分差价所能节省的总额还要多。当我终于拥有回答“该去哪个Costco”的技术架构时,我却让这个提问对我个人而言永久失去了意义。
如果觉得车牌眼熟(或不熟),建议阅读我的[另一篇文章](https://www.jack.bio/blog/licenseplate),了解如何获得佛罗里达州最优雅的双字母车牌组合。
*特别感谢Kai (https://x.com/mcmgoodall)、Neesh (https://x.com/Neesh774)、Ari (https://x.com/aridutilh)、Landon、Mark和Jaden的校对工作,以及Creamcheese Babgel团队的其他成员。 <3*
*——戴着车牌的阿尔宙斯*