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
If the caller supplies a Google Shopping/search URL, navigate to it directly. Preserve its meaningful
q,udm=28,hl, andglparameters, but remove analytics/session parameters such assei,ved,ei,gs_lp,sca_esv,sclient,biw,bih, andshopmdwhen 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.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.
- 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)}})()Sort by numeric
priceonly when the caller asks for the cheapest listings. Deduplicate repeated cards and preserverawevidence. A card containing& morerepresents multiple merchant offers; retain that text unless separate offer rows are actually rendered.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 asdiv[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.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.
If the clean URL reaches
/sorry/, shows#captcha-form, or displays a reCAPTCHA or unusual-traffic challenge, usesolve(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, returnblocked: trueand no inferred products.
Site-Specific Gotchas
udm=28is the non-obvious parameter that opens Google Shopping from a direct/searchURL.- Do not start at
https://shopping.google.com/; it can redirect to/sorry/indexwith a session-specific opaque challenge token. Use the clean/search?q=...&udm=28URL directly. - Google Shopping cards commonly expose structured evidence through
div[role=button]aria-labelvalues 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
hlandglwhen the requested market matters and report the visible currency. - Consent, login, CAPTCHA, and unusual-traffic pages are blocked states. Return
blocked:trueand 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.