Extract Shopee Brazil Product Details

Site shopee.com.brTask extract-product-detailsVersion v2Updated Oct 14, 2026Category product-research

Extract price, selected variation, technical specifications, description, included items, condition, warranty, and seller data from a Shopee Brazil product page. This skill was captured from a live agent session on shopee.com.br and is published here as a reusable recipe for agents.

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.

Extract the complete buyer-facing details of a Shopee Brazil product listing from its product page, including the current price, selected variation, technical sheet, description, included items, condition, warranty, and seller information.

Use Cases

Use when a Shopee Brazil product URL is available in the form https://shopee.com.br/product/{shop-id}/{item-id}, or when the input is a Shopee Brazil short link such as https://br.shp.ee/{code} that must first be resolved. The product URL contains opaque shop and item identifiers; do not guess either identifier from a product name.

Automation Flow

  1. If given a br.shp.ee short link, navigate directly to it with waitUntil: "domcontentloaded", then read the final location.href. Follow the redirect to obtain the canonical /product/{shop-id}/{item-id} URL. If the canonical product URL is already supplied, skip this step.
  2. Navigate directly to https://shopee.com.br/product/{shop-id}/{item-id} with waitUntil: "domcontentloaded".
  3. On the loaded product page, run this single evaluate() extractor. It reads visible product-page content, selected variation controls, structured metadata, and seller sections without additional navigation:
(() => {
const clean = s => (s || '').replace(/\u00a0/g, ' ').replace(/[ \t]+/g, ' ').replace(/\n[ \t]+/g, '\n').trim();
const text = el => clean(el?.innerText || el?.textContent || '');
const visible = el => { if (!el) return false; const r = el.getBoundingClientRect(); if (!(r.width || r.height)) return false; const s = getComputedStyle(el); return s.display !== 'none' && s.visibility !== 'hidden'; };
const uniq = a => [...new Set(a.map(clean).filter(Boolean))];
const body = text(document.body);
const firstText = selectors => {
  for (const selector of selectors) {
    const el = [...document.querySelectorAll(selector)].find(visible);
    if (el && text(el)) return text(el);
  }
  return '';
};
const meta = name => {
  const el = document.querySelector(`meta[property="${name}"],meta[name="${name}"]`);
  return clean(el?.content || '');
};
const section = labels => {
  const wanted = labels.map(x => x.toLowerCase());
  const nodes = [...document.querySelectorAll('h1,h2,h3,h4,h5,h6,[role="heading"],strong,b')].filter(visible);
  const head = nodes.find(n => wanted.some(x => text(n).toLowerCase().includes(x)));
  if (!head) return '';
  const out = [];
  let n = head.nextElementSibling;
  while (n && out.length < 40) {
    if (/^(H[1-6])$/.test(n.tagName) || n.getAttribute('role') === 'heading') break;
    const t = text(n);
    if (t) out.push(t);
    n = n.nextElementSibling;
  }
  return clean(out.join('\n'));
};
const priceText = firstText([
  '[class*="price"]', '[data-testid*="price"]', '[data-e2e*="price"]',
  'div[aria-label*="R$"]', 'span[aria-label*="R$"]'
]);
const priceRe = /R\$\s*[\d.]+(?:,\d{2})?/gi;
// Shopee renders crossed-out, installment and shipping prices alongside the current one.
// Collect visible R$ values, dropping those inside struck/installment/original nodes, and
// take the first remaining as the current price while returning the rest for disambiguation.
const nonCurrentSel = 'del, s, [class*="through"], [class*="strike"], [class*="original"], [class*="installment"], [class*="parcel"], [class*="shipping"], [class*="frete"]';
const nonCurrent = el => el && el.closest(nonCurrentSel);
const priceNodes = [...document.querySelectorAll('[class*="price"], [data-testid*="price"], [data-e2e*="price"], [aria-label*="R$"]')].filter(el => visible(el) && !nonCurrent(el));
const currentText = el => { const c = el.cloneNode(true); c.querySelectorAll(nonCurrentSel).forEach(n => n.remove()); return [clean(c.innerText), clean(el.getAttribute('aria-label'))].join(' '); };
const priceCandidates = uniq(priceNodes.flatMap(el => currentText(el).match(priceRe) || []));
const price = priceCandidates[0] || '';
const variationEls = [...document.querySelectorAll('button[aria-pressed],button[class*="selected"],button[class*="Selected"],[role="radio"],[role="option"],[class*="variation"] button,[class*="product-variation"] button')].filter(visible);
const variation = uniq(variationEls.map(el => {
  const a = el.getAttribute('aria-label') || '';
  const t = text(el);
  return (a || t).replace(/^(Selecionado|Selected)[:\s-]*/i, '');
}).filter(x => x && x.length < 160 && !/^(comprar agora|adicionar ao carrinho|comprar)$/i.test(x))).slice(0, 30);
const specSection = section(['ficha técnica', 'informações do produto', 'especificações', 'detalhes do produto']);
const descSection = section(['descrição', 'description']);
const includedSection = section(['itens inclusos', 'conteúdo da embalagem', 'o que vem na caixa']);
const conditionSection = section(['condição', 'condition']);
const warrantySection = section(['garantia', 'warranty']);
const sellerSection = section(['vendedor', 'seller', 'loja']);
const specPairs = {};
const specRoot = [...document.querySelectorAll('table,dl,[class*="spec"],[class*="Spec"],[class*="attribute"],[class*="Attribute"]')].find(visible);
if (specRoot) {
  [...specRoot.querySelectorAll('tr')].forEach(row => {
    const cells = [...row.querySelectorAll('th,td')].map(text).filter(Boolean);
    if (cells.length >= 2) specPairs[cells[0]] = cells.slice(1).join(' | ');
  });
  [...specRoot.querySelectorAll('dt')].forEach(dt => {
    const dd = dt.nextElementSibling;
    if (dd && text(dt)) specPairs[text(dt)] = text(dd);
  });
}
const sellerName = firstText(['[class*="seller"] [class*="name"]','[class*="shop-name"]','[class*="ShopName"]','a[href*="/shop/"]']);
const title = firstText(['h1','[data-testid="product-title"]','[class*="product-title"]']) || meta('og:title');
return {
  url: location.href,
  title,
  price,
  priceCandidates,
  selectedVariation: variation,
  technicalSpecifications: { text: specSection, pairs: specPairs },
  description: descSection,
  includedItems: includedSection,
  condition: conditionSection,
  warranty: warrantySection,
  seller: { name: sellerName, details: sellerSection },
  extractionNote: 'Empty fields mean the corresponding section was not visible or was not present on the loaded page.'
};
})()

Possible Friction Points

  • Shopee product pages use the opaque URL pattern /product/{shop-id}/{item-id}; the shop and item IDs must come from the supplied URL, a resolved Shopee short link, or another explicit resolution step.
  • br.shp.ee links are redirect-style Shopee Brazil short links. Resolve them first and use the final canonical product URL rather than guessing identifiers.
  • Variation controls may be rendered as buttons with aria-label, aria-pressed, or role-radio state. The extractor reports the currently selected controls and does not silently change the buyer’s selection.
  • Shopee frequently changes hashed CSS class names and may lazy-render description or seller content. Prefer the semantic selectors and heading-based section extraction above; if a section is empty, wait for the page content to render and rerun the evaluator rather than guessing values.
  • Some listings omit condition, warranty, included-items, or technical-sheet sections. Preserve those fields as empty strings or empty objects instead of inferring them.
  • Price extraction may encounter multiple prices such as installments or crossed-out prices; the returned price is the first visible R$ value associated with the page and should be checked against the product price region when several prices are displayed.

Call it

GET https://production-sfo.browserless.io/skills?token=TOKEN-HERE&domain=shopee.com.br&task=extract-product-details