188 lines
5.1 KiB
Python
188 lines
5.1 KiB
Python
import datetime
|
|
from typing import Any
|
|
|
|
from bs4 import Tag
|
|
|
|
from gallery.sketch.parse.core import ParseError
|
|
from gallery.sketch.parse.date import DATE_PARSER
|
|
from gallery.sketch.parse.html import (
|
|
HTMLParser,
|
|
TableParser,
|
|
TableValueParser,
|
|
TagParser,
|
|
)
|
|
from gallery.sketch.parse.json import JSONParser
|
|
from gallery.sketch.weather.model import (
|
|
Cloudness,
|
|
Location,
|
|
Precipitation,
|
|
Sky,
|
|
WeatherResponse,
|
|
WeatherValue,
|
|
WindDirection,
|
|
)
|
|
from gallery.sketch.weather.parse.wind import WIND_DIRECTION_PARSER
|
|
|
|
from . import PROVIDER
|
|
|
|
|
|
class LocationNameParser(TagParser[str]):
|
|
selector = "[class^=Suggest_placeholder]"
|
|
|
|
def extract(self, data: Tag) -> str:
|
|
return data.text.strip()
|
|
|
|
|
|
class DateParser(TagParser[datetime.date]):
|
|
selector = "[class^=AppForecastDayHeader_dayTitle]"
|
|
|
|
def extract(self, data: Tag) -> datetime.date:
|
|
return DATE_PARSER.parse(data.text.split(",")[-1].strip())
|
|
|
|
|
|
class TimeParser(TableValueParser[datetime.datetime]):
|
|
selector = "[style$='-part']"
|
|
model_key = "date"
|
|
|
|
HOUR_MAP = {
|
|
"Утром": 6,
|
|
"Днём": 12,
|
|
"Вечером": 18,
|
|
"Ночью": 0,
|
|
}
|
|
|
|
def extract(self, data: Tag) -> datetime.datetime:
|
|
hour = self.HOUR_MAP[data.text.strip()]
|
|
return datetime.datetime.min.replace(hour=hour)
|
|
|
|
|
|
class TemperatureParser(TableValueParser[list[int]]):
|
|
selector = "[style$='-temp']"
|
|
model_key = "temperature"
|
|
|
|
def extract(self, data: Tag) -> list[int]:
|
|
return [int(data.text.strip().rstrip("°"))]
|
|
|
|
|
|
class SkyParser(TableValueParser[Sky]):
|
|
selector = "[style$='-text']"
|
|
model_key = "sky"
|
|
|
|
CLOUDNESS_MAP = {
|
|
"ясно": Cloudness.CLEAR,
|
|
"облачно с прояснениями": Cloudness.CLOUDY,
|
|
"малооблачно": Cloudness.CLOUDY,
|
|
"пасмурно": Cloudness.MAINLY_CLOUDY,
|
|
}
|
|
|
|
PRECIPITATION_MAP = {
|
|
"небольшой дождь": Precipitation.SMALL_RAIN,
|
|
"дождь": Precipitation.RAIN,
|
|
"ливень": Precipitation.SHOWER,
|
|
}
|
|
|
|
def extract(self, data: Tag) -> Sky:
|
|
sky = data.text.strip()
|
|
cloudness = Cloudness.CLEAR
|
|
precipitation = Precipitation.NO
|
|
thunder = False
|
|
fog = False
|
|
if sky in self.CLOUDNESS_MAP:
|
|
cloudness = self.CLOUDNESS_MAP[sky]
|
|
elif sky in self.PRECIPITATION_MAP:
|
|
precipitation = self.PRECIPITATION_MAP[sky]
|
|
else:
|
|
raise ParseError("Unknown sky value", sky)
|
|
return Sky(
|
|
cloudness=cloudness,
|
|
precipitation=precipitation,
|
|
thunder=thunder,
|
|
fog=fog,
|
|
)
|
|
|
|
|
|
class WindSpeedParser(TableValueParser[int]):
|
|
selector = "[style$='-wind']:not([class*=AppForecastDayPart_wind_formatted])"
|
|
model_key = "wind.speed"
|
|
|
|
def extract(self, data: Tag) -> int:
|
|
return int(data.text.strip())
|
|
|
|
|
|
class WindDirectionParser(TableValueParser[WindDirection | None]):
|
|
selector = "[style$='-dir']"
|
|
model_key = "wind.direction"
|
|
|
|
def extract(self, data: Tag) -> WindDirection | None:
|
|
return WIND_DIRECTION_PARSER.parse(data.text.strip())
|
|
|
|
|
|
class HumidityParser(TableValueParser[int]):
|
|
selector = "[style$='-hum']"
|
|
model_key = "humidity"
|
|
|
|
def extract(self, data: Tag) -> int:
|
|
return int(data.text.strip().rstrip("%"))
|
|
|
|
|
|
class PressureParser(TableValueParser[list[int]]):
|
|
selector = "[style$='-press']"
|
|
model_key = "pressure"
|
|
|
|
def extract(self, data: Tag) -> list[int]:
|
|
return [int(data.text.strip().rstrip("%"))]
|
|
|
|
|
|
class YandexTableParser(TableParser[WeatherValue]):
|
|
selector = ""
|
|
value_parsers = [
|
|
TimeParser(),
|
|
TemperatureParser(),
|
|
SkyParser(),
|
|
WindSpeedParser(),
|
|
WindDirectionParser(),
|
|
HumidityParser(),
|
|
PressureParser(),
|
|
]
|
|
|
|
def create_model(self, data: dict) -> WeatherValue:
|
|
return WeatherValue.model_validate(data)
|
|
|
|
|
|
class YandexForecastParser(HTMLParser[list[WeatherResponse]]):
|
|
table_parser = YandexTableParser()
|
|
date_parser = DateParser()
|
|
location_parser = LocationNameParser()
|
|
|
|
def extract(self, data: Tag) -> list[WeatherResponse]:
|
|
location = self.location_parser.parse(data)
|
|
articles = data.select("article[class^=AppForecastDay]")
|
|
return [
|
|
WeatherResponse(
|
|
location=location,
|
|
date=self.date_parser.parse(item),
|
|
period="day",
|
|
values=self.table_parser.parse(item),
|
|
)
|
|
for item in articles
|
|
]
|
|
|
|
|
|
class YandexLocationParser(JSONParser[list[Location]]):
|
|
|
|
def extract(self, data: Any) -> list[Location]:
|
|
return [
|
|
Location(
|
|
id=str(item["geoId"]),
|
|
name=item["title"],
|
|
provider=PROVIDER,
|
|
lat=item["coords"]["lat"],
|
|
lon=item["coords"]["lon"],
|
|
country="ru",
|
|
country_code="ru",
|
|
district=item["description"],
|
|
subdistrict="",
|
|
)
|
|
for item in data
|
|
]
|