Ir para o conteúdo principal
Piloterr
Voltar ao blog
18 de agosto de 2026

Extrair dados estruturados com ChatGPT depois de carregar a página

Cole uma URL de produto no ChatGPT e muitas vezes você receberá um preço. Abra a página em produção: o número está errado, desatualizado ou inventado. Não se trata de um extrator defeituoso. O modelo nunca abriu a URL. Inferiu o valor a partir dos dados de treino e formulou-o com confiança.

Trate o ChatGPT como um parser, não como um navegador. A Piloterr carrega a página. Tente primeiro JSON-LD ou um seletor CSS. Apenas o texto restante é enviado a um modelo OpenAI. O Pydantic descarta qualquer objeto que não cumpra o schema.

A demonstração utiliza books.toscrape.com, um catálogo público destinado à prática. Limite-se a páginas públicas, respeite o robots.txt e consulte O scraping é legal? antes de visar um site comercial.

Neste tutorial:

  1. Carregar uma ficha com Website Crawler (1 crédito).
  2. Ler título, preço, estoque e UPC em CSS quando o markup for estável.
  3. Recorrer a structured outputs do gpt-5.6-luna quando não for.
  4. Dividir uma categoria em article.product_pod, e não no meio de uma palavra.

Duas tarefas, duas ferramentas

Um modelo de linguagem lê texto. Ele não executa JavaScript, não rotaciona endereços IP e não ultrapassa um challenge da Cloudflare. Se você pedir que recupere uma URL, obtém uma página bloqueada, uma SPA vazia ou um payload inventado.

Mantenha uma separação explícita:

  1. Carregar uma página completa (Piloterr, ou um endpoint da biblioteca se existir).
  2. Extrair campos (CSS, JSON-LD ou um modelo).

O ChatGPT é pertinente quando o markup é irregular, ou muda com frequência suficiente para que a manutenção de um conjunto amplo de seletores custe mais do que um schema. Amazon, LinkedIn e alvos semelhantes já dispõem de APIs mantidas. Use-as.

Analisar o DOM antes de chamar o modelo

Comece por um extrator CSS. Neste catálogo o título é h1, o preço p.price_color e o UPC uma linha de tabela. Esse caminho consome menos tokens do que gpt-5.6-luna e devolve a mesma string no dia seguinte.

Python
from bs4 import BeautifulSoup
from pydantic import BaseModel


class Book(BaseModel):
    title: str
    price: str
    availability: str
    upc: str | None = None
    isbn: str | None = None


def extract_book_css(html: str) -> Book | None:
    soup = BeautifulSoup(html, "html.parser")
    title = soup.select_one("article.product_page h1")
    price = soup.select_one("article.product_page p.price_color")
    stock = soup.select_one("article.product_page p.instock.availability")
    if not (title and price and stock):
        return None

    upc = None
    for row in soup.select("article.product_page table tr"):
        label = row.select_one("th")
        value = row.select_one("td")
        if label and value and label.get_text(strip=True) == "UPC":
            upc = value.get_text(strip=True)
            break

    return Book(
        title=title.get_text(strip=True),
        price=price.get_text(strip=True),
        availability=" ".join(stock.get_text().split()),
        upc=upc,
        isbn=None,
    )

isbn permanece None: esta página não tem ISBN. Não peça ao modelo para inventar um.

Procure, nesta ordem:

  • Uma API da biblioteca (Amazon Product e assim por diante)
  • application/ld+json na página
  • Um nó CSS ou XPath estável
  • Um modelo para o restante: especificações não estruturadas, FAQ, textos que misturam unidades e avisos

Preços já em texto claro passam pelo Price-Parser sem LLM.

Python
import json


def json_ld_blocks(html: str) -> list[dict]:
    soup = BeautifulSoup(html, "html.parser")
    blocks = []
    for node in soup.select('script[type="application/ld+json"]'):
        if not node.string:
            continue
        try:
            parsed = json.loads(node.string)
        except json.JSONDecodeError:
            continue
        blocks.extend(parsed if isinstance(parsed, list) else [parsed])
    return blocks

Escolha o modo de recuperação antes do prompt

Um modo inadequado produz HTML vazio. O modelo «extrai» então a partir de uma página de autenticação.

ModoCréditosQuando
Website Crawler1HTML estático ou APIs JSON. O mais rápido. Sem JS.
Website Rendering2Conteúdo depois do JavaScript. Precisa de wait_in_seconds ou wait_for.
Website WebUnlocker3Domínios na allowlist atrás de Cloudflare, DataDome, PerimeterX, Akamai. Sem JS.

Comparativo: Crawler vs Rendering vs WebUnlocker. Este tutorial utiliza Crawler, porque books.toscrape.com é renderizado no servidor.

Carregar o HTML

