<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: orange</title>
    <description>The latest articles on DEV Community by orange (@orange_k).</description>
    <link>https://dev.to/orange_k</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F4111284%2F0f25361a-e8da-4f8c-9f39-ddd03a53adb2.png</url>
      <title>DEV Community: orange</title>
      <link>https://dev.to/orange_k</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/orange_k"/>
    <language>en</language>
    <item>
      <title>I Had an AI Agent Scrape Google Maps Instead of Writing a Scraper. Here's What It Cost.</title>
      <dc:creator>orange</dc:creator>
      <pubDate>Sat, 12 Sep 2026 03:16:57 +0000</pubDate>
      <link>https://dev.to/orange_k/i-had-an-ai-agent-scrape-google-maps-instead-of-writing-a-scraper-heres-what-it-cost-56ik</link>
      <guid>https://dev.to/orange_k/i-had-an-ai-agent-scrape-google-maps-instead-of-writing-a-scraper-heres-what-it-cost-56ik</guid>
      <description>&lt;p&gt;&lt;strong&gt;Summary:&lt;/strong&gt; I ran a Claude agent with only browser tools (no pre-written scraper code) against 60 coffee-shop listings on Google Maps across 3 US cities, and compared it to a plain Playwright script on the same pages. The agent got every field right on every checked row, but cost 190k-540k processed tokens and 23-35 seconds per place, against 6-14 seconds and zero model tokens for the script. Full numbers and caveats below.&lt;/p&gt;

&lt;p&gt;A thread in r/automation asked whether an LLM agent could just replace a scraper for a simple site instead of someone writing selectors. I didn't answer with an opinion. I ran it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The setup
&lt;/h2&gt;

&lt;p&gt;The agent was Claude Sonnet with browser tools only: open pages, search inside them, pull page text, run JavaScript in the page. No scraper was written for it in advance; the only code involved was short in-page JavaScript the agent itself wrote and ran during the runs. It searched Google Maps for "coffee near " and collected name, rating, review count, address, and phone for each result.&lt;/p&gt;

&lt;p&gt;I ran it four times: two passes over the same 10 places in Times Square, NYC (to check repeatability), one pass over 10 places in Union Square, SF, and one pass over 30 places in Millennium Park, Chicago. 60 place records total, all in the US, all on one site. An earlier Shibuya pass is excluded entirely; none of its numbers appear here.&lt;/p&gt;

&lt;p&gt;Token and context numbers came from a small script (&lt;code&gt;measure.py&lt;/code&gt;) that reads the agent's own transcript (JSONL) and sums usage fields per model call. "Processed" is the raw sum of every token count the API reported across all calls, cache reads included. "Weighted" is the same usage with plain input counted at 1x, cache reads at 0.1x, cache writes at 2x, and output at 5x, an internal cost proxy I use to compare runs, not a real price. I did not compute a dollar cost for this test.&lt;/p&gt;

&lt;p&gt;Before the agent even opens a page, its context already sits around 31k tokens (system prompt, tool definitions, instructions). Every number below is on top of that baseline.&lt;/p&gt;

&lt;h2&gt;
  
  
  The agent runs
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Run&lt;/th&gt;
&lt;th&gt;Target&lt;/th&gt;
&lt;th&gt;Rows&lt;/th&gt;
&lt;th&gt;Tool uses&lt;/th&gt;
&lt;th&gt;Model calls&lt;/th&gt;
&lt;th&gt;Wall&lt;/th&gt;
&lt;th&gt;Context end&lt;/th&gt;
&lt;th&gt;Tokens processed&lt;/th&gt;
&lt;th&gt;Weighted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;Times Square NYC, 10&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;47&lt;/td&gt;
&lt;td&gt;54&lt;/td&gt;
&lt;td&gt;292 s&lt;/td&gt;
&lt;td&gt;87k&lt;/td&gt;
&lt;td&gt;2.93M&lt;/td&gt;
&lt;td&gt;492k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;Times Square NYC, 10 (repeat)&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;63&lt;/td&gt;
&lt;td&gt;70&lt;/td&gt;
&lt;td&gt;347 s&lt;/td&gt;
&lt;td&gt;108k&lt;/td&gt;
&lt;td&gt;5.38M&lt;/td&gt;
&lt;td&gt;781k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;Union Square SF, 10&lt;/td&gt;
&lt;td&gt;10/10&lt;/td&gt;
&lt;td&gt;58&lt;/td&gt;
&lt;td&gt;67&lt;/td&gt;
&lt;td&gt;290 s&lt;/td&gt;
&lt;td&gt;77k&lt;/td&gt;
&lt;td&gt;3.64M&lt;/td&gt;
&lt;td&gt;507k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;Millennium Park Chicago, 30&lt;/td&gt;
&lt;td&gt;30/30&lt;/td&gt;
&lt;td&gt;74&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;680 s&lt;/td&gt;
&lt;td&gt;123k&lt;/td&gt;
&lt;td&gt;5.67M&lt;/td&gt;
&lt;td&gt;804k&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Per-place cost isn't flat. Run 1: about 29 s wall, 293k processed, 49k weighted per place. Run 4, which had 3x the places: about 23 s wall, 189k processed, 27k weighted per place, the cheapest per-record run of the four.&lt;/p&gt;

