De volta ao blog

Extraia Dados de HTML em Python Com Respondo da Scrapeless

Ava Wilson
Ava Wilson

Expert in Web Scraping Technologies

21-Sep-2026

TL;DR:

  • Respondo é a biblioteca Python de código aberto da Scrapeless para transformar HTML e JSON em registros. Funciona nas páginas e respostas de API que você já tem e não possui dependências em tempo de execução.
  • Instale a versão 0.6 do GitHub. O pacote respondo no PyPI ainda está na versão 0.4.0, que é anterior às receitas de campo, ajudantes de JSON Lines e modo em lote.
  • Uma receita de campo mapeia cada coluna para um seletor. As receitas podem ler um atributo em vez de texto e podem viver em um arquivo JSON ao lado do seu código.
  • A saída CSV é segura para planilhas por padrão. Valores que começam como fórmulas recebem um prefixo de apóstrofo, e números permanecem numéricos.
  • Respondo não busca nem renderiza páginas. A Scrapeless Universal Scraping API coleta o HTML, e Respondo o transforma em linhas.
  • Tente a etapa de coleta no plano gratuito da Scrapeless e extraia sua primeira página em poucos minutos.

Para extrair dados de HTML em Python, você precisa de duas coisas: o próprio HTML e um código que transforma tags em campos que você pode usar. A segunda parte tende a crescer em loops únicos e código CSV que diferem para cada site.

Respondo empacota essa segunda parte. Você descreve cada campo uma vez, como um seletor mais um atributo opcional, e Respondo retorna uma lista de dicionários que você pode escrever em CSV ou JSON Lines, do Python ou da linha de comando. Este guia cria um pequeno catálogo de livros a partir de um site de prática pública e, em seguida, combina Respondo com Scrapeless para a etapa de coleta.

O Que é Respondo

Respondo é uma caixa de ferramentas de extração local mantida sob a organização Scrapeless no GitHub e publicada sob a licença MIT. Seu repositório de código-fonte no GitHub descreve a divisão em uma linha: colete com Scrapeless, depois extraia, transforme e exporte com Respondo.

A biblioteca funciona em conteúdo que você já possui. Suas funções HTML se baseiam no módulo html.parser da biblioteca padrão, portanto, o ambiente instalado não contém nada além do próprio Respondo. A versão 0.6 cobre quatro tipos de trabalho:

  • registros repetidos de HTML, usando seletores no estilo CSS e receitas de campo reutilizáveis;
  • consultas, achatamento, projeção e patches de mesclagem em documentos JSON;
  • resumos de página, feeds e sitemaps;
  • um comando respondo com 21 modos, incluindo processamento em lote de uma pasta inteira.

Respondo não busca URLs nem inicia navegadores, e não contém cliente da API Scrapeless. Se você ainda está decidindo o que envolve a análise, nossa visão geral de o que é análise de dados cobre os conceitos.

Instale o Respondo 0.6

Instale o Respondo diretamente do GitHub, fixado em um commit. Ele precisa do Python 3.9 ou posterior:

bash Copy
python -m pip install "git+https://github.com/scrapeless-ai/respondo@be376d112ecf681011a079e809acae46b7e1ff59"
respondo --version
text Copy
respondo 0.6.0

O fixo do commit mantém seu ambiente no código exato com o qual este guia foi testado. Um simples pip install respondo instala a versão 0.4.0 do PyPI, e as funções usadas abaixo não estão nela. Após a instalação, pip list mostra apenas respondo e pip.

Defina uma Receita de Campo

Uma receita de campo é um dicionário que mapeia cada coluna de saída para uma regra de como encontrá-la dentro de um item repetido. Uma string simples é um seletor cujo texto se torna o valor. Um dicionário adiciona opções:

  • selector encontra o elemento dentro do item atual.
  • attr lê um atributo, como href ou title em vez do texto.
  • required: True levanta um erro quando um item não tem correspondência.
  • many: True retorna uma lista, e default define um valor padrão para um valor ausente.

Seletores cobrem tags, classes, IDs, testes de atributos, combinadores de descendente e filho, e grupos de vírgula. Esse é um subconjunto deliberado da especificação de Seletores W3C: pseudo-classes como :nth-child e os combinadores de irmãos são rejeitados com uma ValueError em vez de ignorados.

As receitas também podem viver em um arquivo JSON, que mantém seletores fora do seu código e permite que a linha de comando os reutilize. Salve isso como book-fields.json:

