import juscraper as jus
trf3 = jus.scraper("trf3")TRF3 — Tribunal Regional Federal da 3ª Região
Public process consultation (cpopg) for the federal courts under the third region (SP and MS), via the PJe ConsultaPublica system at pje1g.trf3.jus.br/pje/.
| Feature | Available |
|---|---|
| cpopg | Yes |
| cposg | No |
| cjsg | Yes |
| cjpg | No |
Looking up a single process
df = trf3.cpopg("5005946-09.2025.4.03.6324")
print(df.shape)
df[["id_cnj", "processo", "classe", "data_distribuicao"]]TRF3 cpopg: 100%|██████████| 1/1 [00:01<00:00, 1.08s/it]
(1, 12)
| id_cnj | processo | classe | data_distribuicao | |
|---|---|---|---|---|
| 0 | 50059460920254036324 | 5005946-09.2025.4.03.6324 | PROCEDIMENTO DO JUIZADO ESPECIAL CÍVEL (436) | 29/09/2025 |
Available columns
df.columns.tolist()['processo',
'data_distribuicao',
'classe',
'assunto',
'jurisdicao',
'orgao_julgador',
'endereco_orgao',
'polo_ativo',
'polo_passivo',
'movimentacoes',
'documentos',
'id_cnj']
The first three columns are the canonical scalars; the trailing four are list-typed and carry the nested arrays (parties, movements, attached documents).
Inspecting movements
movs = df.iloc[0]["movimentacoes"]
print(f"{len(movs)} events recorded")
for m in movs[:3]:
print(f" {m['data']} - {m['descricao']}")11 events recorded
29/04/2026 12:51:15 - Remetidos os Autos ao CEJUSC ou Centros de Conciliação/Mediação da Subseção Judiciária
29/04/2026 12:37:09 - Expedida/certificada a intimação eletrônica
29/04/2026 12:37:08 - Proferido despacho de mero expediente
Inspecting parties
print("Polo ativo:")
for p in df.iloc[0]["polo_ativo"]:
print(f" - {p['participante']}")
print()
print("Polo passivo:")
for p in df.iloc[0]["polo_passivo"]:
print(f" - {p['participante']}")Polo ativo:
- JOAO PAULO NASCIMENTO DA SILVA - CPF: 003.XXX.XXX-XX (AUTOR)
Polo passivo:
- CAIXA ECONOMICA FEDERAL - CEF - CNPJ: 00.3XX.XXX/XXXX-XX (REU) Departamento Jurídico - Caixa Econômica Federal
Looking up multiple processes at once
cnjs = [
"50059460920254036324",
"50061271020254036324",
"50035362120254036342",
]
df_batch = trf3.cpopg(cnjs)
df_batch[["id_cnj", "processo", "classe"]]
TRF3 cpopg: 0%| | 0/3 [00:00<?, ?it/s]
TRF3 cpopg: 33%|███▎ | 1/3 [00:01<00:02, 1.42s/it]
TRF3 cpopg: 67%|██████▋ | 2/3 [00:03<00:01, 1.63s/it]
TRF3 cpopg: 100%|██████████| 3/3 [00:04<00:00, 1.30s/it]
TRF3 cpopg: 100%|██████████| 3/3 [00:04<00:00, 1.37s/it]
| id_cnj | processo | classe | |
|---|---|---|---|
| 0 | 50059460920254036324 | 5005946-09.2025.4.03.6324 | PROCEDIMENTO DO JUIZADO ESPECIAL CÍVEL (436) |
| 1 | 50061271020254036324 | 5006127-10.2025.4.03.6324 | PROCEDIMENTO DO JUIZADO ESPECIAL CÍVEL (436) |
| 2 | 50035362120254036342 | 5003536-21.2025.4.03.6342 | PROCEDIMENTO DO JUIZADO ESPECIAL CÍVEL (436) |
Handling processes the public portal cannot return
When a CNJ does not surface in the public consultation (sigilo, archived, or simply not found), the row carries only id_cnj. The other columns come back as None/NaN, so callers can still distinguish “looked up but missing” from “never tried”.
import pandas as pd
df_missing = trf3.cpopg("00000000020994030000")
print("processo:", df_missing.iloc[0].get("processo"))
print("classe:", df_missing.iloc[0].get("classe"))
TRF3 cpopg: 0%| | 0/1 [00:00<?, ?it/s]
TRF3 cpopg: 100%|██████████| 1/1 [00:00<00:00, 8.34it/s]
TRF3 cpopg: 100%|██████████| 1/1 [00:00<00:00, 8.19it/s]
processo: None
classe: None
Splitting download from parse
cpopg is a thin wrapper over cpopg_download (raw HTML) + cpopg_parse (HTML → DataFrame). Splitting them is useful when you want to cache the raw HTMLs to disk before processing.
htmls = trf3.cpopg_download("5005946-09.2025.4.03.6324")
print(f"got {len(htmls)} HTML(s), {len(htmls[0])} chars")
df_again = trf3.cpopg_parse(htmls, ["50059460920254036324"])
df_again[["id_cnj", "processo", "data_distribuicao"]]
TRF3 cpopg: 0%| | 0/1 [00:00<?, ?it/s]
TRF3 cpopg: 100%|██████████| 1/1 [00:00<00:00, 2.43it/s]
TRF3 cpopg: 100%|██████████| 1/1 [00:00<00:00, 2.43it/s]
got 1 HTML(s), 85297 chars
| id_cnj | processo | data_distribuicao | |
|---|---|---|---|
| 0 | 50059460920254036324 | 5005946-09.2025.4.03.6324 | 29/09/2025 |
Downloading peças (anexos)
By default cpopg returns metadata only: process info, parties, movements, and a documentos list (id / date / description). The actual peça files (HTML viewer payloads) are not downloaded.
To also download the peças, pass download_pecas=True and a diretorio. Each peça is saved as <diretorio>/<cnj>/<id_processo_doc>.html (a self- contained XHTML with images embedded as data: URLs), and the returned DataFrame gains a pecas column with the list of saved paths.
The flag lives on cpopg (instead of a separate cpopg_download_pecas method) because the ca tokens that identify each peça are bound to the Seam conversation that produced the detail page — peças must be fetched from the same requests.Session. A separate method would have to re-issue the detail GET internally just to obtain fresh tokens; folding the optional download into cpopg avoids that duplicate round-trip.
import tempfile
with tempfile.TemporaryDirectory() as tmp:
df_pecas = trf3.cpopg(
"5005946-09.2025.4.03.6324",
download_pecas=True,
diretorio=tmp,
)
saved = df_pecas.iloc[0]["pecas"]
print(f"saved {len(saved)} peça(s)")
for p in saved[:3]:
print(" ", p)TRF3 cpopg: 0%| | 0/1 [00:00<?, ?it/s]TRF3 cpopg: 100%|██████████| 1/1 [00:00<00:00, 1.31it/s]TRF3 cpopg: 100%|██████████| 1/1 [00:00<00:00, 1.31it/s]
saved 4 peça(s)
C:\Users\jtrec\AppData\Local\Temp\tmpkxwx55p9\50059460920254036324\582695147.html
C:\Users\jtrec\AppData\Local\Temp\tmpkxwx55p9\50059460920254036324\581769486.html
C:\Users\jtrec\AppData\Local\Temp\tmpkxwx55p9\50059460920254036324\578698643.html
Searching case law (cjsg)
cjsg searches the TRF3 case-law portal at web.trf3.jus.br/jurisprudencia, a system separate from the PJe. The base argument picks one of the four result sets the portal keeps apart:
base |
Content |
|---|---|
"acordaos" (default) |
TRF3 appellate judgments |
"turmas_recursais" |
judgments of the federal small-claims appeal panels (JEF) |
"monocraticas" |
single-judge decisions of the TRF3 |
"monocraticas_turmas_recursais" |
single-judge decisions of the appeal panels |
Each call opens its own session on the portal and reads the result list, which already carries the ementa and the full text of every document: the cost is three fixed requests (opening the tab, the search POST and the GET of its redirect, which is page 1) plus one per extra page, with sleep_time between pages.
df_cjsg = trf3.cjsg(
"medicamento",
paginas=range(1, 3),
data_julgamento_inicio="2026-01-01",
data_julgamento_fim="2026-06-30",
)
print(df_cjsg.shape)
df_cjsg[["processo", "classe", "orgao_julgador", "relator", "data_julgamento", "meio_publicacao"]].head()The other bases
In the single-judge bases, ementa holds the text of the decision (there is no ementa) and orgao_julgador is empty; data_julgamento is the date of the decision.
In part of the older appeal-panel judgments (turmas_recursais), ementa is null because the portal sends an empty ementa block; inteiro_teor still carries the full text. When this was measured, every ementa from 2013 and 2014 was empty, and some from 2011.
for base in ["turmas_recursais", "monocraticas", "monocraticas_turmas_recursais"]:
df_base = trf3.cjsg(
"medicamento",
base=base,
paginas=1,
data_julgamento_inicio="2026-01-01",
data_julgamento_fim="2026-06-30",
)
print(base, df_base.shape, df_base["relator"].iloc[0])Filters
relator and classe are free text matched against the portal’s index, not dropdown IDs. Use the spelling of the form’s dropdowns: relator="NERY JUNIOR", classe="AI - AGRAVO DE INSTRUMENTO".
orgao_julgador picks one option of the órgão dropdown of the tab, by its label ("3ª Turma", "11ª Turma Recursal da Seção Judiciária de São Paulo") or by its indexed name ("TERCEIRA TURMA", "11ª TURMA RECURSAL DE SÃO PAULO"), ignoring case and spacing. The value in the orgao_julgador column also works: as in the portal’s form, "1a. seção" becomes "1A", so "QUINTA TURMA - 1A. SEÇÃO" matches the option QUINTA TURMA - 1a. SEÇÃO. The search reads the dropdown from the tab it already opens and sends the pair the browser sends, the label and the indexed name of the option, and the portal returns the documents recorded under either (measured with “medicamento”: "QUINTA TURMA" finds 576 documents, against 443 for the label alone and 133 for the indexed name alone). A label shared by more than one option, such as "5ª Turma" in the acórdãos base, raises ValueError listing the indexed names that resolve it; text that matches no option is sent as is and only finds what the portal records with that spelling. The monocratic bases have no órgão filter.
The form has a single date pair, so pass either data_julgamento_* or data_publicacao_*, not both.
df_filtrado = trf3.cjsg(
"medicamento",
relator="NERY JUNIOR",
classe="AI - AGRAVO DE INSTRUMENTO",
data_publicacao_inicio="2026-01-01",
data_publicacao_fim="2026-06-30",
paginas=1,
)
df_filtrado[["processo", "relator", "relator_acordao", "data_publicacao"]]