Google Maps Place Reviews, Images & Information

Site google.comTask scrape-google-maps-place-reviews-images-infoVersion v2Updated Aug 20, 2026Category maps

Given a Google Maps place URL or place name, resolve the intended listing and extract visible place information, review records, and user/photo image URLs without posting, rating, or otherwise modifying the listing. This skill was captured from a live agent session on google.com and publishes here verbatim, exactly as an agent receives it.

NoteSelectors and URL schemes drift as sites change. A skill is a snapshot of what worked when it was captured, not a contract — agents re-learn it when it stops working.

Purpose

Extract structured information from a Google Maps place listing: canonical URL, place identity, address, contact and category details, rating/review count, visible reviews, and photo/video image URLs. The workflow is read-only and must not submit reviews, ratings, edits, forms, or contact actions. Place names may be resolved through a direct Maps search URL; opaque place identifiers must always be read from Google's result URL rather than guessed.

When to Use

  • Scraping reviews and listing information from a supplied Google Maps place URL.
  • Collecting visible business photos or user-uploaded images associated with a place.
  • Resolving a place name, landmark, station, or address to the correct Google Maps listing.
  • Building a read-only place dataset containing identity, address, rating, reviews, and media.
  • Checking the location or nearby context of a named place in Google Maps.

Workflow

  1. If the caller supplies a canonical Google Maps place URL, navigate to it directly. Remove analytics-only parameters such as entry, g_ep, and skid when desired, but preserve the /maps/place/ path, coordinates, and opaque place identifier in /data/ or !1s... segments. Do not visit the Google homepage.

  2. If only {place-name} is supplied, use one direct Maps search navigation:

https://www.google.com/maps/search/?api=1&query={url-encoded-place-name}

Google also accepts the shorter path form:

https://www.google.com/maps/search/{url-encoded-place-name}

For an area-qualified request, include the locality or street in the query, for example {place-name} {area-or-address}. After Maps renders, inspect anchors containing /maps/place/, select the result whose visible name, locality, and place type best match the request, read its complete href, and navigate directly to that href. Never fabricate an opaque Maps identifier from a name or coordinates. If the result URL already contains /@{latitude},{longitude},..., preserve those coordinates as resolved evidence, not as reusable constants.

  1. In one browser-agent call, navigate with waitUntil: "domcontentloaded", wait briefly for Maps rendering, then evaluate this extractor on the initial place page:
(()=>{const clean=s=>(s||'').replace(/\\s+/g,' ').trim();const href=location.href,u=new URL(href),body=clean(document.body?.innerText),main=document.querySelector('[role="main"]')||document.body;const title=clean(document.querySelector('h1')?.textContent)||clean(main.querySelector('[aria-label*="About "]')?.getAttribute('aria-label')?.replace(/^About\\s+/i,''))||null;const aria=[...document.querySelectorAll('[aria-label]')].map(e=>clean(e.getAttribute('aria-label'))).filter(Boolean);const address=aria.find(x=>/^Address:/i.test(x))?.replace(/^Address:\\s*/i,'')||clean(document.querySelector('[data-item-id^="address"]')?.textContent)||clean([...document.querySelectorAll('button')].find(b=>/address/i.test(b.getAttribute('data-item-id')||''))?.textContent)||null;const ratingLabel=aria.find(x=>/stars?/i.test(x)&&/[0-5](?:\\.[0-9])?/i.test(x));const rating=ratingLabel?Number((ratingLabel.match(/[0-5](?:\\.[0-9])?/)||[])[0]):Number((body.match(/\\b([0-5]\\.[0-9])\\s*\\(([^)]+)\\)/)||[])[1])||null;const reviewCount=Number((body.match(/\\(([\\d,.]+)\\)/)||[])[1]?.replace(/[,.]/g,''))||null;const info={};for(const b of document.querySelectorAll('button[data-item-id],button[aria-label],[data-item-id]')){const label=clean(b.getAttribute('aria-label')||b.getAttribute('data-item-id')),text=clean(b.textContent);if(label&&text)info[label]=text}const reviews=[...document.querySelectorAll('[data-review-id]')].map(card=>{const text=clean(card.querySelector('.wiI7pd,[lang]')?.textContent)||clean(card.innerText),author=clean(card.querySelector('.d4r55,[class*="d4r55"]')?.textContent)||null,ratingText=clean(card.querySelector('[role="img"][aria-label*="star" i]')?.getAttribute('aria-label'))||'',date=clean(card.querySelector('.rsqaWe,[class*="rsqaWe"]')?.textContent)||null;return{reviewId:card.getAttribute('data-review-id'),author,rating:Number((ratingText.match(/[0-5](?:\\.[0-9])?/)||[])[0])||null,date,text,raw:clean(card.innerText)}}).filter(r=>r.text||r.raw);const images=[...document.querySelectorAll('a[aria-label^="Photo"],a[aria-label="Video"],img')].map((e,i)=>{const img=e.matches('img')?e:e.querySelector('img');return{index:i+1,type:e.getAttribute('aria-label')==='Video'?'video':'photo',label:clean(e.getAttribute('aria-label')),href:e.href||null,src:img?.currentSrc||img?.src||null,alt:clean(img?.alt)}}).filter(x=>x.href||x.src);const unique=a=>[...new Map(a.map(x=>[(x.href||x.src||JSON.stringify(x)),x])).values()];const blocked=/consent|sign in|captcha|unusual traffic|verify you are human/i.test(body)&&!title;const coord=(href.match(/@(-?\\d+(?:\\.\\d+)?),(-?\\d+(?:\\.\\d+)?)/)||[]);return{url:href,isGoogleMaps:/((^|\\.)google\\.com)$/.test(u.hostname)&&u.pathname.startsWith('/maps'),blocked,place:title,address,rating:Number.isFinite(rating)?rating:null,reviewCount,info,reviews:unique(reviews),images:unique(images),coordinates:coord[1]?{latitude:Number(coord[1]),longitude:Number(coord[2])}:null,bodyExcerpt:body.slice(0,2000)}})()
  1. If complete reviews are requested and the initial page exposes button[aria-label^="More reviews"], click it, wait for the reviews panel, and rerun the review portion of the evaluator against [data-review-id] cards. Deduplicate by data-review-id; return only reviews actually rendered, not an invented total. Use visible sort/filter controls only when requested and when no stable URL equivalent is available.

  2. If the full photo gallery is requested, click the visible photo gallery control or button[aria-label^="Photo of "], wait for the gallery, and rerun the image portion of the evaluator. Prefer anchor href or currentSrc; retain the displayed thumbnail URL when no larger link is present.

  3. Report the canonical final URL, explicit visible fields, resolved coordinates when present, and unavailable fields as null or empty arrays. Do not infer business facts from coordinates, map tiles, image filenames, or review-count text alone.

