lacspace-leads 0.2.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # lacspace-leads
2
2
 
3
- **Free, open-source local-business lead finder.** Name a city, area and business type — it drives a real browser over Google Maps and collects each listing's **name, category, rating, reviews, address, phone, website** and more, then exports to **JSON, CSV or Excel**. No API keys, no paid services.
3
+ **Free, open-source local-business lead finder.** Name a city, area and business type — it drives a real browser over Google Maps and collects each listing's **name, category, rating, reviews, address, phone, website, email and social links**, then exports to **JSON, NDJSON, CSV or Excel**. No API keys, no paid services.
4
4
 
5
5
  ```bash
6
6
  npx lacspace-leads restaurants --city Kathmandu --area Baneshwor -f xlsx
@@ -11,10 +11,14 @@ That opens a browser, searches Maps for *"restaurants in Baneshwor, Kathmandu"*,
11
11
  ## Why it's different
12
12
 
13
13
  - **Free & keyless** — uses a real browser (via [Playwright](https://playwright.dev)), not a paid Places API.
14
- - **Any format** — JSON, CSV or Excel out of the box (Excel/CSV via `@lacspace/xlsx` + `@lacspace/csv`).
15
- - **Pick your fields** — only collect what you need.
14
+ - **Sweep a whole city** — comma-separate areas and it runs each search, then **merges and de-duplicates** into one list: `--area "Thamel,Baneshwor,Patan"`.
15
+ - **Rich enrichment** — visit each website to pull an **email** and links for **Facebook, Instagram, WhatsApp, LinkedIn, X, YouTube, TikTok and Telegram** a few sites in parallel.
16
+ - **CRM-ready data** — normalise phones to **E.164** (`--country NP` → `+9779…`), and tidy website URLs (unwrap Google redirects, strip `utm_*`/`fbclid`).
17
+ - **Any format** — JSON, NDJSON, CSV or Excel; write to a file or pipe to `stdout` with `-o -`.
18
+ - **Pick your fields** — choose exactly what you collect, or a ready-made **preset** (`--preset outreach`).
19
+ - **Sort & filter** — `--sort reviews --desc`, `--min-rating`, `--has-email`, and more.
16
20
  - **Permission-first** — it tells you what it's about to do and asks before opening a browser.
17
- - **Library too** — `import { searchLeads } from "lacspace-leads"`.
21
+ - **Library too** — `import { searchLeads, searchLeadsBatch } from "lacspace-leads"`.
18
22
 
19
23
  ## Install
20
24
 
@@ -32,18 +36,24 @@ npx lacspace-leads [type] [options]
32
36
 
33
37
  | Option | Meaning |
34
38
  | --- | --- |
35
- | `-t, --type <text>` | Business type / keyword, e.g. `"dental clinic"` |
36
- | `--city <text>` | City, e.g. `Kathmandu` |
37
- | `--area <text>` | Area / neighbourhood, e.g. `Baneshwor` |
39
+ | `-t, --type <text>` | Business type / keyword, e.g. `"dental clinic"`. Comma-separate for several. |
40
+ | `--city <text>` | City, e.g. `Kathmandu`. Comma-separate for several. |
41
+ | `--area <text>` | Area / neighbourhood, e.g. `Baneshwor`. Comma-separate to **sweep a whole city**. |
38
42
  | `-q, --query <text>` | Raw query, used verbatim (overrides city/area/type) |
39
- | `--fields <list>` | Columns: `name,category,rating,reviews,priceLevel,address,phone,website,email,facebook,instagram,whatsapp,plusCode,latitude,longitude,hours,mapsUrl` |
40
- | `-f, --format <fmt>` | `json` · `csv` · `xlsx` (default `json`) |
41
- | `-o, --out <file>` | Output file (default: a slug + date) |
43
+ | `--fields <list>` | Columns: `name,category,rating,reviews,priceLevel,address,phone,website,email,facebook,instagram,whatsapp,linkedin,twitter,youtube,tiktok,telegram,plusCode,latitude,longitude,hours,mapsUrl` |
44
+ | `--preset <name>` | Field bundle: `minimal` · `outreach` · `contact` · `geo` · `full` · `everything` |
45
+ | `-f, --format <fmt>` | `json` · `ndjson` · `csv` · `xlsx` (default `json`) |
46
+ | `-o, --out <file>` | Output file, or `-` for **stdout** (default: a slug + date) |
42
47
  | `--sheet <name>` | Excel sheet name (default `Leads`) |
43
- | `-n, --limit <n>` | Max listings to collect (default `60`) |
48
+ | `-n, --limit <n>` | Max listings **per search** (default `60`) |
49
+ | `--total <n>` | Cap the merged result when sweeping several searches |
44
50
  | `--no-details` | Skip opening each listing — names + Maps URLs only, much faster |
51
+ | `--sort <key>` | `rating` · `reviews` · `name` · `priceLevel` (missing values last) |
52
+ | `--desc` / `--asc` | Sort direction (default: `desc` for numbers, `asc` for name) |
45
53
  | `--delay <ms>` | Pause between listings (default `700`) |
46
54
  | `--max-time <s>` | Stop collecting after n seconds |
55
+ | `--lang <locale>` | Browser locale, e.g. `en-US`, `ne-NP` (default `en-US`) |
56
+ | `--region <cc>` | Region bias for results, e.g. `np`, `us` |
47
57
  | `--headless` | Run the browser without a visible window |
48
58
  | `-y, --yes` | Skip prompts and the browser-open confirmation |
49
59
 
@@ -52,8 +62,16 @@ npx lacspace-leads [type] [options]
52
62
  | Option | Meaning |
53
63
  | --- | --- |
54
64
  | `--emails` | Also find an email from each website |
55
- | `--socials` | Also find Facebook / Instagram / WhatsApp |
65
+ | `--socials` | Also find Facebook / Instagram / WhatsApp / LinkedIn / X / YouTube / TikTok / Telegram |
56
66
  | `--enrich` | Both of the above |
67
+ | `--concurrency <n>` | How many websites to enrich in parallel (default `3`) |
68
+
69
+ **Clean-up** (data quality):
70
+
71
+ | Option | Meaning |
72
+ | --- | --- |
73
+ | `--country <c>` | Normalise phones to **E.164** for this country — an ISO-2 code (`NP`, `US`) or a calling code (`977`) |
74
+ | `--no-clean-urls` | Don't tidy website URLs (by default it unwraps Google redirects and strips tracking params) |
57
75
 
58
76
  **Filters** (drop leads that don't qualify):
59
77
 
@@ -68,6 +86,37 @@ npx lacspace-leads [type] [options]
68
86
 
69
87
  Run with no arguments for an interactive walkthrough.
70
88
 
89
+ ## Sweep a whole city
90
+
91
+ Comma-separate areas (and/or types) and `lacspace-leads` runs each search in turn, then **merges and de-duplicates** into a single list — sorted and filtered across the whole set:
92
+
93
+ ```bash
94
+ # Every coffee shop across three neighbourhoods, phones as +977…, best-reviewed first
95
+ npx lacspace-leads "coffee shop" \
96
+ --city Kathmandu --area "Thamel,Baneshwor,Patan" \
97
+ --country NP --sort reviews --desc -f xlsx
98
+
99
+ # Two business types at once, capped at 100 unique leads total
100
+ npx lacspace-leads --type "gym,fitness studio" --city Pokhara --total 100 -f csv
101
+ ```
102
+
103
+ ## Field presets
104
+
105
+ Skip spelling out `--fields` with a ready-made bundle:
106
+
107
+ | Preset | Columns |
108
+ | --- | --- |
109
+ | `minimal` | name, phone, website |
110
+ | `outreach` | name, phone, email, website, address |
111
+ | `contact` | name, phone, email, website, facebook, instagram, whatsapp |
112
+ | `geo` | name, address, latitude, longitude, plusCode, mapsUrl |
113
+ | `full` | everything Maps shows (no website enrichment) |
114
+ | `everything` | every field, including enriched ones |
115
+
116
+ ```bash
117
+ npx lacspace-leads salons --city Pokhara --preset outreach --country NP -f csv
118
+ ```
119
+
71
120
  ## Convert anything (JSON ↔ CSV ↔ Excel)
72
121
 
73
122
  A general converter is built in — it works on any tabular file, not just leads:
@@ -113,13 +162,36 @@ const { data, binary } = serialize(leads, "xlsx");
113
162
  writeFileSync("leads.xlsx", binary ? Buffer.from(data as Uint8Array) : data);
114
163
  ```
