diff --git a/src/data_collection/metar_sources.py b/src/data_collection/metar_sources.py index 98a49510..b28507bc 100644 --- a/src/data_collection/metar_sources.py +++ b/src/data_collection/metar_sources.py @@ -44,16 +44,40 @@ class MetarSourceMixin: try: url = "https://aviationweather.gov/api/data/metar" - params = { - "ids": icao, - "format": "json", - "hours": 24, - "_t": int(time.time()), - } - response = self.session.get(url, params=params, timeout=self.timeout) - response.raise_for_status() + history_hours = 24 + + def _request_metar_records(hours: int, timeout: float) -> list: + response = self._http_get( + url, + params={ + "ids": icao, + "format": "json", + "hours": hours, + }, + timeout=timeout, + ) + response.raise_for_status() + payload = response.json() + return payload if isinstance(payload, list) else [] + + try: + data = _request_metar_records( + history_hours, + getattr(self, "metar_timeout_sec", self.timeout), + ) + except httpx.HTTPError as primary_exc: + history_hours = 2 + logger.warning( + f"METAR {icao} 24h 请求失败,尝试 latest fallback: {primary_exc}" + ) + try: + data = _request_metar_records( + history_hours, + getattr(self, "metar_latest_timeout_sec", 2.5), + ) + except httpx.HTTPError: + raise primary_exc - data = response.json() if not data: return None @@ -63,7 +87,7 @@ class MetarSourceMixin: def _parse_rawob_time(obs): raw = obs.get("rawOb", "") - match = re.search(r"(\d{2})(\d{2})(\d{2})Z", raw) + match = re.search(r"\b(\d{2})(\d{2})(\d{2})Z\b", raw) if match: _day, hour, minute = ( int(match.group(1)), @@ -199,6 +223,7 @@ class MetarSourceMixin: "today_obs": today_obs, "recent_obs": recent_obs_raw, "unit": unit, + "history_hours": history_hours, } logger.info( @@ -246,9 +271,8 @@ class MetarSourceMixin: "ids": icao, "format": "json", "hours": 24, - "_t": int(time.time()), } - response = self.session.get( + response = self._http_get( url, params=params, timeout=getattr(self, "metar_timeout_sec", self.timeout), diff --git a/src/data_collection/weather_sources.py b/src/data_collection/weather_sources.py index 0a03886e..80eb50db 100644 --- a/src/data_collection/weather_sources.py +++ b/src/data_collection/weather_sources.py @@ -130,12 +130,22 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour self.metar_timeout_sec = max( 2.0, float(os.getenv("POLYWEATHER_METAR_TIMEOUT_SEC", "4")) ) + self.metar_latest_timeout_sec = max( + 1.0, float(os.getenv("POLYWEATHER_METAR_LATEST_TIMEOUT_SEC", "2.5")) + ) self.metar_cluster_timeout_sec = max( 2.0, float(os.getenv("POLYWEATHER_METAR_CLUSTER_TIMEOUT_SEC", "3.5")) ) + self.user_agent = str( + os.getenv( + "POLYWEATHER_USER_AGENT", + "PolyWeather/1.0 (+https://polyweather-pro.vercel.app)", + ) + ).strip() self.session = httpx.Client( timeout=self.timeout, follow_redirects=True, + headers={"User-Agent": self.user_agent}, limits=httpx.Limits(max_connections=50, max_keepalive_connections=20), ) self.open_meteo_cache_ttl_sec = int( @@ -235,6 +245,7 @@ class WeatherDataCollector(OpenMeteoCacheMixin, SettlementSourceMixin, MetarSour timeout=self.timeout, follow_redirects=True, proxy=proxy, + headers={"User-Agent": self.user_agent}, limits=httpx.Limits(max_connections=50, max_keepalive_connections=20), ) logger.info(f"正在使用天气数据代理: {proxy}")