Saltar para o conteúdo
Comece grátis
Voltar ao blogue

Como analisar JSON em Python: desde respostas de API até JSON oculto em páginas web

Suciu DanÚltima atualização em 13 min read
Como analisar JSON em Python: desde respostas de API até JSON oculto em páginas web
Resumo: Para analisar JSON em Python, utilize o módulo json integrado: json.loads() para uma string ou bytes, json.load() para um ficheiro e response.json() do Requests para uma resposta HTTP. Verifique o estado HTTP e a validade do JSON separadamente, porque uma resposta 200 pode ainda assim ser HTML. Este guia aborda também erros de descodificação, acesso a dados aninhados, validação, ficheiros grandes e JSON escondido em páginas web.

O módulo integrado do Python json analisa JSON. Utilize json.loads() para uma cadeia de caracteres ou bytes, json.load() para um ficheiro e o response.json() para uma resposta HTTP. Este guia aborda erros de descodificação, acesso aninhado seguro, validação, ficheiros grandes, precisão numérica e JSON oculto em páginas web.

Pontos-chave

  • Dê preferência a json.loads para cadeias de caracteres/bytes e json.load para ficheiros; abra ficheiros com UTF-8 (ou utf-8-sig quando houver um BOM).
  • Trate o estado HTTP e a validade do JSON como verificações separadas: uma resposta 200 pode ainda assim ser HTML.
  • Navegue por dados aninhados e confusos com .get() cadeias ou um pequeno dig() auxiliar; valide com Pydantic ou JSON Schema após a análise.
  • Transmita matrizes enormes com o ijson, acrescente as execuções de scraping como JSON Lines e avalie o orjson nas suas próprias cargas de dados.
  • Dê preferência ao JSON incorporado na página (JSON-LD, __NEXT_DATA__) ou os campos estruturados de uma API de scraping em vez de seletores CSS instáveis quando o layout HTML muda frequentemente.

Como se analisa uma cadeia de caracteres JSON em Python?

Chame json.loads() a um str ou bytes valor. Devolve um objeto Python — normalmente um dict ou list— que reflete a estrutura JSON.

import json

raw = """
{
  "sku": "WSA-BOTTLE-750",
  "title": "Insulated Steel Water Bottle 750ml",
  "price": 19.99,
  "currency": "USD",
  "in_stock": true,
  "variants": [
    {"color": "matte black", "capacity_ml": 750},
    {"color": "ocean blue", "capacity_ml": 750}
  ],
  "seller": {
    "name": "TrailGear Co",
    "rating": 4.8,
    "country": "US"
  }
}
"""

product = json.loads(raw)
print(product["sku"], product["variants"][0]["color"])
# WSA-BOTTLE-750 matte black

# bytes work the same way
product_from_bytes = json.loads(raw.encode("utf-8"))
assert product_from_bytes["title"] == product["title"]

A mesma lista de produtos aparece em todo o texto, pelo que cada exemplo se baseia num registo do tipo «scraped».

Como se lê um ficheiro JSON em Python?

Abra o ficheiro com codificação UTF-8 e passe o objeto do ficheiro para json.load().

import json
from pathlib import Path

path = Path("product.json")
with path.open(encoding="utf-8") as f:
    product = json.load(f)

print(product["currency"])  # USD

O que fazer quando um ficheiro JSON começa com um BOM

