mirror of
https://github.com/PaiGramTeam/PaiGram.git
synced 2024-11-26 02:11:03 +00:00
添加 wiki
模块
This commit is contained in:
parent
207a7a7e12
commit
0a0817b9cf
28
model/README.md
Normal file
28
model/README.md
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
# model 目录说明
|
||||||
|
|
||||||
|
|
||||||
|
## apihelpe 模块
|
||||||
|
|
||||||
|
用于获取米忽悠BBS的数据写的请求模块
|
||||||
|
|
||||||
|
## wiki 模块
|
||||||
|
|
||||||
|
### 计划
|
||||||
|
|
||||||
|
当成设计之初为考虑摆脱并消除第三方 `metadata` 数据结发生变化的影响
|
||||||
|
|
||||||
|
### 选择
|
||||||
|
|
||||||
|
关于选择那个Wiki数据获取我考虑了很久,还是选择了国外的蜜蜂网,原因还是有两个方面
|
||||||
|
|
||||||
|
页面形式几乎固定 未来不会发生变化
|
||||||
|
|
||||||
|
最主要还是有参考代码 极大减少了我工作量
|
||||||
|
|
||||||
|
毕竟如果从零开始真的头顶很凉 〒▽〒
|
||||||
|
|
||||||
|
### 感谢
|
||||||
|
|
||||||
|
| Nickname | Contribution |
|
||||||
|
|:----------------------------------------------------------:|--------------|
|
||||||
|
| [Crawler-ghhw](https://github.com/DGP-Studio/Crawler-ghhw) | 本项目参考的爬虫代码 |
|
0
model/wiki/__init__.py
Normal file
0
model/wiki/__init__.py
Normal file
186
model/wiki/characters.py
Normal file
186
model/wiki/characters.py
Normal file
@ -0,0 +1,186 @@
|
|||||||
|
import re
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from .helpers import get_headers
|
||||||
|
|
||||||
|
|
||||||
|
class Characters:
|
||||||
|
CHARACTERS_LIST_URL = "https://genshin.honeyhunterworld.com/db/char/characters/?lang=CHS"
|
||||||
|
ROOT_URL = "https://genshin.honeyhunterworld.com"
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.client = httpx.AsyncClient(headers=get_headers())
|
||||||
|
|
||||||
|
async def _get_soup(self, url: str) -> Optional[BeautifulSoup]:
|
||||||
|
request = await self.client.get(url)
|
||||||
|
return BeautifulSoup(request.text, "lxml")
|
||||||
|
|
||||||
|
async def get_all_characters_url(self):
|
||||||
|
url_list = []
|
||||||
|
soup = await self._get_soup(self.CHARACTERS_LIST_URL)
|
||||||
|
character_list = soup.find_all('div', {'class': 'char_sea_cont'})
|
||||||
|
for character in character_list:
|
||||||
|
character_link = self.ROOT_URL + character.a['href']
|
||||||
|
url_list.append(character_link)
|
||||||
|
return url_list
|
||||||
|
|
||||||
|
def get_characters_info_template(self):
|
||||||
|
characters_info_dict = {
|
||||||
|
"name": "",
|
||||||
|
"title": "",
|
||||||
|
"rarity": 0,
|
||||||
|
"element": {"name": "", "icon": ""},
|
||||||
|
"description": "",
|
||||||
|
"constellations": {},
|
||||||
|
"skills": {
|
||||||
|
"normal_attack": self.get_skills_info_template(),
|
||||||
|
"skill_e": self.get_skills_info_template(),
|
||||||
|
"skill_q": self.get_skills_info_template(),
|
||||||
|
"skill_replace": self.get_skills_info_template(),
|
||||||
|
},
|
||||||
|
"gacha_splash": ""
|
||||||
|
}
|
||||||
|
return characters_info_dict
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_skills_info_template():
|
||||||
|
skills_info_dict = {
|
||||||
|
"icon": "",
|
||||||
|
"name": "",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
return skills_info_dict
|
||||||
|
|
||||||
|
async def get_characters(self, url: str):
|
||||||
|
characters_info_dict = self.get_characters_info_template()
|
||||||
|
soup = await self._get_soup(url)
|
||||||
|
main_content = soup.find("div", {'class': 'wrappercont'})
|
||||||
|
char_name = main_content.find('div', {'class': 'custom_title'}).text
|
||||||
|
characters_info_dict["name"] = char_name
|
||||||
|
# 基础信息
|
||||||
|
char_info_table = main_content.find('table', {'class': 'item_main_table'}).find_all('tr')
|
||||||
|
for char_info_item in char_info_table:
|
||||||
|
content = char_info_item.find_all('td')
|
||||||
|
if content[0].text == "Title":
|
||||||
|
char_title = content[1].text
|
||||||
|
characters_info_dict["title"] = char_title
|
||||||
|
if content[0].text == "Allegiance":
|
||||||
|
char_allegiance = content[1].text
|
||||||
|
characters_info_dict["allegiance"] = char_allegiance
|
||||||
|
if content[0].text == "Rarity":
|
||||||
|
char_rarity = len(content[1].find_all('div', {'class': 'sea_char_stars_wrap'}))
|
||||||
|
characters_info_dict["rarity"] = char_rarity
|
||||||
|
if content[0].text == "Element":
|
||||||
|
char_element_icon_url = self.ROOT_URL + content[1].find('img')['data-src'].replace("_35", "")
|
||||||
|
characters_info_dict["element"]["icon"] = char_element_icon_url
|
||||||
|
if content[0].text == "Astrolabe Name":
|
||||||
|
char_astrolabe_name = content[1].text
|
||||||
|
if content[0].text == "In-game Description":
|
||||||
|
char_description = content[1].text
|
||||||
|
characters_info_dict["description"] = char_description
|
||||||
|
|
||||||
|
# 角色属性表格 咕咕咕
|
||||||
|
skill_dmg_wrapper = main_content.find('div', {'class': 'skilldmgwrapper'}).find_all('tr')
|
||||||
|
|
||||||
|
# 命之座
|
||||||
|
constellations_title = main_content.find('span', {'class': 'item_secondary_title'}, string="Constellations")
|
||||||
|
constellations_table = constellations_title.findNext('table', {'class': 'item_main_table'}).find_all('tr')
|
||||||
|
constellations_list = []
|
||||||
|
constellations_list_index = 0
|
||||||
|
for index, value in enumerate(constellations_table):
|
||||||
|
# 判断第一行
|
||||||
|
if index % 2 == 0:
|
||||||
|
constellations_dict = {
|
||||||
|
"icon": "",
|
||||||
|
"name": "",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
constellations_list.append(constellations_dict)
|
||||||
|
icon_url = self.ROOT_URL + value.find_all('img', {'class': 'itempic'})[-1]['data-src']
|
||||||
|
constellations_name = value.find_all('a', href=re.compile("/db/skill"))[-1].text
|
||||||
|
constellations_list[constellations_list_index]["icon"] = icon_url
|
||||||
|
constellations_list[constellations_list_index]["name"] = constellations_name
|
||||||
|
if index % 2 == 1:
|
||||||
|
constellations_description = value.find('div', {'class': 'skill_desc_layout'}).text
|
||||||
|
constellations_list[constellations_list_index]["description"] = constellations_description
|
||||||
|
constellations_list_index += 1
|
||||||
|
|
||||||
|
characters_info_dict["constellations"] = constellations_list
|
||||||
|
|
||||||
|
# 技能
|
||||||
|
skills_title = main_content.find('span', string='Attack Talents')
|
||||||
|
|
||||||
|
# 普攻
|
||||||
|
normal_attack_area = skills_title.find_next_sibling()
|
||||||
|
normal_attack_info = normal_attack_area.find_all('tr')
|
||||||
|
normal_attack_icon = self.ROOT_URL + normal_attack_info[0].find('img', {'class': 'itempic'})['data-src']
|
||||||
|
normal_attack_name = normal_attack_info[0].find('a', href=re.compile('/db/skill/')).text
|
||||||
|
normal_attack_desc = normal_attack_info[1].find('div', {'class': 'skill_desc_layout'}).text.replace(" ", "\n")
|
||||||
|
normal_attack = characters_info_dict["skills"]["normal_attack"]
|
||||||
|
normal_attack["icon"] = normal_attack_icon
|
||||||
|
normal_attack["name"] = normal_attack_name
|
||||||
|
normal_attack["description"] = normal_attack_desc
|
||||||
|
|
||||||
|
normal_attack_table_area = normal_attack_area.find_next_sibling()
|
||||||
|
# normal_attack_table = normal_attack_table_area.find_all('tr')
|
||||||
|
|
||||||
|
skill_e_area = normal_attack_table_area.find_next_sibling()
|
||||||
|
skill_e_info = skill_e_area.find_all('tr')
|
||||||
|
skill_e_icon = self.ROOT_URL + skill_e_info[0].find('img', {'class': 'itempic'})['data-src']
|
||||||
|
skill_e_name = skill_e_info[0].find('a', href=re.compile('/db/skill/')).text
|
||||||
|
skill_e_desc = skill_e_info[1].find('div', {'class': 'skill_desc_layout'}).text.replace(" ", "\n")
|
||||||
|
skill_e = characters_info_dict["skills"]["skill_e"]
|
||||||
|
skill_e["icon"] = skill_e_icon
|
||||||
|
skill_e["name"] = skill_e_name
|
||||||
|
skill_e["description"] = skill_e_desc
|
||||||
|
|
||||||
|
skill_e_table_area = skill_e_area.find_next_sibling()
|
||||||
|
# skillE_table = skillE_table_area.find_all('tr')
|
||||||
|
|
||||||
|
load_another_talent_q: bool = False
|
||||||
|
if char_name == "神里绫华" or char_name == "莫娜":
|
||||||
|
load_another_talent_q = True
|
||||||
|
|
||||||
|
skill_q_area = skill_e_table_area.find_next_sibling()
|
||||||
|
skill_q_info = skill_q_area.find_all('tr')
|
||||||
|
skill_q_icon = self.ROOT_URL + skill_q_info[0].find('img', {'class': 'itempic'})['data-src']
|
||||||
|
skill_q_name = skill_q_info[0].find('a', href=re.compile('/db/skill/')).text
|
||||||
|
skill_q_desc = skill_q_info[1].find('div', {'class': 'skill_desc_layout'}).text.replace(" ", "\n")
|
||||||
|
skill_q_table_area = skill_q_area.find_next_sibling()
|
||||||
|
# skill_q_table = skill_q_table_area.find_all('tr')
|
||||||
|
|
||||||
|
if load_another_talent_q:
|
||||||
|
skill_replace = characters_info_dict["skills"]["skill_replace"]
|
||||||
|
skill_replace["icon"] = skill_q_icon
|
||||||
|
skill_replace["name"] = skill_q_name
|
||||||
|
skill_replace["description"] = skill_q_desc
|
||||||
|
else:
|
||||||
|
skill_q = characters_info_dict["skills"]["skill_q"]
|
||||||
|
skill_q["icon"] = skill_q_icon
|
||||||
|
skill_q["name"] = skill_q_name
|
||||||
|
skill_q["description"] = skill_q_desc
|
||||||
|
|
||||||
|
if load_another_talent_q:
|
||||||
|
skill_q2_area = skill_q_table_area.find_next_sibling()
|
||||||
|
skill_q2_info = skill_q2_area.find_all('tr')
|
||||||
|
skill_q2_icon = self.ROOT_URL + skill_q2_info[0].find('img', {'class': 'itempic'})['data-src']
|
||||||
|
skill_q2_name = skill_q2_info[0].find('a', href=re.compile('/db/skill/')).text
|
||||||
|
skill_q2_desc = skill_q2_info[1].find('div', {'class': 'skill_desc_layout'}).text.replace(" ", "\n")
|
||||||
|
skill_q2 = characters_info_dict["skills"]["skill_q"]
|
||||||
|
skill_q2["icon"] = skill_q2_icon
|
||||||
|
skill_q2["name"] = skill_q2_name
|
||||||
|
skill_q2["description"] = skill_q2_desc
|
||||||
|
|
||||||
|
# 角色图片
|
||||||
|
char_pic_area = main_content.find('span', string='Character Gallery').find_next_sibling()
|
||||||
|
all_char_pic = char_pic_area.find("div", {"class": "gallery_cont"})
|
||||||
|
|
||||||
|
gacha_splash_rext = all_char_pic.find("span", {"class": "gallery_cont_span"}, string="Gacha Splash")
|
||||||
|
gacha_splash_pic_url = self.ROOT_URL + gacha_splash_rext.previous_element.previous_element["data-src"].replace(
|
||||||
|
"_70", "")
|
||||||
|
characters_info_dict["gacha_splash"] = gacha_splash_pic_url
|
||||||
|
|
||||||
|
return characters_info_dict
|
27
model/wiki/helpers.py
Normal file
27
model/wiki/helpers.py
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import re
|
||||||
|
|
||||||
|
ID_RGX = re.compile(r"/db/[^.]+_(?P<id>\d+)")
|
||||||
|
|
||||||
|
|
||||||
|
def get_headers():
|
||||||
|
headers = {
|
||||||
|
"accept": "text/html,application/xhtml+xml,application/xml;"
|
||||||
|
"q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9",
|
||||||
|
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
|
||||||
|
"AppleWebKit/537.36 (KHTML, like Gecko) Chrome/102.0.5005.63 Safari/537.36",
|
||||||
|
"referer": "https://genshin.honeyhunterworld.com/db/char/hutao/?lang=CHS",
|
||||||
|
}
|
||||||
|
return headers
|
||||||
|
|
||||||
|
|
||||||
|
def get_id_form_url(url: str):
|
||||||
|
matches = ID_RGX.search(url)
|
||||||
|
if matches is None:
|
||||||
|
return -1
|
||||||
|
entries = matches.groupdict()
|
||||||
|
if entries is None:
|
||||||
|
return -1
|
||||||
|
try:
|
||||||
|
return int(entries.get('id'))
|
||||||
|
except (IndexError, ValueError, TypeError):
|
||||||
|
return -1
|
85
model/wiki/metadata/ascension.json
Normal file
85
model/wiki/metadata/ascension.json
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
{
|
||||||
|
"504": {
|
||||||
|
"city": "Mondstadt",
|
||||||
|
"name": "高塔孤王",
|
||||||
|
"star": {
|
||||||
|
"value": 5,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||||
|
},
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_504.png"
|
||||||
|
},
|
||||||
|
"524": {
|
||||||
|
"city": "Mondstadt",
|
||||||
|
"name": "凛风奔狼",
|
||||||
|
"star": {
|
||||||
|
"value": 5,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||||
|
},
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_524.png"
|
||||||
|
},
|
||||||
|
"544": {
|
||||||
|
"city": "Mondstadt",
|
||||||
|
"name": "狮牙斗士",
|
||||||
|
"star": {
|
||||||
|
"value": 5,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||||
|
},
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_544.png"
|
||||||
|
},
|
||||||
|
"514": {
|
||||||
|
"city": "Liyue",
|
||||||
|
"name": "孤云寒林",
|
||||||
|
"star": {
|
||||||
|
"value": 5,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||||
|
},
|
||||||
|
"Key": "Weapon_Guyun",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_514.png"
|
||||||
|
},
|
||||||
|
"534": {
|
||||||
|
"city": "Liyue",
|
||||||
|
"name": "雾海云间",
|
||||||
|
"star": {
|
||||||
|
"value": 5,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||||
|
},
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_534.png"
|
||||||
|
},
|
||||||
|
"554": {
|
||||||
|
"city": "Liyue",
|
||||||
|
"name": "漆黑陨铁",
|
||||||
|
"star": {
|
||||||
|
"value": 5,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||||
|
},
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_554.png"
|
||||||
|
},
|
||||||
|
"564": {
|
||||||
|
"city": "Inazuma",
|
||||||
|
"name": "远海夷地",
|
||||||
|
"star": {
|
||||||
|
"value": 5,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||||
|
},
|
||||||
|
"Key": "Weapon_DistantSea",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_564.png"
|
||||||
|
},
|
||||||
|
"574": {
|
||||||
|
"city": "Inazuma",
|
||||||
|
"name": "鸣神御灵",
|
||||||
|
"star": {
|
||||||
|
"value": 5,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||||
|
},
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_574.png"
|
||||||
|
},
|
||||||
|
"584": {
|
||||||
|
"city": "Inazuma",
|
||||||
|
"name": "今昔剧画",
|
||||||
|
"star": {
|
||||||
|
"value": 5,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/5star.png"
|
||||||
|
},
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/weapon/i_584.png"
|
||||||
|
}
|
||||||
|
}
|
92
model/wiki/metadata/elite.json
Normal file
92
model/wiki/metadata/elite.json
Normal file
@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"63": {
|
||||||
|
"name": "号角",
|
||||||
|
"star": {
|
||||||
|
"value": 4,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||||
|
},
|
||||||
|
"key": "Elite_Horn",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_63.png"
|
||||||
|
},
|
||||||
|
"73": {
|
||||||
|
"name": "地脉",
|
||||||
|
"star": {
|
||||||
|
"value": 4,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||||
|
},
|
||||||
|
"key": "Elite_LeyLine",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_73.png"
|
||||||
|
},
|
||||||
|
"83": {
|
||||||
|
"name": "混沌",
|
||||||
|
"star": {
|
||||||
|
"value": 4,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||||
|
},
|
||||||
|
"key": "Elite_Chaos",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_83.png"
|
||||||
|
},
|
||||||
|
"93": {
|
||||||
|
"name": "雾虚",
|
||||||
|
"star": {
|
||||||
|
"value": 4,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||||
|
},
|
||||||
|
"key": "Elite_MistGrass",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_93.png"
|
||||||
|
},
|
||||||
|
"103": {
|
||||||
|
"name": "祭刀",
|
||||||
|
"star": {
|
||||||
|
"value": 4,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||||
|
},
|
||||||
|
"key": "Elite_SacrificialKnife",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_103.png"
|
||||||
|
},
|
||||||
|
"143": {
|
||||||
|
"name": "骨片",
|
||||||
|
"star": {
|
||||||
|
"value": 4,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||||
|
},
|
||||||
|
"key": "Elite_BoneShard",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_143.png"
|
||||||
|
},
|
||||||
|
"153": {
|
||||||
|
"name": "刻像",
|
||||||
|
"star": {
|
||||||
|
"value": 4,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||||
|
},
|
||||||
|
"key": "Elite_Statuette",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_153.png"
|
||||||
|
},
|
||||||
|
"173": {
|
||||||
|
"name": "混沌2",
|
||||||
|
"star": {
|
||||||
|
"value": 4,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||||
|
},
|
||||||
|
"key": "Elite_Chaos2",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_173.png"
|
||||||
|
},
|
||||||
|
"176": {
|
||||||
|
"name": "隐兽",
|
||||||
|
"star": {
|
||||||
|
"value": 4,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||||
|
},
|
||||||
|
"key": "Elite_Concealed",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_176.png"
|
||||||
|
},
|
||||||
|
"183": {
|
||||||
|
"name": "棱镜",
|
||||||
|
"star": {
|
||||||
|
"value": 4,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/4star.png"
|
||||||
|
},
|
||||||
|
"key": "Elite_Prism",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_183.png"
|
||||||
|
}
|
||||||
|
}
|
83
model/wiki/metadata/monster.json
Normal file
83
model/wiki/metadata/monster.json
Normal file
@ -0,0 +1,83 @@
|
|||||||
|
{
|
||||||
|
"23": {
|
||||||
|
"name": "史莱姆",
|
||||||
|
"star": {
|
||||||
|
"value": 3,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||||
|
},
|
||||||
|
"key": "Monster_Slime",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_23.png"
|
||||||
|
},
|
||||||
|
"33": {
|
||||||
|
"name": "面具",
|
||||||
|
"star": {
|
||||||
|
"value": 3,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||||
|
},
|
||||||
|
"Key": "Monster_Mask",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_33.png"
|
||||||
|
},
|
||||||
|
"43": {
|
||||||
|
"name": "绘卷",
|
||||||
|
"star": {
|
||||||
|
"value": 3,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||||
|
},
|
||||||
|
"Key": "Monster_Scroll",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_43.png"
|
||||||
|
},
|
||||||
|
"53": {
|
||||||
|
"name": "箭簇",
|
||||||
|
"star": {
|
||||||
|
"value": 3,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||||
|
},
|
||||||
|
"Key": "Monster_Scroll",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_43.png"
|
||||||
|
},
|
||||||
|
"113": {
|
||||||
|
"name": "徽记",
|
||||||
|
"star": {
|
||||||
|
"value": 3,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||||
|
},
|
||||||
|
"Key": "Monster_Insignia",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_113.png"
|
||||||
|
},
|
||||||
|
"123": {
|
||||||
|
"name": "鸦印",
|
||||||
|
"star": {
|
||||||
|
"value": 3,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||||
|
},
|
||||||
|
"Key": "Monster_RavenInsignia",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_123.png"
|
||||||
|
},
|
||||||
|
"133": {
|
||||||
|
"name": "花蜜",
|
||||||
|
"star": {
|
||||||
|
"value": 3,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||||
|
},
|
||||||
|
"Key": "Monster_Nectar",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_133.png"
|
||||||
|
},
|
||||||
|
"163": {
|
||||||
|
"name": "刀镡",
|
||||||
|
"star": {
|
||||||
|
"value": 3,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||||
|
},
|
||||||
|
"Key": "Monster_Handguard",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_163.png"
|
||||||
|
},
|
||||||
|
"187": {
|
||||||
|
"name": "浮游",
|
||||||
|
"star": {
|
||||||
|
"value": 3,
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/back/item/3star.png"
|
||||||
|
},
|
||||||
|
"Key": "Monster_Spectral",
|
||||||
|
"icon": "https://genshin.honeyhunterworld.com/img/upgrade/material/i_187.png"
|
||||||
|
}
|
||||||
|
}
|
215
model/wiki/weapons.py
Normal file
215
model/wiki/weapons.py
Normal file
@ -0,0 +1,215 @@
|
|||||||
|
import re
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Optional
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
import os
|
||||||
|
|
||||||
|
import ujson
|
||||||
|
from bs4 import BeautifulSoup
|
||||||
|
|
||||||
|
from .helpers import get_headers, get_id_form_url
|
||||||
|
|
||||||
|
|
||||||
|
class WeaponType(Enum):
|
||||||
|
Sword = "sword" # 单手剑
|
||||||
|
Claymore = "claymore" # 双手剑
|
||||||
|
PoleArm = "polearm" # 长柄武器
|
||||||
|
Bow = "bow" # 弓
|
||||||
|
Catalyst = "catalyst" # 法器
|
||||||
|
|
||||||
|
|
||||||
|
class Weapons:
|
||||||
|
IGNORE_WEAPONS_ID = [
|
||||||
|
"1001", "1101", "1406",
|
||||||
|
"2001", "2101", "2204", "2406", "2407",
|
||||||
|
"3001", "3101", "3204", "3404",
|
||||||
|
"4001", "4101", "4201", "4403", "4405", "4406",
|
||||||
|
"5001", "5101", "5201", "5404", "5404", "5405",
|
||||||
|
] # 忽略的武器包括一星、二星武器,beta表格内无名武器,未上架到正服的武器
|
||||||
|
|
||||||
|
# 地址
|
||||||
|
ROOT_URL = "https://genshin.honeyhunterworld.com"
|
||||||
|
|
||||||
|
# 正则表达式
|
||||||
|
# /db/weapon/w_3203/?lang=CHS
|
||||||
|
_GET_WEAPON_ID_BY_URL_RGX = re.compile(r"/db/[^.]+_(?P<weapon_id>\d+)")
|
||||||
|
|
||||||
|
TEXT_MAPPING = {
|
||||||
|
"Type": "类型",
|
||||||
|
"Rarity": "Rarity",
|
||||||
|
"Base Attack": "基础攻击力"
|
||||||
|
}
|
||||||
|
|
||||||
|
def __init__(self):
|
||||||
|
self.client = httpx.AsyncClient(headers=get_headers())
|
||||||
|
project_path = os.path.dirname(__file__)
|
||||||
|
characters_file = os.path.join(project_path, "metadata", "ascension.json")
|
||||||
|
monster_file = os.path.join(project_path, "metadata", "monster.json")
|
||||||
|
elite_file = os.path.join(project_path, "metadata", "elite.json")
|
||||||
|
with open(characters_file, "r", encoding="utf-8") as f:
|
||||||
|
self._ascension_json: dict = ujson.load(f)
|
||||||
|
with open(monster_file, "r", encoding="utf-8") as f:
|
||||||
|
self._monster_json: dict = ujson.load(f)
|
||||||
|
with open(elite_file, "r", encoding="utf-8") as f:
|
||||||
|
self._elite_json: dict = ujson.load(f)
|
||||||
|
|
||||||
|
def _get_weapon_id_by_url(self, url: str) -> int:
|
||||||
|
matches = self._GET_WEAPON_ID_BY_URL_RGX.search(url)
|
||||||
|
if matches is None:
|
||||||
|
return -1
|
||||||
|
entries = matches.groupdict()
|
||||||
|
if entries is None:
|
||||||
|
return -1
|
||||||
|
try:
|
||||||
|
art_id = int(entries.get('weapon_id'))
|
||||||
|
except (IndexError, ValueError, TypeError):
|
||||||
|
return -1
|
||||||
|
return art_id
|
||||||
|
|
||||||
|
async def _get_soup(self, url: str) -> Optional[BeautifulSoup]:
|
||||||
|
request = await self.client.get(url)
|
||||||
|
return BeautifulSoup(request.text, "lxml")
|
||||||
|
|
||||||
|
async def get_weapon_url_list(self, weapon_type: WeaponType):
|
||||||
|
weapon_url_list = []
|
||||||
|
url = self.ROOT_URL + f"/db/weapon/{weapon_type.value}/?lang=CHS"
|
||||||
|
soup = await self._get_soup(url)
|
||||||
|
weapon_table = soup.find("span", {"class": "item_secondary_title"},
|
||||||
|
string="Released (Codex) Weapons").find_next_sibling()
|
||||||
|
weapon_table_rows = weapon_table.find_all("tr")
|
||||||
|
for weapon_table_row in weapon_table_rows:
|
||||||
|
content = weapon_table_row.find_all("td")[2]
|
||||||
|
if content.find("a") is not None:
|
||||||
|
weapon_url = self.ROOT_URL + content.find("a")["href"]
|
||||||
|
weapon_id = self._get_weapon_id_by_url(weapon_url)
|
||||||
|
if weapon_id not in self.IGNORE_WEAPONS_ID:
|
||||||
|
weapon_url_list.append(weapon_url)
|
||||||
|
return weapon_url_list
|
||||||
|
|
||||||
|
async def get_all_weapon_url(self):
|
||||||
|
all_weapon_url = []
|
||||||
|
temp_data = await self.get_weapon_url_list(WeaponType.Bow)
|
||||||
|
all_weapon_url.extend(temp_data)
|
||||||
|
temp_data = await self.get_weapon_url_list(WeaponType.Sword)
|
||||||
|
all_weapon_url.extend(temp_data)
|
||||||
|
temp_data = await self.get_weapon_url_list(WeaponType.PoleArm)
|
||||||
|
all_weapon_url.extend(temp_data)
|
||||||
|
temp_data = await self.get_weapon_url_list(WeaponType.Catalyst)
|
||||||
|
all_weapon_url.extend(temp_data)
|
||||||
|
temp_data = await self.get_weapon_url_list(WeaponType.Claymore)
|
||||||
|
all_weapon_url.extend(temp_data)
|
||||||
|
return all_weapon_url
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_weapon_info_template():
|
||||||
|
weapon_info_dict = {
|
||||||
|
"name": "",
|
||||||
|
"description": "",
|
||||||
|
"atk":
|
||||||
|
{
|
||||||
|
"min": 0,
|
||||||
|
"max": 999999,
|
||||||
|
"name": "基础攻击力"
|
||||||
|
},
|
||||||
|
"secondary":
|
||||||
|
{
|
||||||
|
"min": 0.1,
|
||||||
|
"max": 999999.9,
|
||||||
|
"name": ""
|
||||||
|
},
|
||||||
|
"star":
|
||||||
|
{
|
||||||
|
"value": -1,
|
||||||
|
"icon": ""
|
||||||
|
},
|
||||||
|
"type":
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"icon": ""
|
||||||
|
},
|
||||||
|
"passive_ability":
|
||||||
|
{
|
||||||
|
"name": "",
|
||||||
|
"description": ""
|
||||||
|
}
|
||||||
|
}
|
||||||
|
materials_dict = {
|
||||||
|
"name": "",
|
||||||
|
"star": {
|
||||||
|
"value": 0,
|
||||||
|
"icon": ""
|
||||||
|
},
|
||||||
|
"city": "",
|
||||||
|
"icon": ""
|
||||||
|
}
|
||||||
|
weapon_info_dict["materials"] = {
|
||||||
|
"ascension": materials_dict,
|
||||||
|
"elite": materials_dict,
|
||||||
|
"monster": materials_dict,
|
||||||
|
}
|
||||||
|
return weapon_info_dict
|
||||||
|
|
||||||
|
async def get_weapon_info(self, url: str):
|
||||||
|
weapon_info_dict = self.get_weapon_info_template()
|
||||||
|
soup = await self._get_soup(url)
|
||||||
|
weapon_content = soup.find("div", {"class": "wrappercont"})
|
||||||
|
data = weapon_content.find("div", {"class": "data_cont_wrapper", "style": "display: block"})
|
||||||
|
weapon_info = data.find("table", {"class": "item_main_table"})
|
||||||
|
weapon_name = weapon_content.find("div", {"class": "custom_title"}).text.replace("-", "").replace(" ", "")
|
||||||
|
weapon_info_dict["name"] = weapon_name
|
||||||
|
weapon_info_row = weapon_info.find_all("tr")
|
||||||
|
for weapon_info_ in weapon_info_row:
|
||||||
|
content = weapon_info_.find_all("td")
|
||||||
|
if len(content) == 3: # 第一行会有三个td,其中一个td是武器图片
|
||||||
|
weapon_info_dict["source_img"] = content[0].find("img", {"class": "itempic lazy"})["data-src"]
|
||||||
|
weapon_info_dict["type"]["name"] = content[2].text
|
||||||
|
elif len(content) == 2:
|
||||||
|
if content[0].text == "Rarity":
|
||||||
|
weapon_info_dict["star"]["value"] = len(
|
||||||
|
content[1].find_all("div", {"class": "sea_char_stars_wrap"}))
|
||||||
|
elif content[0].text == "Special (passive) Ability":
|
||||||
|
weapon_info_dict["passive_ability"]["name"] = content[1].text
|
||||||
|
elif content[0].text == "Special (passive) Ability Description":
|
||||||
|
weapon_info_dict["passive_ability"]["description"] = content[1].text
|
||||||
|
elif content[0].text == "In-game Description":
|
||||||
|
weapon_info_dict["description"] = content[1].text
|
||||||
|
elif content[0].text == "Secondary Stat":
|
||||||
|
weapon_info_dict["secondary"]["name"] = content[1].text
|
||||||
|
|
||||||
|
stat_table = data.find("span", {"class": "item_secondary_title"},
|
||||||
|
string=" Stat Progression ").find_next_sibling()
|
||||||
|
stat_table_row = stat_table.find_all("tr")
|
||||||
|
for stat_table_ in stat_table_row:
|
||||||
|
content = stat_table_.find_all("td")
|
||||||
|
# 通过等级判断
|
||||||
|
if content[0].text == "1":
|
||||||
|
weapon_info_dict["atk"]["min"] = int(content[1].text)
|
||||||
|
weapon_info_dict["secondary"]["min"] = float(content[2].text)
|
||||||
|
elif content[0].text == "80+":
|
||||||
|
item_hrefs = content[3].find_all("a")
|
||||||
|
for item_href in item_hrefs:
|
||||||
|
item_id = get_id_form_url(item_href["href"])
|
||||||
|
ascension = self.get_ascension(str(item_id))
|
||||||
|
if ascension.get("name") is not None:
|
||||||
|
weapon_info_dict["materials"]["ascension"] = ascension
|
||||||
|
monster = self.get_monster(str(item_id))
|
||||||
|
if monster.get("name") is not None:
|
||||||
|
weapon_info_dict["materials"]["monster"] = monster
|
||||||
|
elite = self.get_elite(str(item_id))
|
||||||
|
if elite.get("name") is not None:
|
||||||
|
weapon_info_dict["materials"]["elite"] = elite
|
||||||
|
elif content[0].text == "90":
|
||||||
|
weapon_info_dict["atk"]["max"] = int(content[1].text)
|
||||||
|
weapon_info_dict["secondary"]["max"] = float(content[2].text)
|
||||||
|
|
||||||
|
return weapon_info_dict
|
||||||
|
|
||||||
|
def get_ascension(self, item_id: str):
|
||||||
|
return self._ascension_json.get(item_id, {})
|
||||||
|
|
||||||
|
def get_monster(self, item_id: str):
|
||||||
|
return self._monster_json.get(item_id, {})
|
||||||
|
|
||||||
|
def get_elite(self, item_id: str):
|
||||||
|
return self._elite_json.get(item_id, {})
|
@ -13,3 +13,4 @@ PyMySQL>=0.9.3
|
|||||||
beautifulsoup4>=4.11.1
|
beautifulsoup4>=4.11.1
|
||||||
python-telegram-bot==20.0a0
|
python-telegram-bot==20.0a0
|
||||||
pyppeteer
|
pyppeteer
|
||||||
|
lxml>=4.9.0
|
Loading…
Reference in New Issue
Block a user