英雄联盟胜负预测--简易肯德基上校

news/2024/11/16 22:30:26/

作为一名英雄联盟老玩家,巅峰时也曾打上过艾欧尼亚超凡大师,通过这关游戏让我认识了很多朋友,它也陪我度过了大部分校园青春。这是我第一次以学者的角度去面对它。

这里我们使用决策树ID3算法完成简易的英雄联盟胜负的预测。

AI遮天传 ML-决策树_老师我作业忘带了的博客-CSDN博客

第一步:战绩获取

首先呢我们可以进入英雄联盟官网,根据不同的游戏id爬取出这个赛季的详细战绩。

这里我们获取前10分钟的特征(因此不考虑纳什男爵等因素)

c47030c06c564890902595b61d2421c6.png

过程虽然有些麻烦但还算简单,感兴趣的可以自己爬一下(注:国内官网 和 opgg 返回的信息各有优缺点,不怕麻烦的建议使用opgg爬取外服某玩家战绩每个event一点点累加,比如我之前就是以大飞老师“hide on bush”为例,局数多也是韩服千分王者质量局。) 

也可以使用现成的:League of Legends Diamond Ranked Games (10 min) | Kaggle

815f4795542a42c99275acc6377379da.png

这里一共包含了9879场钻一到大师段位的单双排对局,对局双方几乎是同一水平。每条数据是前10分钟的对局情况,每支队伍有19个特征,红蓝双方共38个特征。这些特征包括英雄击杀、死亡,视野、金钱、经验、等级情况等等。

第二步:数据处理

导入工具包

import numpy as np
import pandas as pd
from collections import Counter
from sklearn.model_selection import train_test_split, cross_validate # 划分数据集函数
from sklearn.metrics import accuracy_score # 准确率函数
RANDOM_SEED = 2020 # 固定随机种子

读入数据

csv_data = './data/high_diamond_ranked_10min.csv' # 数据路径
data_df = pd.read_csv(csv_data, sep=',') # 读入csv文件为pandas的DataFrame
data_df = data_df.drop(columns='gameId') # 去掉对局ID列

数据概览

对数据分布有个大致了解,比如我们可以通过.iloc[0]取出数据的第一行并输出。不难看出每个特征都存成了float64浮点数,该对局蓝色方开局10分钟有小优势。同时也可以发现有些特征列是重复冗余的,比如blueGoldDiff表示蓝色队金币优势,redGoldDiff表示红色方金币优势,这两个特征是完全对称的互为相反数,等等。

比如blueKills蓝色方击杀英雄数在前十分钟的平均数是6.14、方差为2.93,中位数是6,百分之五十以上的对局中该特征在4-8之间,等等。

68ca09f892e04b71a0f82b3e6ade3814.png

增删特征

除去一些冗杂信息,且像上面说的一般蓝色方击杀等于红色方死亡,或者蓝色与红色方方金币数可以合并为经济差,又或者红色方发生了1血那么蓝色方就没有发生...

drop_features = ['blueGoldDiff', 'redGoldDiff', 'blueExperienceDiff', 'redExperienceDiff', 'blueCSPerMin', 'redCSPerMin', 'blueGoldPerMin', 'redGoldPerMin'] # 需要舍去的特征列
df = data_df.drop(columns=drop_features) # 舍去特征列
info_names = [c[3:] for c in df.columns if c.startswith('red')] # 取出要作差值的特征名字(除去red前缀)
for info in info_names: # 对于每个特征名字df['br' + info] = df['blue' + info] - df['red' + info] # 构造一个新的特征,由蓝色特征减去红色特征,前缀为br
# 其中FirstBlood为首次击杀最多有一只队伍能获得,brFirstBlood=1为蓝,0为没有产生,-1为红
df = df.drop(columns=['blueFirstBlood', 'redFirstBlood']) # 原有的FirstBlood可删除

经过这一步此时变量df里由原来的38个特征-8(不需要的)+15(红蓝差)-2(一血),还剩下43个特征值,外加蓝色方是否获胜,一共44列。

特征离散化

