Price Check
What something actually costs to land in Hong Kong.
- Python
- BeautifulSoup
- extruct
I buy a fair amount from outside Hong Kong. US$249 plus US$40 shipping and a 2.5% card fee is not the same purchase as a HK$1,990 shelf price, and a UK shop's £199 quietly drops 20% VAT when it ships here. Doing that per shop, every time, is the tax on buying carefully.
The other half is the alerting. Every deal tool I've tried is a noise engine. It tells me a shop put a banner up, not whether the price is good.
So this does the sums and keeps the receipts. For each watched item it reads the shop page, strips foreign VAT when the shop removes it for HK delivery, converts to HKD, adds the card fee and shipping, and gets one comparable number per store, ranked against every price ever recorded. Right now: 18 products, 74 sources, six currencies, about 2,100 readings.
Silence is most of the product
It has buzzed 17 times in 18 days.
Every one was ranked against the item's own recorded history, not a shop calling something a sale. Reseller listings are shown but never count toward it. And it won't repeat itself. If it already told me a pillow hit HK$1,547, it stays quiet at HK$1,551.
One of the 17 was wrong. That's the next section.
Three wrong numbers
One store served whichever currency it guessed from my IP. The old code noticed the mismatch, wrote a note, then did the maths at the configured currency anyway. ¥11,400 read as S$11,400 and landed at HK$71,018. A Fortress filter returned HK$158 on one check when it reads HK$538 every other day. That one became the wrong alert.
One flag fixes both. A reading can be stored and shown but barred from every actionable path: best price, chart, aggregates, alerts. A wrong-currency price is held that way outright. So is a price more than 40% under that store's own recent median, until the next check agrees. A real cut still lands, a day later.
The third was subtler. A pillow headlined at HK$2,627 while two cheaper stores sat in the list, having merely failed their latest scan. A 403 is routine, and a failed scan was being read as no price at all. Stores are now priced by their last confirmed reading, tagged with its age, and stop vouching after seven days.
Groceries went the other way
Weekly staples are the opposite shape. Same items every week, money lost store by store rather than day by day. Scraping a supermarket per item would be fragile and rude, so that tab rides the Consumer Council's Online Price Watch open data: about 2,600 products across eight chains, refreshed daily. Four more chains sit on the roster empty, because the feed doesn't carry them.
Physical stores only, because the output is a walking route. Kai Bo, Don Don Donki and the wet markets publish no prices, so they stay off. Better absent than guessed at.
What it doesn't do
No multi-user, no affiliate anything, no discovering deals I never asked about. Those are decisions.
The gaps are real. Best Buy and Sweetwater block even a real browser, and show as failed rather than quietly dropped. Parsing rides on shop markup, so a redesign breaks a source until I fix it. About 8% of readings fail. It can mine free web archives for older prices when I add a product, but they rarely hold any. Ten readings in the database predate the tool. History is as old as the tool.
Eighteen days old. It runs nightly at 2am on my Mac, dripped across six hours so no shop sees a burst. The dashboard is a local page. 512 tests pass. Nothing to download, nothing to sign up for.
Stack: Python throughout. requests with BeautifulSoup and extruct for reading pages, because most shops declare their price in JSON-LD or microdata, and only the awkward ones need a hand-written CSS selector. Playwright drives headless Chromium for the shops a plain fetch can't read, 45 of the 74. SQLite for history, YAML for anything I'd otherwise hardcode, and a dashboard of plain HTML, CSS and JavaScript on Python's own HTTP server. No build step.