Como Construir um Pipeline de Dados de Treinamento de IA com o Scrapeless
Advanced Data Extraction Specialist
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.webunlockerretorna HTML renderizado dePOST /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 comjson.dumpse 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:
- Buscar — recuperar cada página renderizada através da API de Scraping Universal, um POST por página.
- Descobrir — seguir a própria paginação do site até que termine, com um limite rígido de páginas como segurança.
- Extrair — analisar o texto e o autor da citação de cada página com o analisador HTML da biblioteca padrão do Python.
- Transformar — transformar cada par em um exemplo de treinamento em formato de chat, dividir 80/20 e escrever JSONL.
- 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
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
# 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
# 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
# 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_KEYcom 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
# 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
{"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.



