Given {area-query}, {place-category}, and an optional {limit}, find visible Google Maps results for that category near the area. Return deduplicated place names, observed listing URLs, opaque place IDs when exposed, coordinates, visible addresses, ratings, review counts, categories, and bounded raw card evidence. This is read-only: do not sign in, contact businesses, submit edits, start navigation, or fabricate identifiers.
Use Cases
- Finding businesses or public places of a category near a named neighborhood, landmark, address, or coordinate.
- Returning a list of nearby coffee shops, restaurants, pharmacies, hotels, beach clubs, or similar place types.
- Resolving Google Maps result-card links into canonical
/maps/place/URLs and observed opaque IDs. - Preserving visible result-card evidence without opening every individual listing.
- Searching a category within an explicitly specified area or map center.
Automation Flow
- Construct one direct Google Maps search URL; do not visit the homepage or type into search fields. For natural-language area and category searches, use:
https://www.google.com/maps/search/?api=1&query={url-encoded-{place-category} near {area-query}}When the caller supplies a map center and zoom, this form is also useful:
https://www.google.com/maps/search/{url-encoded-place-category}/@{latitude},{longitude},{zoom}zNatural-language queries and caller-supplied coordinates are safe inputs. Never fabricate an opaque area or place identifier.
Navigate once with
waitUntil: "domcontentloaded", allow dynamic result cards to render, and run the evaluator below in the same browser-agent call. If Google redirects to a consent page and no place cards are present, accept or reject consent only using the visible localized control (for example,button[aria-label="Alles afwijzen"]), wait for Maps to return, and rerun the evaluator. Do not retain the consent URL or session parameters.Select cards whose visible name, locality, category, and address best match
{area-query}and{place-category}. Prefer results visibly inside or immediately near the requested area; do not claim that the collection is exhaustive.Extract the complete observed
/maps/place/href from each result card. Read the opaque identifier from!1s...when present and coordinates from/@lat,lngor!3dlat!4dlng; never derive an identifier from a business name. Remove only obvious session or analytics parameters when constructing a clean returned URL, while retaining the observed URL for provenance.Deduplicate by opaque place ID when available, otherwise by canonical place URL and normalized name-plus-coordinate key. Preserve the first visible rank and raw card text. If the caller requests more results than initially rendered, scroll only the visible results pane in bounded increments, rerun the evaluator, and stop at
{limit}, when no new result IDs appear, or when the caller's geographic/category bound would be exceeded.If a result needs full listing details, navigate separately to its observed place URL and use Get Google Maps place reviews and information. Do not open every result merely to discover fields already visible on search cards. Do not click call, website, booking, directions, save, suggest-an-edit, or review controls unless explicitly requested.
Run this self-contained evaluator on the loaded Maps search page, replacing its limit with {limit} and its placeCategory with {place-category}. resultCount reports every deduplicated card found, so compare it with the returned places length to see whether the page held more than the caller asked for:
(()=>{const limit=100,placeCategory='';const clean=s=>(s||'').replace(/\s+/g,' ').trim();const u=new URL(location.href),body=clean(document.body?.innerText);const blocked=/captcha|unusual traffic|verify you are human|automated queries|consent required|sign in/i.test(body)&&!document.querySelector('a[href*="/maps/place/"]');const rows=[...document.querySelectorAll('a[href*="/maps/place/"]')].map(a=>{const href=new URL(a.getAttribute('href'),location.origin).href;const text=clean(a.innerText||a.getAttribute('aria-label'));const id=(href.match(/!1s([^!/?]+)/)||[])[1]||null;const at=href.match(/@(-?\d+(?:\.\d+)?),(-?\d+(?:\.\d+)?)/);const three=href.match(/!3d(-?\d+(?:\.\d+)?)!4d(-?\d+(?:\.\d+)?)/);const card=a.closest('[role="article"],div[role="feed"]>div')||a.parentElement;const cardText=card?.innerText||text||'';const raw=clean(cardText);const lines=cardText.split(/\r?\n+/).map(clean).filter(Boolean);const starLabel=clean(card?.querySelector('[role="img"][aria-label*="star" i],[aria-label*="star" i]')?.getAttribute('aria-label'));const compact=raw.match(/(\d(?:[.,]\d)?)\s*\(\s*([\d.,]+)\s*\)/);const rating=(starLabel.match(/(\d(?:[.,]\d)?)/)||[])[1]||(compact||[])[1]||(raw.match(/(?:^|\s)([0-5](?:[.,]\d)?)\s*stars?/i)||[])[1]||null;const reviews=(compact||[])[2]||(starLabel.match(/([\d.,]+)\s*(?:reviews?|avaliações|avis|bewertungen|reseñas)/i)||[])[1]||(raw.match(/([\d,]+)\s+reviews?/i)||[])[1]||null;const parts=lines.filter(x=>x!==text).flatMap(x=>x.split('\u00b7')).map(clean).filter(Boolean);const address=parts.find(x=>/\d/.test(x)&&/[A-Za-z]/.test(x))||null;const wanted=placeCategory?new RegExp(placeCategory.split(/\s+/).filter(Boolean).map(w=>w.replace(/[.*+?^${}()|[\]\\]/g,'\\$&')).join('|'),'i'):null;const category=(wanted?parts.find(x=>wanted.test(x)):null)||null;return{name:text||null,url:href,placeId:id,latitude:at?Number(at[1]):three?Number(three[1]):null,longitude:at?Number(at[2]):three?Number(three[2]):null,rating:rating?Number(rating.replace(',','.')):null,reviewCount:reviews?Number(String(reviews).replace(/[.,](?=\d{3}\b)/g,'')):null,address,category,lines:lines.slice(0,12),raw}}).filter(x=>x.name&&x.url);const keys=new Set();const places=rows.filter(x=>{const k=x.placeId||x.url;if(keys.has(k))return false;keys.add(k);return true});return{url:location.href,isGoogleMaps:/(^|\.)google\.com$/i.test(u.hostname)&&u.pathname.startsWith('/maps'),isSearch:/\/maps\/(search|place\/)/i.test(u.pathname),query:u.searchParams.get('query')||null,blocked,places:places.slice(0,limit),resultCount:places.length,bodyExcerpt:body.slice(0,2200)}})()Possible Friction Points
- Google Maps category search can be reached directly with
/maps/search/?api=1&query=...; a homepage visit, area search, Nearby button, and category typing are unnecessary. - Direct API-form searches may redirect to
consent.google.combefore returning to Maps. This is a consent state, not a place result. Use the visible localized reject/accept control once, then return to the clean Maps search and extract cards; never reuse the consent URL,continuevalue, cookies, or session parameters. - A natural-language query such as
{place-category} near {area-query}is preferable when the caller supplies names rather than coordinates. A coordinate-centered/maps/search/{category}/@{lat},{lng},{zoom}zURL can preserve a supplied map viewport. - Result links commonly contain opaque IDs in
!1s..., coordinates in/@lat,lng, or coordinates in!3dlat!4dlng. Read these from observed links and never guess them. - Search-result URLs may include long nested query state,
entry,g_ep,authuser, or other session parameters. They are provenance, not reusable inputs; return a cleaned URL when safe and preserve the original separately if needed. - Search cards can include businesses just outside the colloquial neighborhood. Match visible address, locality, category, and map context, and report borderline results rather than silently treating the neighborhood boundary as exact.
- Google may render duplicate anchors for one place or change card markup. Deduplicate by observed place ID first, then canonical URL and normalized name-plus-coordinate fallback. Keep the first anchor for a key: the first one carries the visible rank and the fullest card text, and a later duplicate is often a compact repeat with fewer fields.
- The card container is read from
[role="article"]or a direct child of the results feed. A baredivancestor would match the anchor's immediate wrapper, which can exclude the sibling text holding the address, category, rating, and review count. - Nearby results may be dynamically loaded in a scrollable feed rather than numbered pages. Scroll only when more results than the first render are requested; record the actual rendered count and stop when no new cards appear.
- Result-card fields are localized and may be absent. Use
nullrather than inferring an address, rating, review count, or category from the business name. - Observed cards pack the category and the street address into one line separated by a middot, as in
Dentist · 3820 Wabash Ave, so each line is split on that separator before category and address are read. Reading whole lines returns the category with the address glued to it. categoryis matched against the caller's{place-category}, not a fixed vocabulary, and the card's splitlinesare returned so the agent can read the category when Maps words it differently or renders it in another language. A hardcoded English type list returnsnullfor dentists, gyms, car washes, and every non-English results page.- Rating and review count are read from the rating element's
aria-labelfirst, then from the compact4.5(1,234)shape cards usually render, and only then from4.5 stars/1,234 reviewsEnglish text. Cards frequently omit the English words entirely, and locales using4,7(312)need the comma read as the decimal separator and the dot as a thousands separator. - Ratings, review counts, opening status, result ordering, and business availability are time-dependent. They are visible search evidence, not guarantees.
- Consent, login, CAPTCHA, and unusual-traffic pages without usable place cards are blocked states. Do not retain challenge URLs, tokens, or session parameters, and do not infer that no nearby places exist.