&lt;p&gt;That drop wasn't from the agent getting more efficient at reading pages generally. In run 4 it switched strategy: instead of using its general-purpose "find" and "get page text" tools on each place page, it ran one JavaScript call per page that pulled the values straight out of &lt;code&gt;aria-label&lt;/code&gt; attributes. That one change is most of why the per-place number fell.&lt;/p&gt;

&lt;p&gt;Runs 1 and 2 did the exact same task (same 10 places) back to back, and the numbers weren't identical: wall time was 19% higher and weighted tokens were 59% higher on the second run. That run also hit a stalled results feed (a spinner stuck at 6 loaded items) and needed a page reload plus a scripted scroll to recover, which explains most of the gap.&lt;/p&gt;

&lt;p&gt;Cost also isn't flat within a single run. For run 4, splitting its calls into thirds and summing context per third gives 1.15M, then 1.64M, then 2.88M: the last third cost about 2.5x the first, because each new call has to re-read a context that keeps growing across the whole run.&lt;/p&gt;

&lt;h2&gt;
  
  
  The script baseline
&lt;/h2&gt;

&lt;p&gt;For comparison, a Sonnet worker (a different, non-browsing agent) wrote a plain Playwright script from a one-paragraph spec, with one iteration to fix the reviews-count wait condition. After that, the script ran with zero model tokens per record; it's ordinary code. To be precise about scope: the script only re-fetched place pages whose URLs the agent had already found. It never did its own search or list discovery on Google Maps, so this is "script vs. agent on fetching a known page," not the whole search-and-collect task.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Set&lt;/th&gt;
&lt;th&gt;URLs&lt;/th&gt;
&lt;th&gt;Wall&lt;/th&gt;
&lt;th&gt;Per URL&lt;/th&gt;
&lt;th&gt;Reviews empty&lt;/th&gt;
&lt;th&gt;Failures&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;NYC 10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;61 s&lt;/td&gt;
&lt;td&gt;6.1 s&lt;/td&gt;
&lt;td&gt;6/10&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;SF 10&lt;/td&gt;
&lt;td&gt;10&lt;/td&gt;
&lt;td&gt;100 s&lt;/td&gt;
&lt;td&gt;10.0 s&lt;/td&gt;
&lt;td&gt;7/10&lt;/td&gt;
&lt;td&gt;0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Chicago 30&lt;/td&gt;
&lt;td&gt;30&lt;/td&gt;
&lt;td&gt;420 s&lt;/td&gt;
&lt;td&gt;14.0 s&lt;/td&gt;
&lt;td&gt;15/30&lt;/td&gt;
&lt;td&gt;1 (h1 never appeared)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;The script's weak spot was the review count, which loads via an async widget: it came back empty on 28 of 50 rows across these three sets, and one Chicago row failed outright (the page's &lt;code&gt;h1&lt;/code&gt; never became visible within the wait, a Playwright timeout). The agent had 0 empty reviews across all 60 rows, because it also read the search results feed, not just the place page, and could fall back to that.&lt;/p&gt;

&lt;p&gt;Per-URL time also rose over the 30-URL Chicago batch. Seven of its 30 rows took 32-36 seconds each, and those were exactly the rows that came back with an empty review count; the other rows took 2-7 seconds, apart from the one failed fetch. The 10-URL SF batch had one such 32-second row out of ten, and the NYC batch none. That's consistent with some kind of throttling or soft bot-detection building up over a long single-browser run, but I didn't isolate the cause; I'm flagging it, not claiming it.&lt;/p&gt;

&lt;h2&gt;
  
  
  Accuracy
&lt;/h2&gt;

&lt;p&gt;I checked the agent's 60 rows against the script's output field by field: name, rating, address, phone, reviews. Zero true value mismatches on name, rating, address, or phone across all 60 rows. Reviews couldn't be checked on 28 of those rows because the script itself came back empty there (not a disagreement, just missing data on the script's side), plus the one hard failure already mentioned.&lt;/p&gt;

&lt;p&gt;For the rows the script couldn't confirm, a separate browser-driven check re-read the live page directly (scoped JavaScript on the rating widget, address button, and phone button &lt;code&gt;aria-label&lt;/code&gt;s). That covered every row the script had left unverified, 29 in total including the failed row, and all 29 matched the agent's original values, including one case where the review count had genuinely ticked up between the agent's run and the recheck (3563 to 3564 on one shop), which is live drift, not an error.&lt;/p&gt;

&lt;p&gt;Put together: 60 rows times 5 fields is 300 field checks, 0 confirmed errors. To be exact about "confirmed": every value was checked by a script and, where the script came up empty, by a second automated browser read of the live page. No human visited any of these 60 shops or looked them up independently outside those two automated checks.&lt;/p&gt;

