Build a targeted business research list

Turn a focused local search into a useful, reviewable business dataset.

  • 7 min read
  • Online + API

A useful business list is narrow, de-duplicated, and honest about what it doesn’t cover. This guide follows one worked example — independent coffee businesses in five Seattle neighborhoods — from brief to a spreadsheet your team can act on, using either the Online scraper or the API.

Step 1

Write a one-paragraph research brief

Answer four questions before you search. They decide your keywords, your filters, and which fields matter.

Who?

Independent specialty coffee shops and roasters — not chains.

Where?

Capitol Hill, Ballard, Fremont, Queen Anne, and Georgetown in Seattle.

Qualified if…

Open, rating ≥ 4.0, at least 25 reviews, and a website.

Used for…

Wholesale partnership research. Fields: name, area, phone, website, email, rating.

Step 2

Build the keyword grid

Combine every category with every area. Smaller areas return more complete coverage than one broad search, because each Google Maps search returns a bounded list of listings.

Keyword grid
Area ↓ / Category →specialty coffee shopscoffee roasters
Capitol Hillspecialty coffee shops in Capitol Hill, Seattlecoffee roasters in Capitol Hill, Seattle
Ballardspecialty coffee shops in Ballard, Seattlecoffee roasters in Ballard, Seattle
Fremontspecialty coffee shops in Fremont, Seattlecoffee roasters in Fremont, Seattle
Queen Annespecialty coffee shops in Queen Anne, Seattlecoffee roasters in Queen Anne, Seattle
Georgetownspecialty coffee shops in Georgetown, Seattlecoffee roasters in Georgetown, Seattle

That’s 10 keywords — it fits in one scrape on the Basic Online plan (10 keywords per scrape), or two scrapes on Lite. Generate longer grids with a few lines of code:

keywords.js
const categories = ["specialty coffee shops","coffee roasters"];
const areas = ["Capitol Hill","Ballard","Fremont","Queen Anne","Georgetown"];

const keywords = categories.flatMap((category) =>
  areas.map((area) => `${category} in ${area}, Seattle`),
);
console.log(keywords.join("\n")); // paste into New scrape, or submit via the API

Step 3

Run the searches

With the Online scraper

  1. Step 1: Paste the grid into New scrape

    One keyword per line, then select Start scraping. The job keeps running if you close the tab.

  2. Step 2: Spot-check the first keyword

    When the first keyword finishes, open View on its CSV. If results are off-target (for example lots of chains or bakeries), cancel, adjust the wording, and resubmit before spending the rest of your allowance.

  3. Step 3: Download every keyword CSV

    You’ll get keyword-1.csv … keyword-10.csv, in the order of your grid.

With the API

Send one POST /search request per keyword. Each response returns up to 20 businesses and costs one request; use page: 2 through 10 for additional pages.

batch.mjs
import { randomUUID } from "node:crypto";

// One search request per keyword. Save each idempotency key with its keyword so retries never duplicate work.
const tasks = keywords.map((q) => ({ q, idempotencyKey: randomUUID() }));

for (const task of tasks) {
  const response = await fetch("https://gmapscrawl.com/api/v1/search", {
    method: "POST",
    headers: {
      Authorization: `Bearer ${process.env.GMSCRAPER_API_KEY}`,
      "Content-Type": "application/json",
      "Idempotency-Key": task.idempotencyKey,
    },
    body: JSON.stringify({
      q: task.q, page: 1, gl: "us", extra: false,
    }),
  });
  const { data, error } = await response.json();
  if (error) throw new Error(`${task.q}: ${error.code}`);
  task.businesses = data.businesses;
  await new Promise((resolve) => setTimeout(resolve, 1_000)); // stay well under your rate limit
}

Read data.businesses directly from each response, as shown in Run your first API search.

Step 4

Clean and qualify the list

Apply the brief in this order — it keeps the logic easy to review:

#StepWhy
1Remove duplicates by Place Id (or Cid)Neighboring areas overlap; the same shop can appear in several keyword files.
2Drop closed or off-category businessesSearch results can include related categories — bakeries, grocery stores, chains.
3Apply your qualification rulesRating, review count, website — whatever your brief says.
4Check contact coverageNote which rows lack a phone or email rather than silently deleting them.
Filter the list
// 1. Import each keyword CSV into a sheet named Raw (append rows under one header).
// 2. Data → Data cleanup → Remove duplicates → select the "Place Id" column.
// 3. In a new sheet, keep well-reviewed businesses that have a website:
=FILTER(Raw!A2:AJ, Raw!W2:W>=4, Raw!V2:V>=25, Raw!AC2:AC<>"")

// Count how many rows are missing a phone number:
=COUNTBLANK(Raw!N2:N)

Step 5

Hand off with context

Before sharing the list, add the context someone else needs to trust it:

AddExample
Source keywordspecialty coffee shops in Ballard, Seattle
Job IDjob_4f1c…
Observed at2026-09-24T08:12:00Z
Coverage notes“Georgetown keyword was partial — 2 of 10 keywords incomplete.”
Qualification rules“Rating ≥ 4.0, ≥ 25 reviews, has website, independent only.”

Plan to refresh the list periodically. Businesses open, close, and change phone numbers; the observedAt value tells you how old each record is.

Good practice

Use the list responsibly

  • Collect only the fields your brief needs.
  • Verify a contact’s source page before using it for outreach.
  • Honor opt-outs and the email, calling, and privacy rules that apply where you and the business operate.
  • Don’t use scraped review text or photos in ways that misrepresent the business.
  • Read the public data notice and terms of service.

Keep going

Take the next step

Build your first focused list.

Turn a research brief into a keyword grid, run it in the cloud, and export a clean, de-duplicated dataset.