追踪 Costco 油价
Tracking Costco gas prices

原始链接: https://www.jack.bio/blog/costco-gas-tracking

由于对 Costco 缺乏集中式的油价数据感到苦恼(该公司将燃油作为“引流商品”),作者自行构建了一套定制的追踪系统。通过对 Costco 的内部 API 进行逆向工程(该 API 允许通过坐标查找仓库),作者编写了一个脚本,对全美约 600 个网点进行扫描,以获取实时数据。 该项目使用 TimescaleDB 存储这些时间序列数据,并利用 Next.js 前端进行可视化展示,成功绘制出了霍尔木兹海峡燃油危机期间的全国价格波动趋势。数据显示,尽管 Costco 采取了特定的定价策略,但其价格仍随全球市场大幅波动,在 155 天内记录了超过 38,000 次价格变动。 最终,这个项目以一种讽刺的方式收尾:在完善了一套为了节省几分钱油费的高科技系统后,作者购买了一辆电动汽车,使这套追踪工具在自己的日常通勤中失去了用武之地。不过,由此建立的数据库依然在线,为其他希望查询当地最便宜 Costco 油价的用户提供了一个可搜索的资源。

这篇 Hacker News 讨论帖围绕着关于追踪好市多(Costco)油价的文章展开。该讨论主要涵盖三个议题: **油价背后的经济学:** 用户讨论好市多加油站是否属于“亏本引流”策略。一些人认为好市多通过亏本运营加油站来带动零售客流,而另一些人则认为该公司只是通过高销量和高效运营维持低利润率,实际上很可能实现了盈亏平衡而非亏损。 **数据追踪:** 多位参与者分享了他们多年来追踪油价的个人经验。一些人利用这些数据来平息与家人的政治争论,尽管许多人也承认,这种经验证据很少能真正改变他人的想法。 **便利性的“代价”:** 讨论的很大一部分集中在省钱与省时的权衡上。许多用户认为,为了省几美元而在长队中等待是不值得的,因为投入的时间成本往往超过了金钱收益。关于避开高峰时段的建议也遭到质疑,因为许多分店始终处于繁忙状态。
相关文章

原文

The live Costco gas price dashboard

TL;DR: I tracked gas prices at 600 Costco warehouses during the Strait of Hormuz fuel crisis and mapped out how they changed.

Back in April, I lived almost exactly between two Costco warehouses, and for years I ran the same mental math every time I needed gas: which of the two was actually cheaper that week.

Unfortunately, Costco doesn't publish gas prices anywhere centrally. The official site buries them on individual warehouse pages, and my two nearest warehouses were rarely priced the same. Costco gas is famously a loss leader, typically running 15 to 20 cents below surrounding stations. That gap is usually enough to make the detour worth it, though not enough to make the choice between my two Costcos obvious.

As a commuter in Tampa, a sprawling city with almost no public transportation, I got a lot more conscious of where I filled up as gas prices climbed that spring. I started getting curious about Costco's prices specifically: were they climbing at the same rate as other gas stations, and could Costco realistically keep running gas as a loss leader if prices kept rising?

There was no good way to see that trend historically. So naturally, I built one.

Guzzling the Gas Data

Unfortunately, this was probably the easiest part of the project. I wish I had a cool reversing method to talk about like the Waffle House story, but Costco gave me very little resistance in collecting this data.

My first find when I started digging through Costco's site was AjaxGetGasPricesService, an endpoint that takes an input of warehouse IDs formatted as ID1_ID2_ID3_ID4. Underscores as an array delimiter is an interesting choice! It was useful, but not quite what I wanted; I needed the location data for each warehouse so I could map them out and figure out which was actually shorter to drive to.

Digging a little further surfaced exactly what I needed: AjaxWarehouseBrowseLookupView. Much like the name suggests, the function looks up warehouses using a latitude and longitude parameter input. Even better, the parameter populateWarehouseDetails stuffs the response with just about every piece of information you could want about a Costco: address, hours, services, food court availability (!!), and most importantly, gas prices.

