Google Shopping Competitor and Review Lookup

Site google.comTask google-shopping-competitor-review-lookupVersion v5Updated Aug 10, 2026Category shopping

Given a Google Shopping search or product-viewer URL, extract visible competing sellers, prices, conditions, ratings, review counts, product links, and rendered product-viewer evidence without fabricating unavailable data. 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

Given {product-query} or a supplied Google Shopping URL, return visible product cards and competing merchant offers, including product title, current price, seller or merchant text, condition, rating, review count, product links, and raw rendered evidence. When a supplied URL or request identifies a product viewer, also extract the viewer's currently rendered offers and review evidence. This is read-only: never purchase, add to cart, contact sellers, or submit reviews.

Google Shopping has no stable, supported public endpoint for a complete competitor or review corpus. Use the rendered Shopping DOM as the source of truth; return only offers and reviews visible in the current page/session.

When to Use

  • Comparing competing sellers or listings for an arbitrary product query.
  • Starting from a Google Shopping search URL or a current-session product-viewer URL.
  • Collecting visible Google Shopping prices and merchant names.
  • Finding ratings and review counts shown in Shopping cards or a selected product viewer.
  • Building a lightweight competitor or price-monitoring dataset.
  • Requests to consult Google Shopping competitors, evaluations, ratings, or reviews without opening checkout.

Workflow

  1. If the caller supplies a Google Shopping/search URL, navigate to it directly. Preserve its meaningful q, udm=28, hl, and gl parameters, but remove analytics/session parameters such as sei, ved, ei, gs_lp, sca_esv, sclient, biw, bih, and shopmd when constructing a reusable URL. If the URL contains a session-specific #sv= viewer hash, use it only in the current session; never fabricate or persist a viewer hash.

  2. If only {product-query} is supplied, construct the clean direct Shopping URL; do not visit the homepage or type into a search box:

https://www.google.com/search?q={url-encoded-product-query}&udm=28&hl={language}&gl={country-code}

Omit hl and gl unless a language or country is requested. The equivalent /shopping?udm=28&q=... form may work, but /search?...&udm=28 is the preferred stable route.

  1. Navigate once with waitUntil: "domcontentloaded", wait about 2–4 seconds, and run this evaluator in the same browser-agent call. It extracts visible cards, merchant text, prices, conditions, ratings, review counts, links, and block status:
(()=>{const clean=s=>(s||'').replace(/\\s+/g,' ').trim();const money=s=>{const m=clean(s).match(/(?:[$€£]|USD|EUR|GBP|BRL|R\\$)\\s*[\\d.,]+|[\\d.,]+\\s*(?:USD|EUR|GBP|BRL|R\\$)/i);if(!m)return null;const n=m[0].match(/[\\d.,]+/);if(!n)return null;let x=n[0];if(/(?:[.,]\\d{2})$/.test(x)&&x.includes(','))x=x.replace(/\\./g,'').replace(',','.');else x=x.replace(/\\.(?=\\d{3}(?:\\D|$))/g,'').replace(/,(?=\\d{3}(?:\\D|$))/g,'');return Number(x)};const parse=el=>{const aria=clean(el.getAttribute('aria-label')),text=clean(el.innerText||el.textContent),raw=clean([aria,text].filter(Boolean).join(' | '));if(!raw||!/(?:current price|price|rated|reviews?|seller|merchant|buy)/i.test(raw))return null;const rating=raw.match(/rated?\\s*([0-5](?:\\.[0-9])?)\\s*out of 5/i)||raw.match(/([0-5](?:\\.[0-9])?)\\s*(?:stars?|\\/5)/i),reviews=raw.match(/([\\d,.]+)\\s*reviews?/i),links=[...el.querySelectorAll('a[href]')].map(a=>a.href).filter(h=>/^https?:/i.test(h));const heading=clean(el.querySelector('h3,[role="heading"]')?.textContent),image=clean(el.querySelector('img')?.alt),title=heading||image||clean((aria.match(/^(.+?)(?=\\.\\s*(?:\\d+%\\s*OFF|current price|price):?)/i)||[])[1])||null;const condition=(raw.match(/\\b(new|used|pre-owned|refurbished|renewed|open box)\\b/i)||[])[1]||null;const currency=(raw.match(/(?:[$€£]|USD|EUR|GBP|BRL|R\\$)/i)||[])[0]||null;return{title,price:money(raw),currency,seller:(raw.match(/(?:current price|price)[: ]+[^|.]*\\.\\s*([^|.]+(?:& more)?)/i)||[])[1]?.trim()||null,condition,rating:rating?Number(rating[1]):null,reviewCount:reviews?Number(reviews[1].replace(/[,.]/g,'')):null,productUrl:links[0]||null,raw}};const body=clean(document.body?.innerText),u=new URL(location.href),blocked=/^\\/sorry(?:\\/|$)/.test(u.pathname)||!!document.querySelector('#captcha-form,iframe[src*="recaptcha"]')||(/captcha|unusual traffic|verify you are human|automated queries/i.test(body)&&!/(shopping|current price|reviews?)/i.test(body));const seen=new Set(),products=[];for(const el of document.querySelectorAll('div[role="button"][aria-label], [aria-label*="Current Price"], [aria-label*="reviews" i], [data-merchant-id]')){const p=parse(el);if(!p)continue;const k=[p.title,p.price,p.seller,p.reviewCount,p.raw].join('|');if(!seen.has(k)){seen.add(k);products.push(p)}}return{query:u.searchParams.get('q')||'',url:location.href,isShopping:!blocked&&(u.searchParams.get('udm')==='28'||u.pathname==='/shopping'),selectedViewer:u.hash.startsWith('#sv='),blocked,products:products.filter(p=>p.price!==null),visibleProductCount:products.length,bodyExcerpt:body.slice(0,1500)}})()
  1. Sort by numeric price only when the caller asks for the cheapest listings. Deduplicate repeated cards and preserve raw evidence. A card containing & more represents multiple merchant offers; retain that text unless separate offer rows are actually rendered.

  2. If the caller explicitly requests viewer-level offers or reviews, select a rendered card only after matching its visible title to {product-title}. Use a semantic selector such as div[role="button"][aria-label^="{title-prefix}"], wait briefly, and rerun the evaluator on the resulting DOM. The viewer may change the URL to a session-specific #sv= hash. Return its visible evidence and current URL, but do not construct, decode, or reuse the hash as a reusable identifier.

  3. Treat ratings and review counts in Shopping cards as summary values, not a complete review corpus. If individual review text is rendered in the viewer, extract only those currently rendered review nodes and preserve their visible author, rating, date, text, and raw text. Do not call undocumented Google endpoints or merchant sites to infer missing reviews.

  4. If the clean URL reaches /sorry/, shows #captcha-form, or displays a reCAPTCHA or unusual-traffic challenge, use solve(type: "recaptcha", wait: true, timeout: 30000) when supported. Wait briefly, revisit the original clean Shopping URL with the same residential proxy/profile, and rerun the evaluator. If the challenge remains, return blocked: true and no inferred products.

