DEV Community

Mr Zack
Mr Zack

Posted on

The scraper said 'Succeeded'. The column was dead for weeks.

Here's a bug class that never trips an alert: the run succeeds, the row count is right, the schema validates — and one numeric column has been 0 on every single row since the source quietly changed its API.

We hit it twice in one morning while auditing our Apify actors.

Case 1: Kalshi liquidity_dollars

Our Kalshi weather markets scraper maps liquidity_dollars from Kalshi's /markets endpoint into a liquidity column. At some point Kalshi started returning "liquidity_dollars": "0.0000" for every market — including NFL games with $800k of volume. Nothing failed. 336 rows per run, all green gates, liquidity = 0 everywhere.

The fix wasn't "remove the column". The same payload still carries yes_bid_size_fp, yes_ask_size_fp, yes_bid_dollars and yes_ask_dollars, which is enough to compute resting collateral at the top of the book:

liquidity ≈ yesBidSize × yesBid + yesAskSize × (1 − yesAsk)
Enter fullscreen mode Exit fullscreen mode

Zero extra requests, and the column is alive again. We also added a liquiditySource field (kalshi | topOfBook) so nobody has to guess where the number came from.

Case 2: Kick likes

Our Kick clips scraper had likes = 0 on 112/112 rows. We checked the raw API (list and detail endpoints, 41k-view clips): Kick itself now reports 0 likes for everything. Nothing to compute from — so the honest fix is documentation: the field stays for compatibility, the README says "rank by views / viewsPerHour instead".

The gate that catches both

We added one rule to our post-run dataset check, and it fired on the very first run:

A numeric column that is non-null on ≥ 50 rows and equals 0 on 100 % of them is a warning.

Fifteen lines of code. It's cheap because "all zeros" is almost never what a real metric looks like; it's valuable because "succeeded + right row count" is exactly the signal that lets dead columns hide.

If you maintain scrapers, add it today. Skip flags/counters that are legitimately zero (isX, position, errors) and let everything else scream.

Bonus: "cap before filter"

Same audit, different actor — an AliExpress search scraper applied maxProducts first and minSold after, so minSold: 500, maxProducts: 60 could return 16 rows. Filtering while paging (and enforcing AliExpress' own leaky "Choice" filter client-side) turned that into a full 60. Third time we've found this pattern in our own code; it's now a standing test case for every actor with a cap and a filter.

Top comments (0)