New skills covering 10 categories: **Security & Audit**: 007 (STRIDE/PASTA/OWASP), cred-omega (secrets management) **AI Personas**: Karpathy, Hinton, Sutskever, LeCun (4 sub-skills), Altman, Musk, Gates, Jobs, Buffett **Multi-agent Orchestration**: agent-orchestrator, task-intelligence, multi-advisor **Code Analysis**: matematico-tao (Terence Tao-inspired mathematical code analysis) **Social & Messaging**: Instagram Graph API, Telegram Bot, WhatsApp Cloud API, social-orchestrator **Image Generation**: AI Studio (Gemini), Stability AI, ComfyUI Gateway, image-studio router **Brazilian Domain**: 6 auction specialist modules, 2 legal advisors, auctioneers data scraper **Product & Growth**: design, invention, monetization, analytics, growth engine **DevOps & LLM Ops**: Docker/CI-CD/AWS, RAG/embeddings/fine-tuning **Skill Governance**: installer, sentinel auditor, context management Each skill includes: - Standardized YAML frontmatter (name, description, risk, source, tags, tools) - Structured sections (Overview, When to Use, How it Works, Best Practices) - Python scripts and reference documentation where applicable - Cross-platform compatibility (Claude Code, Antigravity, Cursor, Gemini CLI, Codex CLI) Co-authored-by: ProgramadorBrasil <214873561+ProgramadorBrasil@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
111 lines
4.1 KiB
Python
111 lines
4.1 KiB
Python
"""
|
|
Scraper genérico para juntas que usam formato padrão de tabela HTML.
|
|
Estados sem scraper customizado herdam deste.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from typing import List, Optional
|
|
|
|
from .base_scraper import AbstractJuntaScraper, Leiloeiro
|
|
|
|
|
|
class GenericJuntaScraper(AbstractJuntaScraper):
|
|
"""
|
|
Scraper genérico para juntas com tabela HTML padrão.
|
|
Subclasses definem apenas estado, junta e url.
|
|
"""
|
|
|
|
estado: str
|
|
junta: str
|
|
url: str
|
|
municipio_default: Optional[str] = None # para estados com capital única dominante
|
|
|
|
async def parse_leiloeiros(self) -> List[Leiloeiro]:
|
|
soup = await self.fetch_page()
|
|
if not soup:
|
|
return []
|
|
|
|
results: List[Leiloeiro] = []
|
|
|
|
# Tentativa 1: tabela HTML
|
|
tables = soup.find_all("table")
|
|
for table in tables:
|
|
rows = table.find_all("tr")
|
|
if len(rows) < 2:
|
|
continue
|
|
|
|
headers = [self.clean(th.get_text()) for th in rows[0].find_all(["th", "td"])]
|
|
if not headers:
|
|
continue
|
|
|
|
col = {(h or "").lower(): i for i, h in enumerate(headers)}
|
|
|
|
# Verificar se parece uma tabela de leiloeiros
|
|
has_name_col = any(
|
|
"nome" in k or "leiloeiro" in k or "auxiliar" in k
|
|
for k in col.keys()
|
|
)
|
|
if not has_name_col and len(headers) < 2:
|
|
continue
|
|
|
|
def gcol(cells, frags):
|
|
for k, i in col.items():
|
|
if any(f in k for f in frags) and i < len(cells):
|
|
return self.clean(cells[i].get_text())
|
|
return None
|
|
|
|
for row in rows[1:]:
|
|
cells = row.find_all(["td", "th"])
|
|
if not cells:
|
|
continue
|
|
nome = gcol(cells, ["nome", "leiloeiro"]) or self.clean(cells[0].get_text())
|
|
if not nome or len(nome) < 3:
|
|
continue
|
|
|
|
results.append(self.make_leiloeiro(
|
|
nome=nome,
|
|
matricula=gcol(cells, ["matr", "registro", "núm", "numero", "nº"]),
|
|
cpf_cnpj=gcol(cells, ["cpf", "cnpj", "documento"]),
|
|
situacao=gcol(cells, ["situ", "status"]),
|
|
municipio=gcol(cells, ["munic", "cidade"]) or self.municipio_default,
|
|
telefone=gcol(cells, ["tel", "fone", "contato"]),
|
|
email=gcol(cells, ["email", "e-mail"]),
|
|
endereco=gcol(cells, ["ender", "logr", "rua"]),
|
|
data_registro=gcol(cells, ["data", "cadastr"]),
|
|
))
|
|
|
|
if results:
|
|
break # Parar na primeira tabela com resultados
|
|
|
|
# Tentativa 2: listas (ul/ol li)
|
|
if not results:
|
|
list_items = soup.select("ul.leiloeiros li, ol.leiloeiros li, .lista-leiloeiros li")
|
|
if not list_items:
|
|
list_items = soup.select("ul li, ol li")
|
|
|
|
for li in list_items:
|
|
text = self.clean(li.get_text(" | "))
|
|
if not text or len(text) < 5:
|
|
continue
|
|
results.append(self.make_leiloeiro(nome=text, municipio=self.municipio_default))
|
|
|
|
# Tentativa 3: divs/articles com conteúdo textual
|
|
if not results:
|
|
content = soup.select_one(
|
|
".conteudo-pagina, .page-content, .entry-content, article, main .content"
|
|
)
|
|
if content:
|
|
import re
|
|
for p in content.find_all(["p", "div", "li"]):
|
|
text = self.clean(p.get_text())
|
|
if not text or len(text) < 5:
|
|
continue
|
|
# Filtrar parágrafos que parecem ser registros de pessoas
|
|
if re.search(r"\b[A-ZÁÉÍÓÚÀÃÕÇ][a-záéíóúàãõç]{2,}", text):
|
|
results.append(self.make_leiloeiro(
|
|
nome=text,
|
|
municipio=self.municipio_default,
|
|
))
|
|
|
|
return results
|