&lt;p&gt;Two things worth naming. One shop's address is shown on the page as "10036 326 W 47th St, New York, NY 10036" (an odd leading zip code, which the script also captured verbatim). Agent run 1 copied it as-is; agent run 2 wrote "326 W 47th St, New York, NY 10036", dropping the leading token without saying so. Arguably a sensible cleanup, but it is one silent edit of a source value in 300 fields, and the kind of thing you'd want an agent to flag rather than fix quietly. And the first attempt at the browser recheck itself grabbed a value from a "related places" carousel card once instead of the page's own header, before I fixed it by anchoring to the specific rating widget, a trap that applies to any extractor, agent or script.&lt;/p&gt;

&lt;h2&gt;
  
  
  What I'd conclude
&lt;/h2&gt;

&lt;p&gt;The agent produced correct data with no scraper code written in advance, using only generic browser tools (search, page-text reads, in-page JavaScript). By the last run it had converged on a script-like approach anyway: one targeted JavaScript extraction per page. The "no-code" agent's cheapest, most reliable mode still looked like a hand-written extractor; it just wrote and ran that extractor itself, per page, inside the same conversation.&lt;/p&gt;

&lt;p&gt;The honest trade-off: the plain script is roughly 1.6x to 5.7x faster per URL (6.1-14 s versus 23-35 s) and burns zero model tokens once written, but is brittle on anything that loads asynchronously (reviews here) and can hard-fail on an unexpected page structure. The agent is slower and far more expensive per record (tens to hundreds of thousands of tokens versus none), but degrades more gracefully: when one source was empty, it used another. For a recurring job, the agent's token cost scales with time per record and with how much context accumulates in a single run, which grew noticeably over a 30-item batch.&lt;/p&gt;

&lt;h2&gt;
  
  
  Can the cost be cut?
&lt;/h2&gt;

&lt;p&gt;The obvious lever is the number of model calls, since every call re-reads the whole context. So I tried the split that the agent itself drifted toward: let the plain script do the bulk, and use the agent only for the rows the script could not fill (the 28 empty review counts and the 1 failed fetch, plus 2 rows I had missed in the first pass, 29 rows in all). The agent re-read those 29 rows in the browser with a handful of batched calls.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Approach (same 50 places)&lt;/th&gt;
&lt;th&gt;Model calls&lt;/th&gt;
&lt;th&gt;Wall&lt;/th&gt;
&lt;th&gt;Tokens processed&lt;/th&gt;
&lt;th&gt;Weighted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Agent only (runs 1, 3, 4)&lt;/td&gt;
&lt;td&gt;207&lt;/td&gt;
&lt;td&gt;1,262 s&lt;/td&gt;
&lt;td&gt;12.24M&lt;/td&gt;
&lt;td&gt;1,803k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Script for all 50 + agent for the 29 gap rows&lt;/td&gt;
&lt;td&gt;34&lt;/td&gt;
&lt;td&gt;1,088 s&lt;/td&gt;
&lt;td&gt;2.88M&lt;/td&gt;
&lt;td&gt;519k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;One-time: writing the script (a Sonnet worker, 1-paragraph spec, 1 iteration, incl. a first 10-URL run)&lt;/td&gt;
&lt;td&gt;66&lt;/td&gt;
&lt;td&gt;744 s&lt;/td&gt;
&lt;td&gt;-&lt;/td&gt;
&lt;td&gt;516k&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Weighted tokens fell by 71%, wall time by 14%. Even with the one-time cost of writing the script, the first batch of 50 came out cheaper than running the agent on everything (1,035k versus 1,803k weighted).&lt;/p&gt;

&lt;p&gt;Two things inflate that number in the hybrid's favor. The script never did list discovery; it re-fetched place URLs the agent had already found, and I did not measure what discovery would cost a script. And the gap-filling agent read its 29 rows in just 4 batched browser calls (23 model calls for the first 27 rows), so part of the saving is plain batching, not the split itself. Run 4 spent 86 calls on 30 rows; the same batching applied to a full agent run would cut its cost too. So I ran the Chicago 30 again the next day with the agent following a recipe I wrote from its own earlier transcript: scroll the list with real wheel events, extract all cards in one JavaScript call, then for each place one batched call of navigate, wait, and a JavaScript read of the aria-labels.&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Chicago 30&lt;/th&gt;
&lt;th&gt;Model calls&lt;/th&gt;
&lt;th&gt;Wall&lt;/th&gt;
&lt;th&gt;Context end&lt;/th&gt;
&lt;th&gt;Tokens processed&lt;/th&gt;
&lt;th&gt;Weighted&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;Run 4: agent chose its own method&lt;/td&gt;
&lt;td&gt;86&lt;/td&gt;
&lt;td&gt;680 s&lt;/td&gt;
&lt;td&gt;123k&lt;/td&gt;
&lt;td&gt;5.67M&lt;/td&gt;
&lt;td&gt;804k&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Run 5b: agent following a written recipe&lt;/td&gt;
&lt;td&gt;41&lt;/td&gt;
&lt;td&gt;512 s&lt;/td&gt;
&lt;td&gt;92k&lt;/td&gt;
&lt;td&gt;2.26M&lt;/td&gt;
&lt;td&gt;623k&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Calls fell by 52% and processed tokens by 60%, but weighted tokens only by 23%: what remains is output and cache writes, which batching does not remove. Two scroll batches were wasted because my scroll coordinate missed the list panel, and one small screenshot was needed to see why. The honest framing is that run 5b measures an agent following a human-written recipe, which is most of the way to a script. Accuracy held: the same no-model script confirmed name, rating, address and phone on the 28 rows it could fetch (it timed out on 2, both branches of the same chain), and the review counts it left empty match the previous day's verified values within live drift. A browser re-read confirmed the two rows the script failed on (name, rating, review count, address and phone all matched).&lt;/p&gt;

