🎯 Um navegador em nuvem personalizável e anti-detecção alimentado por Chromium desenvolvido internamente, projetado para rastreadores web e agentes de IA. 👉Experimente agora
De volta ao blog

Como Construir um Pipeline de Dados de Treinamento de IA com o Scrapeless

Emily Chen
Emily Chen

Advanced Data Extraction Specialist

17-Jul-2026

TL;DR:

  • Um dataset de ajuste fino é um produto de pipeline, não um download. Este guia constrói um do início ao fim: busca páginas públicas renderizadas através da API de Scraping Universal do Scrapeless, extrai pares rotulados com a biblioteca padrão do Python e escreve JSONL em formato de chat pronto para OpenAI com uma divisão de treino/validação.
  • A camada de fetch é um POST HTTP por página. O ator unlocker.webunlocker retorna HTML renderizado de POST /api/v1/unlocker/request — sem navegador para gerenciar e sem pool de proxy para rodar do seu lado.
  • Tudo até o upload roda apenas com uma chave Scrapeless. A demonstração cobre todas as 10 páginas de um site de citações públicas e produz 100 exemplos supervisionados; apenas o trabalho final de ajuste fino precisa de uma chave OpenAI e orçamento.
  • Erros de formato são os mais baratos de prevenir. Cada linha de treinamento é um objeto {"messages": [...]} com turnos de sistema, usuário e assistente — escreva-o com json.dumps e o arquivo será analisado no primeiro upload.
  • Proveniência é parte do dataset. Apenas páginas públicas, volume limitado e termos do site e diretrizes de robôs checados antes da coleta — a seção de responsabilidade cobre o que os dados de treinamento adicionam às perguntas usuais de scraping.
  • Gratuito para começar. Crie sua chave de API no plano gratuito em app.scrapeless.com.

Introdução: o dataset é a parte difícil

Ajustar um LLM é administrativamente fácil — enviar um arquivo, criar um trabalho, esperar. O que o trabalho não pode corrigir é o arquivo. Um modelo ajustado em cem exemplos bem formados e rotulados corretamente da tarefa pode superar um ajustado em dez mil linhas ruidosas, e essa diferença é decidida antes do início do treinamento, no pipeline que coletou e moldou os dados.

Esse pipeline é um problema de scraping para a maioria das equipes, porque o conhecimento de domínio que vale a pena ajustar vive em páginas da web: descrições de produtos, documentação, listagens, avaliações, material de referência. O lado conceitual da jornada é abordado no guia sobre como funciona o treinamento de modelos de IA do início ao fim; este post é a metade executável. Você construirá um pipeline completo contra um site de demonstração pública — quotes.toscrape.com, um site criado para prática de scraping — e terminará com arquivos train.jsonl e val.jsonl que o endpoint de ajuste fino da OpenAI aceita como estão.

A tarefa de demonstração: ensinar um modelo a atribuir citações famosas. Pequena o suficiente para rodar em minutos, estruturada exatamente como a coisa real.

Pipeline em um Relance

O pipeline tem cinco etapas, e as quatro primeiras são executadas de verdade neste guia com apenas uma chave API do Scrapeless:

  1. Buscar — recuperar cada página renderizada através da API de Scraping Universal, um POST por página.
  2. Descobrir — seguir a própria paginação do site até que termine, com um limite rígido de páginas como segurança.
  3. Extrair — analisar o texto e o autor da citação de cada página com o analisador HTML da biblioteca padrão do Python.
  4. Transformar — transformar cada par em um exemplo de treinamento em formato de chat, dividir 80/20 e escrever JSONL.
  5. Treinar — enviar ambos os arquivos e criar o trabalho de ajuste fino (esta etapa precisa de uma chave OpenAI; o código é mostrado e rotulado de acordo).

Fluxo por página: renderizar ou buscar → descobrir próxima página → extrair pares → transformar em exemplos → armazenar como JSONL.

Por que a API de Scraping Universal do Scrapeless