决策树ID3算法一般是基于离散特征的,本例中存在很多连续的数值特征,例如队伍金币。直接应用该算法每个值当作一个该特征的一个取值可能造成严重的过拟合,因此需要对特征进行离散化,即将一定范围内的值映射成一个值,例如对用户年龄特征,将0-10映射到0,11-18映射到1,19-25映射到2,25-30映射到3,等等类似,然后在决策树构建时使用映射后的值计算信息增益。

DISCRETE_N = 10
discrete_df = df.copy()
for c in df.columns[1:]: if len(df[c].unique()) <= DISCRETE_N: continueelse:discrete_df[c] = pd.qcut(df[c], DISCRETE_N, precision=0, labels=False, duplicates='drop')

注: 有些特征本身取值就很少,可以跳过离散化。

数据集准备

随机取一部分如20%作测试集,剩下作训练集。sklearn提供了相关工具函数train_test_split。sklearn的输入输出一般为numpy的array矩阵,需要先将pandas的DataFrame取出为numpy的array矩阵。

all_y = discrete_df['blueWins'].values # 所有标签数据
feature_names = discrete_df.columns[1:] # 所有特征的名称
all_x = discrete_df[feature_names].values # 所有原始特征值
# 划分训练集和测试集
x_train, x_test, y_train, y_test = train_test_split(all_x, all_y, test_size=0.2, random_state=RANDOM_SEED)# ((9879,), (9879, 43), (7903, 43), (1976, 43), (7903,), (1976,))

第三步:决策树模型的实现

删删改改了很多次,路漫漫其修远~