&lt;h2&gt;
  
  
  What I did not test
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;Anything beyond these 60 places across 3 US cities on one site. No other verticals, countries, or the earlier Shibuya pass.&lt;/li&gt;
&lt;li&gt;List discovery cost for the script: it only re-fetched place-detail URLs the agent had already found, never its own search or scroll-and-collect.&lt;/li&gt;
&lt;li&gt;Whether the per-URL slowdown in the 30-item batch was really throttling; I noticed the pattern but didn't isolate the cause.&lt;/li&gt;
&lt;li&gt;Dollar cost. Weighted numbers are an internal proxy, not a price.&lt;/li&gt;
&lt;li&gt;Thousands of records, or any site besides Google Maps.&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This was a personal measurement, not a client project or anything running in production.&lt;/p&gt;

&lt;p&gt;I also publish Google Maps related actors on Apify Store (&lt;a href="https://apify.com/reverenced_garnet" rel="noopener noreferrer"&gt;https://apify.com/reverenced_garnet&lt;/a&gt;).&lt;/p&gt;

</description>
      <category>ai</category>
      <category>webscraping</category>
      <category>playwright</category>
      <category>claude</category>
    </item>
    <item>
      <title>Checking If a Business's Google Profile Actually Matches Its Own Website</title>
      <dc:creator>orange</dc:creator>
      <pubDate>Sun, 06 Sep 2026 03:50:33 +0000</pubDate>
      <link>https://dev.to/orange_k/checking-if-a-businesss-google-profile-actually-matches-its-own-website-413o</link>
      <guid>https://dev.to/orange_k/checking-if-a-businesss-google-profile-actually-matches-its-own-website-413o</guid>
      <description>&lt;p&gt;If you do local SEO work, you've run into this: a Google Business Profile says one phone number, the website footer says another, and nobody noticed until a customer called the wrong number. Or the postal code on the GBP listing is a leftover from an old office. This kind of drift is called a NAP (Name, Address, Phone) inconsistency, and it's widely cited as a local search ranking factor. But checking it by hand means opening every listing and every website side by side.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;&lt;a href="https://apify.com/reverenced_garnet/apify-gmaps-nap-checker" rel="noopener noreferrer"&gt;Google Maps NAP Consistency Checker&lt;/a&gt;&lt;/strong&gt;, an Apify Actor that takes Google Maps scraper output, fetches each business's own website (lightly: homepage plus one likely subpage), and checks whether the name, postal code, and phone number on the Google Business Profile actually show up on the site.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does, and what it doesn't
&lt;/h2&gt;

&lt;p&gt;This Actor checks one thing: does a business's own website agree with its Google Business Profile on name, postal code, and phone number. It does not check third-party directories (Yelp, Facebook, etc.). That's a different problem with a different competitor landscape. It does not crawl an entire website; it fetches at most two pages per business (homepage, plus a subpage if one with a keyword like "contact" or "about" is linked from it). It does not use an LLM. It's regex and string matching against fetched text, which makes it fast, cheap, and predictable. There's no model that can hallucinate a match that isn't there.&lt;/p&gt;

&lt;p&gt;Businesses with no independent website (only a social profile, or nothing) are skipped entirely, because there's nothing to fetch and compare against.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works
&lt;/h2&gt;

&lt;p&gt;For each place with a real website, the Actor:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Checks &lt;code&gt;robots.txt&lt;/code&gt; for that domain before fetching anything, and skips the business if the checker's user agent isn't allowed.&lt;/li&gt;
&lt;li&gt;Fetches the homepage HTML (up to 3 MB), strips &lt;code&gt;&amp;lt;script&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;style&amp;gt;&lt;/code&gt;, and comments before converting to text, so JavaScript variables and tracking IDs don't get misread as phone numbers.&lt;/li&gt;
&lt;li&gt;Looks for an internal link containing a keyword like "contact," "about," "access," or "info" (also handles Japanese equivalents), and fetches that one extra page if found. Mail and tel links and JS pseudo-links are excluded from that search. An early version tried to fetch &lt;code&gt;mailto:info@...&lt;/code&gt; as if it were a page URL, which is exactly the kind of bug you only find on real sites.&lt;/li&gt;
&lt;li&gt;Pulls out &lt;code&gt;href="tel:..."&lt;/code&gt; numbers directly (a strong signal), plus any phone-shaped strings in the visible text, covering Japanese formats (&lt;code&gt;03-6434-9090&lt;/code&gt;) and US formats (&lt;code&gt;(512) 828-3835&lt;/code&gt;, &lt;code&gt;+1 512-828-3835&lt;/code&gt;).&lt;/li&gt;
&lt;li&gt;Compares postal code and phone number found on the site against what the Google Business Profile has, using the scraper's own &lt;code&gt;postalCode&lt;/code&gt; field when available, with digit-only and hyphenated variants both checked so formatting differences don't cause false mismatches.&lt;/li&gt;
&lt;li&gt;Checks whether a meaningful fraction of the business name's word tokens appear anywhere on the page, as a rough "is this even the right site" sanity check.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Every result also gets a &lt;code&gt;changeStatus&lt;/code&gt;: run it again later on the same list and matches that were previously flagged as mismatched but are now fine get tagged &lt;code&gt;FIXED&lt;/code&gt;. Useful for proving a flagged issue actually got fixed on a later run.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Apify Console
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Run a Google Maps scraper (e.g. &lt;a href="https://apify.com/compass/crawler-google-places" rel="noopener noreferrer"&gt;Google Maps Scraper&lt;/a&gt;) for the businesses you want to audit.&lt;/li&gt;
&lt;li&gt;Open &lt;a href="https://apify.com/reverenced_garnet/apify-gmaps-nap-checker" rel="noopener noreferrer"&gt;Google Maps NAP Consistency Checker&lt;/a&gt;, paste that dataset array into the &lt;strong&gt;Google Maps places&lt;/strong&gt; field.&lt;/li&gt;
&lt;li&gt;Click Start. You're billed per business actually checked. Places with no independent website are skipped for free.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Chaining two Actors with apify-client (Python)
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;apify_client&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;ApifyClient&lt;/span&gt;

&lt;span class="n"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;token&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;YOUR_APIFY_TOKEN&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;

&lt;span class="c1"&gt;# 1. Scrape a set of businesses
&lt;/span&gt;&lt;span class="n"&gt;scrape_run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;compass/crawler-google-places&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;searchStringsArray&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;plumber in Austin, TX&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;maxCrawledPlacesPerSearch&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;places&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;scrape_run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="c1"&gt;# 2. Check NAP consistency for each business's own website
&lt;/span&gt;&lt;span class="n"&gt;nap_run&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reverenced_garnet/apify-gmaps-nap-checker&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
    &lt;span class="n"&gt;run_input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;places&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="n"&gt;places&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;list&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;nap_run&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]).&lt;/span&gt;&lt;span class="nf"&gt;iterate_items&lt;/span&gt;&lt;span class="p"&gt;())&lt;/span&gt;

