Como Fazer Scraping de Dados da AliExpress em Escala

Aprenda a fazer scraping de preços, estoque e variantes da AliExpress com Python, detecte bloqueios que retornam HTTP 200, e colete em escala com a Bright Data.
43 min de leitura
How to Scrape AliExpress blog image

Fazer scraping da AliExpress coleta o preço, estoque, variantes, avaliação e reviews que o monitoramento de preços e o rastreamento de concorrentes precisam. A maior parte não está no HTML, porque a página carrega depois a partir de uma API interna assinada.

Nos meus testes, uma requisição simples retornou HTTP 200 e nenhum preço, então um pipeline registraria um sucesso com uma linha vazia. A partir de um único endereço IP, a API assinada começou a bloquear chamadas em cerca de uma dúzia de requisições.

Este guia mostra como obter esses dados, detectar uma falha que parece um sucesso, e usar a API Scraper AliExpress da Bright Data em escala.

Resumo

  • O objeto window.runParams que tutoriais mais antigos analisam está vazio. Dados de preço, estoque e SKU carregam de mtop.aliexpress.pdp.pc.query, que precisa de uma assinatura MD5.
  • Detecte bloqueios por marcadores, não por códigos de status. Requisições bloqueadas retornaram HTTP 200, um cabeçalho bxpunish, e um marcador no corpo como rgv587_flag.
  • Uma assinatura correta não impediu bloqueios. Duas execuções de 60 chamadas de um único endereço IP, sem atraso, obtiveram dados de 7 e 15.
  • Em escala, faça scraping das páginas públicas de produto /item/. A API Scraper AliExpress da Bright Data extrai dados estruturados, e a API de Navegador permite que seu script leia a resposta completa da API de cada página.

O que acontece quando você faz scraping da AliExpress com um cliente HTTP

Comece com uma requisição HTTP, enviada sem navegador ou proxy. Se expirar, sua rede não consegue alcançar a AliExpress diretamente, e um proxy ou um dos métodos gerenciados abaixo envia a requisição de uma rede que consegue.

Tudo abaixo usa 3 pacotes. O script da API Scraper em uma seção posterior lê sua chave API da Bright Data a partir de uma variável de ambiente:

pip install curl_cffi playwright requests
export BRIGHTDATA_API_KEY="your-api-key"    # PowerShell: $env:BRIGHTDATA_API_KEY="your-api-key"

Busque uma página de produto com curl_cffi e leia o <title>, o objeto window.runParams que tutoriais mais antigos analisam, e o objeto bootstrap que a página passa para seu próprio JavaScript. Esses 3 valores mostram se um cliente HTTP simples é suficiente:

import re
from curl_cffi import requests as cffi

# qualquer URL de produto que ainda exista funciona
ITEM = "https://www.aliexpress.com/item/1005001398867186.html"

# nenhum User-Agent próprio: curl_cffi envia um que corresponde ao navegador que ele imita
r = cffi.get(ITEM, impersonate="chrome", timeout=30)
html = r.text

print("status      ", r.status_code)
print("bytes       ", len(html))
print("title       ", repr(re.search(r"<title>(.*?)</title>", html, re.S).group(1)))
print("runParams   ", repr(re.search(r"window\.runParams\s*=\s*(\{.*?\});", html, re.S).group(1)))
print("isCSR       ", re.search(r"isCSR\s*=\s*(\w+)", html).group(1))
print("crawler flag", re.search(r'"crawler":(\w+)', html).group(1))

A busca é bem-sucedida:

status       200
bytes        77226
title        ''
runParams    '{\n                    }'
isCSR        true
crawler flag false

A página é uma casca HTML, sem preço, estoque, SKU, avaliação, ou reviews. Antes de qualquer JavaScript executar, a casca já tem as tags Open Graph. og:title contém o nome real do produto enquanto <title> está em branco, e og:image, og:url, e og:type (definido como product) também estão lá. og:description contém apenas um slogan genérico da AliExpress, e a página não tem JSON-LD, então nenhuma das fontes fornece dados do produto.

Um navegador que carrega a mesma URL na mesma hora renderiza o preço, avaliação, e variantes:

Página de produto da AliExpress para um disjuntor de corrente residual TOMZN renderizado em um navegador, mostrando um preço de venda de $7.73 e um preço de lista de $14.40, uma avaliação de 4.7 de 80 avaliações e 489 vendidos, sete botões de variante de corrente nominal de 16A a 63A, o painel do vendedor TOMZN Official Store, e botões Comprar agora e Adicionar ao carrinho

Renderizado através da API de Navegador da Bright Data com segmentação por geolocalização definida para os EUA. A API interna da AliExpress retorna o mesmo preço de lista de $14.40 e avaliação 4.7 para este produto. O preço de venda vem de uma promoção temporária e muda ao longo do tempo.

Buscar esse preço de lista no código-fonte da página não encontra nada:

Aba view-source do navegador para www.aliexpress.us/item/3256801212552434.html com a barra de busca procurando por 14.40 e relatando 0/0 correspondências, acima do início da seção head da página, onde uma tag meta og:title nomeia o TOMZN RCBO Circuit Breaker 16-63A 220V Surge Protector e og:description contém um slogan genérico da AliExpress

Capturado na loja dos EUA, que serve a mesma casca de 77 KB.

Os campos de catálogo são baratos de coletar. Uma única requisição GET sem desbloqueio retorna os campos de catálogo, então você pode mapear IDs de produto para títulos e imagens principais sem renderizar. Pipelines de preço e sortimento precisam de dados de preço, estoque, e variante, que vêm do gateway de API interna da AliExpress. Planeje essas 2 tarefas separadamente.

Deixe o curl_cffi escolher o User-Agent. impersonate="chrome" é um alias em vez de uma versão. No código-fonte da biblioteca, curl_cffi 0.16.1 mapeia o alias para chrome150 através de uma constante DEFAULT_CHROME, e essa constante muda sempre que um novo perfil é lançado. Se você não definir o cabeçalho, a biblioteca também envia o User-Agent Chrome/150 correspondente.

Se você passar seu próprio User-Agent, ele substitui o User-Agent da biblioteca, então uma string Chrome/141 fixa é enviada com um handshake TLS do Chrome 150. A incompatibilidade não gera erro, então você pode implantar sem perceber. Omita o cabeçalho e deixe a biblioteca parear o User-Agent com o handshake, ou fixe o perfil em uma versão e combine a string com ele. Engenheiros que constroem clientes TLS relatam que uma identidade incompatível é mais fácil de detectar do que uma consistente, e que o User-Agent fica desatualizado muito antes do handshake TLS.

A flag isCSR mostra como a página renderiza. Seu valor é true porque a AliExpress moveu a página de detalhes do produto para renderização do lado do cliente (CSR). window.runParams é um objeto vazio não utilizado que extratores baseados em regex ainda correspondem. Um parser que o lê não gera erro e não retorna nada.

