V1.0
This commit is contained in:
@@ -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")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user