Update stj.py
Browse files
stj.py
CHANGED
|
@@ -1,7 +1,6 @@
|
|
| 1 |
import asyncio
|
| 2 |
import glob
|
| 3 |
import os
|
| 4 |
-
import subprocess
|
| 5 |
import time
|
| 6 |
from pathlib import Path
|
| 7 |
|
|
@@ -14,23 +13,21 @@ URL_TESAURO = "https://scon.stj.jus.br/SCON/thesaurus/"
|
|
| 14 |
SCREENSHOT_FILE = "stj_tesauro_debug.png"
|
| 15 |
ERRO_FILE = "stj_erro.png"
|
| 16 |
|
|
|
|
| 17 |
BROWSER_ARGS = [
|
| 18 |
"--disable-background-timer-throttling",
|
| 19 |
"--disable-backgrounding-occluded-windows",
|
| 20 |
"--disable-renderer-backgrounding",
|
| 21 |
"--disable-component-update",
|
| 22 |
"--no-sandbox",
|
| 23 |
-
"--disable-setuid-sandbox",
|
| 24 |
"--disable-dev-shm-usage",
|
| 25 |
"--disable-gpu",
|
| 26 |
"--no-zygote",
|
| 27 |
"--lang=pt-BR",
|
| 28 |
"--window-size=1366,900",
|
| 29 |
-
# Reduz o "headless fingerprint" em ambientes container
|
| 30 |
"--disable-blink-features=AutomationControlled",
|
| 31 |
]
|
| 32 |
|
| 33 |
-
|
| 34 |
# ============================================================
|
| 35 |
# UTILITÁRIOS
|
| 36 |
# ============================================================
|
|
@@ -42,26 +39,9 @@ def safe_unlink(path: str) -> None:
|
|
| 42 |
except OSError:
|
| 43 |
pass
|
| 44 |
|
| 45 |
-
|
| 46 |
-
def start_xvfb() -> None:
|
| 47 |
-
"""Sobe Xvfb no display :99 se ainda não estiver rodando."""
|
| 48 |
-
try:
|
| 49 |
-
subprocess.run(["pgrep", "-f", "Xvfb"], check=True, capture_output=True)
|
| 50 |
-
print("🖥️ Xvfb já está rodando.")
|
| 51 |
-
except subprocess.CalledProcessError:
|
| 52 |
-
print("🖥️ Iniciando Xvfb...")
|
| 53 |
-
subprocess.Popen(
|
| 54 |
-
["Xvfb", ":99", "-screen", "0", "1366x900x24"],
|
| 55 |
-
stdout=subprocess.DEVNULL,
|
| 56 |
-
stderr=subprocess.DEVNULL,
|
| 57 |
-
)
|
| 58 |
-
time.sleep(2)
|
| 59 |
-
print("🖥️ Xvfb iniciado no display :99")
|
| 60 |
-
|
| 61 |
-
|
| 62 |
def find_playwright_chrome() -> str:
|
| 63 |
-
"""
|
| 64 |
-
print("🔎 Procurando Chrome
|
| 65 |
patterns = [
|
| 66 |
"/ms-playwright/chromium-*/chrome-linux/chrome",
|
| 67 |
"/ms-playwright/chromium-*/chrome-linux64/chrome",
|
|
@@ -72,51 +52,24 @@ def find_playwright_chrome() -> str:
|
|
| 72 |
path = sorted(matches)[-1]
|
| 73 |
print(f"✅ Chrome encontrado em: {path}")
|
| 74 |
return path
|
| 75 |
-
|
| 76 |
-
|
| 77 |
-
if os.path.isdir(home_cache):
|
| 78 |
-
for pattern in (
|
| 79 |
-
f"{home_cache}/chromium-*/chrome-linux/chrome",
|
| 80 |
-
f"{home_cache}/chromium-*/chrome-linux64/chrome",
|
| 81 |
-
):
|
| 82 |
-
matches = glob.glob(pattern)
|
| 83 |
-
if matches:
|
| 84 |
-
path = sorted(matches)[-1]
|
| 85 |
-
print(f"✅ Chrome encontrado no cache: {path}")
|
| 86 |
-
return path
|
| 87 |
-
|
| 88 |
-
# Fallback: instaladores "normais"
|
| 89 |
-
for alt in (
|
| 90 |
-
"/usr/bin/google-chrome-stable",
|
| 91 |
-
"/usr/bin/google-chrome",
|
| 92 |
-
"/usr/bin/chromium",
|
| 93 |
-
):
|
| 94 |
if os.path.isfile(alt):
|
| 95 |
print(f"✅ Chrome encontrado em: {alt}")
|
| 96 |
return alt
|
| 97 |
-
|
| 98 |
-
raise FileNotFoundError("Nenhum binário do Chrome foi encontrado no container.")
|
| 99 |
-
|
| 100 |
|
| 101 |
def is_page_not_found(body_text: str) -> bool:
|
| 102 |
-
markers = ["Página não encontrada", "oops!"
|
| 103 |
return any(m.lower() in body_text.lower() for m in markers)
|
| 104 |
|
| 105 |
-
|
| 106 |
def is_cloudflare_challenge(body_text: str, title: str) -> bool:
|
| 107 |
-
markers = [
|
| 108 |
-
"just a moment",
|
| 109 |
-
"verify you are human",
|
| 110 |
-
"checking your browser",
|
| 111 |
-
"cloudflare",
|
| 112 |
-
"verificação de segurança",
|
| 113 |
-
]
|
| 114 |
haystack = f"{title}\n{body_text}".lower()
|
| 115 |
return any(m in haystack for m in markers)
|
| 116 |
|
| 117 |
-
|
| 118 |
async def get_page_state(tab) -> tuple[str, str]:
|
| 119 |
-
"""Retorna (body_text, title)
|
| 120 |
try:
|
| 121 |
body = await tab.evaluate("document.body ? document.body.innerText : ''")
|
| 122 |
except Exception:
|
|
@@ -127,122 +80,80 @@ async def get_page_state(tab) -> tuple[str, str]:
|
|
| 127 |
title = ""
|
| 128 |
return body or "", title or ""
|
| 129 |
|
| 130 |
-
|
| 131 |
-
# ============================================================
|
| 132 |
-
# BYPASS CLOUDFLARE — ETAPA 1: verify_cf nativo (OpenCV)
|
| 133 |
-
# ============================================================
|
| 134 |
-
async def try_native_verify_cf(tab, timeout: int = 45) -> bool:
|
| 135 |
-
"""
|
| 136 |
-
Usa o verify_cf embutido do nodriver (template matching com OpenCV).
|
| 137 |
-
Retorna True se a página deixou de mostrar challenge.
|
| 138 |
-
"""
|
| 139 |
-
print("🛡️ [1/2] verify_cf nativo (OpenCV)...")
|
| 140 |
-
start = time.time()
|
| 141 |
-
while time.time() - start < timeout:
|
| 142 |
-
body, title = await get_page_state(tab)
|
| 143 |
-
if not is_cloudflare_challenge(body, title):
|
| 144 |
-
print("✅ Sem challenge do Cloudflare (verify_cf nativo OK).")
|
| 145 |
-
return True
|
| 146 |
-
|
| 147 |
-
try:
|
| 148 |
-
result = await tab.verify_cf()
|
| 149 |
-
if result:
|
| 150 |
-
print(f"🖱️ verify_cf reportou clique em: {result}")
|
| 151 |
-
except Exception as e:
|
| 152 |
-
# Erro clássico "cannot unpack non-iterable NoneType" quando não há checkbox
|
| 153 |
-
print(f"⚠️ verify_cf: {e.__class__.__name__} (ignorado)")
|
| 154 |
-
|
| 155 |
-
await tab.sleep(2)
|
| 156 |
-
|
| 157 |
-
body, title = await get_page_state(tab)
|
| 158 |
-
if not is_cloudflare_challenge(body, title):
|
| 159 |
-
return True
|
| 160 |
-
print("❌ verify_cf nativo não resolveu dentro do tempo.")
|
| 161 |
-
return False
|
| 162 |
-
|
| 163 |
-
|
| 164 |
-
# ============================================================
|
| 165 |
-
# BYPASS CLOUDFLARE — ETAPA 2: CFVerify (fallback)
|
| 166 |
-
# ============================================================
|
| 167 |
-
async def try_cf_verify(tab, max_retries: int = 15) -> bool:
|
| 168 |
-
"""Fallback usando a biblioteca nodriver-cf-verify (se instalada)."""
|
| 169 |
-
print("🛡️ [2/2] CFVerify (fallback)...")
|
| 170 |
-
try:
|
| 171 |
-
from nodriver_cf_verify import CFVerify
|
| 172 |
-
except ImportError:
|
| 173 |
-
print("⚠️ nodriver-cf-verify não instalado — pulando fallback.")
|
| 174 |
-
return False
|
| 175 |
-
|
| 176 |
-
try:
|
| 177 |
-
verifier = CFVerify(_browser_tab=tab, _debug=True)
|
| 178 |
-
success = await verifier.verify(
|
| 179 |
-
_max_retries=max_retries,
|
| 180 |
-
_interval_between_retries=1,
|
| 181 |
-
_reload_page_after_n_retries=5,
|
| 182 |
-
)
|
| 183 |
-
return bool(success)
|
| 184 |
-
except Exception as e:
|
| 185 |
-
print(f"⚠️ CFVerify lançou exceção: {e}")
|
| 186 |
-
return False
|
| 187 |
-
|
| 188 |
-
|
| 189 |
# ============================================================
|
| 190 |
-
#
|
| 191 |
# ============================================================
|
| 192 |
async def resolver_cloudflare(tab) -> bool:
|
| 193 |
-
"""Tenta
|
|
|
|
| 194 |
body, title = await get_page_state(tab)
|
| 195 |
if not is_cloudflare_challenge(body, title):
|
| 196 |
print("🟢 Página já está limpa (sem Cloudflare).")
|
| 197 |
return True
|
| 198 |
|
| 199 |
-
|
| 200 |
-
|
| 201 |
-
|
| 202 |
-
|
| 203 |
-
|
| 204 |
-
|
| 205 |
-
|
| 206 |
-
await tab.sleep(
|
| 207 |
-
|
| 208 |
-
|
| 209 |
-
|
| 210 |
-
|
| 211 |
-
|
| 212 |
|
|
|
|
| 213 |
return False
|
| 214 |
|
| 215 |
-
|
|
|
|
|
|
|
| 216 |
async def main() -> None:
|
| 217 |
-
print("🚀 Iniciando nodriver
|
| 218 |
-
|
| 219 |
-
# 1) Display virtual
|
| 220 |
-
start_xvfb()
|
| 221 |
-
os.environ["DISPLAY"] = ":99"
|
| 222 |
-
|
| 223 |
-
# 2) Limpeza de artefatos
|
| 224 |
safe_unlink(SCREENSHOT_FILE)
|
| 225 |
safe_unlink(ERRO_FILE)
|
| 226 |
|
| 227 |
-
|
| 228 |
try:
|
| 229 |
-
# 3) Chrome dinâmico
|
| 230 |
chrome_path = find_playwright_chrome()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 231 |
|
| 232 |
-
|
| 233 |
-
browser = await uc.start(
|
| 234 |
-
headless=False,
|
| 235 |
-
no_sandbox=True,
|
| 236 |
-
browser_executable_path=chrome_path,
|
| 237 |
-
browser_args=BROWSER_ARGS,
|
| 238 |
-
)
|
| 239 |
tab = await browser.get(URL_TESAURO)
|
| 240 |
await tab.sleep(3)
|
| 241 |
|
| 242 |
-
#
|
| 243 |
-
|
| 244 |
-
if not ok:
|
| 245 |
-
print("❌ Não foi possível resolver o Cloudflare.")
|
| 246 |
body, title = await get_page_state(tab)
|
| 247 |
print(f" Título atual: {title!r}")
|
| 248 |
try:
|
|
@@ -252,13 +163,12 @@ async def main() -> None:
|
|
| 252 |
pass
|
| 253 |
return
|
| 254 |
|
| 255 |
-
#
|
| 256 |
await tab.sleep(1)
|
| 257 |
body_text, title = await get_page_state(tab)
|
| 258 |
|
| 259 |
if is_page_not_found(body_text):
|
| 260 |
print("❌ A página retornada é um 404 do STJ.")
|
| 261 |
-
print(f" URL: {URL_TESAURO}")
|
| 262 |
return
|
| 263 |
|
| 264 |
if "tesauro" not in body_text.lower() and "vocabulário" not in body_text.lower():
|
|
@@ -266,7 +176,7 @@ async def main() -> None:
|
|
| 266 |
else:
|
| 267 |
print("✅ Página do Tesauro carregada com sucesso!")
|
| 268 |
|
| 269 |
-
#
|
| 270 |
try:
|
| 271 |
await tab.save_screenshot(SCREENSHOT_FILE)
|
| 272 |
print(f"📸 Screenshot salvo em: {os.path.abspath(SCREENSHOT_FILE)}")
|
|
@@ -276,12 +186,11 @@ async def main() -> None:
|
|
| 276 |
print("═" * 50)
|
| 277 |
|
| 278 |
except Exception as e:
|
| 279 |
-
print(f"💥 Erro inesperado: {e}")
|
| 280 |
finally:
|
| 281 |
if browser:
|
| 282 |
browser.stop()
|
| 283 |
print("🛑 Navegador encerrado.")
|
| 284 |
|
| 285 |
-
|
| 286 |
if __name__ == "__main__":
|
| 287 |
uc.loop().run_until_complete(main())
|
|
|
|
| 1 |
import asyncio
|
| 2 |
import glob
|
| 3 |
import os
|
|
|
|
| 4 |
import time
|
| 5 |
from pathlib import Path
|
| 6 |
|
|
|
|
| 13 |
SCREENSHOT_FILE = "stj_tesauro_debug.png"
|
| 14 |
ERRO_FILE = "stj_erro.png"
|
| 15 |
|
| 16 |
+
# Argumentos para ambiente de container. O modo headless já é ativado via parâmetro.
|
| 17 |
BROWSER_ARGS = [
|
| 18 |
"--disable-background-timer-throttling",
|
| 19 |
"--disable-backgrounding-occluded-windows",
|
| 20 |
"--disable-renderer-backgrounding",
|
| 21 |
"--disable-component-update",
|
| 22 |
"--no-sandbox",
|
|
|
|
| 23 |
"--disable-dev-shm-usage",
|
| 24 |
"--disable-gpu",
|
| 25 |
"--no-zygote",
|
| 26 |
"--lang=pt-BR",
|
| 27 |
"--window-size=1366,900",
|
|
|
|
| 28 |
"--disable-blink-features=AutomationControlled",
|
| 29 |
]
|
| 30 |
|
|
|
|
| 31 |
# ============================================================
|
| 32 |
# UTILITÁRIOS
|
| 33 |
# ============================================================
|
|
|
|
| 39 |
except OSError:
|
| 40 |
pass
|
| 41 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 42 |
def find_playwright_chrome() -> str:
|
| 43 |
+
"""Localiza o binário do Chrome na imagem do Playwright ou no cache."""
|
| 44 |
+
print("🔎 Procurando Chrome...")
|
| 45 |
patterns = [
|
| 46 |
"/ms-playwright/chromium-*/chrome-linux/chrome",
|
| 47 |
"/ms-playwright/chromium-*/chrome-linux64/chrome",
|
|
|
|
| 52 |
path = sorted(matches)[-1]
|
| 53 |
print(f"✅ Chrome encontrado em: {path}")
|
| 54 |
return path
|
| 55 |
+
# Fallback para instalações padrão
|
| 56 |
+
for alt in ("/usr/bin/google-chrome-stable", "/usr/bin/google-chrome", "/usr/bin/chromium"):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 57 |
if os.path.isfile(alt):
|
| 58 |
print(f"✅ Chrome encontrado em: {alt}")
|
| 59 |
return alt
|
| 60 |
+
raise FileNotFoundError("Nenhum binário do Chrome foi encontrado.")
|
|
|
|
|
|
|
| 61 |
|
| 62 |
def is_page_not_found(body_text: str) -> bool:
|
| 63 |
+
markers = ["Página não encontrada", "oops!"]
|
| 64 |
return any(m.lower() in body_text.lower() for m in markers)
|
| 65 |
|
|
|
|
| 66 |
def is_cloudflare_challenge(body_text: str, title: str) -> bool:
|
| 67 |
+
markers = ["just a moment", "verify you are human", "checking your browser", "cloudflare"]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 68 |
haystack = f"{title}\n{body_text}".lower()
|
| 69 |
return any(m in haystack for m in markers)
|
| 70 |
|
|
|
|
| 71 |
async def get_page_state(tab) -> tuple[str, str]:
|
| 72 |
+
"""Retorna (body_text, title) de forma segura."""
|
| 73 |
try:
|
| 74 |
body = await tab.evaluate("document.body ? document.body.innerText : ''")
|
| 75 |
except Exception:
|
|
|
|
| 80 |
title = ""
|
| 81 |
return body or "", title or ""
|
| 82 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 83 |
# ============================================================
|
| 84 |
+
# BYPASS CLOUDFLARE (simplificado)
|
| 85 |
# ============================================================
|
| 86 |
async def resolver_cloudflare(tab) -> bool:
|
| 87 |
+
"""Tenta resolver o desafio do Cloudflare com verify_cf nativo."""
|
| 88 |
+
print("🛡️ Tentando resolver Cloudflare (verify_cf nativo)...")
|
| 89 |
body, title = await get_page_state(tab)
|
| 90 |
if not is_cloudflare_challenge(body, title):
|
| 91 |
print("🟢 Página já está limpa (sem Cloudflare).")
|
| 92 |
return True
|
| 93 |
|
| 94 |
+
# Tenta o verify_cf nativo por algumas vezes
|
| 95 |
+
for i in range(10):
|
| 96 |
+
try:
|
| 97 |
+
# O erro NoneType é esperado quando não há checkbox; é ignorado.
|
| 98 |
+
await tab.verify_cf()
|
| 99 |
+
except Exception:
|
| 100 |
+
pass
|
| 101 |
+
await tab.sleep(3)
|
| 102 |
+
body, title = await get_page_state(tab)
|
| 103 |
+
if not is_cloudflare_challenge(body, title):
|
| 104 |
+
print("✅ Challenge do Cloudflare resolvido!")
|
| 105 |
+
return True
|
| 106 |
+
print(f"🔄 Tentativa {i+1}/10...")
|
| 107 |
|
| 108 |
+
print("❌ Não foi possível resolver o Cloudflare.")
|
| 109 |
return False
|
| 110 |
|
| 111 |
+
# ============================================================
|
| 112 |
+
# FLUXO PRINCIPAL
|
| 113 |
+
# ============================================================
|
| 114 |
async def main() -> None:
|
| 115 |
+
print("🚀 Iniciando nodriver (headless, retry)...")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 116 |
safe_unlink(SCREENSHOT_FILE)
|
| 117 |
safe_unlink(ERRO_FILE)
|
| 118 |
|
| 119 |
+
# Localiza o Chrome antes de tentar iniciar
|
| 120 |
try:
|
|
|
|
| 121 |
chrome_path = find_playwright_chrome()
|
| 122 |
+
except FileNotFoundError as e:
|
| 123 |
+
print(f"💥 {e}")
|
| 124 |
+
return
|
| 125 |
+
|
| 126 |
+
browser = None
|
| 127 |
+
# Lógica de retry para contornar a falha de conexão inicial
|
| 128 |
+
for tentativa in range(1, 4):
|
| 129 |
+
print(f"🔄 Tentativa de inicialização {tentativa}/3...")
|
| 130 |
+
try:
|
| 131 |
+
browser = await uc.start(
|
| 132 |
+
headless=True, # Modo headless é mais estável em containers
|
| 133 |
+
sandbox=False, # Substitui no_sandbox=True
|
| 134 |
+
browser_executable_path=chrome_path,
|
| 135 |
+
browser_args=BROWSER_ARGS,
|
| 136 |
+
)
|
| 137 |
+
print("✅ Navegador iniciado com sucesso.")
|
| 138 |
+
break # Sai do loop se conectar
|
| 139 |
+
except Exception as e:
|
| 140 |
+
print(f"⚠️ Falha na tentativa {tentativa}: {e}")
|
| 141 |
+
if tentativa < 3:
|
| 142 |
+
print("⏳ Aguardando 3 segundos para tentar novamente...")
|
| 143 |
+
await asyncio.sleep(3)
|
| 144 |
+
else:
|
| 145 |
+
print("💥 Todas as tentativas de inicialização falharam.")
|
| 146 |
+
return
|
| 147 |
+
|
| 148 |
+
if not browser:
|
| 149 |
+
return
|
| 150 |
|
| 151 |
+
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 152 |
tab = await browser.get(URL_TESAURO)
|
| 153 |
await tab.sleep(3)
|
| 154 |
|
| 155 |
+
# Tenta o bypass do Cloudflare
|
| 156 |
+
if not await resolver_cloudflare(tab):
|
|
|
|
|
|
|
| 157 |
body, title = await get_page_state(tab)
|
| 158 |
print(f" Título atual: {title!r}")
|
| 159 |
try:
|
|
|
|
| 163 |
pass
|
| 164 |
return
|
| 165 |
|
| 166 |
+
# Valida se a página é a correta
|
| 167 |
await tab.sleep(1)
|
| 168 |
body_text, title = await get_page_state(tab)
|
| 169 |
|
| 170 |
if is_page_not_found(body_text):
|
| 171 |
print("❌ A página retornada é um 404 do STJ.")
|
|
|
|
| 172 |
return
|
| 173 |
|
| 174 |
if "tesauro" not in body_text.lower() and "vocabulário" not in body_text.lower():
|
|
|
|
| 176 |
else:
|
| 177 |
print("✅ Página do Tesauro carregada com sucesso!")
|
| 178 |
|
| 179 |
+
# Salva o screenshot final
|
| 180 |
try:
|
| 181 |
await tab.save_screenshot(SCREENSHOT_FILE)
|
| 182 |
print(f"📸 Screenshot salvo em: {os.path.abspath(SCREENSHOT_FILE)}")
|
|
|
|
| 186 |
print("═" * 50)
|
| 187 |
|
| 188 |
except Exception as e:
|
| 189 |
+
print(f"💥 Erro inesperado durante a navegação: {e}")
|
| 190 |
finally:
|
| 191 |
if browser:
|
| 192 |
browser.stop()
|
| 193 |
print("🛑 Navegador encerrado.")
|
| 194 |
|
|
|
|
| 195 |
if __name__ == "__main__":
|
| 196 |
uc.loop().run_until_complete(main())
|