# 定义决策树类
class DecisionTree(object):def __init__(self, classes, features,max_depth=10, min_samples_split=10,impurity_t='entropy'):'''传入一些可能用到的模型参数,也可能不会用到classes表示模型分类总共有几类features是每个特征的名字,也方便查询总的共特征数max_depth表示构建决策树时的最大深度min_samples_split表示构建决策树分裂节点时,如果到达该节点的样本数小于该值则不再分裂impurity_t表示计算混杂度(不纯度)的计算方式,例如entropy或gini'''self.classes = classesself.features = featuresself.max_depth = max_depthself.min_samples_split = min_samples_splitself.impurity_t = impurity_tself.root = None  # 定义根节点,未训练时为空def impurity(self, data):'''计算某个特征下的信息增益:param data: ,numpy一维数组:return: 混杂度'''cnt = Counter(data)  # 计数每个值出现的次数probability_lst = [1.0 * cnt[i] / len(data) for i in cnt]if self.impurity_t == 'entropy':  # 如果是信息熵return -np.sum([p * np.log2(p) for p in probability_lst if p > 0]), cnt  # 返回熵 和可能用到的数据次数(方便以后使用)return 1 - np.sum([p * p for p in probability_lst]), cnt  # 否则返回gini系数def gain(self, feature, label):'''计算某个特征下的信息增益:param feature: 特征的值,numpy一维数组:param label: 对应的标签,numpy一维数组:return: 信息增益'''c_impurity, _ = self.impurity(label)  # 不考虑特征时标签的混杂度# 记录特征的每种取值所对应的数组下标f_index = {}for idx, v in enumerate(feature):if v not in f_index:f_index[v] = []f_index[v].append(idx)# 计算根据该特征分裂后的不纯度,根据特征的每种值的数目加权和f_impurity = 0for v in f_index:# 根据该特征取值对应的数组下标 取出对应的标签列表 比如分支1有多少个正负例 分支2有...f_l = label[f_index[v]]f_impurity += self.impurity(f_l)[0] * len(f_l) / len(label)  # 循环结束得到各分支混杂度的期望gain = c_impurity - f_impurity  # 得到gain# 有些特征取值很多,天然不纯度高、信息增益高,模型会偏向于取值很多的特征比如日期,但很可能过拟合# 计算信息增益率可以缓解该问题splitInformation = self.impurity(feature)[0]  # 计算该特征在标签无关时的不纯度gainRatio = gain / splitInformation if splitInformation > 0 else gain  # 除数不为0时为信息增益率return gainRatio, f_index  # 返回信息增益率,以及每个特征取值的数组下标(方便以后使用)def expand_node(self, feature, label, depth, skip_features=set()):'''递归函数分裂节点:param feature:二维numpy(n*m)数组,每行表示一个样本,n行,有m个特征:param label:一维numpy(n)数组,表示每个样本的分类标签:param depth:当前节点的深度:param skip_features:表示当前路径已经用到的特征在当前ID3算法离散特征的实现下,一条路径上已经用过的特征不会再用(其他实现有可能会选重复特征)'''l_cnt = Counter(label)  # 计数每个类别的样本出现次数if len(l_cnt) <= 1:  # 如果只有一种类别了,无需分裂,已经是叶节点return label[0]  # 只需记录类别if len(label) < self.min_samples_split or depth > self.max_depth:  # 如果达到了最小分裂的样本数或者最大深度的阈值return l_cnt.most_common(1)[0][0]  # 则只记录当前样本中最多的类别f_idx, max_gain, f_v_index = -1, -1, None  # 准备挑选分裂特征for idx in range(len(self.features)):  # 遍历所有特征if idx in skip_features:  # 如果当前路径已经用到,不用再算continuef_gain, fv = self.gain(feature[:, idx], label)  # 计算特征的信息增益,fv是特征每个取值的样本下标# if f_gain <= 0: # 如果信息增益不为正,跳过该特征#   continueif f_idx < 0 or f_gain > max_gain:  # 如果个更好的分裂特征f_idx, max_gain, f_v_index = idx, f_gain, fv  # 则记录该特征# if f_idx < 0: # 如果没有找到合适的特征,即所有特征都没有信息增益#   return l_cnt.most_common(1)[0][0] # 则只记录当前样本中最多的类别decision = {}  # 用字典记录每个特征取值所对应的子节点,key是特征取值,value是子节点skip_features = set([f_idx] + [f for f in skip_features])  # 子节点要跳过的特征包括当前选择的特征for v in f_v_index:  # 遍历特征的每种取值decision[v] = self.expand_node(feature[f_v_index[v], :], label[f_v_index[v]],  # 取出该特征取值所对应的样本depth=depth + 1, skip_features=skip_features)  # 深度+1,递归调用节点分裂# 返回一个元组,有三个元素# 第一个是选择的特征下标,第二个特征取值和对应的子节点(字典),第三个是到达当前节点的样本中最多的类别return (f_idx, decision, l_cnt.most_common(1)[0][0])def traverse_node(self, node, feature):'''预测样本时从根节点开始遍历节点,根据特征路由。:param node: 当前到达的节点,例如self.root:param feature: 长度为m的numpy一维数组'''assert len(self.features) == len(feature)  # 要求输入样本特征数和模型定义时特征数目一致if type(node) is not tuple:  # 如果到达了一个节点是叶节点(不再分裂),则返回该节点类别return nodefv = feature[node[0]]  # 否则取出该节点对应的特征值,node[0]记录了特征的下标if fv in node[1]:  # 根据特征值找到子节点,注意需要判断训练节点分裂时到达该节点的样本是否有该特征值(分支)return self.traverse_node(node[1][fv], feature)  # 如果有,则进入到子节点继续遍历return node[-1]  # 如果没有,返回训练时到达当前节点的样本中最多的类别def fit(self, feature, label):'''训练模型:param feature:feature为二维numpy(n*m)数组,每行表示一个样本,有m个特征:param label:label为一维numpy(n)数组,表示每个样本的分类标签'''assert len(self.features) == len(feature[0])  # 输入数据的特征数目应该和模型定义时的特征数目相同self.root = self.expand_node(feature, label, depth=1)  # 从根节点开始分裂,模型记录根节点def predict(self, feature):'''预测:param feature:输入feature可以是一个一维numpy数组也可以是一个二维numpy数组如果是一维numpy(m)数组则是一个样本,包含m个特征,返回一个类别值如果是二维numpy(n*m)数组则表示n个样本,每个样本包含m个特征,返回一个numpy一维数组'''assert len(feature.shape) == 1 or len(feature.shape) == 2  # 只能是1维或2维if len(feature.shape) == 1:  # 如果是一个样本return self.traverse_node(self.root, feature)  # 从根节点开始路由return np.array([self.traverse_node(self.root, f) for f in feature])  # 如果是很多个样本def get_params(self, deep):  # 要调用sklearn的cross_validate需要实现该函数返回所有参数return {'classes': self.classes, 'features': self.features,'max_depth': self.max_depth, 'min_samples_split': self.min_samples_split,'impurity_t': self.impurity_t}def set_params(self, **parameters):  # 要调用sklearn的GridSearchCV需要实现该函数给类设定所有参数for parameter, value in parameters.items():setattr(self, parameter, value)return self# 定义决策树模型,传入算法参数
DT = DecisionTree(classes=[0, 1], features=feature_names, max_depth=5, min_samples_split=10, impurity_t='gini')DT.fit(x_train, y_train)  # 在训练集上训练
p_test = DT.predict(x_test)  # 在测试集上预测,获得预测值
print(p_test)  # 输出预测值
test_acc = accuracy_score(p_test, y_test)  # 将测试预测值与测试集标签对比获得准确率
print('accuracy: {:.4f}'.format(test_acc))  # 输出准确率

