import os
from getpass import getpass
import juscraper as jus
pdpj = jus.scraper("pdpj", sleep_time=0.2, verbose=1)
token = os.environ.get("JUSCRAPER_PDPJ_TOKEN") or getpass("Cole o JWT do PDPJ: ")
pdpj.auth(token=token)True
pesquisa) e contagem (contar)
Este notebook demonstra como usar o agregador pdpj do juscraper para consultar a API DATALAKE - API Processos do PDPJ. A API e parecida com a do Jus.br: requer um JWT no header Authorization: Bearer <token>, mas expoe varios endpoints documentados (busca profunda com filtros, contagem, partes, movimentos, documentos, texto e binario).
token (POST), clique nela.access_token – esse e o JWT.O token expira em algumas horas. Para uso interativo, prefira coloca-lo em variavel de ambiente (
JUSCRAPER_PDPJ_TOKEN).
O endpoint /existe aceita um CNJ (com ou sem mascara) e devolve True/False. O scraper aceita string (retorna bool) ou lista (retorna DataFrame).
cpopg(id_cnj) chama GET /processos/{numeroProcesso} e devolve um DataFrame. Cada linha tem:
processo: CNJ pesquisado (so digitos)numero_processo: CNJ formatado pela API (NNNNNNN-DD.AAAA.J.TR.OOOO)sigla_tribunal, segmento_justica, nivel_sigilo, data_atualizacaodetalhes: dict completo da resposta (tramitacoes, classes, assuntos, partes etc.)df = pdpj.cpopg("1002988-64.2019.4.01.4100")
df[["processo", "numero_processo", "sigla_tribunal", "segmento_justica"]]| processo | numero_processo | sigla_tribunal | segmento_justica | |
|---|---|---|---|---|
| 0 | 10029886420194014100 | 1002988-64.2019.4.01.4100 | TRF1 | JUSTICA_FEDERAL |
# o dict completo fica em 'detalhes' -- util para extrair campos especificos
import json
detalhes = df.iloc[0]["detalhes"]
tramitacao = detalhes["tramitacoes"][0]
print(json.dumps({
"instancia": tramitacao.get("instancia"),
"natureza": tramitacao.get("natureza"),
"competencia": tramitacao.get("dadosCompetencia"),
"tribunal": tramitacao.get("tribunal"),
}, indent=2, ensure_ascii=False)){
"instancia": "PRIMEIRO_GRAU",
"natureza": "CIVEL",
"competencia": {
"id": 7065,
"idLocal": 248451,
"nome": "Cível (exceto Ambiental/Agrário)"
},
"tribunal": {
"idCodex": 3,
"sigla": "TRF1",
"nome": "Tribunal Regional Federal da 1ª Região",
"segmento": "JUSTICA_FEDERAL",
"jtr": "401"
}
}
cpopg aceita lista. Cada CNJ vira uma (ou mais) linhas no DataFrame final, com a coluna processo ligando a entrada do usuario a saida.
processos = [
"10029886420194014100",
"50211226520184036100",
"10122143420204013300",
]
df_multi = pdpj.cpopg(processos)
df_multi[["processo", "numero_processo", "sigla_tribunal", "data_atualizacao"]]| processo | numero_processo | sigla_tribunal | data_atualizacao | |
|---|---|---|---|---|
| 0 | 10029886420194014100 | 1002988-64.2019.4.01.4100 | TRF1 | 2026-04-28T04:11:48.852 |
| 1 | 50211226520184036100 | 5021122-65.2018.4.03.6100 | TRF3 | 2026-04-30T06:45:26.116 |
| 2 | 10122143420204013300 | 1012214-34.2020.4.01.3300 | TRF1 | 2026-04-27T21:33:46.760 |
Para cada processo, a API expoe sub-recursos com cada uma dessas listas. O scraper achata o JSON em DataFrames.
# Lista de documentos: cada linha e um documento. id_documento e o UUID
# que identifica o documento na API (usado depois em download_documents).
docs = pdpj.documentos("10029886420194014100")
docs[["sequencia", "data_juntada", "nome", "tipo_nome", "id_documento", "arquivo_tipo"]].head()| sequencia | data_juntada | nome | tipo_nome | id_documento | arquivo_tipo | |
|---|---|---|---|---|---|---|
| 0 | 10 | 2019-12-13T14:54:51.811 | Ato ordinatório.html | Ato ordinatório | d68cccee-b756-51e5-ae87-edb296ac220f | TEXT_HTML |
| 1 | 8 | 2019-07-17T09:32:26.372 | Decisão.html | Decisão | d5b2fe52-8747-59c8-9953-8f320971a492 | TEXT_HTML |
# Lista de movimentos -- omitimos `descricao` do display por LGPD
# (pode citar nome de partes). A coluna existe no DataFrame retornado.
movs = pdpj.movimentos("10029886420194014100")
movs[["sequencia", "data_hora", "tipo_nome"]].head()| sequencia | data_hora | tipo_nome | |
|---|---|---|---|
| 0 | 13 | 2020-04-22T19:28:49.17 | Baixa Definitiva |
| 1 | 12 | 2020-04-22T19:27:25.643 | Documento |
| 2 | 11 | 2020-02-12T04:21:16.861 | Decurso de Prazo |
| 3 | 10 | 2019-12-13T15:04:04.938 | Expedição de documento |
| 4 | 9 | 2019-12-13T14:54:51.831 | Documento |
# Lista de partes -- omitimos `nome` e `documento_numero` do display
# por LGPD; ambas as colunas existem no DataFrame retornado.
partes = pdpj.partes("10029886420194014100")
partes[["polo", "tipo_parte", "tipo_pessoa", "situacao"]].head()| polo | tipo_parte | tipo_pessoa | situacao | |
|---|---|---|---|---|
| 0 | ATIVO | AUTOR | FISICA | ATIVO |
| 1 | PASSIVO | RÉU | JURIDICA | ATIVO |
pesquisa) e contagem (contar)O endpoint GET /processos aceita uma colecao grande de filtros. O scraper valida-os via pydantic com nomes em snake_case e converte para o camelCase aceito pela API. Filtros principais:
| snake_case (juscraper) | camelCase (API) | Tipo / Exemplo |
|---|---|---|
numero_processo |
numeroProcesso |
"1002988..." |
cpf_cnpj_parte |
cpfCnpjParte |
"123.456.789-00" |
nome_parte |
nomeParte |
"FULANO" |
polo_parte |
poloParte |
"ATIVO" / "PASSIVO" |
oab_representante |
oabRepresentante |
"123456" |
id_classe |
idClasse |
"7,12728" |
id_assunto_judicial |
idAssuntoJudicial |
"7,8" |
id_orgao_julgador |
idOrgaoJulgador |
["12345", "67890"] -> "12345,67890" |
instancia |
instancia |
"PRIMEIRO_GRAU" |
segmento_justica |
segmentoJustica |
"JUSTICA_FEDERAL" |
tribunal |
tribunal |
"TRF1,TJSP" (max 5) |
data_atualizacao_inicio / _fim |
dataHoraAtualizacaoInicio/Fim |
ISO datetime |
data_primeiro_ajuizamento_inicio/_fim |
dataHoraPrimeiroAjuizamentoInicio/Fim |
ISO datetime |
campo_ordenacao |
campoOrdenacao |
"dataHoraUltimoMovimento" |
itens_por_pagina |
maxElementsSize |
1-100, default 100 |
A paginacao e feita via cursor searchAfter; o scraper cuida disso e respeita paginas (int, list, range, ou None = todas).
1
# Busca por um CNJ especifico (1 pagina, normalmente devolve 1 resultado).
df_search = pdpj.pesquisa(
numero_processo="10029886420194014100",
paginas=1,
)
df_search[["numero_processo", "sigla_tribunal", "data_atualizacao", "data_ultimo_movimento"]]| numero_processo | sigla_tribunal | data_atualizacao | data_ultimo_movimento | |
|---|---|---|---|---|
| 0 | 1002988-64.2019.4.01.4100 | TRF1 | 2026-04-28T04:11:48.852 | 2020-04-22T19:28:49.17 |
# Exemplo com varios filtros: processos do TRF1 atualizados em uma janela.
# Descomente para rodar -- pode demorar conforme volume.
#
df_janela = pdpj.pesquisa(
tribunal="TRF1",
data_atualizacao_inicio="2026-04-01T00:00:00.000",
data_atualizacao_fim="2026-04-30T23:59:59.999",
paginas=range(1, 4),
)
df_janela.shape(300, 8)
Nomes invalidos viram TypeError com sugestao de typo (igual cjsg/cjpg):
download_documents(base_df, ...) aceita dois shapes de entrada:
documentos(cnj) – uma linha por documento, ja com id_documento exposto.cpopg(cnj) – uma linha por processo. O scraper extrai a lista de documentos do campo detalhes.Flags:
with_text=True (default): baixa o texto extraido pela API (UTF-8, ja limpo).with_binary=False (default): baixa o arquivo bruto (HTML, PDF, imagem…).max_docs_per_process=None: limite de documentos baixados por processo.# Baixa apenas o primeiro documento do processo, com texto.
# Nao imprimimos o conteudo aqui por LGPD (documentos podem citar
# nomes de partes/advogados); apenas confirmamos que veio.
docs = pdpj.documentos("50211226520184036100")
out = pdpj.download_documents(docs, max_docs_per_process=1, with_text=True)
print("colunas:", list(out.columns))
print("tamanho do texto:", len(out.iloc[0]["texto"]), "chars")colunas: ['processo', 'numero_processo', 'id_documento', 'id_codex', 'id_origem', 'sequencia', 'data_juntada', 'nome', 'nivel_sigilo', 'tipo_codigo', 'tipo_nome', 'arquivo_id', 'arquivo_tipo', 'arquivo_tamanho', 'arquivo_tamanho_texto', 'arquivo_paginas', '_raw', 'texto', '_raw_texto']
tamanho do texto: 23532 chars
# Tambem da pra passar o resultado de cpopg direto
df_cpopg = pdpj.cpopg("50211226520184036100")
out2 = pdpj.download_documents(df_cpopg, max_docs_per_process=2, with_text=True, with_binary=True)
out2[["processo", "id_documento", "nome", "tipo_nome"]]| processo | id_documento | nome | tipo_nome | |
|---|---|---|---|---|
| 0 | 50211226520184036100 | 98cff81c-17e4-5b0f-8a64-039f3a1bcf80 | Acórdão.html | Acórdão |
| 1 | 50211226520184036100 | c7ba25c6-2fff-55fa-a6bb-7242dc519739 | Acórdão.html | Acórdão |
Estrategia simples: salvar metadados + documentos em parquet por processo, particionando por CNJ. Util quando vai baixar lotes grandes.
import os
from tqdm import tqdm
processos = [
"10029886420194014100",
"50211226520184036100",
"10122143420204013300",
]
raiz = "pdpj/cpopg"
os.makedirs(raiz, exist_ok=True)
for cnj in tqdm(processos):
base = pdpj.cpopg(cnj)
if base.empty:
continue
pasta = os.path.join(raiz, cnj)
os.makedirs(pasta, exist_ok=True)
base.to_parquet(os.path.join(pasta, "metadata.parquet"), index=False)
docs = pdpj.download_documents(base, with_text=True, with_binary=False)
if not docs.empty:
docs.to_parquet(os.path.join(pasta, "documents.parquet"), index=False) 0%| | 0/3 [00:00<?, ?it/s] 33%|███▎ | 1/3 [00:00<00:01, 1.25it/s] 67%|██████▋ | 2/3 [00:12<00:07, 7.24s/it]100%|██████████| 3/3 [00:26<00:00, 10.17s/it]100%|██████████| 3/3 [00:26<00:00, 8.73s/it]
RuntimeError: Autenticacao necessaria: chame pdpj.auth(token) antes de qualquer endpoint.ValueError: Token JWT expirado: gere um novo no portal (o token vive algumas horas).download_documents registra warning e segue.sleep_time ao instanciar (jus.scraper("pdpj", sleep_time=1.0)).| Metodo | Endpoint REST | Saida |
|---|---|---|
auth(token) |
- | True (bool) |
existe(cnj) |
GET /processos/{n}/existe |
bool ou DataFrame |
cpopg(cnj) |
GET /processos/{n} |
DataFrame |
documentos(cnj) |
GET /processos/{n}/documentos |
DataFrame |
movimentos(cnj) |
GET /processos/{n}/movimentos |
DataFrame |
partes(cnj) |
GET /processos/{n}/partes |
DataFrame |
pesquisa(**filtros) |
GET /processos |
DataFrame |
contar(**filtros) |
GET /processos:contar |
int |
download_documents(df) |
GET /processos/{n}/documentos/{id}/{texto,binario} |
DataFrame |
Schemas pydantic com a fonte da verdade: juscraper.aggregators.pdpj.schemas.