json Copy
{
  "title": {"selector": "h3 a", "attr": "title", "required": true},
  "price": ".price_color",
  "availability": ".availability",
  "url": {"selector": "h3 a", "attr": "href"}
}

O campo title lê o atributo title do link intencionalmente. O texto visível do link no site de prática é encurtado para nomes longos, enquanto o atributo contém o título completo.

Extrair Registros e Escrever CSV

extract_records pega o HTML, um seletor para o item repetido e a receita, e retorna um dicionário por item. Este exemplo usa dois itens copiados da categoria de mistério do site de prática:

python Copy
from respondo import extract_records, normalize_url, records_to_csv

PAGE_URL = "https://books.toscrape.com/catalogue/category/books/mystery_3/index.html"
html = """
<article class="product_pod">
  <h3><a href="../../../sharp-objects_997/index.html" title="Sharp Objects">Sharp Objects</a></h3>
  <p class="price_color">£47.82</p>
  <p class="instock availability"><i class="icon-ok"></i> In stock</p>
</article>
<article class="product_pod">
  <h3><a href="../../../in-a-dark-dark-wood_963/index.html" title="In a Dark, Dark Wood">In a Dark, Dark ...</a></h3>
  <p class="price_color">£19.63</p>
  <p class="instock availability"><i class="icon-ok"></i> In stock</p>
</article>
"""

books = extract_records(html, "article.product_pod", {
    "title": {"selector": "h3 a", "attr": "title", "required": True},
    "price": ".price_color",
    "availability": ".availability",
    "url": {"selector": "h3 a", "attr": "href"},
})
for book in books:
    book["url"] = normalize_url(book["url"], base=PAGE_URL)

print(records_to_csv(books), end="")
text Copy
title,price,availability,url
Sharp Objects,£47.82,In stock,https://books.toscrape.com/catalogue/sharp-objects_997/index.html
"In a Dark, Dark Wood",£19.63,In stock,https://books.toscrape.com/catalogue/in-a-dark-dark-wood_963/index.html

Três detalhes são tratados para você. O texto de .availability volta cortado, sem o elemento de ícone. O href relativo se torna uma URL absoluta, resolvida contra o endereço da página da maneira que as regras de resolução de referência da especificação de URI descrevem. E o título que contém uma vírgula é colocado entre aspas no CSV.

records_to_csv também protege contra injeção de fórmula de planilha, um risco que a entrada OWASP sobre injeção CSV descreve. Uma string que começa com =, +, -, @, um tab ou uma quebra de linha recebe um apóstrofo na frente, então =HYPERLINK(1) é escrito como '=HYPERLINK(1), enquanto um número real como -5 permanece como está. Passe escape_formulas=False apenas quando o arquivo nunca alcançar uma planilha.

Avance mais: Resumos de Página, JSON Lines e o CLI

Registros são uma saída. O mesmo pacote também resume páginas inteiras e lida com JSON Lines, e seu comando respondo executa a extração de registro a partir do shell, para um arquivo ou uma pasta inteira.

Resumir uma página inteira

extract_page retorna o título, texto, metadados, cabeçalhos, links, imagens e tabelas de um documento em uma única chamada. Execute-o em uma cópia salva da página da categoria mistério:

python Copy
from respondo import extract_page

with open("mystery-page-1.html", encoding="utf-8") as handle:
    page = extract_page(
        handle.read(),
        base="https://books.toscrape.com/catalogue/category/books/mystery_3/index.html",
    )

print(sorted(page))
print(page["headings"][:2])
print(len(page["links"]), "links,", len(page["images"]), "images")
text Copy
['headings', 'images', 'links', 'meta', 'tables', 'text', 'title']
[{'level': 1, 'id': '', 'text': 'Mystery'}, {'level': 3, 'id': '', 'text': 'Sharp Objects'}]
95 links, 20 images

O texto, cabeçalhos e links ignoram scripts, estilos e o cabeçalho do documento, e os links relativos são resolvidos contra base.

Consultar JSON Lines

jsonl_dumps escreve registros como JSON Lines, um objeto compacto por linha, e iter_jsonl os lê de volta de forma preguiçosa. json_query então extrai valores com uma pequena linguagem de caminho que sempre retorna uma lista:

python Copy
from respondo import iter_jsonl, json_query

with open("mystery-books.jsonl", encoding="utf-8") as handle:
    books = list(iter_jsonl(handle))

