From 32d47617ee97a89587c589f0efeb90a4af1a8f86 Mon Sep 17 00:00:00 2001 From: xtaodada Date: Mon, 24 Apr 2023 20:29:14 +0800 Subject: [PATCH] =?UTF-8?q?=E2=9C=A8=20support:=20light=20cones?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- func/fetch_avatars.py | 1 + func/fetch_light_cones.py | 120 ++++++++++++++++++++++++++++++++++++++ func/fetch_materials.py | 1 + main.py | 29 +++++++-- models/light_cone.py | 10 +--- 5 files changed, 148 insertions(+), 13 deletions(-) create mode 100644 func/fetch_light_cones.py diff --git a/func/fetch_avatars.py b/func/fetch_avatars.py index 8865f25..f7b6246 100644 --- a/func/fetch_avatars.py +++ b/func/fetch_avatars.py @@ -114,6 +114,7 @@ async def fetch_avatars_infos(): async def dump_avatars(path: Path): data = [avatar.dict() for avatar in all_avatars] + data.sort(key=lambda x: x["id"]) async with aiofiles.open(path, "w", encoding="utf-8") as f: await f.write(ujson.dumps(data, indent=4, ensure_ascii=False)) diff --git a/func/fetch_light_cones.py b/func/fetch_light_cones.py new file mode 100644 index 0000000..d6af11b --- /dev/null +++ b/func/fetch_light_cones.py @@ -0,0 +1,120 @@ +import asyncio +import re +from pathlib import Path +from typing import List, Dict + +import aiofiles +import ujson +from bs4 import BeautifulSoup + +from func.client import client +from func.fetch_materials import all_materials_map, all_materials_name +from func.url import info_url +from models.enums import Quality, Destiny +from models.light_cone import LightCone, LightConePromote, LightConeItem +from models.wiki import Children + +all_light_cones: List[LightCone] = [] +all_light_cones_map: Dict[int, LightCone] = {} +all_light_cones_name: Dict[str, LightCone] = {} + + +async def fetch_light_cones(data: Children): + for content in data.list: + m_destiny = Destiny(re.findall(r'命途/(.*?)\\', content.ext)[0]) + m_quality = Quality(re.findall(r'星级/(.*?)\\', content.ext)[0]) + light_cone = LightCone( + id=content.content_id, + name=content.title, + desc=content.summary, + icon=content.icon, + big_pic="", + quality=m_quality, + destiny=m_destiny, + promote=[], + ) + all_light_cones.append(light_cone) + all_light_cones_map[light_cone.id] = light_cone + all_light_cones_name[light_cone.name] = light_cone + + +def parse_promote(light_cone: LightCone, soup: BeautifulSoup) -> None: + """解析光锥突破数据""" + mater = soup.find("div", {"data-part": "material"}) + trs = mater.find_all("tr") + required_levels = [20, 30, 40, 50, 60, 70] + max_level = [30, 40, 50, 60, 70, 80] + for i in range(0, min(len(trs), 6)): + promote = LightConePromote( + required_level=required_levels[i], + max_level=max_level[i], + items=[], + ) + materials = trs[i].find_all("li", {"class": "obc-tmpl__material-item"}) + for material in materials: + try: + mid = int(re.findall(r"content/(\d+)/detail", material.find("a").get("href"))[0]) + except AttributeError: + continue + name = material.find("p", {"class": "obc-tmpl__material-name"}).text + item = all_materials_map.get(mid) + if not item: + item = all_materials_name.get(name) + try: + count = int(material.find("span", {"class": "obc-tmpl__material-num"}).text) + except (AttributeError, ValueError): + count = 1 + if name == "信用点": + promote.coin = count + elif item: + promote.items.append( + LightConeItem( + item=item, + count=count, + ) + ) + else: + print(f"unknown material: {mid}: {name}") + light_cone.promote.append(promote) + + +async def fetch_info(light_cone: LightCone): + print(f"Fetch light_cone info: {light_cone.id}: {light_cone.name}") + params = { + 'app_sn': 'sr_wiki', + 'content_id': str(light_cone.id), + } + resp = await client.get(info_url, params=params) + data = resp.json()["data"]["content"]["contents"][0]["text"] + soup = BeautifulSoup(data, "lxml") + table = soup.find("table", {"class": "obc-tml-light-table--pc"}) + tr = table.find_all("tr")[-1] + td = tr.find_all("td")[-1] + light_cone.desc = td.get_text().strip() + pic_td = soup.find("td", {"class": "obc-tmpl-character__avatar"}) + light_cone.big_pic = pic_td.find("img").get("src") + parse_promote(light_cone, soup) + + +async def fetch_light_cones_infos(): + tasks = [] + for light_cone in all_light_cones: + tasks.append(fetch_info(light_cone)) + await asyncio.gather(*tasks) + + +async def dump_light_cones(path: Path): + data = [light_cone.dict() for light_cone in all_light_cones] + data.sort(key=lambda x: x["id"]) + async with aiofiles.open(path, "w", encoding="utf-8") as f: + await f.write(ujson.dumps(data, indent=4, ensure_ascii=False)) + + +async def read_light_cones(path: Path): + async with aiofiles.open(path, "r", encoding="utf-8") as f: + data = ujson.loads(await f.read()) + for light_cone in data: + m = LightCone(**light_cone) + all_light_cones.append(m) + all_light_cones_map[m.id] = m + all_light_cones_name[m.name] = m diff --git a/func/fetch_materials.py b/func/fetch_materials.py index 3d2ab35..ebd68e3 100644 --- a/func/fetch_materials.py +++ b/func/fetch_materials.py @@ -78,6 +78,7 @@ async def fetch_materials_infos(): async def dump_materials(path: Path): data = [material.dict() for material in all_materials] + data.sort(key=lambda x: x["id"]) async with aiofiles.open(path, "w", encoding="utf-8") as f: await f.write(ujson.dumps(data, indent=4, ensure_ascii=False)) diff --git a/main.py b/main.py index c7a7ad1..b5a4fc6 100644 --- a/main.py +++ b/main.py @@ -2,15 +2,20 @@ import asyncio from pathlib import Path from func.fetch_all import get_list from func.fetch_avatars import fetch_avatars, fetch_avatars_infos, dump_avatars, read_avatars +from func.fetch_light_cones import fetch_light_cones, fetch_light_cones_infos, dump_light_cones, read_light_cones from func.fetch_materials import fetch_materials, fetch_materials_infos, dump_materials, read_materials data_path = Path("data") data_path.mkdir(exist_ok=True) -async def main(override: bool = True): +async def main( + override_materials: bool = True, + override_avatars: bool = True, + override_light_cones: bool = True, +): main_data = await get_list() - if override: + if override_materials: await fetch_materials(main_data[4]) await fetch_materials(main_data[5], "消耗品") await fetch_materials(main_data[8], "任务道具") @@ -20,10 +25,22 @@ async def main(override: bool = True): await dump_materials(data_path / "materials.json") else: await read_materials(data_path / "materials.json") - await fetch_avatars(main_data[0]) - await fetch_avatars_infos() - await dump_avatars(data_path / "avatars.json") + if override_avatars: + await fetch_avatars(main_data[0]) + await fetch_avatars_infos() + await dump_avatars(data_path / "avatars.json") + else: + await read_avatars(data_path / "avatars.json") + if override_light_cones: + await fetch_light_cones(main_data[1]) + await fetch_light_cones_infos() + await dump_light_cones(data_path / "light_cones.json") + else: + await read_light_cones(data_path / "light_cones.json") if __name__ == '__main__': - asyncio.run(main()) + override_materials = True + override_avatars = True + override_light_cones = True + asyncio.run(main(override_materials, override_avatars, override_light_cones)) diff --git a/models/light_cone.py b/models/light_cone.py index 4ee01ee..d134a5e 100644 --- a/models/light_cone.py +++ b/models/light_cone.py @@ -1,23 +1,19 @@ # 光锥 from pydantic import BaseModel -from typing import TYPE_CHECKING - from models.enums import Quality, Destiny - -if TYPE_CHECKING: - from models.material import Material +from models.material import Material class LightConeItem(BaseModel): - item: "Material" + item: Material """物品""" count: int """数量""" class LightConePromote(BaseModel): - required_level: int = 0 + required_level: int """突破所需等级""" promote_level: int = 0 """突破等级"""