212 lines
6.9 KiB
Python
212 lines
6.9 KiB
Python
import datetime
|
||
import logging
|
||
import re
|
||
from typing import Iterable
|
||
|
||
import dateparser
|
||
from bs4 import Tag
|
||
|
||
from gallery.sketch.weather.model import (
|
||
Cloudness,
|
||
Precipitation,
|
||
Sky,
|
||
WindDirection,
|
||
WindDirectionDeg,
|
||
)
|
||
|
||
from .core import BaseWidgetParser, RowParser
|
||
|
||
logger = logging.getLogger("gismeteo")
|
||
|
||
ONE_DAY_PARSER = BaseWidgetParser(".widget.widget-oneday .widget-items")
|
||
DAYS_PARSER = BaseWidgetParser(".widget.widget-days .widget-items")
|
||
|
||
|
||
class LocationParser:
|
||
PATTERN = re.compile('{"ru":{"city":{"name":"(.*?)"')
|
||
|
||
def parse_location(self, data: str) -> str | None:
|
||
match = self.PATTERN.search(data)
|
||
if match:
|
||
return match.group(1)
|
||
return None
|
||
|
||
|
||
LOCATION_PARSER = LocationParser()
|
||
|
||
|
||
class DateParser(RowParser[datetime.datetime]):
|
||
KEY = "date"
|
||
|
||
def parse_row(self, tag: Tag) -> Iterable[datetime.datetime]:
|
||
datetime_time_row = tag.select_one(".widget-row.widget-row-datetime-time")
|
||
if datetime_time_row:
|
||
for item in datetime_time_row.select(".row-item > time-value"):
|
||
timestamp = int(item.attrs["timestamp"])
|
||
time = datetime.datetime.fromtimestamp(timestamp)
|
||
yield time
|
||
else:
|
||
for item in tag.select(".widget-row.widget-row-date > .row-item"):
|
||
date_str = item.text
|
||
date = dateparser.parse(date_str, languages=["ru"])
|
||
yield date
|
||
|
||
|
||
class SkyParser(RowParser[Sky]):
|
||
KEY = "sky"
|
||
|
||
CLOUDNESS_MAP: dict[str, Cloudness] = {
|
||
"ясно": Cloudness.CLEAR,
|
||
"безоблачно": Cloudness.CLEAR,
|
||
"малооблачно": Cloudness.PARTLY_CLOUDY,
|
||
"облачно": Cloudness.CLOUDY,
|
||
"пасмурно": Cloudness.MAINLY_CLOUDY,
|
||
}
|
||
|
||
PRECIPITATION_MAP: dict[str, Precipitation] = {
|
||
"без осадков": Precipitation.NO,
|
||
"небольшой дождь": Precipitation.SMALL_RAIN,
|
||
"сильный дождь": Precipitation.HEAVY_RAIN,
|
||
"очень сильный дождь": Precipitation.HEAVY_RAIN,
|
||
"ливневый дождь": Precipitation.SHOWER,
|
||
"дождь": Precipitation.RAIN,
|
||
"ливень": Precipitation.SHOWER,
|
||
"снег": Precipitation.SNOW,
|
||
"небольшой снег": Precipitation.SNOW,
|
||
"сильный снег": Precipitation.HEAVY_SNOW,
|
||
"мокрый снег": Precipitation.SNOW,
|
||
"снег с дождём": Precipitation.SNOW,
|
||
"сильный снег с дождём": Precipitation.HEAVY_SNOW,
|
||
"небольшой снег с дождём": Precipitation.SNOW,
|
||
"небольшой мокрый снег": Precipitation.SNOW,
|
||
"град": Precipitation.HAIL,
|
||
}
|
||
|
||
THUNDER = "гроза"
|
||
FOG = "дымка"
|
||
|
||
def parse_row(self, tag: Tag) -> Iterable[Sky]:
|
||
for item in tag.select(".widget-row[data-row=icon-tooltip] > .row-item"):
|
||
sky_str = item.attrs["data-tooltip"]
|
||
values = {item.strip().lower() for item in sky_str.split(",")}
|
||
cloudness = Cloudness.CLEAR
|
||
precipitation = Precipitation.NO
|
||
thunder = False
|
||
fog = False
|
||
if self.THUNDER in values:
|
||
thunder = True
|
||
values.remove(self.THUNDER)
|
||
if self.FOG in values:
|
||
fog = True
|
||
values.remove(self.FOG)
|
||
for k, v in self.CLOUDNESS_MAP.items():
|
||
if k in values:
|
||
cloudness = v
|
||
values.remove(k)
|
||
break
|
||
for k, v in self.PRECIPITATION_MAP.items():
|
||
if k in values:
|
||
precipitation = v
|
||
values.remove(k)
|
||
break
|
||
if values:
|
||
logger.warning("unknown sky values: %s:", values)
|
||
yield Sky(
|
||
cloudness=cloudness,
|
||
precipitation=precipitation,
|
||
thunder=thunder,
|
||
fog=fog,
|
||
)
|
||
|
||
|
||
class TemperatureParser(RowParser[list[int]]):
|
||
KEY = "temperature"
|
||
|
||
def parse_row(self, tag: Tag) -> Iterable[list[int]]:
|
||
for item in tag.select(".widget-row-chart[data-row=temperature-air] > .chart > .values > .value"):
|
||
yield [int(value.attrs["value"]) for value in item.select("temperature-value")]
|
||
|
||
|
||
class WindSpeedParser(RowParser[int]):
|
||
KEY = "wind_speed"
|
||
|
||
def parse_row(self, tag: Tag) -> Iterable[int]:
|
||
for item in tag.select(".widget-row-wind > .row-item > .wind-speed > speed-value"):
|
||
yield int(item.attrs["value"])
|
||
|
||
|
||
class WindGustParser(RowParser[int]):
|
||
KEY = "wind_gust"
|
||
|
||
def parse_row(self, tag: Tag) -> Iterable[int]:
|
||
for item in tag.select(".widget-row-wind > .row-item > .wind-gust"):
|
||
value = item.select_one("speed-value")
|
||
yield int(value.attrs["value"]) if value else 0
|
||
|
||
|
||
class WindDirectionParser(RowParser[WindDirection]):
|
||
KEY = "wind_direction"
|
||
|
||
WIND_DIRECTION_MAP: dict[str, WindDirection] = {
|
||
"—": WindDirection.CALM,
|
||
"штиль": WindDirection.CALM,
|
||
"с": WindDirection.N,
|
||
"св": WindDirection.NE,
|
||
"в": WindDirection.E,
|
||
"юв": WindDirection.SE,
|
||
"ю": WindDirection.S,
|
||
"юз": WindDirection.SW,
|
||
"з": WindDirection.W,
|
||
"сз": WindDirection.NW,
|
||
}
|
||
|
||
def parse_row(self, tag: Tag) -> Iterable[float]:
|
||
for item in tag.select(".widget-row-wind > .row-item > .wind-speed > .wind-direction"):
|
||
wind_direction_str = item.text.lower().strip()
|
||
yield WindDirectionDeg.from_direction(self.WIND_DIRECTION_MAP[wind_direction_str]).value
|
||
|
||
|
||
class PrecipitationParser(RowParser[float]):
|
||
KEY = "precipitation"
|
||
|
||
def parse_row(self, tag: Tag) -> Iterable[float]:
|
||
for item in tag.select(".widget-row[data-row=precipitation-bars] > .row-item"):
|
||
value = item.select_one("precipitation-value")
|
||
if value:
|
||
yield float(value.attrs["value"])
|
||
else:
|
||
yield 0
|
||
|
||
|
||
class PressureParser(RowParser[list[int]]):
|
||
KEY = "pressure"
|
||
|
||
def parse_row(self, tag: Tag) -> Iterable[list[int]]:
|
||
for item in tag.select(".widget-row-chart[data-row=pressure] > .chart > .values > .value"):
|
||
yield [int(value.attrs["value"]) for value in item.select("pressure-value")]
|
||
|
||
|
||
class HumidityParser(RowParser[int]):
|
||
KEY = "humidity"
|
||
|
||
def parse_row(self, tag: Tag) -> Iterable[int]:
|
||
for item in tag.select(
|
||
".widget-row[data-row=humidity] > .row-item, .widget-row[data-row=humidity-avg] > .row-item"
|
||
):
|
||
yield int(item.text)
|
||
|
||
|
||
ROW_PARSERS: list[RowParser] = [
|
||
DateParser(),
|
||
SkyParser(),
|
||
TemperatureParser(),
|
||
WindSpeedParser(),
|
||
WindGustParser(),
|
||
WindDirectionParser(),
|
||
PrecipitationParser(),
|
||
PressureParser(),
|
||
HumidityParser(),
|
||
]
|
||
|
||
ROW_PARSERS_MAP: dict[str, RowParser] = {parser.KEY: parser for parser in ROW_PARSERS}
|