print(len(books), "records")
print(json_query(books, "$[*].title")[:3])
print(json_query(books, "[-1].price"))
text Copy
20 records
['Sharp Objects', 'In a Dark, Dark Wood', 'The Past Never Ends']
['£20.89']

A sintaxe do caminho cobre chaves de ponto, chaves entre aspas, índices negativos e * curingas. Não possui filtros ou descida recursiva, e um caminho que não combina com nada retorna uma lista vazia.

Execute a mesma receita a partir da linha de comando

O comando respondo lê um arquivo local e escreve JSON por padrão, ou CSV e JSON Lines com --format:

bash Copy
respondo records mystery-page-1.html --selector article.product_pod --fields book-fields.json --format csv | head -4
text Copy
title,price,availability,url
Sharp Objects,£47.82,In stock,../../../sharp-objects_997/index.html
"In a Dark, Dark Wood",£19.63,In stock,../../../in-a-dark-dark-wood_963/index.html
The Past Never Ends,£56.50,In stock,../../../the-past-never-ends_942/index.html

No modo records, as URLs permanecem exatamente como aparecem na página, mesmo quando --base é passado. Resolva-as em Python com normalize_url quando precisar de links absolutos.

Processar uma pasta de páginas salvas

O modo em lote executa uma receita em cada arquivo correspondente em um diretório, na ordem do nome do arquivo, e escreve uma linha de resultado por arquivo:

bash Copy
respondo records responses/ --batch --pattern '*.html' \
  --selector article.product_pod --fields book-fields.json \
  --format jsonl --output results.jsonl
python -c "import json; [print(row['source'], row['status'], len(row['result'])) for row in map(json.loads, open('results.jsonl'))]"
text Copy
mystery-page-1.html ok 20
mystery-page-2.html ok 12

Cada linha contém source, status, result e error, assim um arquivo ilegível não para o restante. As duas páginas contêm todos os 32 livros na categoria. O modo em lote nunca sobrescreve: executar o mesmo comando novamente para com respondo: batch output exists e status de saída 1, e um caminho de saída dentro da pasta de entrada é recusado como inseguro.

Onde o Respondo Para: Coletar a Página com Scrapeless

Respondo analisa o que lhe é dado e nada mais. Ele não baixa páginas nem executa JavaScript, então seus seletores veem apenas o HTML que recebem. Para essa etapa, a API de Raspagem Universal Scrapeless busca uma URL e retorna a página, então as duas partes permanecem separadas: a chave da API pertence à chamada de coleta, e a extração é executada localmente.

Configurando isso agora? O plano gratuito Scrapeless cobre suas primeiras solicitações.

Este script coleta a página da categoria mistério ao vivo através do ator unlocker.webunlocker, e depois executa a mesma receita nela. Ele lê sua chave da variável de ambiente SCRAPELESS_API_KEY:

python Copy
import json
import os
import urllib.request

from respondo import extract_records, jsonl_dumps, normalize_url, records_to_csv

PAGE_URL = "https://books.toscrape.com/catalogue/category/books/mystery_3/index.html"
BOOK_FIELDS = {
    "title": {"selector": "h3 a", "attr": "title", "required": True},
    "price": ".price_color",
    "availability": ".availability",
    "rating": {"selector": "p.star-rating", "attr": "class"},
    "url": {"selector": "h3 a", "attr": "href"},
}


