• Oct 18, 2026
  • 3 min read

From Google Maps to a UK Lead List: Apify, Business Websites and Companies House

Once I’d decided that cold email made sense only to UK companies (why), the job became a filter: start from every business of one kind in one city, and end with the few that are (a) companies, (b) reachable at a business address, and (c) worth writing to.

Three steps, each a make target that writes a file, so any step can be re-run alone.

make scrape Q="vet" L="Exeter, UK" MAX=100       # raw.json
make enrich RUN=data/<run>                       # leads.csv
make batch  RUN=data/<run> ARGS="--limit=30"     # batch/smartlead.csv + PDFs

Step 1: scrape the listings (and nothing else)

Apify’s Google Maps scraper (compass/crawler-google-places) takes a search term and a location. The defaults collect far more than a lead list needs. An earlier experiment of mine kept 4,000 Google reviews with reviewers’ names for a few hundred businesses: personal data about private individuals, with no use at all.

So the input switches that off, and only business fields are written:

run_input = {
    "searchStringsArray": [query],
    "locationQuery": location,
    "language": "en",
    "maxCrawledPlacesPerSearch": max_places,
    "maxReviews": 0,          # reviewers are private individuals
    "maxImages": 0,
    "skipClosedPlaces": True,
}
keep = ["title", "website", "phone", "city", "postalCode", "countryCode", "categoryName"]
places = [{k: it.get(k) for k in keep} for it in items if not it.get("permanentlyClosed")]

One gotcha: apify-client 3.x returns a Run object rather than a dict (run.default_dataset_id). My first run finished on Apify and then crashed reading the result. If that happens, fetch the actor’s last_run() instead of paying for a second run.

Step 2: enrich, from public sources only

Two questions per business:

  1. Is there a business email on its own website? Not from a paid enrichment database: from the homepage and contact pages of the business itself (how, in the next post).
  2. Is it a company? The UK register, Companies House, has a free API. An active Ltd, LLP or PLC can be emailed; sole traders can’t (matching without false positives, in a later post).

The output keeps the evidence next to the answer: the page the email came from, the registered name and number, and whether the postcode matched.

Step 3: audit, and only keep sites with something to say

Each remaining website goes through the same audit engine as my public audit tool: mobile speed, search basics, security, broken links. The batch picks one concrete finding per site and writes it as a plain sentence. Sites that are fine are not emailed: no finding, no pitch (the email it feeds is a later post).

A first run, in numbers

One niche, one UK city, 30 places:

StageCount
Places found (all UK)30
With a website26
With an email on their own domain16
Confirmed company (Companies House) or “Ltd” in the name7
With a finding worth an email7

Thirty businesses became seven well-founded emails. Six of the seven sites took over 10 seconds to show their main content on a phone, which Google’s own PageSpeed test confirmed (why I double-check speed numbers is a later post).

What lives where

  • Lead data stays in data/<run>/, excluded from git, deleted after 90 days with make clean-old.
  • Keys (APIFY_TOKEN, CH_API_KEY) live in .env, readable only by me.
  • The niche, city and businesses never appear in content like this post.

Takeaways

  • Filter towards fewer, better rows. Thirty in, seven out is a good result.
  • Collect only what the email needs, starting at the scraper’s input.
  • Keep the evidence for every decision: which page gave the email, which register entry made it a company.