Skip to content
2BKK

Blog · Data

Importing 7,000 business listings without junk

By 2BKK · Published 27 September 2026

bangkok.biz has about seven thousand business listings. Nobody typed them in. They come from the Google Places API through a pipeline that fetches, cleans, enriches and only then publishes. The pipeline is the product; the pages are its output. This is how it is put together and what it took to stop it from publishing junk.

The order of the steps matters

  1. Fetch. Query the API by category and area, page through the results, store the raw response as it arrived. Raw data is never edited. If a later step goes wrong, the raw copy is the way back.
  2. Clean. De-duplicate across categories (a restaurant that is also a bar appears twice), normalise names and addresses, drop listings that are closed or have no address, and map Google's types to our own categories.
  3. Enrich. Add what the API does not give you in a usable form: a readable description, opening details in our format, area assignment from coordinates, and a check that the listing's photos actually exist.
  4. Publish and sitemap. Only listings that pass the previous steps get a page and a sitemap entry. Everything else stays in the database, unpublished, until it is fixed.

Running these out of order is how the first version published half-empty pages. Enriching before cleaning enriched duplicates. Publishing before enriching produced pages with a name, a pin and nothing else.

Three traps that make a fresh import look broken

Missing reviews. The API returns a rating and a review count on a search result but not the reviews themselves, and not every listing has them at all. A page that promises reviews and shows none looks broken. The rule: the template only renders what the record has, and the record only has what we could fetch.

Invisible photos. A photo reference in the API response is not a photo. Some references return nothing, some return a placeholder, some return an image that is not of the business. Every photo is fetched, checked for size and content type, and stored under our own path before the listing is allowed to show it. Listings with no usable photo show none, and an owner-supplied photo always wins over an imported one and is never overwritten by a re-run.

Untagged listings. Google's types are broad. "Establishment" tells you nothing. Listings that end up with no category of ours are held back rather than dumped into "other", because a category page with a hundred unrelated businesses is worse than no page.

Cost control

The Places API charges per request and per field. The free credit ran out in the first month. What kept the bill sane afterwards:

  • Request only the fields the pipeline uses, and the expensive ones (photos, opening details) only for listings that passed cleaning.
  • Cache every raw response. A re-run of cleaning or enrichment never calls the API again.
  • Scrape in batches with a hard budget per run and a log of the spend, so a mistake in a query costs a batch, not a month.

Owner data beats imported data

Owners can claim a listing and upgrade it. Anything an owner enters, from the description to the featured photo, is stored separately from the imported record and takes precedence in the template. A pipeline re-run refreshes the imported side and leaves the owner's side alone. The one time this rule was not in place, a re-run replaced an owner's photo with Google's, and we heard about it.

What the pipeline could not fix

Structure is not the same as quality. Seven thousand correct pages built from the same template still read as generated at scale, and Google treated them that way in the May to June 2026 update. The demotion post covers what we changed. The pipeline is still the right way to build a directory; it is not a substitute for first-hand data on the pages that matter.

Have a site with the same problems?

We fix what we write about. Send a few lines and we tell you whether we can help.