def fetch_html(url):
    payload = {"actor": "unlocker.webunlocker", "input": {"url": url, "method": "GET", "js_render": False}}
    request = urllib.request.Request(
        "https://api.scrapeless.com/api/v2/unlocker/request",
        data=json.dumps(payload).encode(),
        headers={"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]},
    )
    with urllib.request.urlopen(request, timeout=120) as response:
        body = json.load(response)
    if body.get("code") != 200:
        raise RuntimeError(f"Scrapeless returned code {body.get('code')}")
    return body["data"]


html = fetch_html(PAGE_URL)
books = extract_records(html, "article.product_pod", BOOK_FIELDS)
for book in books:
    book["url"] = normalize_url(book["url"], base=PAGE_URL)
    book["rating"] = book["rating"].split()[-1]

print(len(books), "books")
print(records_to_csv(books[:3]), end="")

with open("mystery-books.jsonl", "w", encoding="utf-8") as handle:
    handle.write(jsonl_dumps(books))
text Copy
20 books
title,price,availability,rating,url
Sharp Objects,£47.82,In stock,Four,https://books.toscrape.com/catalogue/sharp-objects_997/index.html
"In a Dark, Dark Wood",£19.63,In stock,One,https://books.toscrape.com/catalogue/in-a-dark-dark-wood_963/index.html
The Past Never Ends,£56.50,In stock,Four,https://books.toscrape.com/catalogue/the-past-never-ends_942/index.html

A API envolve a página em um envelope JSON, {"code": 200, "data": "<html>…"}, e urlopen gera uma HTTPError para uma falha HTTP antes que o envelope seja lido. A classificação vem da lista de classes de p.star-rating, cuja última classe nomeia o número de estrelas. O guia de início rápido da API de Raspagem Universal lista as outras opções de solicitação, como país de proxy e manuseio de redirecionamento.

Solução de Problemas

O que você vê Causa Solução
ValueError: required field has no matches Um item não possui um campo marcado required Verifique o seletor em relação à página ou descarte required e use default
ValueError: unsupported selector syntax O seletor usa uma pseudo-classe como :nth-child Selecione por classe, ID ou atributo em vez disso
ValueError: expected a tag, class, ID or attribute selector O seletor usa + ou ~ Use combinadores de descendentes ou filhos
Uma coluna está vazia para cada linha O conteúdo é adicionado pelo JavaScript após o carregamento Solicite a página com js_render habilitado
URLs relativas na saída do CLI O modo records mantém os valores dos atributos como estão Resolva-os com normalize_url em Python
Um apóstrofo antes de alguns valores CSV A fuga de fórmula está ativada por padrão Mantenha-o, ou passe escape_formulas=False para consumidores confiáveis
respondo: batch output exists O arquivo de saída já está lá Escolha um novo nome de arquivo
respondo: batch unsafe output path O arquivo de saída está dentro da pasta de entrada Escreva os resultados em outro lugar

Para páginas que constroem seu conteúdo no navegador, renderizando páginas com a API Universal Scraping passa pelas opções, e a documentação do JS Render lista os parâmetros. Confira preços para ver quanto custam as requisições renderizadas.

Conclusão

Respondo transforma a parte de extração de um trabalho de scraping em configuração: um seletor para o item repetido e uma receita para seus campos. A partir daí, extract_records retorna dicionários que records_to_csv ou jsonl_dumps transformam em arquivos. O comando respondo executa a mesma receita em uma pasta e relata um resultado para cada página.

Mantenha as duas partes separadas. Instale 0.6 do GitHub, colete páginas com a API Universal Scraping, e deixe Respondo trabalhar no que voltar, sem conexão de rede ou credenciais próprias.

Pronto para fornecer a Respondo páginas reais? Comece com o plano gratuito do Scrapeless e colete sua primeira página.

FAQ

Q: O que é Respondo?

Respondo é uma biblioteca Python de código aberto da Scrapeless que extrai, transforma e exporta dados de HTML e JSON que você já possui. Não possui dependências de tempo de execução e funciona completamente na sua máquina.

Q: Como instalo o Respondo 0.6?

Instale-o do GitHub com python -m pip install "git+https://github.com/scrapeless-ai/respondo@be376d112ecf681011a079e809acae46b7e1ff59". O pacote PyPI está na versão 0.4.0 e não possui os recursos deste guia.

Q: O Respondo pode baixar páginas da web?

Não. O Respondo apenas analisa o conteúdo que você passa para ele. Use a API Universal Scraping da Scrapeless, ou outra fonte de HTML, para a etapa de download.

Q: Como extraio dados de HTML para CSV em Python com o Respondo?

Chame extract_records com o HTML, um seletor para o item repetido e uma receita de campo, depois passe o resultado para records_to_csv. Do shell, respondo records page.html --selector … --fields recipe.json --format csv faz o mesmo.

Q: Quais seletores CSS o Respondo suporta?

Tags, classes, IDs, testes de atributo, combinadores de descendentes e filhos, e grupos separados por vírgula. Pseudo-classes e combinadores de irmãos levantam um ValueError.

Q: Por que meu CSV tem um apóstrofo na frente de alguns valores?

Respondo adiciona um prefixo a strings que começam com =, +, -, @, uma tabulação ou uma quebra de linha, para que as planilhas não as executem como fórmulas. Números são deixados de lado, e escape_formulas=False desativa o prefixo.

Q: O Respondo lida com páginas renderizadas com JavaScript?

Respondo analisa o HTML que recebe e não executa scripts. Busque essas páginas com a renderização JavaScript habilitada na API Universal Scraping, depois passe o HTML renderizado para o Respondo.

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