&lt;span class="n"&gt;mismatches&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;results&lt;/span&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;r&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;reasons&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]]&lt;/span&gt;
&lt;span class="nf"&gt;print&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;mismatches&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; of &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="nf"&gt;len&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; checked businesses have a NAP issue&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  Input
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;places&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;array (required)&lt;/td&gt;
&lt;td&gt;Place objects from a Google Maps scraper's dataset output. Only places with their own (non-social-media) website are checked. The rest are skipped.&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Output
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Ultimate Plumber"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"placeId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ChIJv79rmEfzGGARhXlSEd8ceiQ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"fetchedOk"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"postalMatch"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"phoneStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"match"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"nameFoundOnSite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"changeStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"FIXED"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[]&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Business name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;placeId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Google Maps place ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;fetchedOk&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;Whether the website could be fetched at all (&lt;code&gt;false&lt;/code&gt; means every other field is unknown, not "matching")&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;postalMatch&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean or null&lt;/td&gt;
&lt;td&gt;Whether the GBP postal code appears on the site; &lt;code&gt;null&lt;/code&gt; if there wasn't enough data on either side to compare&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;phoneStatus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;match&lt;/code&gt;, &lt;code&gt;mismatch&lt;/code&gt;, &lt;code&gt;gbp_missing_on_site_found&lt;/code&gt;, &lt;code&gt;site_missing_gbp_has&lt;/code&gt;, &lt;code&gt;both_missing&lt;/code&gt;, or &lt;code&gt;unknown&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;nameFoundOnSite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean or null&lt;/td&gt;
&lt;td&gt;Whether most of the business name's word tokens appear on the page&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;changeStatus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NEW&lt;/code&gt;, &lt;code&gt;UPDATED&lt;/code&gt;, &lt;code&gt;UNCHANGED&lt;/code&gt;, or &lt;code&gt;FIXED&lt;/code&gt; versus the previous run on this Actor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reasons&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;array&lt;/td&gt;
&lt;td&gt;Plain-English list of what didn't match&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Note that &lt;code&gt;fetchedOk: false&lt;/code&gt; and &lt;code&gt;postalMatch: null&lt;/code&gt; are deliberately kept separate from a plain "no mismatch found." If the site couldn't be fetched, or there wasn't a postal code to compare on one side, the Actor says so instead of quietly reporting a false match.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use case: auditing NAP consistency before a local SEO engagement
&lt;/h2&gt;

