一、选题背景
近年来电子竞技在当今社会越来越来受欢迎,同时电子竞技也成了亚运会项目之一。英雄联盟便是奥运会上的项目类型之一,我国便曾在亚运会的英雄联盟项目上拿下冠军。所以我便选择了,英雄联盟这个项目来作为我的设计目标。
二、主题式网络爬虫设计方案
1.主题式网络爬虫名称
OPGG里英雄联盟位置排名数据,及其可视化
2.主题式网络爬虫爬取的内容与数据特征分析
爬取opgg中上单及射手的排名,名字,胜率,出场率,并对其进行分析。
数据来源:" http://www.op.gg/champion/statistics
"
3.主题式网络爬虫设计方案概述
(1)实现思路
先对目标页面进行分析,利用urllib.爬虫库和BeautifulSoup库进行爬取解析,后分别用BeautifulSoup和正则表达式,分别查找所需要的数据。然后再保存为.csv文件,最后进行可视化分析。
(2)技术难点
request库出现问题,被迫学习使用urllib.request库,在编写re库的正则表达式中,发现自己熟练程度低,出错较多,在数据可视化上也出现了忘记代码的问题。
三、主题页面的结构特征分析
1.主题页面的结构与特征分析
首先得了解到本次爬取的网页为” http://www.op.gg/champion/statistics
”
首先是本机的usr-agent查询,做准备
由网站界面可以看出,右侧有英雄的详细信息,以Garen为例,胜率为53.84%,选取率为16.99%,常用位置为上单。
2.Htmls 页面解析
现对网页源代码进行分析
代码中共有5个tbody标签(tbody标签开头结尾均有”tbody”,故共有10个”tbody”),对字段内容分析,分别为上单、打野、中单、ADC、辅助信息。
再对tbody标签进行查找
由此代码可看出,英雄名、胜率及选取率都在td标签中,而每一个英雄信息在一个tr标签中,td父标签为tr标签,tr父标签为tbody标签
3.节点(标签)查找方法与遍历方法
计划将Beautifulsoup查找,re库.正则表达式搭配查找
四、网络爬虫程序设计
1.数据爬取与采集
1 def askurl(urlbase): 2 import.urllib.request 引用urllib.request库 3 #模拟浏览器头部信息,向网站发送信息
4 header={ 5 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62"
6 } 7 #模拟用户代理
8 request = urllib.request.Request(urlbase,headers=header) 9 #异常超时处理
10 try: 11 # html=""
12 response = urllib.request.urlopen(request,timeout=5) 13 html = response.read().decode("utf-8") 14 # print(html)
15 except Exception as a: 16 print(a) 17 return html
2.数据解析与整理
(1)采用BeautifulSoup解析提取数据
1 import pandas as pd #导入pandas库
2 import bs4 # 导入bs4库
3 from bs4 import BeautifulSoup # 导入BeautifulSoup库
4 url = "http://www.op.gg/champion/statistics"
5 # 获得html文档信息
6 html = askurl(url) 7 #解析数据
8 soup = BeautifulSoup(html,"html.parser") 9 top = [] 10 name = [] #建立空列表用于储存数据
11 winRate = [] 12 pickRate = [] 13 # 遍历上单tbody标签的儿子标签
14 for tr in soup.find(name = "tbody",attrs = "tabItem champion-trend-tier-TOP").children: 15 # 判断tr是否为标签类型,去除空行
16 if isinstance(tr,bs4.element.Tag): 17 # 查找tr标签下的td标签
18 tds = tr('td') 19 #排名
20 top.append(tds[0].string) 21 # 英雄名
22 name.append(tds[3].find(attrs = "champion-index-table__name").string) 23 # 胜率 %百分号对后续有影响,去除
24 winRate.append(tds[4].string.replace('%','')) 25 # 选取率
26 pickRate.append(tds[5].string.replace('%','')) 27
28 #将准确获得的数据保存到列表中
29 df1 = pd.DataFrame(data=[top, name, winRate, pickRate], index=['排名', '英雄名', '英雄胜率', '英雄出场率']) 30 # 对文本进行,行换列,列换行
31 df2 = pd.DataFrame(df1.values.T, columns=df1.index) 32 # 保存数据到xlsx文件中
33 df2.to_excel('上单top.xlsx')
(2)利用正则提取法提取数据
1 from bs4 import BeautifulSoup 2 import re 3 import pandas as pd 4 ADtop = [] 5 ADname = [] # 设立空表格
6 ADwinrate = [] 7 ADpickrate = [] 8 url = "http://www.op.gg/champion/statistics"
9 # 获得html文档信息
10 html = askurl(url) 11 # 解析数据
12 soup = BeautifulSoup(html, "html.parser") 13 # 取得射手标签内容
14 ADCdata = soup.find_all("tbody", class_="tabItem champion-trend-tier-ADC") 15 # 将BeautifulSoup类型转换为字符串类型,以便使用re库下的正则搜索
16 strADCdata = str(ADCdata) 17 # 通过网页源码得知,需要收集的排名,英雄名信息为23条
18 for i in range(0, 23): 19 # 排名 设定约束
20 findTop = re.compile(r'<td class="champion-index-table__cell champion-index-table__cell--rank">(\d*?)</td>') 21 # 运用约束查找
22 ADtop.append(re.findall(findTop, strADCdata)[i]) 23 # 英雄名字 设定约束
24 findName = re.compile(r'<div class="champion-index-table__name">(.*?)</div>') 25 # 运用约束查找
26 ADname.append(re.findall(findName, strADCdata)[i]) 27 # 运用正则表达式查找时发现胜率与选取率除了标签,内容完全相同,导致在上面的循环中会出现交叉获得的问题出现。
28 for i in range(0, 46, 2): 29 # 英雄胜率 设定约束 获得偶数的胜率
30 findWin = re.compile( 31 r'<td class="champion-index-table__cell champion-index-table__cell--value">(\d{0,3}\.\d{2})%</td>') 32 ADwinrate.append(re.findall(findWin, strADCdata)[i]) 33 # 英雄出场率 设定约束 获得奇数的出场率
34 findappear = re.compile( 35 r'<td class="champion-index-table__cell champion-index-table__cell--value">(\d{0,3}\.\d{2})%</td') 36 ADpickrate.append(re.findall(findappear, strADCdata)[i + 1]) 37
38 # 将数据保存到列表中
39 df3 = pd.DataFrame(data=[ADtop, ADname, ADwinrate, ADpickrate], index=['排名', '英雄名', '英雄胜率', '英雄出场率']) 40 # 对文本进行,行换列,列换行
41 df4 = pd.DataFrame(df3.values.T, columns=df3.index) 42 # 保存数据到xlsx文件中
43 df4.to_excel('射手top.xlsx')
3.对数据进行清洗和处理
TOPrank = pd.DataFrame(pd.read_excel('上单top.xlsx')) print(TOPrank.head()) # #无无效列 # 检查是否有重复值 print(TOPrank.duplicated()) # 检查是否有空值 print(TOPrank['排名'].isnull().value_counts()) # 异常值处理 print(TOPrank.describe()) # 发现“排名”字段的最大值为56而平均值为28,假设异常值为56 # print(top.replace([56, top['排名'].mean()]))
4.数据分析与可视化
1:运用pyecharts制作表格视图
1 import pandas as pd 2 from pyecharts.components import Table 3 from pyecharts.options import ComponentTitleOpts 4
5 # 分别导入上单数据
6 df_top = pd.read_excel('上单top.xlsx') 7 TOPTop = df_top['排名'].values.tolist() 8 TOPName = df_top['英雄名'].values.tolist() 9 TOPWin = df_top['英雄胜率'].values.tolist() 10 TOPpick = df_top['英雄出场率'].values.tolist() 11
12 #绘制上单表格视图
13 table2 = Table() 14 headers2 = ["排名", "英雄名", "英雄胜率", "英雄出场率"] 15 rows2 = [ 16
17 ] 18 for i in range(0, 56): 19 rows2.append(df_top.iloc[i].values.tolist()[1:]) #转换插入格式
20 table2.add(headers2, rows2) #插入数据
21 table2.set_global_opts( 22 title_opts=ComponentTitleOpts(title="上单-强度排行", subtitle="实时更新") #设置标题与副标题
23 ) 24 table2.render("上单pyecharts表格.html")
2:运用pyecharts制作柱状图
1 import pandas as pd 2 from pyecharts.charts import Bar 3 from pyecharts.globals import ThemeType 4 from pyecharts.options import global_options as opts 5
6 # 分别导入上单数据
7 df_top = pd.read_excel('上单top.xlsx') 8 TOPTop = df_top['排名'].values.tolist() 9 TOPName = df_top['英雄名'].values.tolist() 10 TOPWin = df_top['英雄胜率'].values.tolist() 11 TOPpick = df_top['英雄出场率'].values.tolist() 12
13 # 绘制上单柱状图
14 c = ( 15 Bar({"theme": ThemeType.MACARONS}) 16 .add_xaxis(TOPName) # 设置x轴
17 .add_yaxis("英雄胜率", TOPWin) # 添加柱状体
18 .add_yaxis("英雄出场率", TOPpick) 19 .set_global_opts( 20 title_opts={"text": "上单强度-示意图", "subtext": "综合胜率与出场率"}, # 设置标题与副标题
21 datazoom_opts=opts.DataZoomOpts(), # 分段
22 xaxis_opts=opts.AxisOpts(name_rotate=60, name="英雄名", axislabel_opts={"rotate": 35}) # 字体倾斜角度
23
24 ) 25 .render("上单强度柱状图.html") 26 )
3:运用pyecharts制作折线图
1 import pandas as pd 2 import pyecharts.options as opts 3 from pyecharts.charts import Line 4 # 分别导入上单数据
5 df_top = pd.read_excel('上单top.xlsx') 6 TOPTop = df_top['排名'].values.tolist() 7 TOPName = df_top['英雄名'].values.tolist() 8 TOPWin = df_top['英雄胜率'].values.tolist() 9 TOPpick = df_top['英雄出场率'].values.tolist() 10 ( 11 Line() 12 .set_global_opts( 13 tooltip_opts=opts.TooltipOpts(is_show=False), 14 xaxis_opts=opts.AxisOpts(type_="category"), 15 yaxis_opts=opts.AxisOpts( 16 type_="value", 17 axistick_opts=opts.AxisTickOpts(is_show=True), 18 splitline_opts=opts.SplitLineOpts(is_show=True), 19 ), 20 ) 21 .add_xaxis(xaxis_data=TOPName) 22 .add_yaxis( 23 series_name="", 24 y_axis=TOPWin, 25 symbol="emptyCircle", 26 is_symbol_show=True, 27 label_opts=opts.LabelOpts(is_show=False), 28 ) 29 .set_global_opts( 30 title_opts={"text": "上单英雄-对应胜率曲线", "subtext": "强度曲线"} , 31 datazoom_opts=opts.DataZoomOpts(),#分段
32 xaxis_opts=opts.AxisOpts(name_rotate=60, name="英雄名", axislabel_opts={"rotate": 35})#字体倾斜角度
33 ) 34 .render("上单胜率折线图.html") 35 )
5.完整代码
1 # -*- coding = utf-8 -*-
2 # @Time : 2021/12/24 14:49
3 # @Author : 真建彬
4 # @Student Number: 2003010221
5 # @File : demo2.py
6
7 def askurl(urlbase): 8 import urllib.request 9 #模拟浏览器头部信息,向网站发送信息
10 header={ 11 "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/96.0.4664.110 Safari/537.36 Edg/96.0.1054.62"
12 } 13 #模拟用户代理
14 request = urllib.request.Request(urlbase,headers=header) 15 #异常超时处理
16 try: 17 # html=""
18 response = urllib.request.urlopen(request,timeout=5) 19 html = response.read().decode("utf-8") 20 # print(html)
21 except Exception as a: 22 print(a) 23 return html 24
25 def main(): 26 from pyecharts.charts import Bar 27 from pyecharts.globals import ThemeType 28 import re 29 import pandas as pd 30 import bs4 # 导入bs4库
31 from bs4 import BeautifulSoup # 导入BeautifulSoup库
32 import pyecharts.options as opts 33 from pyecharts.charts import Line 34 from pyecharts.components import Table 35 from pyecharts.options import ComponentTitleOpts 36 url = "http://www.op.gg/champion/statistics"
37 # 获得html文档信息
38 html = askurl(url) 39 #解析数据
40 soup = BeautifulSoup(html,"html.parser") 41 top = [] 42 name = [] #建立空列表用于储存数据
43 winRate = [] 44 pickRate = [] 45 # 遍历上单tbody标签的儿子标签
46 for tr in soup.find(name = "tbody",attrs = "tabItem champion-trend-tier-TOP").children: 47 # 判断tr是否为标签类型,去除空行
48 if isinstance(tr,bs4.element.Tag): 49 # 查找tr标签下的td标签
50 tds = tr('td') 51 #排名
52 top.append(tds[0].string) 53 # 英雄名
54 name.append(tds[3].find(attrs = "champion-index-table__name").string) 55 # 胜率 %百分号对后续有影响,去除
56 winRate.append(tds[4].string.replace('%','')) 57 # 选取率
58 pickRate.append(tds[5].string.replace('%','')) 59
60 #将准确获得的数据保存到列表中
61 df1 = pd.DataFrame(data=[top, name, winRate, pickRate], index=['排名', '英雄名', '英雄胜率', '英雄出场率']) 62 # 对文本进行,行换列,列换行
63 df2 = pd.DataFrame(df1.values.T, columns=df1.index) 64 # 保存数据到xlsx文件中
65 df2.to_excel('上单top.xlsx') 66 ##读取文件
67 TOPrank = pd.DataFrame(pd.read_excel('上单top.xlsx')) 68 print(TOPrank.head()) 69
70 # #无无效列
71
72 # 检查是否有重复值
73 print(TOPrank.duplicated()) 74
75 # 检查是否有空值
76 print(TOPrank['排名'].isnull().value_counts()) 77
78 # 异常值处理
79 print(TOPrank.describe()) 80 # 发现“排名”字段的最大值为56而平均值为28,假设异常值为56
81 # print(top.replace([56, top['排名'].mean()]))
82
83
84
85 ADtop=[] 86 ADname=[] #设立空表格
87 ADwinrate=[] 88 ADpickrate=[] 89 #取得射手标签内容
90 ADCdata = soup.find_all("tbody", class_="tabItem champion-trend-tier-ADC") 91 #将BeautifulSoup类型转换为字符串类型,以便使用re库下的正则搜索
92 strADCdata = str(ADCdata) 93 #通过网页源码得知,需要收集的排名,英雄名信息为23条
94 for i in range(0,23): 95 #排名 设定约束
96 findTop = re.compile(r'<td class="champion-index-table__cell champion-index-table__cell--rank">(\d*?)</td>') 97 #运用约束查找
98 ADtop.append(re.findall(findTop,strADCdata)[i]) 99 #英雄名字 设定约束
100 findName=re.compile(r'<div class="champion-index-table__name">(.*?)</div>') 101 #运用约束查找
102 ADname.append(re.findall(findName,strADCdata)[i]) 103 #运用正则表达式查找时发现胜率与选取率除了标签,内容完全相同,导致在上面的循环中会出现交叉获得的问题出现。
104 for i in range(0,46,2): 105 #英雄胜率 设定约束 获得偶数的胜率
106 findWin=re.compile(r'<td class="champion-index-table__cell champion-index-table__cell--value">(\d{0,3}\.\d{2})%</td>') 107 ADwinrate.append(re.findall(findWin,strADCdata)[i]) 108 #英雄出场率 设定约束 获得奇数的出场率
109 findappear=re.compile(r'<td class="champion-index-table__cell champion-index-table__cell--value">(\d{0,3}\.\d{2})%</td') 110 ADpickrate.append(re.findall(findappear,strADCdata)[i+1]) 111
112 # 将数据保存到列表中
113 df3 =pd.DataFrame(data=[ADtop, ADname, ADwinrate, ADpickrate], index=['排名','英雄名','英雄胜率','英雄出场率']) 114 # 对文本进行,行换列,列换行
115 df4 = pd.DataFrame(df3.values.T, columns=df3.index) 116 # 保存数据到xlsx文件中
117 df4.to_excel('射手top.xlsx') 118
119 ADCrank = pd.DataFrame(pd.read_excel('射手top.xlsx')) 120 print(ADCrank.head()) 121
122 # #无无效列
123
124 # 检查是否有重复值
125 print(ADCrank.duplicated()) 126
127 # 检查是否有空值
128 print(ADCrank['排名'].isnull().value_counts()) 129
130 # 异常值处理
131 print(ADCrank.describe()) 132 # 发现“排名”字段的最大值为23而平均值为12,假设异常值为23
133 # print(top2.replace([23, top2['排名'].mean()]))
134
135 #数据可视化:
136
137
138 #分别导入射手数据
139 df_ADC = pd.read_excel('射手top.xlsx') 140 ADCTop = df_ADC['排名'].values.tolist() 141 ADCName = df_ADC['英雄名'].values.tolist() 142 ADCWin = df_ADC['英雄胜率'].values.tolist() 143 ADCpick = df_ADC['英雄出场率'].values.tolist() 144
145 #分别导入上单数据
146 df_top = pd.read_excel('上单top.xlsx') 147 TOPTop = df_top['排名'].values.tolist() 148 TOPName = df_top['英雄名'].values.tolist() 149 TOPWin = df_top['英雄胜率'].values.tolist() 150 TOPpick = df_top['英雄出场率'].values.tolist() 151
152 #利用pyecharts做表格视图
153
154 #绘制射手表格视图
155 table1 = Table() 156 headers1 = ["排名", "英雄名", "英雄胜率", "英雄出场率"] #设置表头
157 rows1 = [ 158
159 ] 160 for i in range(0, 23): 161 rows1.append(df_ADC.iloc[i].values.tolist()[1:]) #转换插入格式
162 table1.add(headers1, rows1) #插入数据
163 table1.set_global_opts( 164 title_opts=ComponentTitleOpts(title="射手-强度排行", subtitle="实时更新") #设置标题与副标题
165 ) 166 table1.render("射手pyecharts表格.html") 167
168
169 #绘制上单表格视图
170 table2 = Table() 171 headers2 = ["排名", "英雄名", "英雄胜率", "英雄出场率"] 172 rows2 = [ 173
174 ] 175 for i in range(0, 56): 176 rows2.append(df_top.iloc[i].values.tolist()[1:]) #转换插入格式
177 table2.add(headers2, rows2) #插入数据
178 table2.set_global_opts( 179 title_opts=ComponentTitleOpts(title="上单-强度排行", subtitle="实时更新") #设置标题与副标题
180 ) 181 table2.render("上单pyecharts表格.html") #记录
182
183 #利用pyecharts绘制柱状图
184
185 #绘制射手柱状图
186 c = ( 187 Bar({"theme": ThemeType.MACARONS}) 188 .add_xaxis(ADCName) #设置x轴
189 .add_yaxis("英雄胜率", ADCWin) #添加柱状体
190 .add_yaxis("英雄出场率", ADCpick) 191 .set_global_opts( 192 title_opts={"text": "射手强度-示意图", "subtext": "综合胜率与出场率"}, #设置标题与副标题
193 datazoom_opts=opts.DataZoomOpts(), # 分段
194 xaxis_opts=opts.AxisOpts(name_rotate=60, name="英雄名", axislabel_opts={"rotate": 35}) # 字体倾斜角度
195
196 ) 197 .render("ADC强度柱状图.html") 198 ) 199 #绘制上单柱状图
200 c = ( 201 Bar({"theme": ThemeType.MACARONS}) 202 .add_xaxis(TOPName) #设置x轴
203 .add_yaxis("英雄胜率", TOPWin) #添加柱状体
204 .add_yaxis("英雄出场率", TOPpick) 205 .set_global_opts( 206 title_opts={"text": "上单强度-示意图", "subtext": "综合胜率与出场率"}, #设置标题与副标题
207 datazoom_opts=opts.DataZoomOpts(), # 分段
208 xaxis_opts=opts.AxisOpts(name_rotate=60, name="英雄名", axislabel_opts={"rotate": 35}) # 字体倾斜角度
209
210 ) 211 .render("上单强度柱状图.html") 212 ) 213
214
215 #利用pyecharts绘制折线图:
216
217 #绘制射手折线图
218 ( 219 Line() 220 .set_global_opts( 221 tooltip_opts=opts.TooltipOpts(is_show=False), 222 xaxis_opts=opts.AxisOpts(type_="category"), 223 yaxis_opts=opts.AxisOpts( 224 type_="value", 225 axistick_opts=opts.AxisTickOpts(is_show=True), 226 splitline_opts=opts.SplitLineOpts(is_show=True), 227 ), 228 ) 229 .add_xaxis(xaxis_data=ADCName) 230 .add_yaxis( 231 series_name="", 232 y_axis=ADCWin, 233 symbol="emptyCircle", 234 is_symbol_show=True, 235 label_opts=opts.LabelOpts(is_show=False), 236 ) 237 .set_global_opts( 238 title_opts={"text": "射手英雄-对应胜率曲线", "subtext": "强度曲线"}, 239 datazoom_opts=opts.DataZoomOpts(), # 分段
240 xaxis_opts=opts.AxisOpts(name_rotate=60, name="英雄名", axislabel_opts={"rotate": 35}) # 字体倾斜角度
241
242 ) 243 .render("ADC胜率折线图.html") 244 ) 245
246 #绘制上单折线图
247 ( 248 Line() 249 .set_global_opts( 250 tooltip_opts=opts.TooltipOpts(is_show=False), 251 xaxis_opts=opts.AxisOpts(type_="category"), 252 yaxis_opts=opts.AxisOpts( 253 type_="value", 254 axistick_opts=opts.AxisTickOpts(is_show=True), 255 splitline_opts=opts.SplitLineOpts(is_show=True), 256 ), 257 ) 258 .add_xaxis(xaxis_data=TOPName) 259 .add_yaxis( 260 series_name="", 261 y_axis=TOPWin, 262 symbol="emptyCircle", 263 is_symbol_show=True, 264 label_opts=opts.LabelOpts(is_show=False), 265 ) 266 .set_global_opts( 267 title_opts={"text": "上单英雄-对应胜率曲线", "subtext": "强度曲线"}, 268 datazoom_opts=opts.DataZoomOpts(), # 分段
269 xaxis_opts=opts.AxisOpts(name_rotate=60, name="英雄名", axislabel_opts={"rotate": 35}) # 字体倾斜角度
270
271 ) 272 .render("上单胜率折线图.html") 273 ) 274
275
276
277
278
279 #调用主函数
280 if __name__=="__main__": 281 main() 282 print('程序运行成功')
五、总结
1.经过对主题数据的分析与可视化,可以得到哪些结论?是否达到预期的目标?
结论:
(1)游戏里英雄强度强,并不代表着高选取率高胜率的结果。
(2)获取和处理数据上我巩固了许多知识,使运用他们变得更加熟悉。
达到了预期的目标。
2.在完成此设计过程中,得到哪些收获?以及要改进的建议?
收获:熟练了爬虫的操作,且能更好的运用正则表达式
建议:有很多小细节卡了我很久,还是需要稳固基础。