Python 3.10+, uma chave API da Piloterr e uma chave OpenAI. As duas no ambiente. Nunca no script.

Bash
python --version
mkdir fetch-then-extract && cd fetch-then-extract
pip install requests beautifulsoup4 "openai>=2" pydantic
export PILOTERR_API_KEY="your-x-api-key"
export OPENAI_API_KEY="your-openai-key"

O Crawler é um GET. query é a URL alvo. O corpo JSON é a string HTML.

Python
import os
import requests

API = "https://api.piloterr.com/v2/website/crawler"
KEY = os.environ["PILOTERR_API_KEY"]
PAGE = "https://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html"
CATALOG = "https://books.toscrape.com/"


def fetch_html(url: str, endpoint: str = API, **extra: object) -> str:
    response = requests.get(
        endpoint,
        headers={"x-api-key": KEY},
        params={"query": url, "allow_redirects": "true", **extra},
        timeout=60,
    )
    response.raise_for_status()
    html = response.json()
    if not isinstance(html, str) or len(html) < 200:
        raise RuntimeError("Fetch returned too little HTML. Try Rendering or WebUnlocker.")
    return html

Algumas centenas de kilobytes é o comportamento esperado. Algumas centenas de caracteres correspondem em geral a uma página de challenge. Troque de modo. Não peça ao modelo para compensar um HTML incompleto.

Para uma ficha com uso intensivo de JavaScript, o mesmo modelo de solicitação aplica-se ao Rendering (2 créditos):

Python
RENDER = "https://api.piloterr.com/v2/website/rendering"

def fetch_rendered(url: str) -> str:
    return fetch_html(url, endpoint=RENDER, wait_in_seconds=4)

Documentação: Website Crawler e Website Rendering. Uma chamada bem-sucedida custa 1 ou 2 créditos. 4xx do cliente e a maior parte dos 5xx da plataforma não. Consulte preços e a versão Markdown /pricing.md.

Reduzir o HTML antes de chamar o modelo

Scripts, SVG e o banner de cookies consomem tokens e ocultam os campos úteis.

Python
def readable_text(html: str) -> str:
    soup = BeautifulSoup(html, "html.parser")
    for tag in soup(["script", "style", "noscript", "svg", "iframe"]):
        tag.decompose()
    main = soup.select_one("article.product_page") or soup.body or soup
    return main.get_text("\n", strip=True)

Structured outputs, depois validação

Quando o CSS falha, o prompt torna-se o extrator. Nomeie cada campo. Use null se o texto não indicar o valor. client.responses.parse vincula a resposta à classe Pydantic: um objeto malformado não é tratado como um dicionário confiável.

Python
from openai import OpenAI

client = OpenAI()

SYSTEM = (
    "Read the page text and fill the schema. "
    "Copy strings as they appear. "
    "Use null when a key is absent. "
    "Do not invent a title, price, UPC, or ISBN."
)


def extract_book_llm(text: str) -> Book:
    response = client.responses.parse(
        model="gpt-5.6-luna",
        input=[
            {"role": "system", "content": SYSTEM},
            {"role": "user", "content": f"Fill the Book schema from this page text.\n\n{text}"},
        ],
        text_format=Book,
        reasoning={"effort": "none"},
    )
    book = response.output_parsed
    if book is None:
        raise ValueError("Model returned no parsed object")
    return book

gpt-5.6-luna basta para copiar campos. Passe para gpt-5.6 apenas se dois preços se contradisserem ou uma tabela for interpretada incorretamente. Execute uma amostra de 20 páginas, meça as falhas e escolha o modelo. client.chat.completions.parse ainda funciona se ficar no Chat Completions. Prefira responses.parse com openai 2.x.

Um único script

Python
if __name__ == "__main__":
    html = fetch_html(PAGE)
    book = extract_book_css(html)
    if book is None:
        book = extract_book_llm(readable_text(html))
    print(book.model_dump_json(indent=2))

Forma esperada na ficha «A Light in the Attic» (o UPC está na página, o ISBN não):

JSON
{
  "title": "A Light in the Attic",
  "price": "£51.77",
  "availability": "In stock (22 available)",
  "upc": "a897fe39b1053632",
  "isbn": null
}

Um ISBN alucinado pode inserir-se numa planilha sem alerta, até que um pedido seja feito com base nele.

Páginas longas: divida nas fichas de produto

Enviar uma categoria inteira num único prompt ultrapassa a janela de contexto. Não recorte em 12 000 caracteres no meio de uma palavra. Analise a lista, depois extraia uma ficha.

Python
def product_cards(html: str) -> list[str]:
    soup = BeautifulSoup(html, "html.parser")
    return [str(card) for card in soup.select("article.product_pod")]


