This commit is contained in:
kolmeasi
2026-05-31 18:57:03 -03:00
parent b90e69c1f9
commit 77ec04cc43
6 changed files with 142575 additions and 54 deletions

View File

@@ -6,9 +6,10 @@ Uso standalone:
python scraper_meteorologia.py --aerodrome SBGR --all-years
python scraper_meteorologia.py --aerodrome SBGR --start-year 2020 --end-year 2025
python scraper_meteorologia.py --aerodrome SBSP --start-year 2023 --end-year 2023 --no-headless
python scraper_meteorologia.py --fetch-catalog # grava catálogo de aeródromos no banco
Importável pelo pipeline:
from scraper_meteorologia import make_driver, scrape_year, SITE_MIN_DATE
from scraper_meteorologia import make_driver, scrape_year, fetch_aerodrome_catalog, SITE_MIN_DATE
"""
import argparse
@@ -152,6 +153,57 @@ def select_aerodrome(driver: webdriver.Chrome, wait: WebDriverWait, code: str) -
return text
def fetch_aerodrome_catalog(headless: bool = True) -> list[dict]:
"""
Acessa o site ICEA e extrai a lista completa de aeródromos disponíveis.
Retorna lista de dicts com chaves 'icao', 'nome', 'uf'.
"""
driver = make_driver(headless=headless)
try:
driver.get(BASE_URL)
WebDriverWait(driver, 30).until(
EC.presence_of_element_located(
(By.CSS_SELECTOR, "select.selectpicker, select")
)
)
time.sleep(1.5) # aguarda Bootstrap Select renderizar todas as opções
raw_options = driver.execute_script("""
var candidates = document.querySelectorAll(
'select.selectpicker, select[name*="localidade"], select[name*="aerodrome"], select'
);
var result = [];
for (var s = 0; s < candidates.length; s++) {
var sel = candidates[s];
if (sel.options.length > 5) {
for (var i = 0; i < sel.options.length; i++) {
var txt = sel.options[i].text.trim();
if (txt) result.push(txt);
}
break;
}
}
return result;
""")
finally:
driver.quit()
catalog = []
for text in (raw_options or []):
if not text or len(text) < 4:
continue
icao = text[:4].upper()
if not re.match(r"[A-Z]{4}", icao):
continue
# formato típico: "SBGR - GUARULHOS / CUMBICA (SP)"
nome = text[4:].lstrip(" -–—").strip()
uf_match = re.search(r"\(([A-Z]{2})\)\s*$", nome)
uf = uf_match.group(1) if uf_match else ""
if uf_match:
nome = nome[: uf_match.start()].strip().rstrip("(),- ")
catalog.append({"icao": icao, "nome": nome, "uf": uf})
return catalog
def set_period(driver: webdriver.Chrome, start: date, end: date) -> None:
s = start.strftime("%d/%m/%Y")
e = end.strftime("%d/%m/%Y")
@@ -394,20 +446,40 @@ exemplos:
python scraper_meteorologia.py --aerodrome SBSP --start-year 2023 --end-year 2023 --no-headless
""",
)
parser.add_argument("--aerodrome", default="SBGR", metavar="ICAO",
parser.add_argument("--aerodrome", default="SBGR", metavar="ICAO",
help="Código ICAO do aeródromo (padrão: SBGR)")
parser.add_argument("--all-years", action="store_true",
parser.add_argument("--all-years", action="store_true",
help="Coleta todos os anos disponíveis (para automaticamente sem dados)")
parser.add_argument("--start-year", type=int, metavar="ANO",
parser.add_argument("--start-year", type=int, metavar="ANO",
help="Ano inicial (obrigatório sem --all-years)")
parser.add_argument("--end-year", type=int, metavar="ANO",
parser.add_argument("--end-year", type=int, metavar="ANO",
help="Ano final (obrigatório sem --all-years)")
parser.add_argument("--output-dir", default="dados", metavar="DIR",
parser.add_argument("--output-dir", default="dados", metavar="DIR",
help="Diretório de saída (padrão: dados/)")
parser.add_argument("--no-headless", action="store_true",
parser.add_argument("--no-headless", action="store_true",
help="Abre janela do Chrome visível (útil para debug)")
parser.add_argument("--fetch-catalog", action="store_true",
help="Baixa lista de aeródromos do site ICEA e grava no banco SQLite")
parser.add_argument("--db-path", default="dados/met.db", metavar="PATH",
help="Caminho do banco SQLite (usado com --fetch-catalog)")
args = parser.parse_args()
# ── fetch-catalog: ação independente ──────────────────────────────────────
if args.fetch_catalog:
from pathlib import Path
import db as _db
print("Acessando site ICEA para obter lista de aeródromos…")
catalog = fetch_aerodrome_catalog(headless=not args.no_headless)
print(f" {len(catalog)} aeródromos encontrados.")
db_path = Path(args.db_path)
db_path.parent.mkdir(parents=True, exist_ok=True)
conn = _db.get_connection(db_path)
_db.ensure_schema(conn)
n = _db.upsert_aerodromes(conn, catalog)
conn.close()
print(f" {n} registros gravados em '{db_path}'.")
return
if not args.all_years and (args.start_year is None or args.end_year is None):
parser.error("Use --all-years ou informe --start-year e --end-year")