&lt;p&gt;Hypothetical workflow: a common first step for a local SEO agency taking on a new client, or a freelancer building a pitch:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Scrape the client's own listing (or every location, for a multi-location business) with a Google Maps scraper.&lt;/li&gt;
&lt;li&gt;Run it through this Actor to see whether the phone number and address on the GBP listing actually match the website.&lt;/li&gt;
&lt;li&gt;Use &lt;code&gt;phoneStatus: gbp_missing_on_site_found&lt;/code&gt; as a quick, low-effort recommendation: "your GBP is missing a phone number your own site already lists, this is a five-minute fix."&lt;/li&gt;
&lt;li&gt;Schedule it monthly with Apify's Scheduler if you're monitoring an account over time, and use &lt;code&gt;changeStatus: FIXED&lt;/code&gt; as evidence the fixes actually got made.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The same pattern applies to a directory or citation-management tool that wants a quick pre-check on whether a business's own site agrees with itself before flagging bigger discrepancies elsewhere.&lt;/p&gt;

&lt;h2&gt;
  
  
  A note on scope and responsible use
&lt;/h2&gt;

&lt;p&gt;This Actor fetches at most two pages per business, respects &lt;code&gt;robots.txt&lt;/code&gt; for the domain being checked, and identifies itself with its own user agent string. It's not a general-purpose crawler. It only looks at data that's already public on the business's own Google Business Profile and website. It's still worth being explicit that finding a phone number or address on a public page doesn't by itself give you consent to use it for outreach; if you use anything found here for contacting a business, you're responsible for complying with applicable law in your jurisdiction.&lt;/p&gt;

&lt;h2&gt;
  
  
  Pricing
&lt;/h2&gt;

&lt;p&gt;Pay-per-event: $0.01 when a run starts, plus $0.01 per business actually checked (only businesses that had a website to fetch). Auditing 100 locations costs about $1.01. Places with no independent website are skipped and not charged. Current pricing is always on the Store page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The Actor is live on the Apify Store: &lt;a href="https://apify.com/reverenced_garnet/apify-gmaps-nap-checker" rel="noopener noreferrer"&gt;Google Maps NAP Consistency Checker&lt;/a&gt;. I built this as part of a small set of post-processing Actors for Google Maps scraper output. If you're already running a scraper for local SEO or lead work, this is a cheap add-on step to catch a class of embarrassing public-facing errors that's otherwise easy to miss.&lt;/p&gt;

</description>
      <category>apify</category>
      <category>python</category>
      <category>seo</category>
      <category>automation</category>
    </item>
    <item>
      <title>Turning Google Maps Scraper Output into a 0-100 Lead Priority Score (No Scraping, No LLM)</title>
      <dc:creator>orange</dc:creator>
      <pubDate>Sat, 05 Sep 2026 23:56:11 +0000</pubDate>
      <link>https://dev.to/orange_k/turning-google-maps-scraper-output-into-a-0-100-lead-priority-score-no-scraping-no-llm-e3p</link>
      <guid>https://dev.to/orange_k/turning-google-maps-scraper-output-into-a-0-100-lead-priority-score-no-scraping-no-llm-e3p</guid>
      <description>&lt;p&gt;If you've ever looked at a lead-gen pipeline for a local SEO or web design agency, you know the pattern: scrape a bunch of businesses off Google Maps, then manually eyeball which ones are worth calling. Missing website? Good lead. Unclaimed Google Business Profile? Also good. Both? Great lead. But sorting hundreds or thousands of rows by hand on multiple criteria doesn't scale.&lt;/p&gt;

&lt;p&gt;I built &lt;strong&gt;&lt;a href="https://apify.com/reverenced_garnet/apify-gmaps-lead-priority" rel="noopener noreferrer"&gt;Google Maps Lead Priority Scorer&lt;/a&gt;&lt;/strong&gt;, an Apify Actor that takes the JSON output you already have from a Google Maps scraper and turns it into a single sortable &lt;code&gt;priorityScore&lt;/code&gt; (0-100) per business, plus a plain-English list of reasons. It does no scraping and makes no LLM or external API calls — it's pure post-processing of data you already paid to collect.&lt;/p&gt;

&lt;h2&gt;
  
  
  The problem with single-signal lead lists
&lt;/h2&gt;

&lt;p&gt;Most "find leads with no website" tools give you exactly one signal. But a business that has no website &lt;strong&gt;and&lt;/strong&gt; an unclaimed GBP listing &lt;strong&gt;and&lt;/strong&gt; fewer reviews than its neighbors is a categorically hotter lead than one that's just missing a website. If you're running four separate scrapers/filters and merging spreadsheets by hand, you're doing manual work a scoring function should do for you.&lt;/p&gt;