Site-Specific Gotchas

  • udm=28 is the non-obvious parameter that opens Google Shopping from a direct /search URL.
  • Do not start at https://shopping.google.com/; it can redirect to /sorry/index with a session-specific opaque challenge token. Use the clean /search?q=...&udm=28 URL directly.
  • Google Shopping cards commonly expose structured evidence through div[role=button] aria-label values containing price, condition, merchant, rating, and review count. Labels are dynamic; use semantic fragments rather than a complete hard-coded label.
  • The DOM contains mixed product-card and comparison elements. Retain records containing product/price/review evidence and deduplicate by title, price, seller, review count, and raw text.
  • /sorry/index, continue, reCAPTCHA URLs, challenge tokens, and #sv= viewer hashes are session-specific and must never be saved as canonical recipe URLs.
  • After CAPTCHA resolution, revisit the clean URL and wait several seconds before extracting or selecting a card; the page may initially contain no interactive cards.
  • Product-viewer cards can expose several merchant offers, but the viewer is dynamic and may show only a subset. Report the rendered subset rather than claiming completeness.
  • Prices, availability, condition, ratings, and review counts are locale- and time-dependent. Use hl and gl when the requested market matters and report the visible currency.
  • Consent, login, CAPTCHA, and unusual-traffic pages are blocked states. Return blocked:true and no fabricated product or review data.
  • Product and merchant links may leave Google. The evaluator only reads URLs already present in the current DOM; do not perform secondary navigation unless explicitly requested.
  • Read-only: do not click purchase, add-to-cart, merchant checkout, or review-submission controls.

Expected Output

{
  "query": "{product-query}",
  "url": "https://www.google.com/search?q={encoded-query}&udm=28",
  "isShopping": true,
  "selectedViewer": false,
  "blocked": false,
  "products": [
    {
      "title": "{product title}",
      "price": 428.99,
      "currency": "$",
      "seller": "{merchant or merchant text}",
      "condition": "Pre-owned",
      "rating": 4.7,
      "reviewCount": 88,
      "productUrl": "{visible-link-or-null}",
      "raw": "{visible aria-label and card text}"
    }
  ],
  "visibleProductCount": 1,
  "bodyExcerpt": "..."
}

For an explicitly requested viewer, additionally return the current viewer URL, selected title, and any newly rendered offer or review evidence, for example {offers, reviews}. For blocked or empty pages, return the clean requested query and URL, blocked:true or an empty products array, and a concise explanation. Never infer competitor prices, ratings, review counts, or review text.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=google.com&task=google-shopping-competitor-review-lookup