Alguns editores guardam ficheiros UTF-8 com uma marca de ordem de bytes (EF BB BF). Esse caractere invisível surge antes do primeiro { e impede a descodificação normal. Abra o ficheiro com encoding="utf-8-sig" para que o Python remova o BOM antes de json.load ser executado. Se já tiver os bytes, descodifique-os com utf-8-sig e, em seguida, chame json.loads.

import json
from pathlib import Path

bom_path = Path("product_bom.json")
with bom_path.open(encoding="utf-8-sig") as f:
    product = json.load(f)

# Or from bytes:
product = json.loads(bom_path.read_bytes().decode("utf-8-sig"))

Como os tipos JSON correspondem aos tipos Python

Os tipos JSON correspondem aos tipos Python conforme definido pela gramática JSON em json.org e pela biblioteca padrão:

JSON

Python (json.loads)

Python → JSON (json.dumps)

object

dict

dict

matriz

list

list / tuple

cadeia de caracteres

str

str

número (int)

int

int

número (real)

float

float / int

true / false

True / False

bool

null

None

None

Por que é que as suas chaves e tuplas mudam após uma viagem de ida e volta

Os nomes dos objetos JSON são sempre cadeias de caracteres. As chaves inteiras do dicionário transformam-se em cadeias de caracteres após dumps → loads. As tuplas transformam-se em listas porque o JSON só tem matrizes. Os conjuntos não são serializáveis e geram TypeError.

import json

print(json.loads(json.dumps({1: "a", 2: "b"})))
# {'1': 'a', '2': 'b'}

print(json.loads(json.dumps({"coords": (10, 20)})))
# {'coords': [10, 20]}

try:
    json.dumps({"tags": {"refillable", "bpa-free"}})
except TypeError as e:
    print(e)
# Object of type set is not JSON serializable

Como se analisa uma resposta JSON de uma API?

Chame response.raise_for_status() para erros HTTP e, em seguida, response.json() para descodificar o corpo. Os códigos de estado e a validade do JSON são aspetos distintos: um código de estado de sucesso não garante que o corpo seja JSON.

import requests

resp = requests.get("https://httpbin.org/json", timeout=15)
resp.raise_for_status()
data = resp.json()
print(type(data), list(data)[:3])

Quando uma resposta 200 não é JSON

As páginas de bloqueio e de desafio costumam devolver HTML com o código HTTP 200. Verifique Content-Type, detete falhas de descodificação e apresente uma pré-visualização curta e segura do corpo:

import json
from typing import Any

import requests


def get_json(url: str, timeout: float = 15.0) -> Any:
    resp = requests.get(url, timeout=timeout)
    resp.raise_for_status()
    content_type = resp.headers.get("Content-Type", "")
    if "json" not in content_type.lower() and "javascript" not in content_type.lower():
        preview = resp.text[:120].replace("\n", " ")
        raise ValueError(
            f"Expected JSON Content-Type, got {content_type!r}. "
            f"Body starts: {preview!r}"
        )
    try:
        return resp.json()
    except json.JSONDecodeError as e:
        preview = resp.text[:120].replace("\n", " ")
        raise ValueError(
            f"JSON decode failed at pos {e.pos}: {e.msg}. "
            f"Body starts: {preview!r}"
        ) from e


product_feed = get_json("https://httpbin.org/json")
# get_json("https://httpbin.org/html")  # raises ValueError

Uma página HTML com um estado 200 é frequentemente uma página de bloqueio, e o nosso guia sobre como fazer scraping sem ser bloqueado explica como evitar isso.

Como se corrige um JSONDecodeError?

JSONDecodeError significa que a entrada não é JSON válido: corpo vazio, carga truncada, HTML, aspas simples, uma vírgula final ou algo semelhante. Leia e.msg, e.lineno, e.colnoe e.pos, depois imprima o texto em torno de pos para que possa ver o ponto de falha. Os detalhes completos sobre atributos e hooks encontram-se na documentação do módulo Python `json`.

import json


def locate_decode_error(text: str) -> None:
    try:
        json.loads(text)
    except json.JSONDecodeError as e:
        start = max(0, e.pos - 20)
        end = min(len(text), e.pos + 20)
        print(f"{e.msg} at line {e.lineno}, col {e.colno}, pos {e.pos}")
        print(f"Around error: {text[start:end]!r}")


locate_decode_error('{"sku": "WSA-BOTTLE-750", "price":')
# Expecting value at line 1, col 36, pos 35

Causas comuns e como cada uma se apresenta

Sintoma (fragmento da mensagem)

Causa provável

Solução

Expecting value na posição 0

Corpo vazio ou HTML/undefined

Verifique o transporte; confirme se obteve o JSON

Expecting value a meio da cadeia de caracteres

Resposta truncada

Repita a tentativa, aumente os tempos de espera ou utilize streaming com verificações de comprimento

Expecting property name enclosed in double quotes

Chaves entre aspas simples ou vírgula final

Corrija o produtor; não «repare» com eval

Extra data

Dois valores JSON numa única cadeia de caracteres

Divida as linhas JSON ou analise um valor de cada vez

Etiquetas HTML na pré-visualização

Bloqueio temporário ou página de início de sessão

Corrija os cabeçalhos, os cookies ou a renderização; consulte o guia de resolução de bloqueios acima

Como aceder com segurança a JSON aninhado?

Indexação rígida (data["seller"]["rating"]) gera um erro KeyError quando falta uma chave. .get() As cadeias devolvem um valor por defeito, mas tornam-se confusas em percursos profundos. Um pequeno dig() percorre as chaves dos dicionários e os índices das listas, e trata um caminho em falta de forma diferente de um null.

from typing import Any


def dig(data: Any, *path: str | int, default: Any = None) -> Any:
    cur: Any = data
    for key in path:
        if isinstance(cur, dict):
            if key not in cur:
                return default
            cur = cur[key]
        elif isinstance(cur, list) and isinstance(key, int):
            if key < 0 or key >= len(cur):
                return default
            cur = cur[key]
        else:
            return default
    return cur


product = {
    "variants": [{"color": "matte black", "capacity_ml": 750}],
    "seller": {"name": "TrailGear Co", "rating": 4.8},
}

print(dig(product, "seller", "rating"))                 # 4.8
print(dig(product, "variants", 0, "color"))             # matte black
print(dig(product, "variants", 9, "color", default="?"))  # ?
print(dig({"seller": {"rating": None}}, "seller", "rating", default="missing"))
# None  — key exists, value is null
print(dig({"seller": {}}, "seller", "rating", default="missing"))
# missing — key absent

Como se validam dados JSON em Python?

A análise sintática apenas verifica a sintaxe. Depois de loads, verifique os tipos e os campos obrigatórios com o Pydantic ou o JSON Schema.

from decimal import Decimal

from pydantic import BaseModel, ValidationError


class Variant(BaseModel):
    color: str
    capacity_ml: int


class Seller(BaseModel):
    name: str
    rating: float
    country: str


class Product(BaseModel):
    sku: str
    title: str
    price: Decimal
    currency: str
    in_stock: bool
    variants: list[Variant]
    seller: Seller
    notes: str | None = None


product = Product.model_validate(
    {
        "sku": "WSA-BOTTLE-750",
        "title": "Insulated Steel Water Bottle 750ml",
        "price": 19.99,
        "currency": "USD",
        "in_stock": True,
        "variants": [{"color": "matte black", "capacity_ml": 750}],
        "seller": {"name": "TrailGear Co", "rating": 4.8, "country": "US"},
    }
)
print(product.price)  # 19.99

try:
    Product.model_validate(
        {
            "sku": "WSA-BOTTLE-750",
            "title": "Insulated Steel Water Bottle 750ml",
            "price": "cheap",
            "currency": "USD",
            "in_stock": True,
            "variants": [],
            "seller": {"name": "TrailGear Co", "rating": 4.8, "country": "US"},
        }
    )
except ValidationError as e:
    print(e.error_count(), "validation error(s)")

A mesma estrutura que um JSON Schema, validada com o jsonschema pacote (ver também «Compreender o JSON Schema»):

import jsonschema

schema = {
    "type": "object",
    "required": ["sku", "title", "price", "currency", "in_stock", "variants", "seller"],
    "properties": {
        "sku": {"type": "string"},
        "title": {"type": "string"},
        "price": {"type": "number"},
        "currency": {"type": "string", "minLength": 3, "maxLength": 3},
        "in_stock": {"type": "boolean"},
        "variants": {
            "type": "array",
            "items": {
                "type": "object",
                "required": ["color", "capacity_ml"],
                "properties": {
                    "color": {"type": "string"},
                    "capacity_ml": {"type": "integer"},
                },
            },
        },
        "seller": {
            "type": "object",
            "required": ["name", "rating", "country"],
            "properties": {
                "name": {"type": "string"},
                "rating": {"type": "number"},
                "country": {"type": "string"},
            },
        },
    },
}

jsonschema.validate(
    {
        "sku": "WSA-BOTTLE-750",
        "title": "Insulated Steel Water Bottle 750ml",
        "price": 19.99,
        "currency": "USD",
        "in_stock": True,
        "variants": [{"color": "matte black", "capacity_ml": 750}],
        "seller": {"name": "TrailGear Co", "rating": 4.8, "country": "US"},
    },
    schema,
)

Como se escreve JSON numa string ou num ficheiro?

Utilize json.dumps para uma cadeia de caracteres e json.dump para um ficheiro. indent formata de forma legível, sort_keys=True estabiliza as diferenças e separators=(",", ":") reduz o tamanho das cargas úteis.

import json
from pathlib import Path

product = {
    "sku": "WSA-BOTTLE-750",
    "title": "Insulated Steel Water Bottle 750ml",
    "price": 19.99,
    "currency": "USD",
    "in_stock": True,
}

print(json.dumps(product, indent=2, sort_keys=True))
print(json.dumps(product, separators=(",", ":")))

with Path("product_out.json").open("w", encoding="utf-8") as f:
    json.dump(product, f, ensure_ascii=False, indent=2)

Escrever texto em línguas que não o inglês sem sequências de escape

Por predefinição, dumps escapa os caracteres não ASCII como \uXXXX. Utilize ensure_ascii=False quando pretender UTF-8 legível em ficheiros e registos.

import json

title = {"title": "Bouteille d'eau 750ml — acier"}
print(json.dumps(title))
# {"title": "Bouteille d'eau 750ml \u2014 acier"}
print(json.dumps(title, ensure_ascii=False))
# {"title": "Bouteille d'eau 750ml — acier"}

Serialização de datetime, Decimal e classes de dados

Passe uma default= função para os tipos que o codificador não reconhece. Também pode criar uma subclasse json.JSONEncoder quando precisar de uma classe de codificador reutilizável.

import json
from dataclasses import asdict, dataclass
from datetime import datetime, timezone
from decimal import Decimal
from typing import Any


def default_encoder(obj: Any) -> Any:
    if isinstance(obj, datetime):
        return obj.isoformat()
    if isinstance(obj, Decimal):
        return str(obj)
    if hasattr(obj, "__dataclass_fields__"):
        return asdict(obj)
    raise TypeError(f"Object of type {type(obj).__name__} is not JSON serializable")


@dataclass
class Offer:
    sku: str
    price: Decimal
    listed_at: datetime


offer = Offer(
    sku="WSA-BOTTLE-750",
    price=Decimal("19.99"),
    listed_at=datetime(2026, 10, 6, 12, 0, tzinfo=timezone.utc),
)
print(json.dumps(offer, default=default_encoder))
# {"sku": "WSA-BOTTLE-750", "price": "19.99", "listed_at": "2026-10-06T12:00:00+00:00"}

Como se mantêm a precisão dos preços e dos números grandes?

Utilize parse_float=Decimal para que valores como 19.99 permaneçam exatos como decimais, em vez de floats binários. A RFC 8259, a norma JSON, não permite NaN ou Infinity. Após a análise, o módulo `decimal` do Python é o local habitual para valores seguros em termos monetários.

import json
from decimal import Decimal

data = json.loads('{"price": 19.99}', parse_float=Decimal)
print(data["price"], type(data["price"]))
# 19.99 <class 'decimal.Decimal'>

# Reject NaN / Infinity when encoding
try:
    json.dumps({"price": float("nan")}, allow_nan=False)
except ValueError as e:
    print(e)

O descodificador do Python aceita NaN e Infinity , por predefinição, como extensão. Passe um parse_constant que lance uma exceção se quiser rejeitá-los na entrada.

Como se detetam chaves duplicadas no JSON?

A RFC 8259, a norma JSON, estabelece que os nomes dos objetos devem ser únicos, mas json.loads mantém o último valor silenciosamente. Um object_pairs_hook pode, em vez disso, lançar uma exceção.

import json
from typing import Any


def detect_duplicates(pairs: list[tuple[str, Any]]) -> dict[str, Any]:
    seen: set[str] = set()
    out: dict[str, Any] = {}
    for key, value in pairs:
        if key in seen:
            raise ValueError(f"Duplicate key: {key!r}")
        seen.add(key)
        out[key] = value
    return out


text = '{"sku": "A", "sku": "B", "price": 1}'
print(json.loads(text)["sku"])  # B — last value wins

try:
    json.loads(text, object_pairs_hook=detect_duplicates)
except ValueError as e:
    print(e)  # Duplicate key: 'sku'

Como se trabalha com ficheiros JSON Lines?

O formato JSON Lines armazena um valor JSON por linha. Acrescente um registo após cada extração, depois leia linha a linha, ignore os espaços em branco e indique o número da linha quando um registo não for descodificado.

import json
from pathlib import Path

path = Path("products.jsonl")

# Append during a run
with path.open("a", encoding="utf-8") as f:
    for rec in (
        {"sku": "WSA-BOTTLE-750", "price": 19.99},
        {"sku": "WSA-LID-01", "price": 4.5},
    ):
        f.write(json.dumps(rec, ensure_ascii=False) + "\n")

# Read with line numbers
records = []
with path.open(encoding="utf-8") as f:
    for lineno, line in enumerate(f, start=1):
        line = line.strip()
        if not line:
            continue
        try:
            records.append(json.loads(line))
        except json.JSONDecodeError as e:
            raise ValueError(f"Invalid JSON on line {lineno}: {e.msg}") from e

print(len(records), "records")

Como analisar um ficheiro JSON que é demasiado grande para caber na memória?

Utilize o ijson no PyPI: itere pelos itens num determinado caminho em vez de carregar o documento na íntegra. Utilize use_float=True apenas quando a precisão binária de números flutuantes for aceitável.

import json
from decimal import Decimal
from pathlib import Path

import ijson

# Build a sample file so the example runs anywhere
large = Path("large_products.json")
items = [{"sku": f"SKU-{i}", "price": i * 1.5} for i in range(5_000)]
large.write_text(json.dumps({"items": items}), encoding="utf-8")

count = 0
total = Decimal("0")
with large.open("rb") as f:
    for item in ijson.items(f, "items.item"):
        count += 1
        total += Decimal(str(item["price"]))

print(count, "items; sum", total)

O orjson é mais rápido do que o módulo json?

Muitas vezes sim, mas avalie com os seus próprios dados. A documentação do orjson no PyPI destaca as diferenças na API que são relevantes na prática: dumps os bytes, as opções são sinalizadores em vez de argumentos-chave e a indentação utiliza OPT_INDENT_2 em vez de um indent número inteiro.

import json
import timeit

import orjson

payload = {
    "sku": "WSA-BOTTLE-750",
    "title": "Insulated Steel Water Bottle 750ml",
    "price": 19.99,
    "variants": [{"color": "matte black", "capacity_ml": 750}],
}

encoded = orjson.dumps(payload)
assert isinstance(encoded, bytes)
decoded = orjson.loads(encoded)
pretty = orjson.dumps(payload, option=orjson.OPT_INDENT_2)

def harness() -> None:
    orjson.dumps(payload)
    json.dumps(payload)

print(timeit.timeit(harness, number=5_000))
# Run locally and compare; numbers vary by machine and payload.

Como se extrai JSON oculto numa página web?

Muitas páginas transportam os seus dados como JSON em <script> , o que costuma ser mais estável do que os seletores CSS no HTML renderizado. Analise esse JSON e, em seguida, percorra-o com as mesmas ferramentas que utiliza para respostas de API.

Ler dados estruturados em JSON-LD

O JSON-LD encontra-se em script[type="application/ld+json"] blocos. Uma página pode apresentar um único objeto, uma lista ou um @graph wrapper. Filtra por @type == "Product" (ou pelo tipo que precisar):

import json
from typing import Any

from bs4 import BeautifulSoup

html = """
<!DOCTYPE html>
<html><head>
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@type": "Product",
  "name": "Insulated Steel Water Bottle 750ml",
  "sku": "WSA-BOTTLE-750",
  "offers": {"@type": "Offer", "price": "19.99", "priceCurrency": "USD"}
}
</script>
<script type="application/ld+json">
[
  {"@type": "BreadcrumbList", "itemListElement": []},
  {"@type": "Product", "name": "Spare Lid", "sku": "WSA-LID-01"}
]
</script>
<script type="application/ld+json">
{
  "@context": "https://schema.org",
  "@graph": [
    {"@type": "Organization", "name": "TrailGear Co"},
    {"@type": "Product", "name": "Bottle Bundle", "sku": "WSA-BUNDLE"}
  ]
}
</script>
</head><body></body></html>
"""


def extract_json_ld_products(page_html: str) -> list[dict[str, Any]]:
    soup = BeautifulSoup(page_html, "html.parser")
    products: list[dict[str, Any]] = []

    def collect(node: Any) -> None:
        if isinstance(node, list):
            for item in node:
                collect(item)
            return
        if not isinstance(node, dict):
            return
        if node.get("@type") == "Product":
            products.append(node)
        graph = node.get("@graph")
        if isinstance(graph, list):
            collect(graph)

    for tag in soup.select('script[type="application/ld+json"]'):
        if not tag.string:
            continue
        collect(json.loads(tag.string))
    return products


for product in extract_json_ld_products(html):
    print(product["sku"], product["name"])

Quando uma página insere o seu JSON-LD com JavaScript, um navegador hospedado que renderiza a página antes de a devolver fornece-lhe o HTML final.

A leitura __NEXT_DATA__ e outro estado do framework

__NEXT_DATA__ e outro estado do framework

O Next.js e frameworks semelhantes incorporam as propriedades da página numa script#__NEXT_DATA__ tag. Analise o texto da tag e, em seguida, percorra-o com dig(). Para um guia passo a passo completo sobre o setor retalhista, consulte o nosso guia de scraping do Walmart, que analisa o registo do produto a partir de __NEXT_DATA__.

import json
from typing import Any

from bs4 import BeautifulSoup


def dig(data: Any, *path: str | int, default: Any = None) -> Any:
    cur: Any = data
    for key in path:
        if isinstance(cur, dict):
            if key not in cur:
                return default
            cur = cur[key]
        elif isinstance(cur, list) and isinstance(key, int):
            if key < 0 or key >= len(cur):
                return default
            cur = cur[key]
        else:
            return default
    return cur


html = """
<!DOCTYPE html>
<html><body>
<script id="__NEXT_DATA__" type="application/json">
{
  "props": {
    "pageProps": {
      "product": {
        "sku": "WSA-BOTTLE-750",
        "title": "Insulated Steel Water Bottle 750ml",
        "price": 19.99
      }
    }
  }
}
</script>
</body></html>
"""

soup = BeautifulSoup(html, "html.parser")
tag = soup.find("script", id="__NEXT_DATA__")
next_data = json.loads(tag.string)
print(dig(next_data, "props", "pageProps", "product", "sku"))
# WSA-BOTTLE-750

Chamar o endpoint JSON que a página carrega em segundo plano

Abra o DevTools → Rede, atualize a página e filtre por Fetch/XHR. Muitas interfaces de utilizador de produtos e listagens carregam um URL JSON que o navegador já acede. Se esse ponto de extremidade for publicamente acessível de acordo com os termos do site, pode solicitá-lo diretamente com os mesmos cabeçalhos que a página utiliza e analisá-lo com response.json(). Dê preferência a APIs documentadas ou claramente públicas, quando estas existirem. Verifique os termos do site e os nossos princípios para dados web responsáveis antes de aceder a pontos de extremidade internos.

Como se analisa JSON a partir da WebScrapingAPI?

A API do Scraper pode devolver campos selecionados como JSON em vez de HTML bruto. Utilize extract_rules para campos nomeados, json_response=1 para o envelope completo (incluindo metadados JSON-LD) ou descodifique um destino JSON a partir de envelope["body"]. Mantenha a chave em WSA_API_KEY.

Obter campos nomeados com extract_rules

Passe um objeto extract_rules para /v2. A resposta contém uma propriedade por chave de regra. As regras aninhadas percorrem cartões repetidos; table_json transformam tabelas HTML em objetos de linha.

import json
import os

import requests

api_key = os.environ["WSA_API_KEY"]

extract_rules = {
    "title": {"selector": "h1", "output": "text"},
    "results": {
        "selector": "article.product_pod",
        "all": 1,
        "output": {
            "name": {"selector": "h3 a", "output": "@title"},
            "url": {"selector": "h3 a", "output": "@href"},
            "price": {"selector": "p.price_color", "output": "text"},
        },
    },
}

resp = requests.get(
    "https://api.webscrapingapi.com/v2",
    params={
        "api_key": api_key,
        "url": "https://books.toscrape.com/",
        "extract_rules": json.dumps(extract_rules),
    },
    timeout=60,
)
resp.raise_for_status()
fields = resp.json()
print(fields["title"])
# All products
print(fields["results"][:2])
# [{'name': 'A Light in the Attic', 'url': 'catalogue/a-light-in-the-attic_1000/index.html', 'price': '£51.77'}, {'name': 'Tipping the Velvet', 'url': 'catalogue/tipping-the-velvet_999/index.html', 'price': '£53.74'}]

A lista de referência das regras de extração enumera todos os tipos de saída, incluindo table_json.

Lendo o envelope de resposta JSON completo

Definir json_response=1 para envolver a extração num objeto JSON com headers, cost, initial-status-code, body, type, e metadata. O metadata objeto inclui microdados extraídos e JSON-LD, pelo que metadata["json-ld"] é um atalho quando só precisa de dados estruturados da página.

import os

import requests

api_key = os.environ["WSA_API_KEY"]

resp = requests.get(
    "https://api.webscrapingapi.com/v2",
    params={
        "api_key": api_key,
        "url": "https://example.com",
        "json_response": 1,
    },
    timeout=60,
)
resp.raise_for_status()
envelope = resp.json()

print(envelope["initial-status-code"], envelope["type"], envelope["cost"])
body = envelope["body"]          # page content as a string
json_ld = envelope.get("metadata", {}).get("json-ld", [])
print(type(body), len(json_ld))

A documentação completa sobre a resposta JSON descreve cada campo do envelope.

Analisar um corpo JSON dentro do envelope

Quando o próprio URL de destino devolve JSON, o envelope continua a colocar essa carga útil body como uma cadeia de caracteres. Consulte type (por exemplo "json") e descodifique com json.loads(envelope["body"]) antes de percorrer os campos.

import json
import os

import requests

api_key = os.environ["WSA_API_KEY"]

resp = requests.get(
    "https://api.webscrapingapi.com/v2",
    params={
        "api_key": api_key,
        "url": "https://httpbin.org/json",
        "json_response": 1,
    },
    timeout=60,
)
resp.raise_for_status()
envelope = resp.json()

if envelope.get("type") == "json":
    payload = json.loads(envelope["body"])
else:
    # HTML or other types still arrive as a string in body
    payload = envelope["body"]

print(type(payload))

No caso dos resultados de pesquisa, uma API SERP que devolve resultados como JSON estruturado evita completamente a análise de HTML.

Que método de análise de JSON deve utilizar?

Adapte a entrada à ferramenta: a biblioteca padrão para cadeias de caracteres e ficheiros, o ijson para matrizes enormes, um script de análise para dados incorporados em HTML e uma API de scraping quando pretender campos ou um envelope em vez de HTML bruto.

O que tem à sua disposição

O que utilizar

Cadeia de caracteres JSON ou bytes

json.loads

Ficheiro JSON (UTF-8)

json.load com encoding="utf-8"

Ficheiro que começa com um BOM

encoding="utf-8-sig" e, em seguida, json.load / json.loads

Pedidos / Resposta HTTP

raise_for_status() + Verificação do Content-Type + response.json()

São necessários valores predefinidos para caminhos aninhados

.get() cadeia ou dig()

São necessários tipos / campos obrigatórios

Modelo Pydantic ou JSON Schema após a análise

Valores monetários ou decimais precisos

parse_float=Decimal

Chaves duplicadas devem ser rejeitadas

object_pairs_hook detetor

Um registo por linha de extração

Linhas JSON (*.jsonl)

Matriz demasiado grande para a RAM

Streaming de iJSON

Codificação/decodificação crítica em termos de débito

orjson (medir na sua carga útil)

Dados incorporados em HTML

JSON-LD / __NEXT_DATA__ / outros script JSON

Campos selecionados de uma página através da API

WebScrapingAPI extract_rules

São necessários cabeçalhos, custo e metadados JSON-LD

json_response=1 envelope

A URL de destino devolve JSON através da API

json.loads(envelope["body"]) quando type é json

Perguntas frequentes

Qual é a diferença entre json.load e json.loads?

json.loads analisa uma cadeia de caracteres JSON ou bytes já presentes na memória. json.load Lê a partir de um objeto semelhante a um ficheiro e analisa esse fluxo. Utilize loads para corpos de API e texto na memória; utilize load para ficheiros abertos com a codificação correta. Ambos devolvem os mesmos tipos de Python para o mesmo JSON.

Como analiso uma resposta JSON de uma API em Python?

Envie o pedido com o Requests (ou outro cliente HTTP), chame raise_for_status(), confirme se o Content-Type parece ser JSON e, em seguida, chame response.json() ou json.loads(response.text). Considere uma página HTML em branco com código 200 como um problema de transporte, e não um problema de sintaxe JSON, e analise uma pré-visualização curta do corpo da resposta quando a descodificação falhar.

Por que recebo a mensagem «JSONDecodeError: Expecting value»?

O descodificador chegou a um ponto onde deveria começar um valor JSON e não encontrou nada válido — corpo vazio, JSON truncado, HTML ou um token como undefined. Imprima e.pos e os caracteres circundantes. Corrija a resposta a montante ou o transporte; não ignore o erro com um except.

Como posso obter com segurança um valor aninhado a partir de JSON em Python?

Evite o uso encadeado de data["a"]["b"] quando as chaves possam estar ausentes. Utilize .get("a", {}).get("b") para percursos superficiais, ou um pequeno dig(data, "a", "b", default=None) função auxiliar que também aceite índices de lista. Lembre-se de que uma chave em falta e um JSON explícito null representam resultados diferentes.

Como validar dados JSON em Python?

A análise sintática verifica apenas a sintaxe. Após loads, valide com um modelo Pydantic (tipos, opcionais, modelos aninhados) ou um documento JSON Schema através do jsonschema pacote. Mantenha os campos monetários como Decimal no modelo quando o arredondamento de valores flutuantes for incorreto e apresente os erros de validação ao chamador.

Como posso manter os preços decimais exatos ao analisar JSON?

Passe parse_float=Decimal para json.loads ou json.load para que os preços numéricos se tornem decimal.Decimal em vez de float. Ao codificar, serialize os decimais como cadeias de caracteres (ou números inteiros de unidades menores) com um default= gancho, e mantenha allow_nan=False para que as constantes numéricas não JSON não se infiltrem na saída.

Como é que se lê um ficheiro JSON muito grande em Python?

Não chame json.load em matrizes com vários gigabytes. Utilize o ijson para iterar itens num determinado caminho, como "items.item" enquanto o ficheiro permanecer no disco. Dê preferência ao JSON Lines quando controlar o produtor, porque cada linha é um registo independente que pode ser transmitido e recuperado linha a linha.

O que é o JSON Lines e quando devo utilizá-lo?

O JSON Lines (application/jsonl) armazena um valor JSON por linha delimitada por uma nova linha. É adequado para fluxos de trabalho de scraping e registo, nos quais se acrescentam registos à medida que chegam e se lêem mais tarde sem carregar o ficheiro na totalidade. Ignora as linhas em branco e indica o número da linha com erro quando uma linha não é descodificada.

O orjson é um substituto direto do json?

Não totalmente. O orjson é normalmente mais rápido, mas dumps as opções de retorno bytes, os sinalizadores de opção substituem muitos argumentos-chave e a indentação utiliza OPT_INDENT_2. Se uma biblioteca esperar um str de dumps, adicione .decode(). Avalie ambos no seu payload antes de alterar o código de produção.

Como posso extrair JSON-LD ou __NEXT_DATA__ de uma página web com Python?

__NEXT_DATA__ de uma página web com Python?

Analise o HTML com o BeautifulSoup, selecione script[type="application/ld+json"] ou script#__NEXT_DATA__e execute json.loads no texto da tag. Trate o JSON-LD como um objeto, uma lista ou uma @graph lista e, em seguida, filtrar por @type. Percorra as propriedades aninhadas com dig(). Para tags renderizadas pelo cliente, recupere o HTML renderizado antes da análise.

Como posso formatar JSON de forma legível em Python?

Chame json.dumps(data, indent=2, sort_keys=True) para obter uma cadeia de caracteres estável e legível, ou json.dump(..., indent=2) ao gravar um ficheiro no disco. Adicione ensure_ascii=False para texto que não esteja em inglês, para que os caracteres não sejam escapados. Com o orjson, passe option=orjson.OPT_INDENT_2 e descodifique os bytes devolvidos se precisar de uma cadeia de caracteres.

Obtenha JSON estruturado a partir de qualquer página

Se preferir obter campos nomeados em vez de gerir seletores CSS, a API do Scraper extract_rules e json_response opções da Scraper API devolvem JSON que pode ser introduzido diretamente nas json.loads e nos auxiliares acima. Configure WSA_API_KEY, comece pela página do produto da API do Scraper e mantenha a validação junto a cada análise.

Sobre o autor

Suciu Dan, Co-fundador @ WebScrapingAPI

Suciu Dan

Co-fundador

Suciu Dan é cofundador da WebScrapingAPI e escreve guias práticos, voltados para programadores, sobre web scraping em Python, web scraping em Ruby e infraestruturas de proxy.

Web Scraping com AWS Lambda: Guia para Python e Java 2026
Guias

Web Scraping com AWS Lambda: Guia para Python e Java 2026

TL;DR: Resumo: A extração de dados da Web com o AWS Lambda funciona melhor quando cada invocação é curta, delimitada e pode ser repetida de forma independente. Comece com HTTP direto, AWS SAM e S3 e, só depois, adicione SQS, contentores, renderização no navegador, proxies ou uma camada de recuperação gerida, apenas quando a carga de trabalho demonstrar que precisa deles.

Suciu Dan32 min read
Ler artigo
Como utilizar o GoSpider: rastrear, limpar URLs e extrair dados
Guias

Como utilizar o GoSpider: rastrear, limpar URLs e extrair dados

TL;DR: Resumo: O GoSpider é um rastreador de linha de comandos destinado a descobrir URLs, não um extrator completo de dados estruturados. Este guia sobre como utilizar o GoSpider mostra como realizar um rastreio limitado, gerir resultados de forma organizada, fazer a transferência de dados do Colly para CSV e seguir um percurso de diagnóstico para respostas 403 ou páginas que exijam renderização em JavaScript.

Suciu Dan23 min read
Ler artigo
Como fazer o Scrape Redfin: Guia Python para Dados de Propriedade
Guias

Como fazer o Scrape Redfin: Guia Python para Dados de Propriedade

TL;DR: A Redfin expõe pontos de extremidade de API ocultos que retornam JSON estruturado para listagens de propriedades, tornando possível ignorar totalmente a análise HTML frágil. Este guia orienta-o na construção de um scraper Python que extrai dados de aluguer e venda, pesquisa por localização, monitoriza novas listagens através de sitemaps XML e exporta resultados limpos para CSV ou JSON.

Suciu Dan14 min read
Ler artigo

Comece a construir

Pronto para expandir a sua recolha de dados?

Junte-se a mais de 2.000 empresas que utilizam a WebScrapingAPI para extrair dados da Web à escala empresarial, sem quaisquer custos de infraestrutura.