parsel Web Scraping: Seletores CSS e XPath em Python
Senior Web Scraping Engineer
TL;DR:
- parsel seleciona dados de HTML com CSS e XPath, sobre o mesmo documento — é o motor de seleção que o Scrapy utiliza, disponível como uma biblioteca independente.
- O que parsel não faz é buscar. Ele não possui um cliente HTTP e não executa JavaScript; você fornece a marcação e ele constrói uma árvore consultável.
- A diferença aparece em um script. Um simples GET em uma página de demonstração renderizada por JavaScript fornece 0 nós de citação ao parsel; a mesma URL obtida através da API de Scraping Universal Scrapeless com
js_renderfornece todas as 10. - CSS e XPath, lado a lado. Uma execução ao vivo obteve os mesmos 10 autores de duas maneiras —
sel.css("small.author::text")esel.xpath("//small[@class='author']/text()")— a partir do HTML renderizado. - As duas camadas permanecem separadas. Scrapeless busca e renderiza; parsel seleciona. Nenhum deles interfere no trabalho do outro.
- Livre para começar do lado da busca. Crie sua chave da API Scrapeless em app.scrapeless.com.
O que parsel é, e o que não é
parsel é uma biblioteca Python para extrair dados de HTML e XML usando seletores CSS e expressões XPath. É a camada de seleção sobre a qual o Scrapy é construído, embalada para que você possa usá-la em qualquer lugar, e envolve o lxml por baixo, de modo que ambas as linguagens de seleção funcionem em relação à mesma árvore de análise rápida. A razão para escolhê-la ao invés de um parser apenas CSS é o XPath: quando um campo é definido pela sua posição, seu texto ou sua relação com um irmão, em vez de uma classe, o XPath expressa isso e o CSS não consegue.
É uma biblioteca de seleção e nada mais. parsel não possui um cliente HTTP, não mantém sessão e não executa JavaScript. Dê-lhe uma string e ele constrói um Selector que você pode consultar; peça-lhe para buscar uma URL e não há um método para isso. Portanto, toda configuração de "scraping web com parsel" consiste em duas camadas: algo que retorna HTML fiel e parsel que seleciona a partir disso. Este guia utiliza a API de Scraping Universal Scrapeless para a primeira camada, porque um cliente HTTP simples retorna a marcação pré-renderizada em qualquer página que construa seu conteúdo com JavaScript. O mais amplo tutorial de scraping web em Python aborda o ecossistema ao redor.
Instalar
parsel e requests são toda a cadeia de ferramentas. A versão contra a qual este guia foi escrito é parsel 1.11.0:
bash
pip install "parsel==1.11.0" requests
Mantenha sua chave no ambiente, nunca no código-fonte:
bash
export SCRAPELESS_API_KEY="sk_sua_chave_scrapeless"
Obtenha HTML que vale a pena analisar
A qualidade da seleção é limitada pela fidelidade da busca, então comece por aí. Em uma página renderizada por JavaScript, a marcação que um cliente HTTP simples recebe não é a marcação que um leitor vê: o conteúdo chega apenas depois que os scripts constroem o DOM, um ciclo de vida definido pela especificação de scripting HTML. Um script conta a diferença através do próprio parsel:
python
# fidelity.py — o que parsel vê: GET simples vs renderização do lado do servidor
import os
import requests
from parsel import Selector
URL = "https://quotes.toscrape.com/js/"
plain = requests.get(URL, timeout=60).text
print("caracteres GET simples:", len(plain), "| nós de citação:", len(Selector(text=plain).css("div.quote")))
resp = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
headers={"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]},
json={"actor": "unlocker.webunlocker", "input": {"url": URL, "js_render": True}},
timeout=120,
)
resp.raise_for_status()
rendered = resp.json().get("data", "")
print("caracteres renderizados:", len(rendered), "| nós de citação:", len(Selector(text=rendered).css("div.quote")))
A execução imprime 0 nós de citação para a busca simples e 10 para a renderizada:
text
caracteres GET simples: 5806 | nós de citação: 0
caracteres renderizados: 8940 | nós de citação: 10
Renderização, desbloqueio e roteamento de proxy acontecem todos do lado do servidor em um único POST — a API de Scraping Universal é a camada de busca, e a marcação renderizada é o que parsel deve selecionar.
Extrair com CSS e XPath
Com HTML real em mãos, parsel faz a extração. css e xpath ambos retornam um SelectorList; get retorna a primeira correspondência e getall retorna todas as correspondências. O pseudo-elemento ::text e o teste do nó text() ambos puxam texto, então você pode ler o mesmo campo de qualquer maneira — CSS segue a especificação de Seletores W3C e XPath segue a especificação XPath W3C:
python
# extract.py — buscar a página renderizada, então selecionar com CSS e XPath
import os
import requests
from parsel import Selector
resp = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
headers={"Content-Type": "application/json", "x-api-token": os.environ["SCRAPELESS_API_KEY"]},
json={"actor": "unlocker.webunlocker", "input": {"url": "https://quotes.toscrape.com/js/", "js_render": True}},
timeout=120,
)
resp.raise_for_status()
sel = Selector(text=resp.json().get("data", ""))
css_nodes = sel.css("div.quote")
xpath_authors = sel.xpath("//div[@class='quote']//small[@class='author']/text()").getall()
print("nós de citação css:", len(css_nodes))
print("autores xpath:", len(xpath_authors))
records = []
for quote in css_nodes:
records.append({
"texto": quote.css("span.text::text").get(),
"autor": quote.xpath(".//small[@class='author']/text()").get(),
"tags": quote.css("a.tag::text").getall(),
})
print("primeiro autor:", records[0]["autor"])
print("primeiras tags:", records[0]["tags"])
A execução ao vivo selecionou todos os 10 registros, com CSS e XPath retornando os mesmos autores:
text
nós de citação css: 10
autores xpath: 10
primeiro autor: Albert Einstein
primeiras tags: ['mudança', 'pensamentos profundos', 'pensando', 'mundo']
Esse é o scraper completo: um POST para buscar e renderizar, um Selector para consultar. Misturar CSS para os campos fáceis e XPath para os posicionais — quote.xpath("...") é executado relativo a cada nó — é o padrão que mantém um scraper legível à medida que as páginas se tornam complicadas.
Obtenha sua chave de API no plano gratuito: app.scrapeless.com
Padrões Avançados
- Use XPath quando o CSS esgotar suas opções. Selecionar por texto (
//a[contains(text(), "Próximo")]), por posição ((//tr)[2]), ou por um eixo (following-sibling::td) é território do XPath; o CSS não tem equivalente. - Cadencie seletores relativos a um nó.
quote.css(...)equote.xpath(".//...")ambos se restringem a esse nó — o.inicial no XPath mantém a referência, que é a diferença entre "autores dentro desta citação" e "todos os autores na página". - Use
get(default="")para evitarNone.sel.css("span.missing::text").get(default="")retorna uma string vazia em vez deNone, o que impede que um loop sobre registros irregulares quebre na página estranha. - Extraia atributos com
::attr()ou@.sel.css("a::attr(href)")esel.xpath("//a/@href")ambos leem um atributo; use a linguagem que o restante do seletor já está utilizando.
Solução de Problemas
- Zero nós de uma página que você consegue ver em um navegador. O conteúdo é renderizado em JavaScript e sua busca retornou o HTML pré-renderizado. Conte um seletor conhecido da mesma forma que o primeiro script faz; uma árvore quase vazia é um problema de busca, resolvido com
js_render, e não um problema de seletor. get()retornaNone. O seletor não correspondeu a nada. Verifique a marcação renderizada, confirme a classe ou caminho e passe umdefaultpara que o código subsequente não quebre na falha.- XPath retorna elementos quando você queria texto. Adicione
/text()ao caminho ou.get()em um seletor CSS::text; um caminho de elemento nu retorna o nó, não sua string. - XPath relativo captura a página inteira. Um caminho começando com
//é absoluto mesmo quando chamado em um nó. Prefixe com.—.//small— para restringi-lo ao elemento atual.
Conclusão
parsel ganha seu lugar como a camada de seleção que fala ambos os dialetos: CSS para os casos comuns, XPath para aqueles que o CSS não consegue alcançar, sobre uma árvore suportada por lxml. A camada que decide se qualquer uma dessas opções é possível é a busca — a contagem de 0 contra 10 do primeiro script confirma isso — e um POST renderizado pelo servidor fecha a lacuna. Conecte os dois e as dez citações da página de demonstração chegam como registros limpos, selecionados da maneira que melhor lê.
Crie uma conta gratuita no Scrapeless para obter uma chave de API, e a documentação do desenvolvedor cobre os parâmetros unlocker.webunlocker. Verifique preços do Scrapeless quando planejar um trabalho recorrente.
FAQ
Q: O parsel pode raspar sites sozinho?
Não. O parsel seleciona dados de HTML que você já possui; ele não possui cliente HTTP e não executa JavaScript. Combine-o com uma camada de busca — aqui a API Universal Scraping do Scrapeless, que renderiza a página do lado do servidor — e o parsel cuida da extração da marcação retornada.
Q: Qual é a diferença entre parsel e seletores do Scrapy?
Nenhum, na prática — parsel é o mecanismo de seleção que o Scrapy utiliza, lançado como uma biblioteca independente. Se você usou response.css ou response.xpath em uma aranha Scrapy, isso é parsel. Usá-lo diretamente permite que você mantenha a API de seleção sem adotar todo o framework.
P: Devo usar CSS ou XPath com parsel?
Ambos, conforme necessário. CSS é mais curto para seleção baseada em classe e tag; XPath lida com seleção por texto, posição ou eixo, o que o CSS não pode expressar. Parsel executa ambos contra a mesma árvore, então misture-os por campo.
P: O parsel lida com páginas renderizadas em JavaScript?
Não por conta própria — ele nunca executa scripts. Se o conteúdo carregar após o HTML inicial, um simples fetch entrega ao parsel uma árvore vazia. Busque a página através da API Scrapeless com js_render primeiro, depois selecione do HTML renderizado, como este guia faz.
P: É legal fazer scraping com parsel?
A biblioteca de seleção não altera as regras de coleta. Busque apenas páginas públicas, respeite os termos do site e as diretrizes de robôs padronizadas por o Protocolo de Exclusão de Robôs, mantenha os volumes limitados e trate quaisquer dados pessoais de acordo com as leis que se aplicam a você.
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.