&lt;p&gt;This Actor combines four signals into one number:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Signal&lt;/th&gt;
&lt;th&gt;Max points&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;No independent website (a bare social-media link doesn't count)&lt;/td&gt;
&lt;td&gt;35&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Unclaimed Google Business Profile&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Other missing profile fields (phone, description, hours, photos, categories)&lt;/td&gt;
&lt;td&gt;25&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;Review count below the median for similar nearby businesses&lt;/td&gt;
&lt;td&gt;15&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Scores map to tiers: &lt;strong&gt;Hot&lt;/strong&gt; (≥70), &lt;strong&gt;Warm&lt;/strong&gt; (40-69), &lt;strong&gt;Cold&lt;/strong&gt; (&amp;lt;40). Businesses marked permanently closed on Google Maps are dropped automatically before scoring, so you never waste an outreach call on a place that no longer exists.&lt;/p&gt;

&lt;h2&gt;
  
  
  How it works: no scraping, no LLM
&lt;/h2&gt;

&lt;p&gt;This is the detail that matters most for a technical audience: this Actor is not a Maps scraper. It expects you to already have place objects (from something like &lt;a href="https://apify.com/compass/crawler-google-places" rel="noopener noreferrer"&gt;Google Maps Scraper&lt;/a&gt;) and does pure computation on top of them — string checks for real vs. social-media-only URLs, null checks across profile fields, and a percentile calculation against other places in the same input batch. No headless browser, no OpenAI call, nothing that can time out or hallucinate. That's also why it's cheap: pay-per-event pricing is $0.005 per run start plus $0.0015 per scored lead returned. Scoring 1,000 already-scraped places costs about $1.51 on top of whatever the scraper itself charged.&lt;/p&gt;

&lt;h2&gt;
  
  
  Usage: Apify Console or the API
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Option 1 — Apify Console
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;Run a Google Maps scraper for your target city/category and grab its dataset output (as JSON).&lt;/li&gt;
&lt;li&gt;Open &lt;a href="https://apify.com/reverenced_garnet/apify-gmaps-lead-priority" rel="noopener noreferrer"&gt;Google Maps Lead Priority Scorer&lt;/a&gt; in Apify Console, paste that array into the &lt;strong&gt;Google Maps places&lt;/strong&gt; input field.&lt;/li&gt;
&lt;li&gt;Optionally set &lt;strong&gt;Minimum priority tier&lt;/strong&gt; to &lt;code&gt;Warm&lt;/code&gt; or &lt;code&gt;Hot&lt;/code&gt; so you're only ever billed for the leads worth calling.&lt;/li&gt;
&lt;li&gt;Click Start. Results come back sorted highest-priority first, downloadable as JSON/CSV/Excel.&lt;/li&gt;
&lt;/ol&gt;

&lt;h3&gt;
  
  
  Option 2 — &lt;code&gt;apify call&lt;/code&gt; from the CLI
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;apify call reverenced_garnet/apify-gmaps-lead-priority &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--input&lt;/span&gt; &lt;span class="s1"&gt;'{
    "places": [
      { "title": "Lemari Coffee", "placeId": "ChIJv79rmEfzGGARhXlSEd8ceiQ" }
    ],
    "minTier": "Warm"
  }'&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Option 3 — Apify API / SDK (chaining two Actors)
&lt;/h3&gt;

&lt;p&gt;A realistic setup looks like this: scrape → score → dataset, all in one script. Here's a minimal Node example using the Apify client that chains a Maps scraper run into this Actor:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;ApifyClient&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;apify-client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;ApifyClient&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;token&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;process&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;env&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;APIFY_TOKEN&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// 1. Run a Google Maps scraper for a city/category&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scrapeRun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;compass/crawler-google-places&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;searchStringsArray&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;coffee shop in Austin, TX&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
    &lt;span class="na"&gt;maxCrawledPlacesPerSearch&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;200&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;places&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scrapeRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listItems&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="c1"&gt;// 2. Score those places for lead priority&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scoreRun&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;actor&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;reverenced_garnet/apify-gmaps-lead-priority&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;call&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="nx"&gt;places&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;minTier&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Warm&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;webhookUrl&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://your-n8n-instance.example.com/webhook/new-leads&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;items&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;leads&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;client&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;dataset&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;scoreRun&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;defaultDatasetId&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;listItems&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;`Got &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;leads&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;length&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; Warm+ leads, top one:`&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;leads&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The same works in Python with the &lt;code&gt;apify-client&lt;/code&gt; package (&lt;code&gt;ApifyClient(token=...)&lt;/code&gt;, &lt;code&gt;.actor(...).call(run_input=...)&lt;/code&gt;, &lt;code&gt;.dataset(...).list_items()&lt;/code&gt;).&lt;/p&gt;

