Update weather.com weather backend to reflect API changes (#783)
This commit is contained in:
parent
e0234c1223
commit
dad5eb0c5c
@ -67,53 +67,27 @@ class WeathercomHTMLParser(HTMLParser):
|
|||||||
# Look for feed information embedded as a javascript variable
|
# Look for feed information embedded as a javascript variable
|
||||||
begin = content.find('window.__data')
|
begin = content.find('window.__data')
|
||||||
if begin != -1:
|
if begin != -1:
|
||||||
|
weather_data = None
|
||||||
self.logger.debug('Located window.__data')
|
self.logger.debug('Located window.__data')
|
||||||
# Look for end of JSON dict and end of javascript statement
|
# Strip the "window.__data=" from the beginning and load json
|
||||||
end = content.find('};', begin)
|
|
||||||
if end == -1:
|
|
||||||
self.logger.debug('Failed to locate end of javascript statement')
|
|
||||||
else:
|
|
||||||
# Strip the "window.__data=" from the beginning
|
|
||||||
json_data = self.load_json(
|
json_data = self.load_json(
|
||||||
content[begin:end + 1].split('=', 1)[1].lstrip()
|
content[begin:].split('=', 1)[1].lstrip()
|
||||||
)
|
)
|
||||||
if json_data is not None:
|
if json_data is not None:
|
||||||
def _find_weather_data(data):
|
try:
|
||||||
'''
|
weather_data = json_data['dal']
|
||||||
Helper designed to minimize impact of potential
|
except KeyError:
|
||||||
structural changes to this data.
|
pass
|
||||||
'''
|
|
||||||
if isinstance(data, dict):
|
|
||||||
if 'Observation' in data and 'DailyForecast' in data:
|
|
||||||
return data
|
|
||||||
else:
|
|
||||||
for key in data:
|
|
||||||
ret = _find_weather_data(data[key])
|
|
||||||
if ret is not None:
|
|
||||||
return ret
|
|
||||||
return None
|
|
||||||
|
|
||||||
weather_data = _find_weather_data(json_data)
|
|
||||||
if weather_data is None:
|
if weather_data is None:
|
||||||
self.logger.debug(
|
self.logger.debug(
|
||||||
'Failed to locate weather data in the '
|
'Failed to locate weather data in the '
|
||||||
'following data structure: %s', json_data
|
'following data: %s', json_data
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self.weather_data = weather_data
|
self.weather_data = weather_data
|
||||||
return
|
return
|
||||||
|
|
||||||
for line in content.splitlines():
|
|
||||||
line = line.strip().rstrip(';')
|
|
||||||
if line.startswith('var adaptorParams'):
|
|
||||||
# Strip off the "var adaptorParams = " from the beginning,
|
|
||||||
# and the javascript semicolon from the end. This will give
|
|
||||||
# us JSON that we can load.
|
|
||||||
weather_data = self.load_json(line.split('=', 1)[1].lstrip())
|
|
||||||
if weather_data is not None:
|
|
||||||
self.weather_data = weather_data
|
|
||||||
return
|
|
||||||
|
|
||||||
|
|
||||||
class Weathercom(WeatherBackend):
|
class Weathercom(WeatherBackend):
|
||||||
'''
|
'''
|
||||||
@ -216,15 +190,15 @@ class Weathercom(WeatherBackend):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
observed = self.parser.weather_data['Observation']
|
observed = self.parser.weather_data['getSunV3CurrentObservationsUrlConfig']
|
||||||
# Observation data stored under a sub-key containing the
|
# Observation data stored under a sub-key containing the
|
||||||
# lat/long coordinates. For example:
|
# lat/long coordinates, locale info, etc. For example:
|
||||||
#
|
#
|
||||||
# geocode:41.77,-88.35:language:en-US:units:e
|
# geocode:41.77,-88.35:language:en-US:units:e
|
||||||
#
|
#
|
||||||
# Since this is the only key under "Observation", we can just
|
# Since this is the only key under "Observation", we can just
|
||||||
# use next(iter(observed)) to get it.
|
# use next(iter(observed)) to get it.
|
||||||
observed = observed[next(iter(observed))]['data']['vt1observation']
|
observed = observed[next(iter(observed))]['data']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
'Failed to retrieve current conditions from API response. '
|
'Failed to retrieve current conditions from API response. '
|
||||||
@ -234,11 +208,10 @@ class Weathercom(WeatherBackend):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
forecast = self.parser.weather_data['DailyForecast']
|
forecast = self.parser.weather_data['getSunV3DailyForecastUrlConfig']
|
||||||
# Same as above, use next(iter(forecast)) to drill down to the
|
# Same as above, use next(iter(forecast)) to drill down to the
|
||||||
# correct nested dict level.
|
# correct nested dict level.
|
||||||
forecast = forecast[next(iter(forecast))]
|
forecast = forecast[next(iter(forecast))]['data']
|
||||||
forecast = forecast['data']['vt1dailyForecast'][0]
|
|
||||||
except (IndexError, KeyError):
|
except (IndexError, KeyError):
|
||||||
self.logger.error(
|
self.logger.error(
|
||||||
'Failed to retrieve forecast data from API response. '
|
'Failed to retrieve forecast data from API response. '
|
||||||
@ -248,11 +221,11 @@ class Weathercom(WeatherBackend):
|
|||||||
return
|
return
|
||||||
|
|
||||||
try:
|
try:
|
||||||
self.city_name = self.parser.weather_data['Location']
|
location = self.parser.weather_data['getSunV3LocationPointUrlConfig']
|
||||||
# Again, same technique as above used to get down to the
|
# Again, same technique as above used to get down to the
|
||||||
# correct nested dict level.
|
# correct nested dict level.
|
||||||
self.city_name = self.city_name[next(iter(self.city_name))]
|
location = location[next(iter(location))]
|
||||||
self.city_name = self.city_name['data']['location']['displayName']
|
self.city_name = location['data']['location']['displayName']
|
||||||
except KeyError:
|
except KeyError:
|
||||||
self.logger.warning(
|
self.logger.warning(
|
||||||
'Failed to get city name from API response, falling back '
|
'Failed to get city name from API response, falling back '
|
||||||
@ -260,19 +233,15 @@ class Weathercom(WeatherBackend):
|
|||||||
)
|
)
|
||||||
self.city_name = self.location_code
|
self.city_name = self.location_code
|
||||||
|
|
||||||
# Cut off the timezone from the end of the string (it's after the last
|
|
||||||
# space, hence the use of rpartition). International timezones (or ones
|
|
||||||
# outside the system locale) don't seem to be handled well by
|
|
||||||
# datetime.datetime.strptime().
|
|
||||||
try:
|
try:
|
||||||
observation_time_str = str(observed.get('observationTime', ''))
|
observation_time_str = str(observed.get('validTimeLocal', ''))
|
||||||
observation_time = datetime.strptime(observation_time_str,
|
observation_time = datetime.strptime(observation_time_str,
|
||||||
'%Y-%d-%yT%H:%M:%S%z')
|
'%Y-%d-%yT%H:%M:%S%z')
|
||||||
except (ValueError, AttributeError):
|
except (ValueError, AttributeError):
|
||||||
observation_time = datetime.fromtimestamp(0)
|
observation_time = datetime.fromtimestamp(0)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
pressure_trend_str = observed.get('barometerTrend', '').lower()
|
pressure_trend_str = observed.get('pressureTendencyTrend', '').lower()
|
||||||
except AttributeError:
|
except AttributeError:
|
||||||
pressure_trend_str = ''
|
pressure_trend_str = ''
|
||||||
|
|
||||||
@ -283,18 +252,14 @@ class Weathercom(WeatherBackend):
|
|||||||
else:
|
else:
|
||||||
pressure_trend = ''
|
pressure_trend = ''
|
||||||
|
|
||||||
|
self.logger.critical('forecast = %s', forecast)
|
||||||
try:
|
try:
|
||||||
high_temp = forecast.get('day', {}).get('temperature', '')
|
high_temp = forecast.get('temperatureMax', [])[0] or ''
|
||||||
except (AttributeError, IndexError):
|
except (AttributeError, IndexError):
|
||||||
high_temp = ''
|
high_temp = ''
|
||||||
else:
|
|
||||||
if high_temp is None:
|
|
||||||
# In the mid-afternoon, the high temp disappears from the
|
|
||||||
# forecast, so just set high_temp to an empty string.
|
|
||||||
high_temp = ''
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
low_temp = forecast.get('night', {}).get('temperature', '')
|
low_temp = forecast.get('temperatureMin', [])[0]
|
||||||
except (AttributeError, IndexError):
|
except (AttributeError, IndexError):
|
||||||
low_temp = ''
|
low_temp = ''
|
||||||
|
|
||||||
@ -310,25 +275,25 @@ class Weathercom(WeatherBackend):
|
|||||||
visibility_unit = 'km'
|
visibility_unit = 'km'
|
||||||
|
|
||||||
self.data['city'] = self.city_name
|
self.data['city'] = self.city_name
|
||||||
self.data['condition'] = str(observed.get('phrase', ''))
|
self.data['condition'] = str(observed.get('wxPhraseMedium', ''))
|
||||||
self.data['observation_time'] = observation_time
|
self.data['observation_time'] = observation_time
|
||||||
self.data['current_temp'] = str(observed.get('temperature', ''))
|
self.data['current_temp'] = str(observed.get('temperature', ''))
|
||||||
self.data['low_temp'] = str(low_temp)
|
self.data['low_temp'] = str(low_temp)
|
||||||
self.data['high_temp'] = str(high_temp)
|
self.data['high_temp'] = str(high_temp)
|
||||||
self.data['temp_unit'] = temp_unit
|
self.data['temp_unit'] = temp_unit
|
||||||
self.data['feelslike'] = str(observed.get('feelsLike', ''))
|
self.data['feelslike'] = str(observed.get('temperatureFeelsLike', ''))
|
||||||
self.data['dewpoint'] = str(observed.get('dewPoint', ''))
|
self.data['dewpoint'] = str(observed.get('temperatureDewPoint', ''))
|
||||||
self.data['wind_speed'] = str(observed.get('windSpeed', ''))
|
self.data['wind_speed'] = str(observed.get('windSpeed', ''))
|
||||||
self.data['wind_unit'] = wind_unit
|
self.data['wind_unit'] = wind_unit
|
||||||
self.data['wind_direction'] = str(observed.get('windDirCompass', ''))
|
self.data['wind_direction'] = str(observed.get('windDirectionCardinal', ''))
|
||||||
# Gust can be None, using "or" to ensure empty string in this case
|
# Gust can be None, using "or" to ensure empty string in this case
|
||||||
self.data['wind_gust'] = str(observed.get('gust', '') or '')
|
self.data['wind_gust'] = str(observed.get('windGust', '') or '')
|
||||||
self.data['pressure'] = str(observed.get('altimeter', ''))
|
self.data['pressure'] = str(observed.get('pressureAltimeter', ''))
|
||||||
self.data['pressure_unit'] = pressure_unit
|
self.data['pressure_unit'] = pressure_unit
|
||||||
self.data['pressure_trend'] = pressure_trend
|
self.data['pressure_trend'] = pressure_trend
|
||||||
self.data['visibility'] = str(observed.get('visibility', ''))
|
self.data['visibility'] = str(observed.get('visibility', ''))
|
||||||
self.data['visibility_unit'] = visibility_unit
|
self.data['visibility_unit'] = visibility_unit
|
||||||
self.data['humidity'] = str(observed.get('humidity', ''))
|
self.data['humidity'] = str(observed.get('relativeHumidity', ''))
|
||||||
self.data['uv_index'] = str(observed.get('uvIndex', ''))
|
self.data['uv_index'] = str(observed.get('uvIndex', ''))
|
||||||
except Exception:
|
except Exception:
|
||||||
# Don't let an uncaught exception kill the update thread
|
# Don't let an uncaught exception kill the update thread
|
||||||
|
Loading…
Reference in New Issue
Block a user