Todo cliente que testei obteve HTTP 200 e a mesma casca de 77 KB. Enviei 20 requisições consecutivas para essa URL de um único endereço IP residencial sem proxy, e todas retornaram HTTP 200. As requisições rodaram em uma única sessão a uma requisição a cada 0,4 segundos, sem rotação de User-Agent. Também busquei a mesma URL como python-requests/2.32.5, como curl/8.7.1, e como 3 perfis de personificação TLS.

Enviar o User-Agent do Googlebot muda "crawler" de false para true no objeto bootstrap. A resposta difere em 1 byte. A flag não adicionou um payload renderizado pelo servidor, então falsificar o Googlebot muda um valor booleano e nada mais.

Em um marketplace renderizado pelo cliente, verifique os dados antes de registrar uma busca como sucesso. O código de status mostra apenas que a requisição HTTP foi bem-sucedida. Neste alvo, a requisição foi bem-sucedida mesmo quando os dados estavam ausentes.

O bloqueio que retorna HTTP 200

A AliExpress realmente bloqueia requisições, mas a linha de status HTTP não mostra o bloqueio.

O próprio robots.txt do site nomeia um endpoint interno em uma regra específica do Googlebot, e esse endpoint pode retornar os dados do produto. Chame-o diretamente e leia os cabeçalhos de resposta antes do corpo:

from curl_cffi import requests as cffi

PID = "1005001398867186"
url = f"https://www.aliexpress.com/aeglodetailweb/api/msite/item?productId={PID}"
r = cffi.get(url, impersonate="chrome", timeout=30,
             headers={"Accept": "application/json",
                      "Referer": f"https://www.aliexpress.com/item/{PID}.html"})

print("status", r.status_code, "bytes", len(r.text))
for k in ("server", "x-rule-name", "bxpunish", "set-cookie"):
    print(f"  {k:12s} {str(r.headers.get(k))[:70]}")
print("body marker:", "rgv587_flag" in r.text, "| x5secdata:", "x5secdata" in r.text)

A resposta vem do sistema antibot Baxia da Alibaba, e tem um código de status de sucesso:

status 200 bytes 2399
  server       Tengine/AServer-Ingress
  x-rule-name  AE_GLOBAL_UNLOGIN
  bxpunish     1
  set-cookie   x5secdata=xffEdBbOh5ArPGTjyI_40B3_06C7jiK2nBFPGlpVzkk2x66w14DH6V
body marker: True | x5secdata: True

O corpo é um script que redireciona para uma URL _____tmd_____/punish através de window.location e define window._config_ = {"action":"captcha", ...}. Termina com <!--rgv587_flag:sm-->, o marcador de bloqueio da Alibaba. O cabeçalho x-rule-name nomeia a regra que correspondeu, AE_GLOBAL_UNLOGIN, que parece ser a regra para tráfego global não autenticado. O mesmo cabeçalho também apareceu em respostas normais, não bloqueadas, então não marca um bloqueio por si só.

Verificações de código de status aceitam essa resposta bloqueada. raise_for_status() não gera erro, e uma verificação status_code == 200 retorna verdadeiro. Se um dashboard conta respostas HTTP 2xx como sucessos, ele relata um pipeline saudável enquanto as linhas estão vazias.

Então detecte bloqueios pelos cabeçalhos e corpo da resposta, não pelo código de status. As strings de erro da Alibaba podem mudar sem aviso, mas um cabeçalho de resposta dedicado e uma string marcadora no corpo são baratos de verificar e baratos de estender:

BLOCK_MARKERS = ("rgv587_flag", "x5secdata", "_____tmd_____", "FAIL_SYS_USER_VALIDATE")

def is_blocked(resp, min_bytes=1024):
    """Verdadeiro quando o Baxia respondeu em vez da aplicação."""
    if resp.headers.get("bxpunish"):
        return True
    body = resp.text
    if len(body) < min_bytes:          # trate um 200 quase vazio como bloqueio também
        return True
    return any(m in body[:4096] for m in BLOCK_MARKERS)

Nas minhas execuções, essas verificações detectaram todo bloqueio que o código de status não mostrou. Execute as mesmas verificações antes do seu parser, e adicione-as antes de escrever qualquer seletor. Sem elas, você só saberia de um bloqueio neste alvo quando seu parser não encontrasse dados.

Registre o motivo de cada requisição falha. Neste alvo, uma página de bloqueio, uma casca vazia, e um preço na moeda errada cada um precisa de uma correção diferente, e uma taxa geral de sucesso esconde os 3. Divida os motivos por país de saída também, porque um preço em moeda errada ainda parece um sucesso.

Assinando chamadas de API de produto você mesmo

Os campos do produto realmente existem como JSON. Se você carregar uma página em um navegador e gravar seu tráfego XHR, pode ver que esses campos vêm de um gateway de API que a página chama.

Um carregamento de página faz chamadas ao gateway MTOP da Alibaba em acs.aliexpress.com/h5/<api>/<version>/, cada uma com jsv, appKey, um timestamp em milissegundos t, e um sign de 32 caracteres. mtop.aliexpress.pdp.pc.query retorna preço, estoque, SKU, loja, e avaliação. O texto de reviews vem de uma segunda chamada assinada, mtop.aliexpress.review.pc.list, no mesmo gateway, então coletar reviews precisa de uma segunda requisição por produto. Ler o script bootstrap inline mostra que a página pode chamar mais de uma API de produto:

Variante da página (viewName) API pré-carregada
newDetail mtop.aliexpress.pdp.pc.query
choiceDetail mtop.aliexpress.itemdetail.pc.asyncPCDetail
h5Detail mtop.aliexpress.itemdetail.msite

Todas as 6 páginas de produto que amostrei tinham viewName = 'newDetail'. As outras 2 APIs, a móvel mtop.aliexpress.itemdetail.msite e mtop.aliexpress.itemdetail.pc.asyncPCDetail, não foram mais fáceis de acessar. Ambas retornaram FAIL_SYS_USER_VALIDATE com bxpunish: 1 na primeira requisição, enviada com o mesmo payload que a API de desktop aceita.

Leia viewName em cada busca para ver quando uma listagem começa a renderizar como uma variante de página diferente. Um scraper fixado em um nome de API perderia essa mudança. O script bootstrap da página define um timeout de pré-carregamento de 15.000 ms e aborta seu próprio XHR em 10.000 ms, então use 10.000 ms como limite superior para seus próprios timeouts.

No painel Network do navegador, filtrado para pdp.pc.query, a própria requisição da página contém todo valor que seu código de assinatura precisa:

Painel Network do Chrome DevTools filtrado para pdp.pc.query, na aba Payload de uma requisição para mtop.aliexpress.pdp.pc.query, listando jsv 2.5.1, appKey 12574478, um t de 13 dígitos, um sign de 32 caracteres, type e dataType originaljsonp, timeout 15000, callback mtopjsonp1, e um objeto data com productId 3256801212552434 e um bloco ext, com um valor de token censurado