GET /AjaxWarehouseBrowseLookupView
  ?latitude=27.95
  &longitude=-82.45
  &hasGas=true
  &populateWarehouseDetails=true
  &countryCode=US

The only catch with populateWarehouseDetails is the response body is massive. As much as I'd like to get every single Costco in the United States all at once, their API caps results at 50 warehouses per call and returns them sorted by distance from the lat/long you provide. To get national coverage, I needed to sweep the map.

Prices Are Sweeping the Country!

The approach was simple enough: lay out a grid of coordinates across the country, sweep each point, and deduplicate the warehouse IDs that come back. With ~600 Costco locations and 50 per response, a 3-degree grid across the continental US plus a few hand-picked points for Alaska and Hawaii gives more than enough overlap to catch every warehouse.

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

I capped how many requests could run at once so I wasn't hammering their servers all at once, and added automatic retries for the rare request that failed.

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())

The whole sweep takes just under 60 seconds and gets me every Costco gas station in the US with current prices, addresses, and coordinates in one shot!

Parking the Data Somewhere

Once I had the sweep working, I needed somewhere to put the data. Since the whole point was to watch prices move over time, this was fundamentally a time-series problem: the same ~600 warehouses, sampled over and over, forever, with every past reading kept so I could look back and see how each one changed.

Thankfully, TimescaleDB was the perfect shoe-in for this. It's an extension on top of Postgres, so I got the query language and tooling I already knew, but with time-series features layered on top. It's also free and quick to stand up (Thank you TigerData!), which is about all I ask from infrastructure on a project like this.

Once I had a place to put the data, I needed somewhere to show it off. Nothing fancy: one quick Next.js app, some Tailwind so it didn't look like a spreadsheet, a mildly frustrating afternoon fighting Cloudflare to get the Workers deployment behaving, and you have a live, searchable database of Costco gas prices.

But it's no fun to build something I can't share with others. Once I had something I was proud of, I set up a page for every station I track and let search engines index all of them, so the next time someone's standing in a Costco parking lot wondering which warehouse to drive to, they can just look it up instead of reverse-engineering an API like I did.

So... Is Gas Getting Cheaper or Not?

I started this project not long after the Strait of Hormuz scare had oil markets on edge. It's such a narrow chokepoint for the world's oil tankers that even the threat of it closing is enough to send gas prices skyrocketing.

Sure, I wanted the data for myself, but yet again there was a voice in my head that wanted to find the answer for more than just my own pocket.

Across the 155

Daily price moves

Tap or drag to explore

US-Iran deal signedJun 17, 2026

Deal collapsesJul 2, 2026

Tankers attacked againJul 23, 2026

Houthis attack tankerAug 13, 2026

June

$4.05

-$0.53 vs prior month

July

$4.03

-$0.02 vs prior month

August (so far)

$4.12

+$0.09 vs prior month

Cheapest states

  • IN$3.31
  • TX$3.38
  • TN$3.40
  • LA$3.44
  • OK$3.48

Priciest states

  • CA$5.23
  • WA$4.86
  • OR$4.46
  • NV$4.44
  • ID$4.29

So, what did I actually learn? Even the mighty Costco isn't immune to the whims of the Strait of Hormuz. A company that treats gas as a rounding error still has to move with the market… eventually.

I responded to this revelation like any rational person - after months spent perfecting a system to shave a few cents off a tank of gas, I traded in my CRV for a Tesla Model Y and now pay more in car payments every month than I could ever have saved chasing 15-cent gaps between two warehouses. At the exact moment I finally had the infrastructure to answer "which Costco should I drive to," I made the question permanently irrelevant for myself, personally, forever.

If the license plate looks familiar (or doesn't), you should go read my other post about how I snagged one of the nicest two-letter combinations in the state of Florida.

Especially thanks to Kai, Neesh, Ari, Landon, Mark, and Jaden for proofreading, and to the rest of Creamcheese Babgel. <3

联系我们 contact @ memedata.com