JSON-LD Web Scraping Com Python: Extraia Dados Estruturados
Advanced Data Extraction Specialist
TL;DR:
- JSON-LD geralmente é o caminho mais curto de uma página para um registro tipado. Procure por
<script type="application/ld+json">antes de escrever seletores para título, autor, data de publicação, imagem ou campos de produto. - Leia cada bloco JSON-LD. Uma página pode dividir dados de organização, breadcrumb, artigo, produto e FAQ em vários scripts.
- Normalize três formas de nível superior. Um bloco JSON-LD pode ser um objeto, um array de objetos, ou um objeto cujo
@graphcontém os nós úteis. - Os campos do Schema.org são opcionais na prática. Selecione o nó pelo
@type, mantenha campos ausentes comoNonee valide apenas os campos que seu pipeline realmente exige. - Beautiful Soup não executa JavaScript. Se uma página injeta JSON-LD após a carga, busque o HTML renderizado primeiro e execute o mesmo parser nessa resposta.
- Você pode testar o parser sem um modelo na nuvem. O exemplo completo em Python abaixo lê um nó
Articlereal, compara seu título com o H1 visível e valida a saída. - Comece com páginas públicas e delimitadas. Crie uma conta gratuita no Scrapeless quando seu alvo precisar de HTML renderizado.
JSON-LD frequentemente contém os campos que um scraper está prestes a reconstruir a partir de elementos de página dispersos. Em um artigo live do Scrapeless, um único objeto Article carrega o título, autor, editora, datas, URL canônica, imagem principal, descrição e palavras-chave. A página visível ainda importa, mas os metadados estruturados fornecem um ponto de partida tipado para o pipeline de extração.
JSON-LD segue a especificação JSON-LD 1.1, enquanto vocabulários como Article, Product e BreadcrumbList vêm do modelo de dados estruturados Schema.org. Nenhum padrão garante que cada editor preencha cada propriedade. Seu parser deve preservar essa incerteza em vez de inventar valores.
Para que o Web Scraping JSON-LD é Bom
JSON-LD é útil quando uma página publica fatos legíveis por máquina ao lado de seu layout voltado para humanos. Nós comuns incluem:
ArticleeNewsArticlepara títulos, datas, autores, imagens e editores;Productpara nomes, marcas, ofertas, avaliações e identificadores;BreadcrumbListpara hierarquia e caminhos de categoria canônicos;Organization,PersoneLocalBusinesspara metadados de entidade;VideoObject,Recipe,Event, e outros tipos específicos de domínio.
O bloco de script não é um endpoint privado. Ele é parte da resposta da página e é destinado a máquinas como crawlers de busca. Isso o torna mais durável do que uma classe CSS gerada, mas não automaticamente completo ou correto. Trate-o como uma fonte para validação, não como um oráculo.
Instalar Beautiful Soup
Este guia usa Python 3.10 ou posterior, requests e beautifulsoup4 4.15.0:
bash
python -m pip install "requests>=2.32,<3" "beautifulsoup4==4.15.0"
A API de busca de árvore do Beautiful Soup pode filtrar tags por atributo, o que é suficiente para coletar todos os scripts correspondentes. A decodificação JSON permanece na biblioteca padrão do Python.
Buscar o HTML Fonte
Comece com uma solicitação HTTP ordinária. O alvo usado aqui publica seu JSON-LD na resposta inicial, portanto, a renderização JavaScript adicionaria custo sem mudar o resultado:
python
import requests
URL = "https://www.scrapeless.com/pt/blog/what-is-web-scraping?utm_source=website&utm_medium=blog&utm_campaign=universalscrapingapi&utm_term=json-ld-structured-data-web-scraping"
response = requests.get(
URL,
headers={"User-Agent": "Mozilla/5.0"},
timeout=30,
)
response.raise_for_status()
print("Bytes HTML:", len(response.content))
text
Bytes HTML: 439487
A contagem de bytes pode mudar quando o pacote da página muda. O invariante útil é que a resposta contém pelo menos um script application/ld+json decodificável.
Analisar Cada Bloco JSON-LD
Não use soup.find(...) a menos que o contrato da página prometa explicitamente um bloco. find_all preserva a possibilidade de que o artigo, breadcrumb e editor vivam em scripts separados:
python
import json
from bs4 import BeautifulSoup
soup = BeautifulSoup(response.text, "html.parser")
scripts = soup.find_all("script", type="application/ld+json")
parsed_blocks = []
for script in scripts:
raw = script.get_text(strip=True)
try:
parsed_blocks.append(json.loads(raw))
except json.JSONDecodeError:
continue
print("Blocos JSON-LD:", len(scripts))
print("blocos decodificados:", len(parsed_blocks))
Pular blocos malformados é seguro apenas quando você também registra quantos foram pulados. Um except silencioso pode transformar uma regressão de metadados em um conjunto de dados vazio que parece bem-sucedido.
Normalizar Objetos, Arrays e @graph
Autores de JSON-LD têm várias maneiras válidas de agrupar nós. Este gerador achata as três formas que um scraper encontra com mais frequência:
python
def iter_nodes(value):
if isinstance(value, list):
for item in value:
yield from iter_nodes(item)
elif isinstance(value, dict):
graph = value.get("@graph")
if isinstance(graph, list):
for item in graph:
yield from iter_nodes(item)
else:
yield value
nodes = [node for block in parsed_blocks for node in iter_nodes(block)]
article = next(node for node in nodes if node.get("@type") == "Article")
print("nós normalizados:", len(nodes))
print("tipo selecionado:", article["@type"])
@type também pode ser um array. Se seu corpus incluí editores que emitem "@type": ["Article", "NewsArticle"], normalize esse campo antes de testar a afiliação.
Construir um Registro Nullable
Objetos aninhados precisam da mesma atenção que campos de nível superior. O autor pode ser um dicionário, uma lista, uma string ou não estar presente. Este alvo usa um dicionário, então o exemplo o lê defensivamente e preserva campos opcionais ausentes como None:
python
visible_h1 = soup.find("h1").get_text(" ", strip=True)
author = article.get("author") or {}
publisher = article.get("publisher") or {}
record = {
"type": article.get("@type"),
"headline": article.get("headline"),
"visible_h1": visible_h1,
"author": author.get("name") if isinstance(author, dict) else None,
"publisher": publisher.get("name") if isinstance(publisher, dict) else None,
"published": article.get("datePublished"),
"modified": article.get("dateModified"),
"image": article.get("image"),
"description": article.get("description"),
"keywords": article.get("keywords"),
"source_url": URL,
}
Manter source_url em cada linha torna possíveis auditorias posteriores. Sem a proveniência, um parser corrigido não consegue identificar quais registros precisam ser reconstruídos.
Validar os Campos que Seu Pipeline Requer
A validação deve refletir o contrato de downstream, e não cada propriedade que o Schema.org permite:
python
required = ("headline", "author", "published", "source_url")
missing = [field for field in required if not record.get(field)]
if missing:
raise ValueError(f"campos obrigatórios ausentes: {missing}")
print("cabeçalho:", record["headline"])
print("H1 visível:", record["visible_h1"])
print("cabeçalhos coincidem:", record["headline"] == record["visible_h1"])
print("autor:", record["author"])
print("editor:", record["publisher"])
print("publicado:", record["published"])
text
cabeçalho: O que é Web Scraping? Guia Definitivo 2025
H1 visível: O que é Web Scraping? Guia Definitivo 2025
cabeçalhos coincidem: True
autor: Emily Chen
editor: Scrapeless
publicado: 2025-09-17T08:35:31.224Z
O cabeçalho coincide nesta página. Não generalize esse resultado: editores às vezes atualizam o H1 visível sem atualizar os metadados estruturados ou usam um cabeçalho de pesquisa mais curto em JSON-LD. Comparar ambas as superfícies é um verificador de qualidade útil.
Pronto para executar o mesmo parser em uma resposta renderizada? Abra uma conta no Scrapeless e mantenha o código de parsing inalterado.
Extrator Completo Executável
O script completo une descoberta, normalização, seleção, mapeamento nullable e validação:
python
import json
import requests
from bs4 import BeautifulSoup
URL = "https://www.scrapeless.com/pt/blog/what-is-web-scraping?utm_source=website&utm_medium=blog&utm_campaign=universalscrapingapi&utm_term=json-ld-structured-data-web-scraping"
def iter_nodes(value):
if isinstance(value, list):
for item in value:
yield from iter_nodes(item)
elif isinstance(value, dict):
graph = value.get("@graph")
if isinstance(graph, list):
for item in graph:
yield from iter_nodes(item)
else:
yield value
response = requests.get(
URL,
headers={"User-Agent": "Mozilla/5.0"},
timeout=30,
)
response.raise_for_status()
soup = BeautifulSoup(response.text, "html.parser")
nodes = []
invalid_blocks = 0
scripts = soup.find_all("script", type="application/ld+json")
for script in scripts:
try:
nodes.extend(iter_nodes(json.loads(script.get_text(strip=True))))
except json.JSONDecodeError:
invalid_blocks += 1
article = next(node for node in nodes if node.get("@type") == "Article")
author = article.get("author") or {}
publisher = article.get("publisher") or {}
visible_h1 = soup.find("h1").get_text(" ", strip=True)
record = {
"type": article.get("@type"),
"headline": article.get("headline"),
json
"visible_h1": visible_h1,
"autor": autor.get("nome") if isinstance(autor, dict) else None,
"editor": editor.get("nome") if isinstance(editor, dict) else None,
"publicado": artigo.get("dataPublicada"),
"imagem": artigo.get("imagem"),
"palavras-chave": artigo.get("palavras-chave"),
"url_fonte": URL,
}
requerido = ("cabeçalho", "autor", "publicado", "url_fonte")
faltando = [campo for campo in requerido if not registro.get(campo)]
if faltando:
raise ValueError(f"faltando campos obrigatórios: {faltando}")
print(f"Bytes HTML: {len(resposta.content)}")
print(f"Blocos JSON-LD: {len(scripts)}")
print(f"nós normalizados: {len(nos)}")
print(f"blocos inválidos: {blocos_invalidos}")
print(f"tipo: {registro['tipo']}")
print(f"cabeçalho: {registro['cabeçalho']}")
print(f"visible H1: {registro['visible_h1']}")
print(f"cabeçalhos correspondem: {registro['cabeçalho'] == registro['visible_h1']}")
print(f"autor: {registro['autor']}")
print(f"editor: {registro['editor']}")
print(f"publicado: {registro['publicado']}")
print(f"caracteres das palavras-chave: {len(registro['palavras-chave'] or '')}")
A execução ao vivo retornou um bloco válido, um nó `Artigo` normalizado, nenhum bloco malformado, cabeçalhos correspondentes, autor `Emily Chen`, editor `Scrapeless`, e 140 caracteres na string de palavras-chave.
## Quando o JSON-LD Aparece Apenas Após Renderização
Beautiful Soup analisa os bytes que recebe; não executa o JavaScript de uma página. Um diagnóstico rápido é comparar a resposta bruta com o DOM do navegador. Se o navegador mostrar um script `application/ld+json`, mas `requests` não encontrar nenhum, busque o HTML renderizado antes de analisar.
> Nota: A solicitação abaixo requer uma conta Scrapeless financiada. A conta de verificação retornou uma resposta de saldo insuficiente durante a verificação final, portanto, essa chamada HTTP é uma lacuna pré-requisito; o parser acima foi executado completamente contra a página pública real.
```python
import os
import requests
renderizado = requests.post(
"https://api.scrapeless.com/api/v2/unlocker/request",
headers={"x-api-token": os.environ["SCRAPELESS_API_KEY"]},
json={
"ator": "unlocker.webunlocker",
"entrada": {
"url": URL,
"método": "GET",
"js_render": True,
},
},
timeout=90,
)
renderizado.raise_for_status()
html = renderizado.json()["data"]
Passe html para BeautifulSoup e reutilize o mesmo normalizador. A API Universal de Scraping fornece a resposta renderizada; não altera o contrato JSON-LD.
Problemas Comuns de Dados JSON-LD
A página tem vários nós correspondentes
Selecione por tipo e identidade. Para variantes de produto, use @id, URL, SKU ou outro campo estável em vez de pegar o primeiro nó Produto.
@type é um array
Converta-o em um conjunto antes de verificar a associação. Um teste de igualdade estrita contra uma string perderá um nó multi-tipo válido.
O script contém entidades HTML ou comentários
JSON-LD deve ser texto JSON válido. Se um editor envolvê-lo em uma sintaxe inválida, registre esse bloco como malformado e conserte o parser para aquela fonte conhecida; não aplique substituições de string amplas que possam corromper valores legítimos.
Metadados estruturados não concordam com o texto visível
Armazene ambos os valores e defina a precedência para o seu caso de uso. Auditorias de busca podem preferir o cabeçalho JSON-LD; monitoramento de conteúdo pode preferir o H1 visível. Um desalinhamento é dado, não apenas um erro.
Um campo muda de objeto para lista
Normalize na fronteira. Autores e imagens comumente alternam entre um objeto e uma matriz à medida que o CMS de um editor evolui.
Conclusão
Um scraper JSON-LD confiável faz quatro coisas: coleta todos os scripts correspondentes, decodifica sem ocultar blocos malformados, normaliza dicionários, listas e @graph, e valida um pequeno contrato a montante. Esse caminho é mais curto e geralmente mais estável do que reconstruir o mesmo registro a partir de seletores de layout de página. Mantenha o DOM visível como uma verificação cruzada, retenda a proveniência da fonte e introduza a renderização apenas quando o HTML inicial provar que é necessário.
Comece com o plano gratuito da Scrapeless, revise a documentação do desenvolvedor, e verifique preços Scrapeless antes de mover um corpus renderizado para a produção.
FAQ
P: É mais fácil fazer scraping de JSON-LD do que do HTML visível?
Sim, quando o editor inclui os campos de que você precisa. JSON-LD oferece propriedades e tipos nomeados, enquanto o HTML visível muitas vezes requer seletores e limpeza de texto; você ainda deve comparar campos críticos com a página renderizada.
**Q: Por que devo analisar cada script `application/ld+json`?**
Uma página pode colocar diferentes entidades em blocos separados. Ler apenas o primeiro script pode retornar a organização ou caminhada e perder o artigo ou produto que você queria.
**Q: O que significa `@graph` para extração?**
`@graph` agrupa vários nós JSON-LD dentro de um objeto. Achate o gráfico, depois selecione nós por `@type`, `@id`, URL ou outro identificador estável.
**Q: E se uma propriedade JSON-LD estiver ausente?**
Mantenha propriedades opcionais como `None` e falhe apenas quando um campo exigido pelo seu próprio contrato subsequente estiver ausente. O Schema.org descreve propriedades possíveis; não obriga os editores a preenchê-las todas.
**Q: O JSON-LD pode diferir da página visível?**
Sim. Metadados e conteúdo visível podem ser atualizados em horários diferentes ou otimizados para superfícies diferentes. Armazene ambos os valores quando a diferença for importante e torne a precedência explícita.
**Q: Preciso de um navegador para extrair JSON-LD?**
Não quando o script estiver presente no HTML inicial. Você precisa de renderização apenas quando o JavaScript do lado do cliente insere ou modifica os dados estruturados após a resposta bruta chegar.
**Q: Extrair JSON-LD público é sempre permitido?**
Nenhuma regra geral torna toda coleta legal ou permitida. Revise os termos do site e as <a href="https://datatracker.ietf.org/doc/html/rfc9309" rel="nofollow"><strong>diretivas de robôs</strong></a>, mantenha o volume de solicitações limitado, colete apenas os campos que você precisa e obtenha aconselhamento legal para usos sensíveis ou comerciais.
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.



