Purpose
Collect structured product listings from Mercado Livre Brasil search-result pages using the site's slug-based search URL and resilient selectors covering current Poly cards and legacy UI Search layouts. The extractor captures title, URL, image, prices, installments, discount labels, seller, official-store status, shipping, badges, rating, review count, condition, and sold-count text.
When to Use
Use when the input is a product keyword or phrase and the required output is the set of product cards currently displayed on the corresponding Mercado Livre Brasil results page. Apply the optional officialStoreOnly post-filter when the caller wants only listings marked as an official store, such as an official brand shop. The workflow is page-local and does not resolve product IDs or open individual product pages.
Workflow
- Build the direct results URL by normalizing
{query}to lowercase, replacing runs of whitespace with hyphens, and URL-encoding the resulting path segment:https://lista.mercadolivre.com.br/{normalized-query}Optional observed marketplace-scope parameter: append?sb=all_mercadolibrewhen the caller requests the all-Mercado-Libre scope. - In one browser call, navigate to that URL with
waitUntil: "domcontentloaded", allow roughly 2 seconds for cards to render, and run this evaluator. SetofficialStoreOnlytotrueonly when requested; it defaults tofalseand therefore does not narrow ordinary searches.
(()=>{
const officialStoreOnly=false;
const clean=s=>(s||'').replace(/\s+/g,' ').trim();
const abs=u=>{try{return new URL(u,location.href).href}catch{return u||''}};
const firstText=(el,selectors)=>{for(const s of selectors){const n=el.querySelector(s),v=clean(n?.textContent);if(v)return v}return ''};
const firstAttr=(el,selectors,attrs)=>{for(const s of selectors){const n=el.querySelector(s);if(!n)continue;for(const a of attrs){const v=n.getAttribute(a);if(v){if(a==='srcset')return abs(v.split(',')[0].trim().split(/\s+/)[0]);return abs(v)}}}return ''};
const money=(card,selector)=>{const el=card.querySelector(selector);if(!el)return null;const f=clean(el.querySelector('.andes-money-amount__fraction')?.textContent),c=clean(el.querySelector('.andes-money-amount__cents')?.textContent||'00');if(!f)return null;const n=parseFloat(f.replace(/\./g,'').replace(',','.')+'.'+c.replace(/\D/g,''));return Number.isFinite(n)?n:null};
const cardSelectors=['li.ui-search-layout__item','.ui-search-result__wrapper','.poly-card','.andes-card'];
const cards=[...new Set(cardSelectors.flatMap(s=>[...document.querySelectorAll(s)]))];
const products=cards.map(card=>{const titleEl=card.querySelector('a.poly-component__title,h2.ui-search-item__title,.ui-search-item__title,[class*="title"] a,a[href*="/MLB-"]');const link=titleEl?.closest('a')||card.querySelector('a[href*="mercadolivre.com.br/MLB"],a[href*="/MLB-"]');const title=clean(titleEl?.textContent||card.querySelector('h2,h3')?.textContent);if(!title||!link)return null;const seller=firstText(card,['.poly-component__seller','.ui-search-item__group__element [class*="seller"]','[class*="seller"]']);const official=!!card.querySelector('.poly-component__seller svg[aria-label="Loja oficial"],[aria-label*="Loja oficial" i]')||/loja oficial/i.test(seller)||/loja oficial/i.test(clean(card.textContent));return {title,url:abs(link.href),image:firstAttr(card,['.poly-component__picture','img','picture source'],['src','data-src','data-lazy-src','srcset']),priceCurrent:money(card,'.poly-price__current')??firstText(card,['.poly-price__current','.ui-search-price__part']),priceCurrentFull:firstText(card,['.poly-price__current','.ui-search-price__part']),priceOriginal:money(card,'.poly-price__previous,.poly-price__labels')??firstText(card,['.poly-price__previous','.poly-price__current s','.andes-money-amount--previous','s']),installments:firstText(card,['.poly-price__installments','.ui-search-installments']),discount:firstText(card,['.poly-price__discount-polylabel','.poly-price__label','.ui-search-price__second-line']),seller,isOfficialStore:official,shipping:firstText(card,['.poly-component__shipping-v2','.poly-component__shipping','.ui-search-item__shipping','[class*="shipping"]']),rating:firstText(card,['.poly-reviews__rating','.ui-search-reviews__rating','[class*="rating"]']),reviews:firstText(card,['.poly-reviews__total','.ui-search-reviews__amount','[class*="reviews"]']),badges:[...card.querySelectorAll('.polylabel-pill,.polylabel-label,.ui-search-item__highlight-label,[class*="badge"]')].map(e=>clean(e.textContent)).filter(Boolean),condition:firstText(card,['.poly-component__condition','[class*="condition"]']),sold:firstText(card,['.poly-component__sold-quantity','[class*="sold"]','[class*="quantity"]'])}}).filter(Boolean);
const unique=[],seen=new Set();for(const p of products)if(!seen.has(p.url)){seen.add(p.url);unique.push(p)}
const result=officialStoreOnly?unique.filter(p=>p.isOfficialStore):unique;
return {url:location.href,title:document.title,bodyLength:document.body.innerHTML.length,cardCount:cards.length,officialStoreOnly,products:result};
})()Site-Specific Gotchas
- The search endpoint is directly reachable at
/{hyphenated-query}onlista.mercadolivre.com.br; do not visit the homepage or type into the search box first. ?sb=all_mercadolibreis an observed optional scope parameter for the all-Mercado-Libre search view; it is not the official-store filter itself.- Official-store status is exposed on current Poly cards by an SVG with
aria-label="Loja oficial"; the evaluator also checks the seller/card text for localized official-store labeling. Treat this as a page marker, not proof of seller identity when the markup is absent. - Mercado Livre may render different card structures, so retain both
poly-*andui-search-*selectors and deduplicate by canonical product URL. - Current Poly cards expose useful fields through
.poly-component__seller,.poly-component__shipping-v2,.poly-price__installments,.poly-price__discount-polylabel,.polylabel-pill, and.poly-component__picture; these are more precise than relying only on card text. - Image URLs may be lazy-loaded in
src,data-src,data-lazy-src, orsrcset; the evaluator checks all of these and converts them to absolute URLs. - Broad fallback selectors containing
title,rating,reviews,shipping, orsellercan match localized layout variants and are less stable than specific Poly/UI Search selectors. - A
domcontentloadednavigation followed by a short render wait helps cards appear. IfcardCountandproductsare zero, inspect the loaded page for an anti-bot or consent interstitial and preserve the same browser session while waiting or retrying; no separate bypass endpoint is established. - Prices, ratings, review counts, seller text, shipping text, and other labels remain localized text.
priceCurrentandpriceOriginalare numeric when the amount markup is parseable; otherwise they retain the displayed text. - The evaluator only extracts cards present in the loaded result page; it does not infer products hidden behind pagination or infinite scrolling.
Expected Output
An object with url, title, bodyLength, cardCount, officialStoreOnly, and products. Each product contains title, absolute url, image, priceCurrent, priceCurrentFull, priceOriginal, installments, discount, seller, isOfficialStore, shipping, rating, reviews, badges, condition, and sold. Optional fields are empty strings, null, or empty arrays when absent, and duplicate product URLs are removed.