playbook/antigravity-awesome-skills/skills/junta-leiloeiros/scripts/scraper/generic_scraper.py

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", ""]),
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