ActorStack.dev

Publishing fill rates, including the ones that got worse

A field list is a promise. A fill rate is a measurement. This is what happened when one Actor's numbers were re-measured at n=741 instead of n=30.

By Oswaldo Carabano6 min read

Short answer

Listing a field says it exists; publishing its fill rate says how often it is actually populated, which is the number that decides whether a pipeline works. When the Nextdoor Actor's rates were re-measured over 741 businesses instead of an initial 30, several came out lower — opening hours at 57.4% rather than 77% — and the lower figures replaced the higher ones in the documentation.

Key points

  • A schema tells you what could be there; a fill rate tells you what usually is.
  • Small samples flatter a scraper. Thirty rows put opening hours at 77%; 741 rows put it at 57.4%.
  • Correcting a number downward in public is cheaper than having a user discover it in production.
  • Anything not on the measured list is explicitly not promised, which is a stronger statement than omitting it.
  • Fields measured and found too sparse — video links at 20.9%, featured flag at 17.5% — are better left out than listed as available.
On this page6 sections

Every scraper listing says which fields it returns. Almost none says how often those fields have anything in them — which is the only number that decides whether the pipeline you are planning will work.

A field list is not a measurement

“Returns business email” is true if one business in the sample had an email. It is also true if all of them did. Those are completely different products and the sentence cannot distinguish them.

The measured answer for Nextdoor is 62.6% over 741 businesses. If you were planning an email-first campaign, that single number changes your row count by a third — and you would rather learn it from documentation than from a half-built sequence.

The correction

The Nextdoor Actor's first fill-rate table was measured on 30 businesses. It looked good. When the sample grew to 741 across 5 cities and 12 categories, several fields came out lower — opening hours most sharply:

Fieldn = 30n = 741
hours_open_status~77%57.4%

The published table is the second one. The first is not quietly gone either — the correction is recorded in the README and on the methodology page, because a number that changes without a trace is indistinguishable from a number that was made up.

Why small samples flatter

Two mechanisms, and the second is the one that catches people who know about the first.

Variance. Thirty rows put a wide confidence interval around any percentage. A true rate of 57% will show up as 77% often enough that you will believe it once.

Selection. More important and less obvious. A first sample is usually one city and one or two categories — and it is usually a category the developer chose because it looked healthy. Dentists in an affluent coastal town maintain their opening hours far better than landscapers in a small inland one. The variance shrinks with more rows; the selection bias does not shrink at all until you cross cities and categories.

Which is why the sample is described as “741 businesses across 5 cities and 12 categories” rather than just “741 businesses”. The breadth is the part that makes it credible.

“Not promised” as a first-class statement

Both Actors' documentation says the same thing after the fill-rate table: anything not listed here is not promised. That is stronger than silence, and it is deliberately stronger.

Silence lets a reader assume. An explicit refusal to promise tells them a field may appear, may be useful, and must not be depended on — which is exactly the state of most fields on most scraped sources, and almost never stated.

When to omit a field entirely

Sometimes the right answer is not a low percentage but no entry at all. On coches.net, two fields were measured and left out of the documented schema:

  • Video links — 20.9%.
  • The featured flag — 17.5%.

Below roughly a quarter, a field creates more work than it saves: a pipeline that handles it has to handle the missing case on four rows out of five, and a pipeline that ignores it has a column of mostly-nulls in a warehouse forever. Listing it as “available” would be technically true and practically misleading.

How to measure your own

You do not need the developer's numbers. Run the smallest job the Actor allows — cents, under per-event pricing — and count:

fill rates from your own sample
SELECT
  COUNT(*)                                              AS rows,
  ROUND(100.0 * COUNT(phone_number) / COUNT(*), 1)      AS phone_pct,
  ROUND(100.0 * COUNT(email)        / COUNT(*), 1)      AS email_pct,
  ROUND(100.0 * COUNT(website_url)  / COUNT(*), 1)      AS website_pct
FROM businesses;

Do it across at least two cities and two categories, because that is where the selection bias lives. The documented figures are an average over a mixed sample; yours will differ, and yours is the one your pipeline has to survive.

Frequently asked questions

What is a fill rate in scraped data?
The share of rows in which a given field actually has a value. A field can be in the schema and empty on most rows, which is why the rate matters more than the field list.
Why did the published fill rates go down?
Because the sample grew from 30 businesses to 741. Several fields came out lower at the larger sample, opening hours most of all, and the documentation was corrected to the lower figures.
How large a sample do I need?
Large enough to cross cities and categories, not just rows. The 741-business measurement spans 5 cities and 12 categories, because a single category can be unrepresentative even with many rows.

Sources

Every URL below was requested and returned a page on the date shown.

  1. Operator claimchecked 18 Aug 2026
    Nextdoor Business & Reviews Scraper — Actor README and input schemaActorStack / Apify Store
  2. Operator claimchecked 18 Aug 2026
    Coches.net Car Listings & Dealer Scraper — Actor README and input schemaActorStack / Apify Store
  3. Platform docschecked 18 Aug 2026
    Dataset storageApify
Racks of network equipment in a dimly lit server room, lit blue by their indicators.
EngineeringExplainer

Cache metadata

Shared caching makes runs fast and cheap and quietly destroys trust — unless the row itself reports where it came from and when.

5 min
Paper receipts and printed documents arranged on a desk beside office stationery.
EngineeringExplainer

Pay-per-event pricing

Charging per delivered row changes what you are allowed to ship. If an error row costs the user money, every bug becomes a billing dispute.

6 min
A laptop screen showing a plain text-mode terminal with a command prompt.
EngineeringExplainer

robots.txt as a spec

A site's robots.txt is the only machine-readable statement it makes about crawling. Treating it as the specification — and downloading it on every run — is why a scraper survives.

7 min