A API de Scraping Universal transforma a coleta de páginas em uma chamada de função determinística: você POSTA uma URL, o serviço cuida da renderização, desbloqueio e roteamento de proxy do lado do servidor, e você recebe o HTML da página de volta no campo data. Para um construtor de datasets, isso vale o dobro. Primeiro, o corpus permanece reprodutível — a mesma forma de solicitação funciona se a fonte é um site de demonstração estático ou um catálogo de produtos pesado em JavaScript, então o código do pipeline não muda quando o alvo muda. Segundo, não há frota de navegador local: um dataset de algumas centenas de páginas é um loop sobre uma função.

O site de demonstração aqui é deliberadamente amigável. O ponto do guia é a forma do pipeline; troque a lista de URLs e o analisador quando você apontá-lo para seu corpus real e mantenha os limites de volume.

Pré-requisitos

  • Python 3 com o pacote requests — todas as outras importações no pipeline são da biblioteca padrão; as execuções neste guia usaram Python 3.12.
  • Uma chave API do Scrapeless — a documentação do desenvolvedor cobre a criação da chave.
  • Apenas para a Etapa 5: uma chave API da OpenAI com acesso de ajuste fino e orçamento. As etapas 1 a 4 funcionam sem ela.
    Exportar a chave Scrapeless para que os scripts a leiam do ambiente:
bash Copy
export SCRAPELESS_API_KEY="sk_your_key_here"

Etapa 1 — Buscar páginas renderizadas

Um POST retorna uma página renderizada. O ator unlocker.webunlocker recebe a URL alvo em input e responde com o HTML no campo data da resposta:

python Copy
# fetch_page.py — Etapa 1: recuperar uma página renderizada através do Scrapeless
import os

import requests

ENDPOINT = "https://api.scrapeless.com/api/v1/unlocker/request"
HEADERS = {
    "Content-Type": "application/json",
    "x-api-token": os.environ["SCRAPELESS_API_KEY"],
}


def fetch_page(url: str) -> str:
    resp = requests.post(
        ENDPOINT,
        headers=HEADERS,
        json={"actor": "unlocker.webunlocker", "input": {"url": url, "method": "GET"}},
        timeout=120,
    )
    resp.raise_for_status()
    return resp.json().get("data", "")


html = fetch_page("https://quotes.toscrape.com/page/1/")
print(f"buscado {len(html):,} caracteres de HTML")
print("markup da citação presente:", '<span class="text"' in html)
print("markup do autor presente:", '<small class="author"' in html)

Contra a página 1 do site de citações, isso retorna 11.021 caracteres de HTML com ambos os marcadores presentes. raise_for_status() mantém as falhas evidentes: uma chave ruim ou um alvo inacessível para o pipeline em vez de escrever um corpus vazio.

Etapa 2 — Descobrir o corpus completo

O site informa onde ele termina, então o crawler segue o site em vez de adivinhar URLs. Cada página do site de demonstração contém um elemento li class="next" até a última; o loop de rastreamento busca, verifica esse marcador e avança o contador de páginas. Dois limites mantêm a etapa honesta: o loop para quando o marcador desaparece, e um limite MAX_PAGES para ele mesmo se o marcador nunca desaparecer. O limite é a diferença entre construir um conjunto de dados e um rastreamento ilimitado — defina-o para o tamanho do corpus que você realmente pretende coletar.

O loop em si é quatro linhas dentro do script do pipeline completo na Etapa 4.

Etapa 3 — Extrair pares citação–autor

A extração transforma HTML em pares rotulados, e a biblioteca padrão é suficiente para isso. html.parser.HTMLParser dispara callbacks por etiqueta; acompanhar duas flags enquanto percorre a página coleta o texto e o autor de cada citação sem qualquer dependência de terceiros:

python Copy
# quote_parser.py — Etapa 3: extração da biblioteca padrão de pares (citação, autor)
from html.parser import HTMLParser