A própria chamada da página, capturada na loja dos EUA, onde o ID desta listagem é 3256801212552434. A página envia originaljsonp e adiciona um bloco timeout e ext. O código de assinatura abaixo envia jsonp e omite ambos, e o gateway aceitou essa requisição mais curta. Um valor de token está censurado.

O valor sign usa o esquema de assinatura H5 da Alibaba, que é curto o suficiente para reimplementar em uma função. O esquema junta o token de sessão, o timestamp, a chave do app, e o corpo exato da requisição com e-comercial, e então aplica hash MD5 no resultado:

import hashlib, json, time
from curl_cffi import requests as cffi

APP_KEY, API, VER = "12574478", "mtop.aliexpress.pdp.pc.query", "1.0"
GATEWAY = f"https://acs.aliexpress.com/h5/{API}/{VER}/"

def sign(token, t, data):
    return hashlib.md5(f"{token}&{t}&{APP_KEY}&{data}".encode()).hexdigest()

def pdp(session, product_id, token=""):
    data = json.dumps({"productId": product_id, "_lang": "en_US", "_currency": "USD",
                       "country": "US", "province": "", "city": "", "channel": "",
                       "pdp_ext_f": "{}", "sourceType": "", "clientType": "pc"},
                      separators=(",", ":"))
    t = str(int(time.time() * 1000))
    params = {"jsv": "2.5.1", "appKey": APP_KEY, "t": t, "sign": sign(token, t, data),
              "api": API, "v": VER, "type": "jsonp", "dataType": "jsonp",
              "callback": "mtopjsonp1", "data": data}
    return session.get(GATEWAY, params=params, impersonate="chrome", timeout=30,
                       headers={"Referer": "https://www.aliexpress.com/"})

s = cffi.Session()
first = pdp(s, "1005001398867186")                       # não assinado, retorna o cookie do token
token = s.cookies.get("_m_h5_tk", "").split("_")[0]
second = pdp(s, "1005001398867186", token)               # assinado com esse token
print("call 1:", len(first.text), "bytes |", first.text[:60])
print("call 2:", len(second.text), "bytes")

A primeira chamada retorna o token, e a segunda chamada retorna o produto:

call 1: 151 bytes | mtopjsonp1({"api":"mtop.aliexpress.pdp.pc.query","data":{},"ret
call 2: 103748 bytes

A chamada 1 retorna FAIL_SYS_TOKEN_EMPTY e um cabeçalho Set-Cookie com _m_h5_tk. A chamada 2 é assinada com a primeira parte do valor desse cookie e retorna JSONP, uma chamada de função envolvendo um objeto JSON. Se você remover o wrapper mtopjsonp1(...), obtém o registro completo do produto, incluindo o título, originalPrice $14.40, salePriceString $6.84, 59.924 unidades de inventário, avaliação 4.7, ID do vendedor 200422816, e categoryPath 13/5/141914.

Você precisa apenas de uma pequena parte desse payload. A resposta tem 27 módulos de nível superior, e 8 deles contêm os campos que um pipeline de preço ou sortimento armazenaria. Serializados como JSON compacto, esses 8 módulos têm 16.494 bytes, o que é 15,9% do objeto de resultado inteiro. O maior módulo é SHIPPING com 34.071 bytes, e o segundo maior é GLOBAL_DATA com 23.783 bytes.

Assinar precisa apenas de um hash MD5, um cookie, e uma tentativa de repetição. Se você já reimplementou o esquema de assinatura H5 para outro site da Alibaba, espere algumas horas de trabalho. Então quanto tempo seu código de assinatura continua funcionando?

Quantas chamadas de API assinadas têm sucesso a partir de um único endereço IP

Executar a chamada assinada em loop responde essa pergunta rapidamente. O teste usa 10 IDs de produto, um token, um endereço IP residencial, e nenhum atraso entre chamadas. O script conta cada código ret que o gateway retorna:

# continua a sessão do código de assinatura: pdp(), s e token estão definidos lá
from collections import Counter
import re

# dez IDs de produto, substitua qualquer um que foi removido
IDS = ["3256812884305776", "1005007712136541", "1005002754717958", "1005002495501001",
       "2255799968025159", "1005008121783683", "3256809736486496", "1005008175462016",
       "1005012238762416", "3256812840307416"]

outcomes, first_fail = Counter(), None
for i in range(1, 61):
    r = pdp(s, IDS[i % len(IDS)], token)
    ret = re.search(r'"ret":\["([^"]+)"', r.text).group(1).split("::")[0]
    key = ret + (" +bxpunish" if r.headers.get("bxpunish") else "")
    outcomes[key] += 1
    if key != "SUCCESS" and first_fail is None:
        first_fail = i
print(dict(outcomes), "| primeiro não-SUCCESS em", first_fail)

O loop parou de obter dados após a 7ª chamada:

{'SUCCESS': 7, 'FAIL_SYS_USER_VALIDATE +bxpunish': 53} | primeiro não-SUCCESS em 8

O array ret de cada resposta bloqueada também continha RGV587_ERROR::SM.

O bloqueio se repetiu, mas a contagem de sucesso mudou. Uma segunda execução do mesmo endereço IP enviou 6,2 requisições por segundo, registrou o código de status e o comprimento da resposta de cada chamada, obteve 15 sucessos em 60, e foi bloqueada pela primeira vez na requisição 12. Retornou HTTP 200 para todas as 60 chamadas, e todas as suas 45 respostas bloqueadas tinham exatamente 543 bytes. Então baseie seu planejamento de capacidade no bloqueio, não em nenhuma das contagens de sucesso.

Uma nova sessão não removeu o bloqueio. Cada uma de 3 novas sessões consecutivas começou de um jar de cookies vazio e foi bloqueada antes que o cliente pudesse obter um token. Uma requisição enviada após uma espera de 60 segundos, novamente de um jar de cookies vazio, também foi bloqueada. Então uma nova tentativa automática adiciona tráfego sem adicionar dados, e engenheiros de proxy alertam que uma lógica de nova tentativa assim gasta orçamento sem qualquer erro visível.

Este teste não usou atraso entre chamadas. Em seguida, teste as variáveis que um scraper de produção adiciona, como um backoff longo, um novo token para cada lote, uma taxa de requisição deliberadamente lenta, e o bloco ext que a própria requisição da página assina. Esse bloco contém uma flag crawler e uma chave x-m-biz-bx-region.

A assinatura permaneceu correta da requisição 7 até a requisição 60, então mais engenharia reversa é improvável de aumentar as contagens de sucesso de 7 e 15 em 60 requisições. Os testes excluem a sessão como causa. Nenhum teste mudou o endereço IP e a impressão digital separadamente, então o bloqueio pode depender de um ou de outro. Sistemas antibot também podem vincular uma impressão digital em muitos endereços IP, então profissionais mudam o endereço IP, a impressão digital, e os cookies juntos.

Três verificações mostram que o cliente não é a causa. No eco de impressão digital tls.peet.ws, o código de assinatura no curl_cffi 0.16.1 correspondeu a um Chromium 152 real nessa máquina, com a mesma impressão digital JA4, t13d1516h2_8daaf6152771_806a8c22fdea, e a mesma impressão digital HTTP/2. Cinco impressões digitais do curl_cffi e das bibliotecas Rust rnet e wreq produziram 100 respostas bloqueadas e nenhum token, mesmo que o wreq envie perfis Chrome mais novos do que o curl_cffi 0.16.1. Patchright, nodriver, e Camoufox cada um teve sucesso 0 vezes em 8 tentativas a partir desse endereço IP, enquanto a API de Navegador da Bright Data, em um IP de saída diferente, retornou o produto no mesmo minuto.

Esse endereço IP já estava bloqueado quando essas 3 ferramentas rodaram, então todas as 3 falharam da mesma forma nele. Os zeros mostram a reputação do endereço IP, e não classificam as ferramentas.

A primeira verificação, o eco de impressão digital do TrackMe em tls.peet.ws, retorna toda impressão digital de uma única requisição:

Página da API de fingerprinting TrackMe listando impressões digitais JA3, JA4, p0f, Akamai HTTP/2 e PeetPrint para o cliente requisitante, com um valor JA4 de t13d1516h2_8daaf6152771_806a8c22fdea e uma impressão digital Akamai HTTP/2 de 52d84b11737d980aef856699f885ca86, acima de um painel de detalhes da requisição mostrando TLS 772, HTTP/2, e suporte a TLS 1.3 e 1.2

Verifique se um perfil reproduz tanto a impressão digital JA4 quanto a HTTP/2 do navegador em cujo nome ele foi batizado.

Um perfil mais novo não foi uma correspondência melhor aqui. O perfil Chrome 152 do wreq enviou um ClientHello com 17 extensões, e o próprio Chrome 152 enviou 16. O número de versão no nome de um perfil não garante a impressão digital que ele envia, então verifique cada perfil contra um eco de impressão digital antes de confiar nele. Aqui, o perfil padrão do curl_cffi correspondeu exatamente ao navegador real.

Fazer engenharia reversa de um endpoint assinado mostra como os dados se parecem, mas não quanto você pode coletar deles. Nesses testes, nem uma assinatura melhor nem um navegador melhor aumentaram essa quantidade.

Deixando a página assinar chamadas de API de produto para você

Uma segunda forma de obter o payload do produto usa automação de navegador em vez de chamadas de API do seu próprio código. A página já faz a chamada assinada. Anexe um manipulador de resposta e leia as respostas que a página recebe. Através da API de Navegador, as requisições da página também passam pela rede de proxy da Bright Data.

Ler a própria resposta da página remove o token, o timestamp, a assinatura MD5, e o jar de cookies do seu código:

import json, re
from playwright.sync_api import sync_playwright

WSS = "wss://brd-customer-<id>-zone-<zone>:<password>@brd.superproxy.io:9222"

def capture_pdp(product_id):
    """Lê a resposta para a chamada que a página já faz."""
    bodies = []

    def keep(response):
        if "mtop.aliexpress.pdp.pc.query" not in response.url:
            return
        try:
            bodies.append(response.text())
        except Exception:
            pass                      # redirecionamentos e páginas fechadas não têm corpo legível

    with sync_playwright() as pw:
        browser = pw.chromium.connect_over_cdp(WSS, timeout=150_000)
        ctx = browser.contexts[0] if browser.contexts else browser.new_context()
        page = ctx.new_page()
        page.on("response", keep)
        try:
            page.goto(f"https://www.aliexpress.com/item/{product_id}.html",
                      wait_until="domcontentloaded", timeout=150_000)
            page.wait_for_timeout(13_000)
        except Exception:
            pass                      # mantém o que keep() coletou antes da navegação terminar
        browser.close()

    for body in sorted((b for b in bodies if isinstance(b, str)), key=len, reverse=True):
        m = re.match(r"\s*mtopjsonp\d*\((.*)\)\s*$", body, re.S)
        try:
            result = json.loads(m.group(1) if m else body).get("data", {}).get("result")
        except (ValueError, AttributeError):
            continue                  # pula um corpo que não é JSON, como uma página HTML
        if isinstance(result, dict) and len(result) > 10:
            return result
    return None

result = capture_pdp("1005001398867186")
if result:
    print(len(result["PRICE"]["skuIdStrPriceInfoMap"]), "SKUs com preço")

WSS conecta a uma zona da API de Navegador, e seus 3 placeholders vêm dos detalhes de acesso dessa zona:

Painel de Controle da Bright Data na aba Overview de uma zona da API de Navegador chamada scraping_browser2 sob Web Access, mostrando Access details com o host brd.superproxy.io, um nome de usuário, uma senha mascarada, e um endpoint Puppeteer e Playwright mostrando wss://brd-customer-, um ID de conta redigido, -zone-scraping_browser2, uma senha mascarada e @brd.superproxy.io:9222

A linha Puppeteer / Playwright é o valor que WSS espera, e seu ID de conta, nome da zona, e senha preenchem seus 3 placeholders. O ID de conta está redigido aqui, e o Painel de Controle mascara a senha.

Mantenha toda resposta, não apenas a primeira. A página chama esse endpoint mais de uma vez por carregamento. Registre o tamanho de todo corpo de resposta que keep() coleta para ver qual contém o produto:

3256812884305776  sizes=[151, 170539]          141 SKUs com preço
1005007712136541  sizes=[151, 244023]           75 SKUs com preço
1005002754717958  sizes=[151, 105852, 106046]   20 SKUs com preço
1005002495501001  sizes=[151,  96065]           18 SKUs com preço
2255799968025159  sizes=[151,  93982]           15 SKUs com preço
1005008121783683  sizes=[151,  63780]            6 SKUs com preço

A resposta de 151 bytes define o cookie do token. A grande resposta depois dela contém o registro do produto, incluindo o mapa de preço de SKU. Quando um carregamento tem mais de uma grande resposta, capture_pdp retorna a maior.

O que os arquivos robots.txt da AliExpress permitem

Leia os arquivos robots.txt antes de escolher um método. Se uma diretiva for nova para você, nosso guia de robots.txt para scraping de dados explica. Os arquivos no host da API e no host principal têm regras diferentes:

$ curl -s https://acs.aliexpress.com/robots.txt
User-agent: *
Disallow: /

$ curl -s https://www.aliexpress.com/robots.txt | grep -E '/item'
Disallow: */aeglodetailweb/api/msite/item?productId*
Disallow: /items/*
Disallow: /item-img/*
Disallow: /item-img/*

O gateway MTOP está completamente proibido. No host principal, toda regra mencionando item se aplica a outra coisa (/items/*, /item-img/*, e a regra do Googlebot no endpoint aeglodetailweb). Nenhuma regra se aplica a /item/1005001398867186.html, enquanto todo /search/* é proibido. Use os proxies residenciais da Bright Data, ou um dos métodos abaixo, para os caminhos que o robots.txt permite.

Verifique também os mapas de site e arquivos de manifesto de agente do host principal. O robots.txt do .com listou mapas de site de itens, e o primeiro arquivo de mapa de site continha 31.999 URLs /item/, então os mapas de site são uma fonte direta de IDs de produto. Nos testes, os 6 caminhos de manifesto de agente (/llms.txt e /ai.txt, mais agent.json, ai-plugin.json, mcp.json, e acp.json sob /.well-known/) redirecionaram com um 302 para /p/error/404.html.

www.aliexpress.us/robots.txt tinha as mesmas regras do arquivo .com, sem as linhas de mapa de site. Como o site redireciona visitantes entre os 2 hosts por região, essas regras se aplicam em ambos os hosts. m.aliexpress.com publica um arquivo mais longo com sua própria regra AdsBot, então verifique o host que você está chamando.

Um host da AliExpress publica uma exceção, o único caminho de API assinada que qualquer um desses arquivos permite. recom-acs.aliexpress.com serve Disallow: / seguido de Allow: /h5/mtop.relationrecommend.aliexpressseorecommend.recommend/*. Chamado com um payload deliberadamente incompleto, esse endpoint retornou FAIL_SYS_BIZPARAM_MISSED sem um cabeçalho bxpunish, enquanto os endpoints proibidos retornaram FAIL_SYS_USER_VALIDATE com esse cabeçalho. Então o robots.txt e o sistema antibot ambos permitiram o mesmo endpoint.

A página de produto é o oposto do gateway. A AliExpress a serve ao público, o robots.txt não a proíbe, e cada um dos 3 produtos da Bright Data a acessou nos testes. Uma renderização do Web Unlocker, a resposta do gateway, e um registro da API Scraper tinham todos o mesmo preço de venda de $6.84 em testes de localidade dos EUA.

Faça scraping das páginas que o site torna públicas, e deixe sua infraestrutura cuidar de endereços IP e impressões digitais. Essa abordagem é menos propensa a quebrar após um redesenho ou mudança de assinatura, enquanto uma rotina de assinatura copiada do pacote JavaScript do site precisa ser refeita após qualquer um dos dois.

3 métodos para fazer scraping da AliExpress em escala

A API de Navegador, Web Unlocker, e API Scraper da Bright Data fornecem cada uma os dados do produto em uma forma diferente. Nossa comparação do Web Unlocker e da API de Navegador explica em geral como diferem. Buscar na Biblioteca de Scrapers do Painel de Controle por AliExpress mostra os scrapers disponíveis para este domínio:

Resultados de busca da Biblioteca de Scrapers da Bright Data para "aliexpress", listando o domínio aliexpress.us sob Resultados para Scrapers e um conjunto de dados pronto para uso chamado "Aliexpress products", acima de um painel Scraper Studio com o título "Build a scraper for any website with Scraper Studio"

Uma busca retorna os scrapers do domínio e um conjunto de dados AliExpress pronto para uso.

A API de Navegador executa seu script em um navegador em nuvem gerenciado com desbloqueio embutido e permite que seu script leia as respostas de rede que a página recebe. Quando eu conectei o Playwright via CDP e carreguei a página do produto, a página mostrou o título e uma caixa de compra com seu preço, e meu script pôde ler todas as 171 respostas desse único carregamento. A API de Navegador também suporta segmentação por geolocalização, então a caixa de compra mostra preços na moeda do país de saída que você escolher. Para preços dos EUA, acrescente -country-us ao nome de usuário da zona.

O preço da API de Navegador é por GB de tráfego, então o peso da página permite estimar o custo de uma execução antes de começar. Em 3 carregamentos, os bytes transferidos nos eventos Network.loadingFinished do CDP tiveram média de 3,03 MB por página de produto, com uma faixa de 2,68 MB a 3,47 MB. O networkidle do Playwright espera 500 ms sem requisições, mas essa página continuou enviando requisições em segundo plano. Então capture_pdp espera por domcontentloaded e depois por um tempo fixo.

O Web Unlocker cuida do desbloqueio, renderiza a página, e retorna um documento. Sua saída bruta para a página do produto foi de 344.355 bytes, comparados com a casca de 77.226 bytes, então o JavaScript executou. Sua saída markdown para esta página foi de 21 KB a 24 KB de texto que uma LLM ou uma regex pode ler diretamente. O comando brightdata scrape na CLI da Bright Data usa saída markdown por padrão.

O preço do Web Unlocker é por requisição, e você paga apenas pelas bem-sucedidas. Defina country em cada requisição para selecionar a loja.

A API Scraper AliExpress retorna dados estruturados. Você envia URLs de produto e recebe registros com os campos já extraídos, como título, preço de lista e de venda, disponibilidade, especificações, vendedor, avaliação, reviews, e URLs de imagem. A API Scraper de Imagens AliExpress é especializada em dados de imagem. O preço da API Scraper é por registro entregue, então você paga apenas pelos resultados.

Produto Unidade de preço O que você recebe Use quando
API de Navegador GB de tráfego Uma sessão de navegador e as respostas de rede da página Você quer as próprias respostas de API da página, como o preço de cada variante
Web Unlocker Requisição bem-sucedida HTML renderizado ou markdown que você analisa Você já mantém um parser, ou envia páginas para uma LLM
API Scraper Registro entregue Registros de produto estruturados Você quer registros, não páginas

Executar seu próprio navegador é a quarta opção, então eu medi isso também. Os 3 navegadores stealth (Patchright, nodriver, e Camoufox) são de código aberto e tiveram média de 14,1 a 15,9 segundos por carregamento. Esse tempo é um mínimo, porque nenhum dos 24 carregamentos alcançou os dados do produto, e um carregamento bem-sucedido também gastaria tempo analisando-o.

Em um endereço IP já bloqueado, todos os 3 navegadores stealth retornaram o cabeçalho e rodapé da página mas nenhum dado de produto. Cada produto da Bright Data tem proxies embutidos, e um navegador que você mesmo executa pode usar os Proxies AliExpress da Bright Data.

Executando a API Scraper em escala

A API Scraper aceita URLs em um endpoint e retorna registros em outro. Sua página no Painel de Controle mostra ambos os modos de scraper ao lado de uma requisição pronta para executar:

Tela de configuração da Biblioteca de Scrapers da Bright Data para o endpoint "Aliexpress products - collect by URL", mostrando duas URLs de exemplo pré-preenchidas na lista de Inputs, uma escolha de modo scraper entre Synchronous (real-time) e Asynchronous, e um exemplo de código Linux Bash de requisição autenticada à direita

O painel esquerdo lista os 2 endpoints do domínio, Collect by URL e Discover by category URL. O dataset_id no exemplo de código à direita é o mesmo valor que o script abaixo usa.

O modo síncrono retorna um snapshot_id quando uma coleta leva muito tempo, e a coleta continua em segundo plano. Para um catálogo completo, acione uma coleta assíncrona em blocos e salve o progresso após cada um, assim se seu processo travar, a execução retoma e você não paga pelas mesmas URLs duas vezes:

import json, os, time, requests

API = "https://api.brightdata.com/datasets/v3"
DATASET = "gd_mlj9v75u1w1jvaxvwp"          # Produtos AliExpress
TOKEN = os.environ["BRIGHTDATA_API_KEY"]   # chave API das configurações da sua conta
CHUNK = 40                                 # blocos maiores dispararam mais rápido por input
H = {"Authorization": f"Bearer {TOKEN}", "Content-Type": "application/json"}

def _await(snapshot, timeout=3600, poll=20):
    deadline = time.time() + timeout
    while time.time() < deadline:
        p = requests.get(f"{API}/progress/{snapshot}", headers=H, timeout=60).json()
        if p.get("status") in ("ready", "failed"):
            return p
        time.sleep(poll)
    raise TimeoutError(snapshot)

def _save(state, state_path):
    with open(state_path, "w") as fh:
        json.dump(state, fh)

def run(urls, state_path="run_state.json", out_path="rows.jsonl"):
    state = {"done": {}, "dead": []}
    if os.path.exists(state_path):
        with open(state_path) as fh:
            state = json.load(fh)

    for n in range(0, len(urls), CHUNK):
        key = str(n // CHUNK)
        done = state["done"].get(key, {})
        if done.get("records") is not None:     # concluído significa baixado, não acionado
            continue
        batch, t0 = urls[n:n + CHUNK], time.time()
        snapshot = done.get("snapshot")         # acionado antes de uma queda: reutilize esse snapshot
        if not snapshot:
            r = requests.post(f"{API}/trigger?dataset_id={DATASET}&include_errors=true",
                              headers=H, json={"input": [{"url": u} for u in batch]},
                              timeout=120)
            r.raise_for_status()
            snapshot = r.json()["snapshot_id"]
            state["done"][key] = {"snapshot": snapshot}
            _save(state, state_path)            # registre o gatilho ANTES da espera

        print(f"bloco {key}: coletando {len(batch)} URLs")
        progress = _await(snapshot)
        rows = requests.get(f"{API}/snapshot/{snapshot}?format=json",
                            headers=H, timeout=600).json()
        with open(out_path, "a") as fh:
            for row in rows:
                if row.get("error_code") == "dead_page":   # listagem removida da AliExpress
                    state["dead"].append(row["input"]["url"])
                elif not row.get("error"):
                    fh.write(json.dumps(row) + "\n")
        state["done"][key].update(records=progress.get("records"), errors=progress.get("errors"))
        _save(state, state_path)
        print(f"bloco {key}: coletado em {(time.time() - t0) / len(batch):.1f}s por input")

    return state

run(["https://www.aliexpress.com/item/1005001398867186.html"])   # passe sua lista completa de URLs

include_errors=true adiciona o relatório de erros aos resultados, então toda URL na sua lista tem um status.

Cada linha em rows.jsonl é um registro de produto. Este script imprime alguns campos do primeiro registro:

import json

with open("rows.jsonl") as fh:
    row = json.loads(fh.readline())
for key in ("item_id", "title", "brand", "price", "sale_price",
            "availability", "star_rating", "review_count"):
    print(f"{key:13s}", row[key])
print("variants     ", [o["option_name"] for o in row["variants"][0]["variant_options"]])

A saída mostra o disjuntor da primeira seção:

item_id       1005001398867186
title         TOMZN Main Switch Residual Current Circuit Breaker With Surge Protector RCBO MCB With Lightning Protection SPD
brand         TOMZN
price         $14.40
sale_price    $8.40
availability  in_stock
star_rating   4.7
review_count  80
variants      ['16A', '20A', '25A', '32A', '40A', '50A', '63A']

O preço de lista, a avaliação 4.7 de 80 reviews, e as 7 variantes de corrente nominal correspondem à página renderizada na primeira seção. O preço de venda vem de uma promoção temporária, então difere entre capturas.

Mantenha todo bloco cheio. Nessas execuções, um gatilho de 40 URLs terminou 2,8 vezes mais rápido por URL do que um de 20. Preencha o último bloco com URLs da sua próxima execução para que rode em tamanho completo. Para centenas de milhares de URLs, deixe a Bright Data entregar cada snapshot para seu armazenamento em nuvem ou para um webhook, assim os resultados chegam sem polling.

Executar run() novamente com o mesmo arquivo de estado terminou em 0,08 segundos, não enviou gatilhos, e não escreveu linhas duplicadas. O script também lida com um bloco que foi acionado antes do processo travar durante a espera. Ele pula um bloco apenas se seus registros já foram baixados. Caso contrário, reutiliza o snapshot salvo, então uma queda durante a espera não perde um snapshot coletado.

Meça com que frequência os preços mudam, depois combine seu cronograma de coleta com essa taxa. Em uma verificação, capturei 12 produtos duas vezes, com 25 minutos de diferença, e nenhum de seus preços mudou. Essa amostra não pode mostrar uma taxa de mudança de preço, então use o método, não o resultado. Dois snapshots dos seus próprios produtos, tirados no intervalo que você está considerando, responderão a pergunta para o seu catálogo.

Discovery, o endpoint de descoberta por categoria do scraper, lista produtos a partir de uma URL semente. Com a página de busca por atacado /w/wholesale-ergonomic-chair.html como semente, uma requisição type=discover_new&discover_by=category_url retornou URLs de produto na primeira execução. Use /w/wholesale-<keyword>.html como seu padrão de URL semente, limite toda execução de descoberta com limit_per_input enquanto calibra, e passe as URLs retornadas diretamente para a execução de coleta.

Se você coleta IDs de produto de outros lugares, como mapas de site de itens ou links de SEO de página de produto, uma única requisição GET sem renderizar mostra se cada ID ainda tem uma página de produto. Páginas de produto retornaram 77,2 a 77,4 KB com um og:title preenchido, enquanto 3 IDs coletados desses links de SEO retornaram 75,26 KB com todo campo og: vazio. Essa diferença de 2 KB é visível na resposta bruta, então descarte os IDs com campos og: vazios antes de enfileirar uma coleta.

2 verificações que mantêm os dados de preço comparáveis

Os dados precisam da mesma validação que as respostas HTTP. Cada uma dessas 2 verificações é barata de adicionar e ajuda você a identificar preços que não são comparáveis antes que cheguem a um dashboard.

O desconto variou com o país de saída, não apenas com a moeda. Busquei um ID de produto através do Web Unlocker em formato markdown a partir de 6 países de saída, e cada país retornou uma promoção diferente:

País de saída Moeda Preço de venda Preço de lista Desconto
Estados Unidos USD $6.84 $14.40 52,5%
Reino Unido GBP £9.83 £11.16 11,9%
Alemanha EUR 11,69€ 13,29€ 12,0%
Brasil BRL R$62,25 R$68,30 8,9%
Austrália AUD AU$18.59 AU$19.42 4,3%
Canadá CAD C$19.86 C$20.70 4,1%

Mesmo os percentuais não são diretamente comparáveis, e cada página mostra por quê. A caixa de compra dos EUA rotula seu preço como Ends :, então a linha dos EUA é uma oferta por tempo limitado capturada em um momento. A página do Reino Unido diz Price includes VAT enquanto a página dos EUA diz Tax excluded. A página alemã afirma 30-day lowest price before the discount, a regra de preço de referência da UE que tende a fazer descontos alemães parecerem menores.

Por causa dessas 3 diferenças, defina um país de saída para cada série de preço, armazene-o na linha, e compare uma série apenas consigo mesma. O Web Unlocker o define com o parâmetro country, e a API de Navegador o define com um sufixo -country-<code> no nome de usuário da zona. Se você precisa das próprias promoções em vez de um pipeline de preço, o Rastreador de Promoções AliExpress monitora promoções de concorrentes para você.

O símbolo da libra do Reino Unido é o de largura total. A loja do Reino Unido usa £, U+FFE1 FULLWIDTH POUND SIGN, então uma regex de moeda escrita com £ (U+00A3) não corresponde a nada. As lojas alemã e brasileira usam uma vírgula como separador decimal no campo de preço. Analise a moeda a partir da configuração da loja que você solicitou e mantenha a análise do número separada do símbolo.

O preço de cada variante e o caminho completo de categoria

A API de Navegador da Bright Data captura o mapa de preço de SKU de uma página de produto da AliExpress, e o Web Unlocker retorna o breadcrumb de categoria do rodapé. Reviews também têm um scraper dedicado, o Scraper de Reviews AliExpress, cujos registros incluem o ID e a URL do produto.

A própria resposta da página contém o mapa de preço de SKU. As capturas registradas com capture_pdp incluíram preços para 141, 75, 20, 18, 15, e 6 SKUs. Para um produto, o mapa de preço tinha 6 preços distintos, de 2.56 a 10.96. O mapa está no módulo PRICE, ao lado do SKU pré-selecionado e do campo region:

Aba Response do Chrome DevTools para uma requisição pdp.pc.query mostrando region US, selectedSkuId 12000059478006652, e um skuIdStrPriceInfoMap cujas três primeiras entradas são pacotes de 1 unidade listados a $7.61, $7.58 e $7.71 com preço de venda de $1.09 cada, depois uma entrada de pacote de 10 unidades listada a $57.34

Essa captura vem de uma listagem multipack. A página pré-seleciona um pacote de 1 unidade a $1.09, e a quarta entrada é um pacote de 10 unidades listado a $57.34, então o mapa contém o próprio preço de cada variante.

Para obter o preço de cada variante, execute capture_pdp através da API de Navegador, leia PRICE.skuIdStrPriceInfoMap, e armazene com item_id como chave. Leia o mapa da região cujos preços você precisa, porque também pode variar por região. Para o disjuntor da primeira seção, o mapa tinha preços de variante diferentes em uma sessão dos EUA e um único preço para todas as 7 variantes em outra região.

O markdown do Web Unlocker contém o caminho de categoria. A página renderizada lista as categorias do produto como um breadcrumb no rodapé, e o GLOBAL_DATA.globalData do gateway tem o mesmo caminho em seu campo categoryPath, 13/5/141914. O parser abaixo lê a página do produto como markdown do Web Unlocker, o formato que brightdata scrape retorna por padrão:

import re

CRUMB   = re.compile(r"^This product belongs to\s.*$", re.M)
CAT_URL = re.compile(r"/category/(\d+)/([a-z0-9-]+)\.html")

def categories_from_markdown(md):
    """IDs e URLs de categoria do rodapé da página de produto."""
    line = CRUMB.search(md)
    if not line:
        return []
    return [{"category_id": cid,
             "url": f"https://www.aliexpress.com/category/{cid}/{slug}.html"}
            for cid, slug in CAT_URL.findall(line.group(0))]

with open("page.md", encoding="utf-8") as fh:    # brightdata scrape <URL do produto> -o page.md
    print(categories_from_markdown(fh.read()))

O \s no final do primeiro padrão é essencial, porque o rodapé coloca U+00A0, um espaço não quebrável, imediatamente após to. O mesmo padrão escrito com um espaço literal não encontra correspondência. Com \s, o parser retorna o caminho de categoria:

[{'category_id': '13',     'url': '.../category/13/home-improvement.html'},
 {'category_id': '5',      'url': '.../category/5/electrical-equipments-supplies.html'},
 {'category_id': '141914', 'url': '.../category/141914/circuit-breakers.html'}]

Obter o caminho de categoria precisa de uma requisição do Web Unlocker por produto. Execute o parser nos produtos cuja taxonomia você precisa, e armazene o resultado com item_id como chave. O texto do rodapé pode mudar sem aviso, então verifique o padrão CRUMB primeiro se o parser retornar uma lista vazia.

O Scraper Studio lida com uma lista de campos personalizada. Quando você descreve os dados que precisa, sua IA gera um scraper pronto para executar com proxies e desbloqueio embutidos, e você pode adicionar ou mudar campos de saída depois. Para monitoramento de preços especificamente, a API Scraper de Preços AliExpress coleta dados de preços da AliExpress para que você possa rastrear como os preços mudam.

Detectando mudanças de página e esquema

Toda falha vista neste alvo foi silenciosa, então monitore a própria página. O script bootstrap da página contém uma versão de build, cosmos_prefetch_mtop version 20240605.195626.856, e quando essa versão muda, a configuração de pré-carregamento pode mudar com ela. Uma impressão digital detecta essa mudança diretamente. Sem uma, seu próprio scraper só mostraria isso como valores nulos.

As 2 impressões digitais abaixo verificam 3 estágios (a casca HTML, a página renderizada, e a linha entregue), e nenhuma depende de analisar campos de produto:

import hashlib, re

def shell_fingerprint(html):
    """De um GET não renderizado. Detecta uma mudança de bootstrap ou configuração de pré-carregamento."""
    view = re.search(r"viewName\s*=\s*'([^']+)'", html)
    api  = re.search(r"api:\s*'(mtop\.[^']+)'", html)
    ver  = re.search(r"cosmos_prefetch_mtop version ([\d.]+)", html)
    return {"viewName": view.group(1) if view else None,
            "prefetch_api": api.group(1) if api else None,
            "gop_version": ver.group(1) if ver else None}

def row_fingerprint(row):
    """De uma linha entregue. Confirma o conjunto de campos que seu pipeline espera."""
    keys = sorted(k for k in row if k not in ("input", "timestamp"))
    return {"fields_sha1": hashlib.sha1(",".join(keys).encode()).hexdigest()[:12]}

Em capturas reais, ambas as impressões digitais retornaram valores estáveis:

shell   GET não renderizado : viewName=newDetail  api=mtop.aliexpress.pdp.pc.query  gop=20240605.195626.856
shell   DOM renderizado     : viewName=newDetail  api=mtop.aliexpress.pdp.pc.query  gop=20240605.195626.856
row     1005012408061151 : fields_sha1=154f2f84da5f
row     1005001398867186 : fields_sha1=154f2f84da5f
row     1005011666876666 : fields_sha1=154f2f84da5f

Uma terceira impressão digital do payload capturado parece útil, mas pode causar falsos positivos. Aplicar hash no conjunto de módulos parece a mesma ideia, mas a contagem de módulos variou com a listagem e o momento da captura, não apenas com uma nova implantação. Os payloads capturados tinham 28, 27, e 28 módulos, e o conjunto de chaves PRICE diferiu entre 2 deles.

A contagem mudou dentro de um produto também. O mesmo ID de produto retornou 27 módulos e depois 26 em 2 capturas com minutos de diferença, então mesmo um histórico por produto precisa de um limite em vez de uma verificação de igualdade. Baseie seus alertas nas impressões digitais de casca e linha, porque seus valores permaneceram os mesmos em toda captura.

Quando comprar o conjunto de dados AliExpress em vez disso

Os 3 métodos todos começam a partir de URLs que você já tem. Se sua pergunta é sobre o mercado inteiro, comece com o conjunto de dados pronto para uso:

Página do Marketplace de Conjuntos de Dados da Bright Data para "Aliexpress products", mostrando a descrição do conjunto de dados com botões Talk to a data expert e Proceed to purchase, e uma aba Data sample com controles de filtro para product_category, brand, store_country e star_rating ao lado de uma tabela de pré-visualização com colunas url, item_id, variant_id, title e description

O painel de filtro permite selecionar registros antes de pagar por eles.

O conjunto de dados AliExpress reduz o trabalho de seleção e o tempo de coleta. Você filtra o conjunto de dados para os registros que precisa, no marketplace ou através da API de Filtro de Conjunto de Dados, e paga apenas pelo seu snapshot filtrado. Esses registros já existem, então não há execução de coleta para esperar. Você pode comprá-lo uma vez ou em um cronograma de atualização que você escolher, e preços atuais estão na página de preços de conjuntos de dados.

Estime o tamanho de entrega antes de encomendá-lo. Registros na amostra do conjunto de dados têm média de 12,75 KB como JSON entregue, então planeje cerca de 1,3 GB por 100.000 registros. O conjunto de dados também entrega em Parquet, e a mesma amostra convertida para Parquet foi quase 8 vezes menor. Essa diferença de tamanho pode determinar se você consulta o arquivo no lugar ou o prepara primeiro.

Use o conjunto de dados para perguntas de sortimento e cobertura em todo o mercado, e use a API Scraper para as listagens específicas que você rastreia. O conjunto de dados atualiza no cronograma que você escolher, e uma coleta da API Scraper retorna cada listagem no momento em que você executa.

Considerações finais

O esquema de assinatura da AliExpress cabe em uma função, mas nos testes, o gateway bloqueou chamadas de um único endereço IP dentro da primeira dúzia de requisições, mesmo com uma assinatura correta. Como um bloqueio pode retornar um código de status de sucesso, baseie toda verificação no cabeçalho bxpunish e em um campo que todo produto tem, como o preço. O robots.txt do site não proíbe o caminho público /item/, então esse é o caminho para fazer scraping em escala.

A API Scraper AliExpress da Bright Data retorna registros estruturados para essas páginas de produto, a API de Navegador captura o preço de cada variante, e o conjunto de dados AliExpress atende perguntas de mercado inteiro. Comece no nível gratuito, que inclui 5.000 requisições por mês sem necessidade de cartão de crédito. Teste com 40 URLs do seu catálogo, e verifique os registros contra os campos que seu pipeline precisa.

Perguntas frequentes

A AliExpress tem uma API para dados de produtos?

Sim, para vendedores, afiliados, e dropshippers aprovados. Na AliExpress Open Platform, um vendedor autoriza um app para sua própria loja através de OAuth 2.0, e a API de afiliados cobre o catálogo promocional. Nenhuma delas cobre o marketplace inteiro, então dados de preço em nível de mercado precisam de coleta de dados de ecommerce.

Como eu obtenho uma URL ou ID de produto da AliExpress?

URLs de produto seguem o padrão /item/<item_id>.html, e a descoberta começa de /w/wholesale-<keyword>.html. IDs dependem do host. Um produto tem o ID 1005001398867186 no .com e 3256801212552434 no .us. Identifique cada produto pela sua URL final após o redirecionamento regional, para que não apareça sob 2 IDs.

Fazer scraping de dados de produtos da AliExpress é gratuito?

Sim, em pequena escala. O nível gratuito da Bright Data inclui 5.000 requisições por mês sem necessidade de cartão de crédito, e cobre a API Scraper, Web Unlocker, e a API de Navegador, então você pode testá-las nas suas próprias URLs primeiro. Playwright e curl_cffi são de código aberto. Em escala de produção, você paga principalmente por endereços IP, porque 2 execuções de 60 chamadas de API assinadas de um único endereço IP obtiveram dados 7 e 15 vezes.

Quanto custa uma API scraper AliExpress?

A API Scraper AliExpress da Bright Data tem preço por registro entregue, e o Web Unlocker tem preço por requisição bem-sucedida, então você paga apenas pelos resultados. A API de Navegador tem preço por tráfego, então o peso da página prevê seu custo. Uma página de produto pesou cerca de 3 MB nos testes. Preços atuais estão na página de preços da Bright Data.

Por que meu scraper AliExpress retorna campos vazios?

Porque o HTML inicial não tem preços ou estoque. window.runParams é um objeto vazio e <title> está em branco, então extratores baseados em seletores não correspondem a nada e não geram erro. O JavaScript da página carrega os campos depois de mtop.aliexpress.pdp.pc.query. Execute o JavaScript ou chame essa API diretamente.

Como eu evito ser bloqueado fazendo scraping da AliExpress?

Envie requisições através da API Scraper ou Web Unlocker da Bright Data, que têm proxies embutidos e resolução de CAPTCHA da AliExpress. Nos testes, nem um novo jar de cookies nem uma espera de 60 segundos removeram um bloqueio de um endereço IP. Requisições bloqueadas também retornaram HTTP 200, então verifique o cabeçalho bxpunish, não o código de status.

Por que os preços da AliExpress mudam?

Porque as promoções são temporárias e variam por país. Em um teste em 6 países de saída, o desconto de um produto variou de 52,5% até 4,1% sobre o preço de lista, e os países diferiram em se os preços incluíam imposto. Defina um país de saída para cada série de preço e armazene-o na linha.

Existe um servidor MCP para AliExpress?

Sim. O MCP Server da Bright Data permite que um agente de IA busque páginas de produto da AliExpress através de suas ferramentas scrape_as_markdown e scrape_batch, que cuidam do desbloqueio e retornam markdown. Em uma chamada de teste, scrape_as_markdown retornou o preço, avaliação, variantes, e caminho de categoria do produto. A página MCP AliExpress explica a configuração.