Extraia Dados de HTML em Python Com Respondo da Scrapeless
Expert in Web Scraping Technologies
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
respondono 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
respondocom 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
python -m pip install "git+https://github.com/scrapeless-ai/respondo@be376d112ecf681011a079e809acae46b7e1ff59"
respondo --version
text
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:
selectorencontra o elemento dentro do item atual.attrlê um atributo, comohrefoutitleem vez do texto.required: Truelevanta um erro quando um item não tem correspondência.many: Trueretorna uma lista, edefaultdefine 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
{
"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
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
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
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
['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
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
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
respondo records mystery-page-1.html --selector article.product_pod --fields book-fields.json --format csv | head -4
text
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
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
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
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
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.