class QuoteParser(HTMLParser):
    """Coletar pares (texto, autor) da marcação de quotes.toscrape.com."""

    def __init__(self):
        super().__init__()
        self.pairs, self._text, self._mode = [], "", None

    def handle_starttag(self, tag, attrs):
        a = dict(attrs)
        if tag == "span" and a.get("class") == "text":
            self._mode = "text"
        elif tag == "small" and a.get("class") == "author":
            self._mode = "author"

    def handle_data(self, data):
        if self._mode == "text":
            self._text = data.strip("“”")
        elif self._mode == "author":
            self.pairs.append((self._text, data.strip()))
        self._mode = None


def parse_quotes(html: str):
    p = QuoteParser()
    p.feed(html)
    return p.pairs

Uma biblioteca de seletores também funcionaria — a razão para mostrar a versão da biblioteca padrão é que todo o pipeline permanece um script de duas dependências (requests mais o próprio Python), que é uma coisa a menos para definir quando o pipeline se move para um agendador.

Etapa 4 — Transformar para JSONL no formato de chat

O endpoint de fine-tuning da OpenAI treina em transcrições de chat: cada linha do arquivo é um objeto {"messages": [...]} com a regra do sistema, a entrada do usuário e a resposta do assistente que você quer que o modelo aprenda. O formato é JSON Lines — a definição do formato JSON Lines é exatamente "um valor JSON por linha" — e a pesquisa sobre ajuste de instrução, como o artigo InstructGPT, é a razão pela qual a forma se parece com uma conversa: os modelos seguem tarefas melhor quando treinados em pares de demonstração.

Este script é todo o pipeline — Etapas 1 a 4 compostas, terminando em dois arquivos:

python Copy
# build_dataset.py — Etapas 1–4: rastrear, extrair, transformar, armazenar
import json
import os

import requests
from html.parser import HTMLParser

ENDPOINT = "https://api.scrapeless.com/api/v1/unlocker/request"
HEADERS = {
    "Content-Type": "application/json",
    "x-api-token": os.environ["SCRAPELESS_API_KEY"],
}
BASE = "https://quotes.toscrape.com"
MAX_PAGES = 15  # limite de segurança acima do tamanho real do site
SYSTEM = "Você atribui citações famosas. Responda apenas com o nome do autor."


class QuoteParser(HTMLParser):
    def __init__(self):
        super().__init__()
        self.pairs, self._text, self._mode = [], "", None

    def handle_starttag(self, tag, attrs):
        a = dict(attrs)
        if tag == "span" and a.get("class") == "text":