[0 1 0 ... 0 1 1]
accuracy: 0.6964

此时的准确率为0.6964,下面我们进行模型调优。

第四步:模型调优

在测试集上对不同的混杂度计算方式、深度、最小分裂阈值进行五折交叉验证。

%%timebest = None # 记录最佳结果
for impurity_t in ['entropy', 'gini']: # 遍历不纯度的计算方式for max_depth in range(1, 6): # 遍历最大树深度for min_samples_split in [50, 100, 200, 500, 1000]: # 遍历节点分裂最小样本数的阈值DT = DecisionTree(classes=[0,1], features=feature_names,  # 定义决策树max_depth=max_depth, min_samples_split=min_samples_split, impurity_t=impurity_t)cv_result = cross_validate(DT, x_train, y_train, scoring=('accuracy'), cv=5) # 5折交叉验证            cv_acc = np.mean(cv_result['test_score']) # 5折平均准确率current = (cv_acc, max_depth, min_samples_split, impurity_t) # 记录参数和结果if best is None or cv_acc > best[0]: # 如果是比当前最佳更好的结果best = current # 记录最好结果print('better cv_accuracy: {:.4f}, max_depth={}, min_samples_split={}, impurity_t={}'.format(*best)) # 输出准确率和参数else:print('cv_accuracy: {:.4f}, max_depth={}, min_samples_split={}, impurity_t={}'.format(*current)) # 输出准确率和参数DT = DecisionTree(classes=[0,1], features=feature_names, max_depth=best[1], min_samples_split=best[2], impurity_t=best[3])  # 取最佳参数
DT.fit(x_train, y_train) # 在训练集上训练
p_test = DT.predict(x_test) # 在测试集上预测,获得预测值
print(p_test) # 输出预测值
test_acc = accuracy_score(p_test, y_test) # 将测试预测值与测试集标签对比获得准确率
print('accuracy: {:.4f}'.format(test_acc)) # 输出准确率
better cv_accuracy: 0.7257, max_depth=1, min_samples_split=50, impurity_t=entropy
cv_accuracy: 0.7257, max_depth=1, min_samples_split=100, impurity_t=entropy
cv_accuracy: 0.7257, max_depth=1, min_samples_split=200, impurity_t=entropy
cv_accuracy: 0.7257, max_depth=1, min_samples_split=500, impurity_t=entropy
cv_accuracy: 0.7257, max_depth=1, min_samples_split=1000, impurity_t=entropy
cv_accuracy: 0.7254, max_depth=2, min_samples_split=50, impurity_t=entropy
cv_accuracy: 0.7254, max_depth=2, min_samples_split=100, impurity_t=entropy
cv_accuracy: 0.7254, max_depth=2, min_samples_split=200, impurity_t=entropy
cv_accuracy: 0.7254, max_depth=2, min_samples_split=500, impurity_t=entropy
cv_accuracy: 0.7257, max_depth=2, min_samples_split=1000, impurity_t=entropy
better cv_accuracy: 0.7267, max_depth=3, min_samples_split=50, impurity_t=entropy
better cv_accuracy: 0.7272, max_depth=3, min_samples_split=100, impurity_t=entropy
better cv_accuracy: 0.7281, max_depth=3, min_samples_split=200, impurity_t=entropy
better cv_accuracy: 0.7286, max_depth=3, min_samples_split=500, impurity_t=entropy
cv_accuracy: 0.7257, max_depth=3, min_samples_split=1000, impurity_t=entropy
cv_accuracy: 0.7237, max_depth=4, min_samples_split=50, impurity_t=entropy
cv_accuracy: 0.7252, max_depth=4, min_samples_split=100, impurity_t=entropy
cv_accuracy: 0.7285, max_depth=4, min_samples_split=200, impurity_t=entropy
better cv_accuracy: 0.7290, max_depth=4, min_samples_split=500, impurity_t=entropy
cv_accuracy: 0.7257, max_depth=4, min_samples_split=1000, impurity_t=entropy
cv_accuracy: 0.7149, max_depth=5, min_samples_split=50, impurity_t=entropy
cv_accuracy: 0.7210, max_depth=5, min_samples_split=100, impurity_t=entropy
cv_accuracy: 0.7254, max_depth=5, min_samples_split=200, impurity_t=entropy
cv_accuracy: 0.7290, max_depth=5, min_samples_split=500, impurity_t=entropy
cv_accuracy: 0.7257, max_depth=5, min_samples_split=1000, impurity_t=entropy
cv_accuracy: 0.7257, max_depth=1, min_samples_split=50, impurity_t=gini
cv_accuracy: 0.7257, max_depth=1, min_samples_split=100, impurity_t=gini
cv_accuracy: 0.7257, max_depth=1, min_samples_split=200, impurity_t=gini
cv_accuracy: 0.7257, max_depth=1, min_samples_split=500, impurity_t=gini
cv_accuracy: 0.7257, max_depth=1, min_samples_split=1000, impurity_t=gini
cv_accuracy: 0.7255, max_depth=2, min_samples_split=50, impurity_t=gini
cv_accuracy: 0.7255, max_depth=2, min_samples_split=100, impurity_t=gini
cv_accuracy: 0.7255, max_depth=2, min_samples_split=200, impurity_t=gini
cv_accuracy: 0.7255, max_depth=2, min_samples_split=500, impurity_t=gini
cv_accuracy: 0.7257, max_depth=2, min_samples_split=1000, impurity_t=gini
cv_accuracy: 0.7218, max_depth=3, min_samples_split=50, impurity_t=gini
cv_accuracy: 0.7235, max_depth=3, min_samples_split=100, impurity_t=gini
cv_accuracy: 0.7250, max_depth=3, min_samples_split=200, impurity_t=gini
cv_accuracy: 0.7257, max_depth=3, min_samples_split=500, impurity_t=gini
cv_accuracy: 0.7257, max_depth=3, min_samples_split=1000, impurity_t=gini
cv_accuracy: 0.7174, max_depth=4, min_samples_split=50, impurity_t=gini
cv_accuracy: 0.7248, max_depth=4, min_samples_split=100, impurity_t=gini
cv_accuracy: 0.7261, max_depth=4, min_samples_split=200, impurity_t=gini
cv_accuracy: 0.7259, max_depth=4, min_samples_split=500, impurity_t=gini
cv_accuracy: 0.7257, max_depth=4, min_samples_split=1000, impurity_t=gini
cv_accuracy: 0.7076, max_depth=5, min_samples_split=50, impurity_t=gini
cv_accuracy: 0.7185, max_depth=5, min_samples_split=100, impurity_t=gini
cv_accuracy: 0.7207, max_depth=5, min_samples_split=200, impurity_t=gini
cv_accuracy: 0.7259, max_depth=5, min_samples_split=500, impurity_t=gini
cv_accuracy: 0.7257, max_depth=5, min_samples_split=1000, impurity_t=gini

