《坦克世界》(World of Tanks, WOT)
是我在本科期间玩过的一款战争网游,由Wargaming
公司研发。2010年10月30日在俄罗斯首发,2011年4月12日在北美和欧洲推出,2011年3月15日在中国由空中网代理推出(2020年,国服由360代理)。游戏背景设定在二战时期,玩家会扮演1930到1960年代的战车进行对战,要求战略和合作性,游戏中的战车根据历史高度还原。
坦克世界官网:https://wotgame.cn/
坦克世界坦克百科:https://wotgame.cn/zh-cn/tankopedia/#wot&w_m=tanks
当前的WOT
有五种坦克类型,11个系别。我们要构建一个关于坦克百科的知识图谱,接下来就要通过爬虫来获取所有坦克的详细信息,比如坦克的等级、火力、机动性、防护能力、侦察能力等等。以当前的八级霸主中国重型坦克BZ-176
为例,坦克的详细信息如下:
常规操作,F12+F5
查看一下页面信息,定位到坦克列表的具体请求:
是一个POST
请求,返回的是一个JSON
格式的数据,包含了该类型坦克的一些基本信息:
特别说明一下:构建该请求
header
时,Content-Length
参数是必须的。
代码实现:
# -*- coding: utf-8 -*-
# Author : xiayouran
# Email : [email protected]
# Datetime: 2023/9/29 22:43
# Filename: spider_wot.py
import os
import time
import json
import requests
class WOTSpider:
def __init__(self):
self.base_headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) '
'Chrome/117.0.0.0 Safari/537.36',
'Accept-Encoding': 'gzip, deflate, br',
'Accept-Language': 'zh-CN,zh;q=0.9',
}
self.post_headers = {
'Accept': 'application/json, text/javascript, */*; q=0.01',
'Content-Length': '135',
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
}
self.from_data = {
'filter[nation]': '',
'filter[type]': 'lightTank',
'filter[role]': '',
'filter[tier]': '',
'filter[language]': 'zh-cn',
'filter[premium]': '0,1'
}
self.tank_list_url = 'https://wotgame.cn/wotpbe/tankopedia/api/vehicles/by_filters/'
self.tank_label = ['lightTank', 'mediumTank', 'heavyTank', 'AT-SPG', 'SPG']
self.tanks = {}
def parser_tanklist_html(self, html_text):
json_data = json.loads(html_text)
for data in json_data['data']['data']:
self.tanks[data[0] + '_' + data[4]] = {
'tank_nation': data[0],
'tank_type': data[1],
'tank_rank': data[3],
'tank_name': data[4],
'tank_name_s': data[5],
'tank_url': data[6],
'tank_id': data[7]
}
def run(self):
for label in self.tank_label:
self.from_data['filter[type]'] = label
html_text = self.get_html(self.tank_list_url, method='POST', from_data=self.from_data)
if not html_text:
print('[{}] error'.format(label))
continue
self.parser_tanklist_html(html_text)
time.sleep(3)
self.save_json(os.path.join(self.data_path, 'tank_list.json'), self.tanks)
if __name__ == '__main__':
tank_spider = WOTSpider()
tank_spider.run()
上述代码只实现了一些重要的函数及变量声明,完整的代码可以从github
上拉取:WOT
坦克具体信息的页面就是一个纯HTML
页面了,一个GET
请求就可以获得。当然啦,具体怎么分析的就不细说了,对爬虫技术感兴趣的同学们可以找找资料,这里就只说一下抓取流程。
先分析GET
请求:https://wotgame.cn/zh-cn/tankopedia/60209-Ch47_BZ_176/
,可以分成三部分:
Part 1
:基本的url
请求:https://wotgame.cn/zh-cn/tankopedia
;
Part 2
:坦克的id
:BZ-176
坦克的id
为60209
,每个坦克都是唯一的,这个参数通过上一个步骤的POST
请求可以获取到;
Part 3
:坦克的名称:Ch47_BZ_176
,这个参数也可以通过上一个步骤的POST
请求可以获取到。
这样就可以为每个坦克构造一个对应的url
了,只需解析该url
对应的界面即可。解析的时候我分成了两部分,先对坦克的基本信息进行解析,比如坦克系别、等级及价格等等,由BeautifulSoup
库实现,坦克的具体信息,比如火力、机动、防护及侦察能力,这些信息是由JavaScript
代码动态请求得到的,这里为了简便没有分析具体的js
代码,而是先使用selenium
库进行网页渲染,然后再使用BeautifulSoup
库进行解析。这里不再细说,下面给出页面解析的代码:
# -*- coding: utf-8 -*-
# Author : xiayouran
# Email : [email protected]
# Datetime: 2023/9/29 22:43
# Filename: spider_wot.py
import requests
from tqdm import tqdm
from bs4 import BeautifulSoup, Tag
from selenium import webdriver
from selenium.webdriver.common.by import By
from selenium.webdriver.support.ui import WebDriverWait
class WOTSpider:
def __init__(self):
pass
def is_span_with_value(self, driver):
try:
element = driver.find_element(By.XPATH, "//span[@data-bind=\"text: ttc().getFormattedBestParam('maxHealth', 'gt')\"]")
data = element.text.strip()
if data:
return True
except:
return False
def get_html_driver(self, url):
self.driver.get(url)
self.wait.until(self.is_span_with_value)
page_source = self.driver.page_source
return page_source
def parser_tankinfo_html(self, html_text):
tank_info = copy.deepcopy(self.tank_info)
soup = BeautifulSoup(html_text, 'lxml')
# tank_name = soup.find(name='h1', attrs={'class': 'garage_title garage_title__inline js-tank-title'}).strip()
tank_statistic = soup.find_all(name='div', attrs={'class': 'tank-statistic_item'})
for ts in tank_statistic:
ts_text = [t for t in ts.get_text().split('\n') if t]
if len(ts_text) == 5:
tank_info['价格'] = {
'银币': ts_text[-3],
'经验': ts_text[-1]
}
else:
tank_info[ts_text[0]] = ts_text[-1]
tank_property1 = soup.find(name='p', attrs='garage_objection')
tank_property2 = soup.find(name='p', attrs='garage_objection garage_objection__collector')
if tank_property1:
tank_info['性质'] = tank_property1.text
elif tank_property2:
tank_info['性质'] = tank_property2.text
else:
tank_info['性质'] = '银币坦克'
tank_desc_tag = soup.find(name='p', attrs='tank-description_notification')
if tank_desc_tag:
tank_info['历史背景'] = tank_desc_tag.text
tank_parameter = soup.find_all(name='div', attrs={'class': 'specification_block'})
for tp_tag in tank_parameter:
param_text = tp_tag.find_next(name='h2', attrs={'class': 'specification_title specification_title__sub'}).get_text()
# spec_param = tp_tag.find_all_next(name='div', attrs={'class': 'specification_item'})
spec_param = [tag for tag in tp_tag.contents if isinstance(tag, Tag) and tag.attrs['class'] == ['specification_item']]
spec_info = {}
for tp in spec_param:
tp_text = [t for t in tp.get_text().replace(' ', '').split('\n') if t]
if not tp_text or not tp_text[0][0].isdigit():
continue
spec_info[tp_text[-1]] = ' '.join(tp_text[:-1])
tank_info[param_text] = spec_info
return tank_info
def run(self):
file_list = [os.path.basename(file)[:-5] for file in glob.glob(os.path.join(self.data_path, '*.json'))]
for k, item in tqdm(self.tanks.items(), desc='Crawling'):
file_name = k.replace('"', '').replace('“', '').replace('”', '').replace('/', '-').replace('\\', '').replace('*', '+')
if file_name in file_list:
continue
tank_url = self.tank_url + str(item['tank_id']) + '-' + item['tank_url']
html_text = self.get_html_driver(tank_url)
# html_text = self.get_html(tank_url, method='GET')
tank_info = self.parser_tankinfo_html(html_text)
self.tanks[k].update(tank_info)
self.save_json(os.path.join(self.data_path, '{}.json'.format(file_name)), self.tanks[k])
time.sleep(1.5)
self.save_json(os.path.join(self.data_path, 'tank_list_detail.json'), self.tanks)
if __name__ == '__main__':
tank_spider = WOTSpider()
tank_spider.run()
大约半个小时即可获取全部的坦克信息,如下:
Selenium
库依赖chromedriver
,需要根据自己的Chrome
浏览器版本下载合适的版本,chromedriver
的官方下载地址为:https://chromedriver.chromium.org/downloads/version-selection
本篇的完整代码及爬取的结果已经同步到仓库中,感兴趣的话可以拉取一下,下一篇文章就基于当前获取到的坦克信息来构造一个关于坦克百科的知识图谱。
开源代码仓库
如果喜欢的话记得给我的GitHub
仓库WOT点个Star哦!ヾ(≧∇≦*)ヾ
公众号已开通:
夏小悠
,关注以获取更多关于Python
文章、AI
领域最新技术、LLM大模型相关论文及内部PPT
等资料^_^