一、選題背景
近年來電子競技在當今社會越來越來受歡迎,同時電子競技也成了亞運會項目之一。英雄聯盟便是奧運會上的項目類型之一,我國便曾在亞運會的英雄聯盟項目上拿下冠軍。所以我便選擇了,英雄聯盟這個項目來作為我的設計目標。
二、主題式網絡爬蟲設計方案
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.在完成此設計過程中,得到哪些收獲?以及要改進的建議?
收獲:熟練了爬蟲的操作,且能更好的運用正則表達式
建議:有很多小細節卡了我很久,還是需要穩固基礎。