使用max_depth=4, min_samples_split=500, impurity_t=entropy在训练集上进行训练,并在测试集上预测,将测试预测值与测试集标签对比获得准确率:显然提升了

[0 1 0 ... 0 1 1]

accuracy: 0.7171

Wall time: 1min 19s

也可以调用sklearn的GridSearchCV自动且多线程搜索参数,和上面的流程类似。

%%timeparameters = {'impurity_t':['entropy', 'gini'], 'max_depth': range(1, 6), 'min_samples_split': [50, 100, 200, 500, 1000]} # 定义需要遍历的参数
DT = DecisionTree(classes=[0,1], features=feature_names) # 定义决策树,可以不传参数,由GridSearchCV传入构建
grid_search = GridSearchCV(DT, parameters, scoring='accuracy', cv=5, verbose=100, n_jobs=4) # 传入模型和要遍历的参数
grid_search.fit(x_train, y_train) # 在所有数据上搜索参数
print(grid_search.best_score_, grid_search.best_params_) # 输出最佳指标和最佳参数DT = DecisionTree(classes=[0,1], features=feature_names, **grid_search.best_params_)  # 取最佳参数
DT.fit(x_train, y_train) # 在训练集上训练
p_test = DT.predict(x_test) # 在测试集上预测,获得预测值
print(p_test) # 输出预测值
test_acc = accuracy_score(p_test, y_test) # 将测试预测值与测试集标签对比获得准确率
print('accuracy: {:.4f}'.format(test_acc)) # 输出准确率
Fitting 5 folds for each of 50 candidates, totalling 250 fits
0.7289642831407777 {'impurity_t': 'entropy', 'max_depth': 4, 'min_samples_split': 500}
[0 1 0 ... 0 1 1]
accuracy: 0.7171
Wall time: 24.6 s