def extract_catalog(html: str) -> list[Book]:
    books: list[Book] = []
    seen: set[str] = set()
    for card_html in product_cards(html):
        book = extract_book_css(card_html) or extract_book_llm(readable_text(card_html))
        key = book.title.casefold()
        if key in seen:
            continue
        seen.add(key)
        books.append(book)
    return books

As fichas da listagem não têm tabela UPC. O CSS devolve None e o modelo preenche título e preço a partir do texto da ficha. Una pelo título, ou conserve o href se o tiver armazenado.

Python
print(extract_catalog(fetch_html(CATALOG)))

Deixe o agente carregar a página, mantenha a mesma separação

Colar HTML na interface do ChatGPT é adequado para uma demonstração. O processamento diário executa-se em Python, ou numa ferramenta chamada pelo agente.

O MCP da Piloterr expõe Crawler e Rendering em https://mcp.piloterr.com/ com o mesmo x-api-key. Claude e Cursor podem carregar uma página. Você valida o JSON no seu código. Notas de especificação: MCP da Piloterr em 2026-07-28.

Práticas a evitar

  • Pedir ao ChatGPT uma URL em produção e confiar na resposta.
  • Enviar HTML bruto, com anúncios e scripts incluídos.
  • Guardar a saída do modelo sem validação Pydantic (ou equivalente).
  • Usar Rendering num catálogo estático e faturar 2 créditos sem necessidade.
  • Extrair páginas autenticadas, ou coletar dados pessoais, com o argumento de que o modelo «precisava de contexto».

FAQ

O ChatGPT consegue extrair um site sozinho?

Não. Não dispõe de um navegador que você controle nem de um método confiável para carregar a URL que você acabou de colar. Pode reformular um texto já recuperado. Carregue com Website Crawler ou Website Rendering, depois extraia.

Por que não chamar requests.get diretamente?

Você pode, no books.toscrape.com. Numa vitrine JavaScript ou num site protegido pela Cloudflare, você obtém um nó raiz vazio ou uma página de challenge. O modelo emitirá campos a partir dessa resposta. A Piloterr devolve a página que um navegador exibiria, e a cobrança ocorre apenas se o carregamento for bem-sucedido.

Quando o Rendering é o modo adequado?

Quando o preço ou a lista não aparecem em «Ver código-fonte», mas estão visíveis no DevTools depois da hidratação. Espere com wait_in_seconds ou wait_for. Se o HTML já veio na primeira resposta, permaneça no Crawler.

Como impedir preços inventados?

CSS ou JSON-LD primeiro. Depois instrução de copiar à letra, reasoning={"effort": "none"}, structured outputs e Pydantic. Compare a string de preço com o Price-Parser. Se a validação falhar, uma nova tentativa ou um seletor. Não complete um preço ausente.

ChatGPT ou uma API da biblioteca?

A API quando o site já está coberto. ChatGPT para um caso pontual ou um template instável. Combinar os dois é adequado: carregamento com Crawler, análise CSS, modelo apenas nos campos que falharam.

E se a lista for longa demais para uma chamada?

Extraia por article ou por linha. Deduplique por título ou URL. Não recorte por índice de caractere, salvo se a página não tiver markup de lista.

Páginas públicas, ritmo razoável, sem autenticação, sem dados pessoais, e os termos do site aplicam-se. Comece por O scraping é legal?. Para reutilização comercial, licença ou feed oficial se existir.

Mais para ler

Guias e notícias sobre web scraping, proxies e extração de dados.

Notícias

O MCP da Piloterr está na spec 2026-07-28

Nosso MCP hospedado em mcp.piloterr.com segue o MCP 2026-07-28: núcleo stateless request/response, mesma URL Streamable HTTP e mesma x-api-key.

Josselin Liebe
Josselin Liebe
Ler
Web Scraping

Web scraping para o setor automotivo: acompanhando preços de carros e peças no AutoScout24, Mobile.de, Leboncoin e Autodoc

Como plataformas de pricing automotivo usam APIs de scraping para monitorar anúncios de carros usados no AutoScout24, Mobile.de e Leboncoin, e preços de peças nas mais de 30 lojas nacionais da Autodoc. Desenho do pipeline, exemplos de API e um script Python.

Josselin Liebe
Josselin Liebe
Ler
Web Scraping

Web scraping para brand protection: caçando falsificações no AliExpress, Alibaba e Amazon

Como plataformas de brand protection usam APIs de scraping para detectar anúncios falsificados, capturar evidências do vendedor e monitorar republicações no AliExpress, Alibaba, Amazon e outros marketplaces. Desenho do pipeline, exemplos de API e um script Python.

Josselin Liebe
Josselin Liebe
Ler

Pronto para começar?

Sua API de web scraping está a um clique. Comece com +500 créditos, sem infraestrutura para configurar, sem proxies para gerenciar e sem cartão de crédito necessário.

  • +500 créditos
  • Sem cartão de crédito
  • Todos os endpoints incluídos