Added
- Novo raspador
stf(STFScraper) para a busca de jurisprudencia do STF (jurisprudencia.stf.jus.br), comlistar_decisoes(acordaos ou decisoes monocraticas, paginado, ate 250 por pagina) econtar_decisoes(total e facetas do portal: base, classe, ministro, UF, orgao). Filtros:pesquisana sintaxe do portal ($como curinga,ou),base,classe,inteiro_teoredata_julgamento_*/data_publicacao_*. O portal fica atras de um desafio JavaScript do AWS WAF: o cookieaws-waf-tokene obtido com Playwright, no novo extrapip install "juscraper[stf]"(seguido deplaywright install chromium), ou pode ser passado emjus.scraper("stf", waf_token=...); as buscas seguem emrequestse o cookie e renovado quando o WAF volta a desafiar. A API so entrega os 10.000 primeiros registros de uma busca: pagina alem disso levantaValueErrorantes de qualquer requisicao, epaginas=NoneemiteUserWarninge para no teto. - Contratos de teste offline para o agregador JusBR (
auth,cpopg,download_documents). Suite emtests/jusbr/cobre 18 cenarios:auth(token)(token valido, expirado ->ValueError, semexp, malformado),cpopg(1 CNJ,list[str], lista vazia, CNJ invalido sem HTTP, sem auth previa) edownload_documents(texto+binario, so texto, so binario, href malformado baixa so binario, ambos hrefs ausentes pulam,max_docs_per_process=1, sem auth). Mocks viaresponses+OrderedRegistrypara o fluxo multi-step lista -> detalhes; samples capturados pelo backend real viatests/fixtures/capture/jusbr.py(depende deJUSBR_JWT/JUSBR_CNJ_1/JUSBR_CNJ_2env vars) com sanitizacao agressiva pos-captura (CNJ neutro, PII redatada, regex defensivo para CPF/e-mail).auth_firefox()ficou fora — depende de cookies reais do Firefox, candidato a cassette VCR na Fase 4 da #113. Wiring deInputAuthJusBR/InputCPOPGJusBR/InputDownloadDocumentsJusBRsegue como follow-up separado (regra do projeto: contrato e wiring nunca no mesmo PR). Refs #104, #113, #141. - Raspador TRF6 (
cpopg— consulta pública de processos de 1º grau via eproc). Acessa o sistema eproc da Seção Judiciária de Minas Gerais emeproc1g.trf6.jus.br/eproc/. O formulário é gated por captcha de texto (imagem PNG embutida inline em base64 no HTML do form, validado server-side); o scraper resolve usando o pacote opcionaltxtcaptcha(CRNN pretrained do HuggingFace, baixado on-demand e cacheado). Cada captcha é vinculado ao cookiePHPSESSID, então cada nova tentativa após rejeição faz um GET fresco do form para obter um captcha novo (controlado pormax_captcha_attempts, default 3). API:cpopg(id_cnj)aceita um CNJ ou lista; devolvepd.DataFramecom colunasid_cnj,processo,classe,data_autuacao,situacao,magistrado,orgao_julgador,assuntos,polo_ativo,polo_passivo,mpf,perito,movimentacoes. Implementação completamente independente emcourts/trf6/(client.py,download.py,parse.py,schemas.py) — sem infra compartilhada com TRF3/TRF5 ou outros tribunais (mesma justificativa: tribunais podem trocar de sistema). Schema pydantic comextra='forbid'no Input. Samples HTML emtests/trf6/samples/cpopg/(form_initial, detail_normal, search_no_results, search_bad_captcha) capturados viatests/fixtures/capture/trf6.py. Cobertura: contrato offline com captcha solver mockado (5 testes incluindo retry após rejeição e fail após N tentativas), schema, integração (@pytest.mark.integration). - Parametros
download_pecas: bool = Falseediretorio: str | None = NoneemcpopgdeTRF1Scraper,TRF3ScrapereTRF5Scraper. Quandodownload_pecas=True, cada peca (documento juntado) e baixada viadocumentoSemLoginHTML.seampara<diretorio>/<cnj>/<id_processo_doc>.html(XHTML auto-contido, imagens embarcadas comodata:URLs) e o DataFrame ganha a colunapecascom a lista de caminhos por processo. DefaultFalse-- comportamento atual decpopg(so metadados + movimentacoes + lista de documentos) preservado. A flag vive nocpopgem vez de em um metodo separado porque os tokenscaque identificam cada peca estao amarrados a conversa Seam do detalhe -- pecas precisam ser baixadas na mesmarequests.Session, entao isolar num metodo a parte exigiria refazer o GET do detalhe so para obter tokens validos. Refs #272. cpopgemTRF1Scraper,TRF3ScrapereTRF5Scraperagora pagina tambem a tabela de documentos juntados (processoDocumentoGridTab), nao so a de movimentacoes. Antes, processos com mais de 15 documentos so devolviam a primeira pagina na colunadocumentos-- e comdownload_pecas=True, so as 15 primeiras pecas eram baixadas. O paginador novo (extract_docs_pagination+merge_docs_pages, mesma forma do par movs ja existente) detecta o slider Richfaces do panel de docs e busca as paginas adicionais via POST AJAX dentro da mesma sessao. Refs #272.- Nova excecao
juscraper.core.exceptions.BotChallengeBlockedError, levantada quando um portal devolve HTTP 403 com bodyAccess Denied(tipico de bot manager Akamai).cpopgemTRF1Scraper,TRF3ScrapereTRF5Scraperdetecta esse caso especifico e propaga a excecao em vez de engolir (como faz com erros transientes por item) -- um bloqueio Akamai e session-wide, nenhum CNJ do batch passaria. A mensagem orienta o usuario a aguardar alguns minutos ou trocar de IP (VPN, hotspot) e inclui aReference #...da Akamai para suporte. 403 semAccess Deniedno body não viraBotChallengeBlockedError: cai no fluxo de retry padrão doHTTPScraper(403 está emRETRYABLE_STATUSES) e, persistindo, esgotamax_retrieslevantandoRetryExhaustedError— ver a entrada correspondente em Changed. - Novo agregador
pdpj(PdpjScraper) para a API DATALAKE - Processos do PDPJ (api-processo-integracao.data-lake.pdpj.jus.br). Autenticacao via JWT comauth(token)e endpoints publicosexiste,cpopg,documentos,movimentos,partes,pesquisa,contaredownload_documents(texto e/ou binario). Validacao via pydantic comextra="forbid". - Metodos
listar_classes,listar_assuntoselistar_orgaosna familia eSAJ (TJAC, TJAL, TJAM, TJCE, TJMS, TJSP), maislistar_varasno TJSP, para descobrir os IDs internos aceitos pelos filtrosclasse/assunto/orgao_julgador/varadecjsg/cjpg. Cada metodo baixa a arvore de selecao do eSAJ (endpoint*TreeSelect.do, a arvore inteira num unico GET) e devolve umpd.DataFrameachatado com as colunasid,nome,id_pai,nivel,selecionavelecaminho— so as linhasselecionavel=Truevalem como filtro. O argumentograu("2"cjsg, default;"1"cjpg, so TJSP) seleciona o grau; pedir uma arvore inexistente no tribunal levantaValueError. Resolve a dor da issue #228 (era pouco intuitivo descobrir os codigos de classe/assunto). Refs #228. - Parametro
count_only=Trueemcjsg(TJAC, TJAL, TJAM, TJCE, TJMS, TJSP) ecjpg(TJSP). Quando passado, o metodo faz so a chamada inicial (POST + 1 GET no caso eSAJ; 1 GET no caso CJPG), extrai o total de resultados da primeira pagina e retornaintem vez dopd.DataFramecompleto. Util para estimativa de wall-clock antes de coletas longas — ~2s/resultado emcjsg/cjpg, entao 5000 hits = ~3h. Comauto_chunk=True(default) e janeladata_julgamento_*> 366 dias, itera as janelas disjuntas (iter_date_windows) e soma; e soma bruta (sem dedup porcd_acordao/id_processo), entao pode divergir ligeiramente delen(cjsg(...))quando ha acordaos republicados em janelas diferentes.paginase ignorado em count_only (emiteUserWarning);auto_chunk=False+ janela > 366d continua levantandoValueError. Refs #92.
Changed
pyarrowdeixa de ser dependência obrigatória e passa para o extradocs(pip install "juscraper[docs]"). A biblioteca nunca importapyarrow: as únicas chamadas dedf.to_parquet(...)no repositório estão em notebooks dedocs/. Com isso, instalar o juscraper deixa de impor uma faixa de versão depyarrowao ambiente de quem o instala — o que resolve duas dores de uma vez: o conflito de resolvedor comcudf-cu12no Google Colab, que motivava o tetopyarrow<20.0.0(refs #25), e o alerta depip-auditsobre PYSEC-2026-113 em projetos downstream, que o teto tornava inescapável ao travar a resolução empyarrow19.x (refs #342). Migração: quem chamavadf.to_parquet(...)sobre um DataFrame devolvido pelo juscraper contando com opyarrowque vinha de carona precisa passar a instalarpyarrow(oufastparquet) explicitamente. Refs #25, #342.- TRF1, TRF3 e TRF5:
cpopgpassa a usarcore.http.HTTPScraper— respostas 5xx/429 agora sao retentadas com backoff exponencial (respeitandoRetry-After) em vez de propagarrequests.HTTPErrorna primeira ocorrencia; esgotarmax_retrieslevantaRetryExhaustedError. O 403Access Deniedda Akamai continua virandoBotChallengeBlockedErrorimediatamente, sem retentativas (bloqueio e session-wide — retentar so queimaria tempo). Mesmas colunas e mesmo payload do request. Refs #281, #294. - JusBR
auth(token): agora validaexpexplicitamente ("verify_exp": Truenas options dojwt.decode). Antes, comverify_signature=False, o PyJWT desativavaverify_exppor padrao e o ramoexcept jwt.ExpiredSignatureErrorera dead code — tokens expirados passavam silenciosamente. Tokens comexpno passado agora levantamValueError("Token JWT expirado.")como ja documentado. Tokens semexpcontinuam aceitos (PyJWT so valida o claim quando ele existe). Refs #141. - JusBR
cpopg: linhas de fallback (CNJ Invalido/Nao encontrado na lista inicial/Erro ao obter ou parsear detalhes) agora populam tambem a colunaprocesso(canonico do projeto), alem deprocesso_pesquisado. Antes, happy-path emitiaprocessoe fallbacks emitiamprocesso_pesquisado— DataFrame misto tinhaNaNespalhado e o schemaOutputCPOPGJusBRdeclaravaprocesso_pesquisadocomo required, divergindo da realidade.OutputCPOPGJusBRagora declaraprocesso: str(alinhado comOutputCJSGBasecanonico);processo_pesquisadocontinua presente em rows de fallback como sinonimo historico viaextra="allow". Refs #141. - JusBR
download_documents: documentos com sohrefTextoou sohrefBinarioagora sao baixados parcialmente (texto ou binario sozinho), em vez de pulados. Documento e pulado apenas quando os dois hrefs faltam. Comportamento anterior faziacontinuequando qualquer UUID nao podia ser extraido — usuario perdia silenciosamente documentos parciais. Linha de saida temtexto=Noneou_raw_binary_api=Nonequando o href correspondente ausenta. Refs #141. TJAPScraper.cjsg/cjsg_downloadagora falham alto quando o backend Tucujuris devolve um envelope de erro (HTTP-200 comstatus == "ERRO"e sem a chavedados), em vez de retornar um DataFrame vazio silenciosamente. O caso mais comum hoje é"A verificação de segurança falhou": desde ~2026 o TJAP passou a exigir um CAPTCHA Cloudflare Turnstile na busca de jurisprudência, validado server-side, que o raspador (HTTP puro, sem navegador) não consegue produzir — entãocjsglevantaTJAPSecurityCheckError(subclasse decore.exceptions.HTTPSemanticError) com a explicação e a referência à issue. Não há solução pela API pública: a coleta de cjsg do TJAP está indisponível enquanto o Turnstile estiver ativo. Outros envelopesERROlevantamTJAPApiError. Busca com zero resultados ("Nenhum resultado encontrado.") continua devolvendo DataFrame vazio. Refs #279.- TJRN, TJRO e TJRR:
cjsgpassa a usarcore.http.HTTPScraper(backoff exponencial centralizado para 429/5xx, respeito aRetry-After) e os helpers decore.parse_utils(clean_html,coerce_date_columnsem TJRN/TJRO) +utils.cnj.format_cnj(strict=False). Mesmas colunas e mesmo payload do request. Duas pequenas mudancas de comportamento herdadas da infra centralizada: (a) esgotarmax_retriesem status retryable agora levantaRetryExhaustedErrorem vez de propagar a ultimarequests.HTTPError; (b) em TJRN/TJRO, resposta 200 com JSON corrompido deixa de fazer retry e passa a propagarValueErrorna primeira ocorrencia (o_fetch_pageantigo capturavaValueErrorno laco de retry; o_request_with_retryso retry-a 429/5xx). Refs #194, #202. - TJAP, TJRS e TJES:
cjsg(ecjpgno TJES) passam a usarcore.http.HTTPScrapere os helpers decore.parse_utils(clean_htmlem TJAP;coerce_date_columnsnos tres). Mesmas colunas e mesmo payload do request. Mudancas de comportamento herdadas da infra centralizada: (a) esgotarmax_retriesem 429/5xx agora levantaRetryExhaustedErrorem vez de propagar a ultimarequests.HTTPError(TJAP e TJES tinham retry local que propagavarequests.RequestExceptionaposMAX_RETRIES=3com backoff2 ** attempt; TJRS nao tinha retry algum e passa a termax_retries=3com mesmo backoff exponencial); (b) em TJAP e TJES, resposta 200 com JSON corrompido deixa de fazer retry e passa a propagarValueErrorna primeira ocorrencia (o_fetch_pageantigo capturavaValueErrorno laco de retry; o_request_with_retryso retry-a 429/5xx); (c) em TJRS, transitorios 429/5xx que antes propagavam imediatamente agora sao retentados atemax_retries=3. Breaking implicito emTJRSScraper.cjsg/cjsg_download: o parametrosession: requests.Session | None = Nonesai da assinatura publica (nao estava documentado no schema pydantic — descrito como "dependencia de runtime, nao da API" — nem coberto por nenhum teste). Quem precisava de uma session customizada (proxies, cookies) deve mutarscraper.sessionapos o__init__. Refs #194, #202. - TJBA, TJPB e TJMT:
cjsgpassa a usarcore.http.HTTPScraper(backoff exponencial centralizado para 429/5xx, respeito aRetry-After) em vez de duplicar retry local em cadadownload.py. TJBA e TJMT tambem migram o looppd.to_datetime(...).dt.dateparacore.parse_utils.coerce_date_columns. TJPB mantempd.to_datetimeexplicito comformat="%d/%m/%Y"porquecoerce_date_columnsnao expoeformat=(datasDD/MM/AAAAambiguas sem hint, mesmo trade-off do TJRR). TJMT mantem_strip_htmllocal porqueclean_htmlsubstitui tags por espaco em vez de remover, alterando o output observavel em 100% das amostras versionadas (<p>foo</p><b>bar</b>->"foo bar"em vez de"foobar"); a unificacao desse helper depende de uma varianteclean_html(separator="")nocore.parse_utils(fora do escopo desta migracao). Em TJBA, o parametrosessionfoi removido da assinatura publica decjsg_downloadecjsg— passarsession=agora cai emextra_forbiddenvia pydantic (TypeErrorclaro em vez de injecao silenciosa de session). Mesmas mudancas de comportamento herdadas da infra centralizada do batch anterior: esgotarmax_retriesem status retryable levantaRetryExhaustedError; resposta 200 com JSON corrompido deixa de fazer retry e propagaValueError/requests.HTTPErrorna primeira ocorrencia. Refs #194, #202. - TJPR e TJMG:
cjsgpassa a usarcore.http.HTTPScraper(backoff exponencial centralizado para 429/5xx, respeito aRetry-After) e os helpers decore.parse_utils(TJMG migra_cleanlocal paraclean_htmle o looppd.to_datetime(...).dt.dateparacoerce_date_columns(date_format="%d/%m/%Y"); TJPR migra o looppd.to_datetimeparacoerce_date_columns). Mesmas colunas e mesmo payload do request. As particularidades de cada um sao preservadas: TJPR continua hitando a home (get_initial_tokens->populate_session) uma vez por chamada para popularJSESSIONIDno cookie jar da session, mas ocookies={'JSESSIONID': ...}redundante saiu (a session ja carrega o cookie automaticamente); otjpr.url.cryptotoken, que era extraido mas nunca consumido, deixa de ser parseado. TJMG mantem_solve_captchalocal emdownload.pycom o laco semantico de 3 tentativas (retry de OCR errado, distinto do retry transport-level centralizado em_request_with_retry); a session continua sendo passada explicitamente para_solve_captchaporque o body DWR leJSESSIONIDdireto do cookie jar. Como efeito colateral da migracao para_request_with_retry, o GET do PNG do captcha e o POST DWR de validacao agora abortam imediatamente em 4xx nao-retryable (antes o status era ignorado e o conteudo seguia para a OCR/parse, o que produzia uma falha tardia mascarada como "OCR errado"); para 429/5xx ha retry transport-level antes de entrar no laco semantico de OCR. User-Agent Chrome custom de ambos os tribunais (ha UA gating do portal) continua via override de_configure_session. Em TJPR, otry/exceptrow-level decjsg_parse(que degrada graciosamente quando a busca da ementa-completa de uma decisao falha) agora tambem capturaRetryExhaustedErroralem derequests.RequestException— sem isso, um 5xx persistente em uma unica ementa derrubaria o DataFrame inteiro no novo regime. Mudancas de comportamento herdadas da infra centralizada do batch anterior: esgotarmax_retriesem status retryable levantaRetryExhaustedError. Refs #194, #202, #248. - TJTO, TJPI e TJSC:
cjsg(ecjpg/cjsg_ementaem TJTO) passa a usarcore.http.HTTPScraper, substituindo o retry exponencial local porself._request_with_retry. Mesmas colunas e mesmo payload do request. Mudanca de comportamento herdada da infra centralizada: esgotarmax_retriesem status retryable agora levantaRetryExhaustedErrorem vez de propagar a ultimarequests.RequestException; o escopo de retry passa de "qualquerRequestException" (incluindoConnectionError/Timeout) para 429/5xx, com respeito aRetry-Afternumerico. Em TJTO, alem disso, o parametro publicosession=foi removido dos metodoscjsg/cjsg_download/cjpg/cjpg_download— a sessao e gerenciada peloHTTPScraper; sobreposicao continua possivel via_configure_session. Refs #194, #202. - TJDFT, TJGO, TJRJ, TJPA e TJPE:
cjsgpassa a usarcore.http.HTTPScraper(backoff exponencial centralizado para 429/5xx, respeito aRetry-After). TJDFT e TJPA tambem migram o looppd.to_datetime(...).dt.dateparacore.parse_utils.coerce_date_columns(TJPA preserva oformat="%Y-%m-%d"original viadate_format=; TJDFT seguia semformat=e mantem o mesmo comportamento). TJGO mantem o_cleanprivado em vez de migrar paracore.parse_utils.clean_htmlporque o backend do Projudi envolve trechos de texto em tags inline (<b>,<i>) eclean_htmlsubstituiria as tags por espaco — inserindo espaco antes de pontuacao (<b>dano moral</b>,->"dano moral ,"em vez de"dano moral,"); mesmo trade-off ja documentado em TJMT no batch 3 e aguardando uma varianteclean_html(separator="")nocore.parse_utils(fora do escopo desta migracao). Mesmas mudancas de comportamento herdadas da infra centralizada do batch anterior: esgotarmax_retriesem status retryable levantaRetryExhaustedErrorem vez de propagar a ultimarequests.HTTPError. Em TJPA, especificamente, o retry exponencial dentro de_fetch_pagefoi removido — agora so 429/5xx fazem retry (antes qualquerRequestException/ValueErrorretentava). Breaking change emTJPEScraper.cjsg/cjsg_download: o parametrosession: requests.Session | None = Nonefoi removido; o override de session passa peloHTTPScraper._request_with_retry(consumido viaself.session). Refs #194, #202. TJSPScraper.cjsgaceitapesquisa=""por default — antes o argumento era obrigatorio etjsp.cjsg(classe="...", assunto="...")levantavaTypeError. Agora o usuario pode buscar so por filtros (sem termo textual), igualando o comportamento decjpg. Refs #229.EsajSearchScraper(base de TJAC/TJAL/TJAM/TJCE/TJMS/TJSP) herda dejuscraper.core.http.HTTPScraperem vez deBaseScraper. A construção deself.session, o User-Agent padrão e o hook_configure_sessionpassam a vir da base compartilhada — o override do TJCE para o adapter TLS continua válido sem alteração. Os GETs paginados em_esaj/download.py::download_cjsg_pagesagora delegam ao retry centralizadoHTTPScraper._request_with_retry: o escopo de retry passa a ser 429/5xx com backoff exponencial e suporte aRetry-After(antes: qualquerrequests.RequestException, incluindoConnectionError/Timeout, com backoff linear modulado porsleep_time). Quando esgota as tentativas, a exceção passa a serjuscraper.core.exceptions.RetryExhaustedErrorem vez darequests.RequestExceptionoriginal — usuários que capturavam a exceção antiga precisam atualizar. Refs #203, #194, #201.ComunicaCNJScraper,JusbrScrapereDatajudScraperagora herdam decore.http.HTTPScraper(refs #204, Fase 3 de #194). A session/headers de cada um passa pelo hook compartilhado_configure_session(User-Agent, Origin/Referer no ComunicaCNJ; UA Chrome no JusBR; UA default doHTTPScraperno Datajud), e a validacaosession=(TypeError quando nao forrequests.Session) passa a ser garantida via heranca (cumpre #185 sem duplicar codigo).ComunicaCNJganha resiliencia a 429/5xx viaself._request_with_retry(antes nao tinha retry algum);JusBRsubstitui orequest_with_retryinterno dodownload.pypelo mesmo, e osfetch_*agora recebem umrequest_fn(tipicamenteself._request_with_retry) em vez dasessioncrua — o contrato de "erro -> None" e preservado capturandoRetryExhaustedError/RequestException.DatajudScraperherdaHTTPScraperapenas para session/headers e cumprimento de #185: a funcaocall_datajud_apicontinua intacta porque o retry especifico (504/Timeout -> reduzsizeporFALLBACK_DIVISOR, 1 retry) e incompativel com o backoff exponencial generico, e a refatoracao virou explicitamente fora de escopo no proprio guarda-chuva #194. Breaking change implicito emComunicaCNJScraper.listar_comunicacoes: quando o servidor devolve 429/5xx persistente, a excecao propagada passa a serjuscraper.core.exceptions.RetryExhaustedError(apos esgotarmax_retries) em vez derequests.HTTPError. Callers que faziamexcept requests.HTTPErrorpara tratar indisponibilidade do CNJ devem passar a capturarRetryExhaustedError(ou ambas). 4xx nao-retryable continua propagandorequests.HTTPErrorviaraise_for_status()como antes. EmJusBRa mudanca fica encapsulada nosfetch_*(retornamNoneem qualquer falha), entao o contrato publico decpopg/download_documentsnao muda.- Filtros
classe,assuntoeorgao_julgadoremcjsg(TJAC, TJAL, TJAM, TJCE, TJMS, TJSP) e emcjpg(TJSP) passam a aceitarint,stroulist[int | str]. Antes so aceitavamstr.comarcaemcjsgaceitaintoustr(single-value, backendcdComarca). Listas viram CSV automaticamente; valoresintviramstr. A chamadatjsp.cjsg(classe=[417], assunto=[3607, 5885])deixa de levantarValidationError. Refs #232. TJSPScraper.cjpgadota o nome canonico singular para IDs de filtro:classe/assunto/varasubstituemclasses/assuntos/varas. Os nomes plurais continuam funcionando como alias deprecados (comDeprecationWarning) por pelo menos um minor release. Passar plural e singular simultaneamente (cjpg(classe=12728, classes=[5885])) levantaValueError. Refs #232.TJBAScraper.cjsg/cjsg_downloadeDatajudScraper.listar_processos/contar_processosadotam o nome canonico singular:classe(TJBA) substituiclasses;assunto(Datajud) substituiassuntos. Os plurais seguem funcionando como alias deprecados (DeprecationWarning) por pelo menos um minor release; plural + singular juntos ->ValueError. Refs #232.TJPEScraper.cjsg/cjsg_downloadpassam a validar filtros viaInputCJSGTJPE(extra="forbid"). Kwargs desconhecidos passam a levantarTypeError(com a mensagem canonica deraise_on_extra_kwargs) em vez de serem silenciosamente descartados pelo**kwargs. Aliases deprecados (query,termo,classe_cnj,assunto_cnj,data_inicio/data_fim,data_julgamento_de/_ate) continuam aceitos comDeprecationWarning. Refs #93, #197.TJRRScraper.cjsg/cjsg_download: o parametrorelatormuda destr | Noneparalist[str] | Nonee passa a aceitar lista de nomes regimentais (ex.:relator=["ALMIRO PADILHA", "ERICK LINHARES"]). O scraper baixa o form GET inicial, extrai o mapanomeRegimental -> bean Java opaco(menuinicial:relatorList) e injeta os valores resolvidos no body. Match insensivel a caixa e diacritico —"cristovao suter","Cristóvão Suter"e"CRISTÓVÃO SUTER"resolvem para o mesmo magistrado; nomes desconhecidos levantamValueErrorlistando os disponiveis na forma canonica (UPPERCASE com acento). Mudanca breaking de tipo, mas o caminho anterior era no-op silencioso (o argumentostrera descartado pelo backend) — nenhum codigo de usuario em producao dependia de uma string especifica ser respeitada. Refs #158.
Deprecated
TJSPScraper.cjpg: parametrosclasses/assuntos/varasemitemDeprecationWarning. Use os singulares canonicosclasse/assunto/vara. Refs #232.TJBAScraper.cjsg/cjsg_download: parametroclassesemiteDeprecationWarning. Useclasse. Refs #232.DatajudScraper.listar_processos/contar_processos: parametroassuntosemiteDeprecationWarning. Useassunto. Refs #232.
Fixed
JusbrScraper.download_documentspassa a tentar a próxima fonte documentada de metadados quandodadosBasicos.documentosoudocumentoscontêm um container malformado, aplicamax_docs_per_processao processo inteiro mesmo quando o mesmonumeroProcessoaparece em várias linhas e impede que metadados externos sobrescrevamnumero_processo,textoe os campos brutos calculados pelo scraper. O método também passa a rejeitarbase_dfque não sejapd.DataFrameemax_docs_per_processnegativo antes de iterar ou fazer chamadas de rede. Refs #312.- O contrato compartilhado de
paginasemcjsgdos 25 tribunais estaduais,cjpgde TJSP/TJES/TJTO,DatajudScraper.listar_processoseComunicaCNJScraper.listar_comunicacoespassa a rejeitar seleções vazias, inteiros/páginas menores ou iguais a zero erangedescendente antes de qualquer chamada de rede. Esses valores contradiziam a paginação 1-based documentada; no DataJud, em particular,paginas=[]era convertido emNonee baixava todas as páginas em vez de falhar. DatajudScraper.listar_processospassa a respeitar a posição física solicitada quandopaginascomeça depois de 1 ou usarangecom passo. O cursorsearch_afteré sequencial e precisa partir da primeira página; antes,paginas=range(3, 6)enviava a primeira requisição sem cursor, mas rotulava e devolvia essa resposta como página 3, retornando na prática as páginas 1–3. Agora o scraper percorre o prefixo necessário, descarta as páginas não solicitadas e devolve apenas 3–5; listas esparsas continuam virando o intervalo contínuo entre mínimo e máximo, conforme o contrato público. Refs #315.TJRRScraper.cjsg/cjsg_download: a paginação volta a avançar —cjsg("dano moral", paginas=range(1, 3))traz processos novos na página 2, em vez de repetir a página 1. O POST AJAX de paginação enviava um payload mínimo (só os parâmetros do datatable + ViewState) que o backend PrimeFaces ignorava, devolvendo sempre a primeira página. Agora o scraper replica o que o navegador envia: ecoa o contexto completo do formulário de resultados (incluindo o termo de busca), dispara o evento de comportamentopagedo PrimeFaces, manda as flags de feature do datatable e o headerFaces-Request: partial/ajax. Verificado ao vivo. Apenas a tabela de acórdãos é paginada; decisões monocráticas (segunda tabela, com paginador próprio) continuam vindo só da primeira página — paginação dessa tabela é follow-up. Refs #287.- TRF1, TRF3 e TRF5 (
cpopg): as movimentações das páginas 2 em diante voltam a vir com acentuação correta. O fragmento AJAX (Richfaces) que pagina a tabela de movimentações é servido em UTF-8, mas o scraper o decodificava como latin-1 — o mesmo encoding da página de detalhe inicial, que de fato é latin-1. O resultado era double-encoding em toda movimentação paginada:"petição"virava"petição","comunicação"virava"comunicação". Processos com até 15 movimentações (uma página) não eram afetados; só os paginados. Verificado ao vivo no TRF1 (processo com 55 movs em 4 páginas): zero mojibake após o fix; o TRF3 não pôde ser validado ao vivo por estar bloqueado por Akamai (#292), mas o sample capturado já está em UTF-8 e seu código é idêntico ao de TRF1/TRF5. A página de detalhe inicial segue em latin-1. TJRJScraper.cjsg/cjsg_downloadchamados semano_inicio/ano_fimvoltam a funcionar. O backend ASP.NET do TJRJ passou a exigir os camposcmbAnoInicio/cmbAnoFimnao-vazios no POST do formulario — enviar vazio (o default quando o usuario nao filtra por ano) fazia o tribunal responderHTTP 500ja na submissao, abortando a coleta comRetryExhaustedError. Agora o scraper replica o padrao do site, preenchendo ambos com o ano corrente (a opcao mais nova do dropdown de anos). Buscas com ano explicito (cjsg(..., ano_inicio=2024, ano_fim=2024)) seguem inalteradas. Refs #278.- TJES (
cjsg/cjpg): respostas HTTP 200 com corpo vazio/nao-JSON do backend Solr (intermitentes) deixavam de quebrar a coleta com umjson.JSONDecodeErroropaco (Expecting value: line 1 column 1). Agora sao tratadas como falha transitoria — retentadas com backoff — e, persistindo, levantamInvalidJSONResponseErrorcom contexto da requisicao (URL, status, content-type). A validacao opcional de corpo JSON (expect_json) fica disponivel a todos os scrapers via_request_with_retry. Refs #275. TJPRScraper.cjsgchamado sem o parametropaginasvoltava apenas a primeira pagina de resultados. A extracao do total de paginas (extract_total_pages) buscava marcadores ("Pagina X de Y", linkspageNumber=N) que nao existem no markup atual do portal TJPR, falhando silenciosamente e assumindo 1 pagina. Agora le o total a partir do link "Ultima Pagina" do paginador —cjsg("dano moral")sempaginasbaixa todas as paginas, coerente com a convencaopaginas=None. Refs #262.core.http.HTTPScraper._request_with_retrypassa a retentar HTTP 403 com backoff exponencial, alinhado com 429/5xx. No fluxo eSAJcjsg(TJAC/TJAL/TJAM/TJCE/TJMS/TJSP), o POST inicial (resultadoCompleta.do) e o GET da primeira pagina (trocaDePagina.do?pagina=1, usado tambem para descobrir o total de paginas) tambem passam a usar o retry centralizado — antes ficavam fora, entao uma unica falha transitoria logo no comeco abortava a coleta. Motivado pelo TJSP eSAJ (#233): o WAF do eSAJ retorna 403 intermitente em raspagens longas mesmo sem o cliente bater no rate limit classico (429), e o refactor #203 tinha removido qualquer retry de 403, fazendo com que uma unica falha transitoria abortasse a coleta de centenas de paginas. 403 persistente (ex.: bloqueio por IP) ainda esgotamax_retriese propagaRetryExhaustedError(status_code=403)— o WAF nao distingue transitorio e permanente pelo status code, entao o comportamento "correto" no caso permanente e o usuario receber a falha apos algumas tentativas. Mesmo backoff (base_backoff ** attempt= 2s, 4s por default — após as tentativas 1 e 2; a 3ª já levantaRetryExhaustedErrorsem dormir) e mesmo respeito aRetry-After. Em queries longas que envolvem bloqueio por IP institucional, a recomendacao continua sendo trocar para uma rede com IP residencial (ou aumentarsleep_timeno construtor do scraper).- TJPE
cjsge TRF1/TRF3/TRF5/TRF6cpopgvoltam a funcionar em installs default (pip install juscraper). Antes os parsers exigiamlxml(BeautifulSoup(html, "lxml")), que nao esta declarado empyproject.toml::dependencies— qualquer usuario semlxmlno ambiente recebiabs4.exceptions.FeatureNotFound. Trocado parahtml.parser(stdlib), padrao usado nos 22+ outros raspadores HTML do repo. Comportamento validado contra os samples versionados via contratos offline. - Endpoints com pydantic wired (TJSP
cjsg/cjpg; familia eSAJcjsgem TJAC/TJAL/TJAM/TJCE/TJMS;cjsgem TJDFT/TJES/TJBA/TJMT/TJAP/TJRS/TJPB/TJTO/TJPR/TJGO/TJRR/TJMG/TJRN/TJPA/TJRO/TJSC/TJPI;cjpgem TJES/TJTO) passam a autopreencher datas parciais. Quando o usuario informa apenasdata_*_inicio,data_*_fimvira a data atual; quando informa apenasdata_*_fim,data_*_iniciovira01/01/1990(e o auto-chunk divide a janela em chunks de 366 dias). Antes, o backend recebia uma data vazia em um dos lados e podia retornar resultado degenerado — nocjpgdo TJSP, em particular, o paginador iterava sobre dezenas de milhares de paginas.UserWarninge emitido sinalizando o auto-fill e sugerindo passar a data explicitamente para restringir a janela. TJRRScraper.cjsg(relator=...)voltou a funcionar. O backend TJRR migrou de um camporelatorde texto livre para umSelectManyCheckbox(menuinicial:relatorList) cujos values sao beans Java serializados, e o scraper estava descartando o argumento silenciosamente (anotado# noqa: ARG001). Agoracjsg(relator=["ALMIRO PADILHA"])parseia o form GET inicial, resolve o nome regimental para o bean correspondente e injeta a lista no body — o filtro chega ao backend e a busca e efetivamente restringida ao(s) relator(es). Refs #158.
Security
- Hardening de logging nos agregadores JusBR e PDPJ: logs em modo verbose/DEBUG deixam de emitir credenciais. O header
Authorization(e demais headers sensíveis) passa a ser redigido como[REDACTED]antes de ir para o log no download do JusBR; o dump claim-a-claim do JWT (verbose > 1) vira apenas a contagem de claims; e osubdo JWT deixa de ser logado noauthdo PDPJ. Redação centralizada no novo helperjuscraper.utils.logging_cfg.redact_headers, também adotado pelo Datajud. Refs #270. - TJMG (
cjsg): a imagem do CAPTCHA passa a ser gravada comtempfile.NamedTemporaryFile(criacao atomica, nome imprevisivel) em vez de um caminho previsivel em/tmp(tjmg_captcha_<timestamp>.png). O caminho previsivel num diretorio compartilhado permitia, em host multiusuario, que um atacante local pre-criasse um symlink no caminho esperado e causasse overwrite de arquivo via TOCTOU (CWE-377). Severidade baixa — exige atacante local no mesmo host; nulo no uso single-user tipico, plausivel em CI/containers multi-tenant. Refs #271. - Downloaders do TJSP (
cpopgvia API,cposgvia HTML eacordao) passam a validar os identificadores vindos do tribunal (cdProcesso,processo.codigo,cdAcordao) antes de construir o caminho de escrita. Um identificador com separador de path (/,\) ou segmento..levantaValueErrorem vez de gravar o arquivo fora do diretorio de download (path traversal / escrita arbitraria de arquivo). Emcpopg/cposgo processo afetado e logado e pulado (os callers ja capturamValueError), e a coleta dos demais continua. Refs #269.