查看节点的特征

best_dt = grid_search.best_estimator_ # 取出最佳模型
print('root', best_dt.features[best_dt.root[0]]) # 输出根节点特征
root brTotalGold

可见前10分钟最能影响胜率的是红蓝双方的经济差 

for fv in best_dt.root[1]: # 遍历根节点的每种特征取值print(fv, '->', best_dt.features[best_dt.root[1][fv][0]], '-> ...') # 输出下一层特征
9 -> redDragons -> ...
1 -> blueTowersDestroyed -> ...
3 -> redEliteMonsters -> ...
5 -> redTowersDestroyed -> ...
2 -> blueTowersDestroyed -> ...
4 -> brTowersDestroyed -> ...
7 -> redEliteMonsters -> ...
8 -> brTowersDestroyed -> ...
0 -> brKills -> ...
6 -> brTowersDestroyed -> ...

根节点这个特征对应取值0-9(我们之前对金币进行了离散化)所对应的下一层的特征...

       

ps:这个其实来自于我们定义的 expand_node 函数返回值,

return (f_idx, decision, l_cnt.most_common(1)[0][0]) ,

第一个是选择的特征下标,第二个特征取值和对应的子节点(字典),第三个是到达当前节点的样本中最多的类别。

它的形状/结果其实是这样的:

(38,{9: (20,{0: (23, {1: 1, 2: 1, 4: 1, 0: 1, 3: 1, 5: 1, 6: 1, 7: 1, 8: 1}, 1), 1: 1},1),1: (8, {0: (7, {0: (20, {1: 0, 0: 0}, 0), 1: 0}, 0), 1: 0}, 0),3: (19, {0: 0, 1: 0, 2: 0}, 0),5: (22, {0: (19, {0: 1, 2: 0, 1: 0}, 1), 1: 1, 2: 1}, 1),2: (8, {0: (22, {0: (6, {0: 0, 1: 0}, 0), 1: 0, 2: 1}, 0), 1: 1}, 0),4: (37, {0: (8, {0: (5, {0: 0, 1: 0, 2: 1}, 0), 1: 1}, 0), -1: 0, 1: 0}, 0),7: (19, {1: 1, 0: 1, 2: 1}, 1),8: (37, {0: (8, {0: (6, {0: 1, 1: 1}, 1), 1: 1}, 1), 1: 1, 2: 1}, 1),0: (31, {0: (5, {0: 0, 1: 0, 2: 0}, 0), 1: 0, 2: 0, 4: 0, 3: 0}, 0),6: (37, {0: (8, {0: (6, {1: 1, 0: 1}, 1), 1: 1}, 1), 1: 0, 2: 1, -1: 0}, 1)},0)