Site-Specific Gotchas

  • Direct /maps/search/?api=1&query=... navigation is the shortest general name-resolution route; no homepage visit or search-box typing is needed.
  • The observed path form /maps/search/{encoded-query} also resolves area-qualified place searches, but the api=1&query= form is preferred for reusable recipes.
  • Google Maps place pages contain opaque identifiers such as 0x...:0x... CIDs and /g/{id} identifiers. Preserve identifiers discovered in result URLs; never guess them.
  • Search result hrefs may include a human-readable name, /@lat,lng coordinates, and opaque identifiers. Select by visible name and locality, not by the first URL alone when several similarly named places exist.
  • The place overview may require a short render wait after domcontentloaded. A successful navigation does not mean the details panel, reviews, or photos has rendered.
  • Review cards commonly use [data-review-id], with text in .wiI7pd and author text in .d4r55; selectors are dynamic and should be treated as fallbacks.
  • Photo links commonly use a[aria-label^="Photo"]; gallery videos may use a[aria-label="Video"]. Distinguish videos from still photos in output.
  • Do not scrape googleusercontent.com separately as a second navigation. Only return image URLs already exposed in the current Maps DOM.
  • Google may show consent, login, CAPTCHA, or an unusual-traffic interstitial. Return blocked: true and no fabricated place data if the listing panel is absent.
  • Map tile /maps/vt/pb requests are implementation details and are not place data; do not call or parse them directly.
  • Session-specific parameters such as entry, skid, g_st, and long analytics payloads are not needed in reusable URLs.
  • Listing details, reviews, photos, and coordinates are time-dependent and may vary by locale or session; report only what is currently rendered.

Expected Output

{
  "url": "https://www.google.com/maps/place/{place}/...",
  "place": "{place name}",
  "address": "{visible address}",
  "coordinates": {"latitude": 0, "longitude": 0},
  "rating": 4.2,
  "reviewCount": 150,
  "info": {"Category": "...", "Phone": "...", "Website": "..."},
  "reviews": [
    {"reviewId": "...", "author": "...", "rating": 5, "date": "...", "text": "...", "raw": "..."}
  ],
  "images": [
    {"index": 1, "type": "photo", "label": "Photo of ...", "href": "...", "src": "...", "alt": "..."}
  ],
  "blocked": false,
  "bodyExcerpt": "..."
}

If Maps is blocked or the requested section did not render, return the URL, blocked: true or a section-specific empty array, and a concise reason.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=google.com&task=scrape-google-maps-place-reviews-images-info