```python
self._mode = "texto"
        elif tag == "small" and a.get("class") == "author":
            self._mode = "autor"

    def handle_data(self, data):
        if self._mode == "texto":
            self._text = data.strip("“”")
        elif self._mode == "autor":
            self.pairs.append((self._text, data.strip()))
        self._mode = None


def fetch_page(url: str) -> str:
    resp = requests.post(
        ENDPOINT,
        headers=HEADERS,
        json={"actor": "unlocker.webunlocker", "input": {"url": url, "method": "GET"}},
        timeout=120,
    )
    resp.raise_for_status()
    return resp.json().get("data", "")


def to_example(text: str, author: str) -> dict:
    return {
        "messages": [
            {"role": "system", "content": SYSTEM},
            {"role": "user", "content": f"Quem disse isso: “{text}”"},
            {"role": "assistant", "content": author},
        ]
    }


pairs, page = [], 1
while page <= MAX_PAGES:
    html = fetch_page(f"{BASE}/page/{page}/")
    parser = QuoteParser()
    parser.feed(html)
    pairs.extend(parser.pairs)
    if 'class="next"' not in html:  # Fase 2: o site diz quando termina
        break
    page += 1

examples = [to_example(t, a) for t, a in pairs]
split = int(len(examples) * 0.8)
for name, rows in (("train.jsonl", examples[:split]), ("val.jsonl", examples[split:])):
    with open(name, "w", encoding="utf-8") as f:
        f.writelines(json.dumps(r, ensure_ascii=False) + "\n" for r in rows)

print(f"páginas rastreadas: {page} | pares extraídos: {len(pairs)}")
print(f"train.jsonl: {split} exemplos | val.jsonl: {len(examples) - split} exemplos")
print("primeira linha de treinamento:")
print(json.dumps(examples[0], ensure_ascii=False)[:180])

A execução rastreia todas as 10 páginas do site, extrai 100 pares e escreve 80 exemplos de treinamento contra 20 exemplos de validação. A divisão de 80/20 oferece ao trabalho de ajuste fino algo para medir a generalização — exemplos de validação que o modelo nunca treina. Ambos os arquivos são escritos com ensure_ascii=False e um json.dumps por linha, que é o seguro possível: JSONL malformado é uma maneira comum de perder uma viagem ao ponto de upload.

Uma verificação de qualidade vale a pena ser feita manualmente, mesmo em um corpus de brinquedo: leia uma amostra de linhas e confirme se o rótulo realmente responde ao input. Um modelo aprende o que quer que o arquivo demonstre, incluindo os erros.

Fase 5 — Enviar o trabalho de ajuste fino

A chamada de treinamento é pequena em comparação com tudo acima. Carregue ambos os arquivos com o propósito de fine-tune, em seguida, crie o trabalho contra um modelo ajustável — a lista atual e os parâmetros estão em o guia de ajuste fino supervisionado da OpenAI.

Nota: Esta fase é a única lacuna pré-requisito do pipeline — precisa de um OPENAI_API_KEY com acesso e orçamento para ajuste fino, que este guia não presume. Tudo acima foi executado de verdade com apenas uma chave Scrapeless.

python Copy
# submit_job.py — Fase 5: carregar o conjunto de dados e criar o trabalho (requer OPENAI_API_KEY)
from openai import OpenAI

client = OpenAI()  # lê OPENAI_API_KEY do ambiente

train = client.files.create(file=open("train.jsonl", "rb"), purpose="fine-tune")
val = client.files.create(file=open("val.jsonl", "rb"), purpose="fine-tune")

job = client.fine_tuning.jobs.create(
    training_file=train.id,
    validation_file=val.id,
    model="gpt-4.1-mini-2025-04-14",
)
print(job.id, job.status)

Quando o trabalho termina, o id do modelo resultante cai na mesma chamada de chat-completions que você já usa — o conjunto de dados decide se esse modelo realmente responde "Albert Einstein" quando apresentado a uma citação que nunca viu.

Obtenha sua chave API no plano gratuito: app.scrapeless.com

Raspagem responsável para dados de treinamento

Dados de treinamento carregam toda a obrigação que as páginas de origem carregavam, além de uma a mais: o modelo reproduzirá padrões de tudo o que você lhe fornecer. Quatro práticas mantêm o lado de coleta defensável.

  • Páginas públicas apenas e leia os termos. Colete apenas o que é exibido sem uma conta e verifique os termos de serviço do site alvo antes de começar a raspagem — o uso em treinamento é explicitamente mencionado por um número crescente de sites.
  • Honre as diretivas de robôs. o Protocolo de Exclusão de Robôs (RFC 9309) é a declaração padrão legível por máquina do que um site permite que os robôs acessem; verifique-o para seus caminhos-alvo antes de coletar.
  • Minimize. Pegue os campos que a tarefa precisa — aqui, texto da citação e autor — não despejos de página inteira que arrastam comentários de usuários, nomes ou outros dados incidentais. Limites de página são parte da minimização.
  • Rastrear a proveniência e licenciamento. Registre de onde cada exemplo veio e quando. Texto que é público para leitura não é automaticamente licenciado para treinamento de modelo em todas as jurisdições; quando o corpus é mais sensível do que citações famosas, faça com que o advogado analise a questão do licenciamento antes que o trabalho seja executado.

O Que Você Recebe de Volta

Dois arquivos, prontos para upload. Cada linha é um exemplo supervisionado completo — a primeira linha de train.jsonl da execução acima:

text Copy
{"messages": [{"role": "system", "content": "Você atribui citações famosas. Responda apenas com o nome do autor."}, {"role": "user", "content": "Quem disse isto: “O mundo como o criamos é um processo do nosso pensamento. Não pode ser mudado sem mudar o nosso pensamento.”"}, {"role": "assistant", "content": "Albert Einstein"}]}

A forma escala sem mudar: um corpus real troca o analisador e a lista de URLs, a regra do sistema descreve sua tarefa em vez da atribuição de citações, e o escritor JSONL, a divisão e o upload permanecem idênticos. Se seu objetivo é recuperação em vez de atualizações de peso, a mesma camada de busca alimenta o pipeline RAG de texto limpo em vez disso — pedaços e embeddings em vez de exemplos de treinamento.

Conclusão

O pipeline garante sua utilidade nas duas extremidades. Na frente, a API Universal Scraping torna a coleta um POST determinístico por página, de modo que o corpus seja reproduzível e o código sobreviva a uma mudança de alvo. Na parte de trás, a transformação disciplinada — formato exato de chat, um objeto JSON por linha, uma verdadeira divisão de validação, uma revisão humana sobre os rótulos — é o que separa um fine-tune que melhora as respostas de um que consome orçamento. Entre essas extremidades, o meio é uma centena de linhas de Python da biblioteca padrão que você agora possui.

Pronto para Construir Seu Pipeline de Dados de Treinamento?

Planos e volumes de solicitações incluídos estão na página de preços, e a camada de busca neste guia funciona no plano gratuito — crie sua chave de API em app.scrapeless.com e a Etapa 1 retorna sua primeira página renderizada em um POST.

FAQ

P: Quanto dado eu preciso para afinar um modelo?

Menos do que a maioria das equipes espera, se os exemplos forem limpos. O fine-tuning supervisionado mostra mudança de comportamento mensurável com corpora na faixa de dezenas a centenas de exemplos bem rotulados para tarefas específicas; mudanças amplas de comportamento precisam de mais. Comece com o menor corpus que represente a tarefa, avalie contra o arquivo de validação e amplie o conjunto de dados onde o modelo realmente falha.

P: Que formato o endpoint de fine-tuning espera?

Chat-format JSON Lines: cada linha é um objeto JSON autônomo com um array messages de turnos do sistema, usuário e assistente, carregado com o propósito fine-tune. O pipeline neste guia escreve esse formato diretamente com json.dumps, um objeto por linha, sem vírgulas finais ou array envolvente.

P: Devo fazer fine-tuning ou usar RAG?

Faça fine-tuning quando quiser que o modelo mude o comportamento — tom, formato, reflexos específicos da tarefa — e recuperação quando quiser que ele conheça fatos atuais. Atualizações de peso incorporam padrões, mas ficam obsoletas; a recuperação permanece atual, mas não ensina ao modelo novos hábitos. Os dois se compõem, e ambos começam da mesma camada de coleta que este guia constrói.

P: É legal treinar um modelo com dados coletados?

Depende do que você coleta e onde você opera, e ler páginas públicas não é automaticamente uma licença para treinar nelas. Termos do site, diretivas de robôs, direitos autorais e as leis de privacidade que cobrem quaisquer dados pessoais no corpus se aplicam — a seção de responsabilidade acima lista as práticas de trabalho, e para qualquer coisa além de conteúdo claramente público e não pessoal, a questão do licenciamento deve ser discutida com um advogado.

P: Este pipeline funciona para modelos de pesos abertos também?

Sim — as etapas de coleta e transformação são independentes do modelo. JSONL em formato de chat é o denominador comum entre pilhas de ajuste; fluxos de trabalho de pesos abertos consomem a mesma estrutura de conversa, então a única etapa que muda é a Etapa 5, onde a chamada de upload é substituída pelo carregador de conjunto de dados do seu framework de treinamento.

Na Scorretless, acessamos apenas dados disponíveis ao público, enquanto cumprem estritamente as leis, regulamentos e políticas de privacidade do site aplicáveis. O conteúdo deste blog é apenas para fins de demonstração e não envolve atividades ilegais ou infratoras. Não temos garantias e negamos toda a responsabilidade pelo uso de informações deste blog ou links de terceiros. Antes de se envolver em qualquer atividade de raspagem, consulte seu consultor jurídico e revise os termos de serviço do site de destino ou obtenha as permissões necessárias.

Artigos mais populares

Catálogo