115
164
 
165
+ Sweep several areas and merge them yourself:
166
+
167
+ ```ts
168
+ import { searchLeadsBatch } from "lacspace-leads";
169
+
170
+ const leads = await searchLeadsBatch(
171
+ [
172
+ { type: "coffee shop", city: "Kathmandu", area: "Thamel" },
173
+ { type: "coffee shop", city: "Kathmandu", area: "Baneshwor" },
174
+ ],
175
+ { limit: 40, country: "NP", sort: "reviews", enrich: true, headless: true },
176
+ );
177
+ // → one de-duplicated, sorted, E.164-normalised list
178
+ ```
179
+
116
180
  | Export | Purpose |
117
181
  | --- | --- |
118
- | `searchLeads(options)` | Run the search, resolve to `Lead[]`. |
119
- | `serialize(leads, format, fields?)` | Serialize to `{ data, binary }` for `json` / `csv` / `xlsx`. |
182
+ | `searchLeads(options)` | Run one search, resolve to `Lead[]`. |
183
+ | `searchLeadsBatch(queries, options)` | Run several searches and merge/dedupe/sort into one `Lead[]`. |
184
+ | `searchLeadsMulti(options)` | Expand comma-separated `type`/`city`/`area` and run as a batch. |
185
+ | `serialize(leads, format, fields?)` | Serialize to `{ data, binary }` for `json` / `ndjson` / `csv` / `xlsx`. |
120
186
  | `toRows(leads, fields?)` | Header-keyed rows, for your own exporter. |
187
+ | `enrichContacts(website)` / `extractEmails` / `extractSocials` | Website enrichment, on tap. |
188
+ | `cleanWebsite` / `normalizePhone` / `sortLeads` | Pure data-cleaning helpers (unit-tested). |
189
+ | `filterLeads` / `dedupeLeads` | Pure post-processing over any `Lead[]`. |
190
+ | `expandQueries` / `resolvePreset` / `FIELD_PRESETS` | Batch expansion + field presets. |
121
191
  | `composeQuery` / `mapsSearchUrl` / `normalizeFields` / `defaultFilename` | Query + helper utilities. |
122
192
 
193
+ Everything is fully typed (`Lead`, `LeadField`, `SearchOptions`, `SortKey`, `OutputFormat`, `Contacts` …) and ships dual **ESM + CJS**.
194
+
123
195
  ## Please use it responsibly
124
196
 
125
197
  Automated scraping of Google Maps is **against Google's Terms of Service**, and heavy use can trigger CAPTCHAs or temporary blocks. This tool is meant for **small, human-scale** collection of **public business information**. You are responsible for how you use it: keep volumes modest, add delays, respect local data-protection law (and don't collect or contact people in ways that break it), and don't resell scraped data as your own. If you need high volume or guaranteed reliability, use the official Google Places API.