http://www.ppmy.cn/news/675356.html

相关文章

爬虫爬虫爬虫学习 day1:op.gg的爬取

学习目标&#xff1a; Python爬虫 学习内容&#xff1a; 1、 python爬虫简单语句的学习 2、 理解爬虫语句 学习产出&#xff1a; 1.库的导入 import requests #导入requests库 import bs4 #导入bs4库 from bs4 import BeautifulSoup #导入BeautifulSoup库2.返回html内容 r …

面板——卡片面板、常规折叠面板、手风琴折叠

1. 卡片面板 2. 普通折叠面板 3. 手风琴面板 在普通面板上加 lay-accordion"" <% page language"java" contentType"text/html; charsetUTF-8"pageEncoding"UTF-8"%> <!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01…

更新Edge后打开网页失败问题

Edge推荐版本更新&#xff08;Microsoft Edge版本 112.0.1722.39 (正式版本) (64 位)&#xff09;&#xff0c;更新后打开后什么也打不开&#xff0c;网页打开失败&#xff0c;设置什么都打开失败 错误截图如下&#xff1a; 网上大概都是下面几个方法&#xff1a; 1、使用兼容…

Python爬取op.gg数据——英雄联盟版本强势英雄推荐

通过爬取韩服op.gg网站的数据&#xff0c;推荐当前版本各路强势英雄&#xff08;韩服比国服先更新&#xff09;。 目录 通过爬取韩服op.gg网站的数据&#xff0c;推荐当前版本各路强势英雄&#xff08;韩服比国服先更新&#xff09;。一、op.gg源码及请求头分析二、源码分析三、…

从零开始 Spring Boot 49:Hibernate Entity Lifecycle

从零开始 Spring Boot 49&#xff1a;Hibernate Entity Lifecycle 图源&#xff1a;简书 (jianshu.com) 本文将介绍 Hibernate 的 Session 接口&#xff0c;以及如何用 Session 的相关 API 转换实体&#xff08;Entity&#xff09;的生命周期状态。 如果缺少的 JPA 和 Hiberna…

python爬取率_利用Python爬取OPGG上英雄联盟英雄胜率及选取率信息

一、分析网站内容 由网站界面可以看出&#xff0c;右侧有英雄的详细信息&#xff0c;以Garen为例&#xff0c;胜率为53.84%&#xff0c;选取率为16.99%&#xff0c;常用位置为上单 现对网页源代码进行分析(右键鼠标在菜单中即可找到查看网页源代码)。通过查找“53.84%”快速定位…

爬虫实战(一)—利用requests、mongo、redis代理池爬取英雄联盟opgg实时英雄数据

概述 可关注微信订阅号 loak 查看实际效果。 代码已托管github&#xff0c;地址为&#xff1a;https://github.com/luozhengszj/LOLGokSpider 包括了项目的所有代码。 此篇文章&#xff0c;主要记录利用Python request模块爬取LOL opgg英雄实时数据&#xff0c;并保存到mongodb…

适合新手练手,用Python爬取OPGG里英雄联盟英雄胜率及选取率,详细讲解加注释(建议收藏练手)

今天来个简单的小项目&#xff0c;适合新手拿来练手&#xff0c;在OPGG上爬取英雄联盟里的法师&#xff0c;ADC&#xff0c;打野&#xff0c;辅助所有英雄的胜率及选取率&#xff0c;是不是感觉很高大上&#xff0c;但是却很简单&#xff0c;只要用三十多行代码就能实现&#x…