&lt;h2&gt;
  
  
  Input schema
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;places&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;array&lt;/td&gt;
&lt;td&gt;Place objects from a Google Maps scraper's dataset output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;minTier&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Hot&lt;/code&gt;, &lt;code&gt;Warm&lt;/code&gt;, or &lt;code&gt;Cold&lt;/code&gt; (default) — minimum tier to include in the output&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;slackWebhookUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string (optional)&lt;/td&gt;
&lt;td&gt;Slack Incoming Webhook URL — posts a run summary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;discordWebhookUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string (optional)&lt;/td&gt;
&lt;td&gt;Discord Channel Webhook URL — posts a run summary&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;webhookUrl&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string (optional)&lt;/td&gt;
&lt;td&gt;Any URL — receives a structured JSON summary (e.g. for n8n/Make)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;notifyOnlyNewOrUpdated&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;Default &lt;code&gt;true&lt;/code&gt; — only include &lt;code&gt;NEW&lt;/code&gt;/&lt;code&gt;UPDATED&lt;/code&gt; leads in notifications&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  Output fields
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"title"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Lemari Coffee"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"placeId"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"ChIJv79rmEfzGGARhXlSEd8ceiQ"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"priorityScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;60&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"tier"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Warm"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"changeStatus"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"NEW"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reasons"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"No independent website (social-media link or none at all)"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="s2"&gt;"No business description"&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hasRealWebsite"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"isUnclaimed"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"completenessScore"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mi"&gt;70&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"reviewPercentileInGroup"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="mf"&gt;25.0&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Field&lt;/th&gt;
&lt;th&gt;Type&lt;/th&gt;
&lt;th&gt;Description&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;title&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Business name&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;placeId&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;Google Maps place ID&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;priorityScore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;number&lt;/td&gt;
&lt;td&gt;0-100 combined lead-priority score&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;tier&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;Hot&lt;/code&gt;, &lt;code&gt;Warm&lt;/code&gt;, or &lt;code&gt;Cold&lt;/code&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;changeStatus&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;string&lt;/td&gt;
&lt;td&gt;
&lt;code&gt;NEW&lt;/code&gt;, &lt;code&gt;UPDATED&lt;/code&gt;, or &lt;code&gt;UNCHANGED&lt;/code&gt; vs. the previous run on this Actor&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reasons&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;array&lt;/td&gt;
&lt;td&gt;Plain-English reasons contributing to the score&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;hasRealWebsite&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;False if no site, or only a social-media link&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;isUnclaimed&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;boolean&lt;/td&gt;
&lt;td&gt;True if the Google Business Profile is unclaimed&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;completenessScore&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;number&lt;/td&gt;
&lt;td&gt;0-100 GBP profile completeness&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;reviewPercentileInGroup&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;number&lt;/td&gt;
&lt;td&gt;Review-count percentile vs. same city+category peers&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;One useful, unusual feature for anyone running this on a schedule: &lt;code&gt;changeStatus&lt;/code&gt;. Run the Actor again later on the same area and every lead gets tagged &lt;code&gt;NEW&lt;/code&gt;, &lt;code&gt;UPDATED&lt;/code&gt;, or &lt;code&gt;UNCHANGED&lt;/code&gt; compared to the previous run, so you only re-review what actually moved instead of re-triaging the whole list every week.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use case: building an outbound list for a local SEO agency
&lt;/h2&gt;

&lt;p&gt;Hypothetical workflow: a typical setup for an agency doing outbound prospecting for, say, dentists in a metro area:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run Google Maps Scraper for &lt;code&gt;"dentist in Denver, CO"&lt;/code&gt;, get back a few hundred place objects.&lt;/li&gt;
&lt;li&gt;Feed that array into this Actor with &lt;code&gt;minTier: "Hot"&lt;/code&gt; — you now only pay for and receive the businesses with the strongest combination of no website, unclaimed profile, and thin review count.&lt;/li&gt;
&lt;li&gt;Set &lt;code&gt;slackWebhookUrl&lt;/code&gt; so the agency's sales channel gets pinged with a summary whenever a run finds new or updated leads.&lt;/li&gt;
&lt;li&gt;Schedule the whole pipeline (scraper → scorer) weekly with Apify's built-in Scheduler. Because of &lt;code&gt;changeStatus&lt;/code&gt;, week-over-week runs only surface leads that changed, so the team isn't re-triaging the same few hundred businesses every Monday.&lt;/li&gt;
&lt;li&gt;Export the dataset as CSV and import into whatever CRM or cold-email tool the agency uses.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The same pattern works for directory sites deciding which unclaimed listings to email about claiming their profile, or freelancers building a target list for a single vertical + city combo before a cold-call sprint.&lt;/p&gt;

&lt;h2&gt;
  
  
  A word on scope and legality
&lt;/h2&gt;

&lt;p&gt;This Actor deliberately does one thing: turn place data you already have into a priority score. It doesn't scrape anything, doesn't call any external API, and doesn't infer anything with an LLM — which is also why it's fast and cheap. It's also worth being explicit that the presence of a phone number or address on a public Google Business Profile doesn't by itself mean you have consent to cold-call or cold-email that business. If you're using this for outreach, you're responsible for complying with applicable law (GDPR, CAN-SPAM, TCPA, etc. depending on where your leads are).&lt;/p&gt;

&lt;h2&gt;
  
  
  Try it
&lt;/h2&gt;

&lt;p&gt;The Actor is live on the Apify Store: &lt;a href="https://apify.com/reverenced_garnet/apify-gmaps-lead-priority" rel="noopener noreferrer"&gt;Google Maps Lead Priority Scorer&lt;/a&gt;. If you're already running a Google Maps scraper as part of a lead-gen pipeline, this is a five-minute addition that replaces a manual sorting/filtering step with a single number you can sort by and trust.&lt;/p&gt;

</description>
      <category>apify</category>
      <category>webdev</category>
      <category>automation</category>
      <category>python</category>
    </item>
  </channel>
</rss>
