Python教你每把轻松吃鸡

“吃鸡”有多热,已经不用多说。但你知道吗?你热爱的“吃鸡”可能是用Python写的。

Python教你每把轻松吃鸡_第1张图片

网易的两大游戏客户端引擎,NeoX 和 Messiah,都是使用 Python 作为脚本语言的。具体来说,服务器端可以说是完全Python,毕竟能够热更新的Python可以不停服!当然游戏的客户端核心引擎部分,还是用C++,一些类似基本渲染还是用C++来实现的。

其实用Python来写大型游戏也不是什么新闻了,新手教程都能出电子书的星际生存游戏——《EVE》也是用python写的。说得更具体点,是Stackless Python。Stackless是在Python基础上编写的一套微线程框架,它能在不产生大量Python自身额外开销的情况下同时容纳数百万条的线程。

吃鸡手游竟然是Python写的?软妹子也要学Python

听起来这么高大上的Python是不是很难学?并没有。

Python入门到实践书籍,Python真的是容易入门到可以作为亲子娱乐项目的编程语言。

Python教你每把轻松吃鸡_第2张图片

 

我第一次见到这本书是在一个学中文的妹子那,当时内心活动非常复杂:什么?妹子你都有孩子了?

不过了解后知道,妹子为了能够扒下豆瓣top250电影的列表,所以打算自学Python,然后妹子又向我推荐了MOOC上嵩天老师的Python课。

而且把Python爬虫课讲得让她一个文科生都听懂了。

 

Python语言在全球范围内的使用频率也越来越高。Python甚至进入了浙江信息技术高考,因为有Python,00后可能每个人都会编程了。这种全民狂热的背后有很重要一个原因是——人工智能的风口来了。

根据数据平台 Kaggle 发布的2017 机器学习及数据科学调查报告,在工具语言使用方面,Python是使用最多的语言。

Python教你每把轻松吃鸡_第3张图片

也正是因为这种广泛使用,Python 社区异常活跃,目前周边算是非常成熟,针对不同的需求都有着自己比较受欢迎的库。

吃鸡手游竟然是Python写的?Python 这么厉害,

是要替代C/C++的地位吗?

前面介绍的“吃鸡”,虽然底层是用C/C++写的 ,但是Python可以调用AI接口,而且代码非常简单,不是说C++写不了上层逻辑,而是Python入门时间按天算的,C++入门时间按年算的……

请问,在有限的生命里,你打算学哪种编程语言?在线等,挺急的。

实际上在果金老师的Python系列课程里,有很多关于深度学习、人工智能、数据处理等内容。如果你也和那个中文系的妹子一样,刷了果金老师的博客,是他的迷妹/迷弟的话,果金老师在两年多的网络教学实战后,又推出了一门进阶课程——《Python 应用基础》微专业

如果没有刷过、甚至从来没有接触过编程也不用担心,《Python应用基础》学员群有系统预习资料、会带你从0开始预习,加入即可告别入门难的烦恼。

这门课将涉及计算机工程、大数据及人工智能等技术领域,人工智能工程师有多值钱,你随便打开一个求职app搜一搜就知道了。现在加入学习,不仅能收获Python同好,也将获得老师的在线答疑,各类资料!

现在还限时推出免费福利:

福利1

加群获取全套入门教程已经在线直播公开课永久权限

直播大纲:

1:Python就业前景

2:Python入门方法

3:Python应用基础内容解读及体验

4:答疑

福利2

金牌解答老师

课程大纲:

第一章 :AI时代的主流语言--Python实际应用

第二章 :想要学习Python? 你得先准备这些

第三章 :一小时Python入门实践

如何获取?

加入Q群

即可领取所有福利

“Python是世界上最好的语言” 我是不会站队的,因为,让柯洁输得没脾气的阿法狗,可是用Python写的!219539519欢迎加入获取

下面是教程:

1. 获取用户信息接口

  • request

API/cgi-bin/gamewap/getpubgmdatacenterindex方法GET参数openid、pass_ticketcookiekey pass_ticket、uin、pgv_pvid、sd_cookie_crttime、sd_userid

  • response

{ "user_info": { "openid": "oODfo0pjBQkcNuR4XLTQ321xFVws", "head_img_url":"http://wx.qlogo.cn/mmhead/Q3auHgzwzM5hSWxxxxxUQPwW9ibxxxx9DlxLTsKWk97oWpDI0rg/96", "nick_name": "望", "role_name": "xxxx", "zone_area_id": 0, "plat_id": 1 }, "battle_info": { "total_1": 75, "total_10": 336, "total_game": 745, "total_kill": 1669 }, "battle_list": [{ "map_id": 1, "room_id": "6575389198189071197", "team_id": 57, "dt_event_time": 1530953799, "rank_in_ds": 3, "times_kill": 1, "label": "前五", "team_type": 1, "award_gold": 677, "mode": 0 }], "appitem": { "AppID": "wx13051697527efc45", "IconURL":"https://mmocgame.qpic.cn/wechatgame/mEMdfrX5RU0dZFfNEdCsMJpfsof1HE0TP3cfZiboX0ZPxqh5aZnHjxPFXUGgsXmibe/0", "Name": "绝地求生 全军出击", "BriefName": "绝地求生 全军出击", "Desc": "官方正版绝地求生手游", "Brief": "枪战 | 808.2M", "WebURL": "https://game.weixin.qq.com/cgi-bin/h5/static/detail_v2/index.html?wechat_pkgid=detail_v2&appid=wx13051697527efc45&show_bubble=0", "DownloadInfo": { "DownloadURL": "https://itunes.apple.com/cn/app/id1304987143", "DownloadFlag": 5 }, "Status": 0, "AppInfoFlag": 45, "Label": [], "AppStorePopUpDialogConfig": { "Duration": 1500, "Interval": 172800, "ServerTimestamp": 1531066098 }, "HasEnabledChatGroup": false, "AppType": 0, "game_tag_list": ["绝地求生", "正版还原", "好友开黑", "百人对战", "超大地图"], "recommend_reason": "正版绝地求生,荒野射击", "size_desc": "808.2M" }, "is_guest": true, "is_blocked": false, "errcode": 0, "errmsg": "ok"}

  • 分析

openid是用户的惟一标识。

2. 获取用户战绩列表接口

  • request

  • API/cgi-bin/gamewap/getpubgmbattlelist方法GET参数openid、pass_ticket、plat_id、after_time、limitcookiekey pass_ticket、uin、pgv_pvid、sd_cookie_crttime、sd_userid

  • response

{"errcode": 0,"errmsg": "ok","next_after_time": 1528120556,"battle_list": [{ "map_id": 1, "room_id": "6575389198111172597", "team_id": 57, "dt_event_time": 1530953799, "rank_in_ds": 3, "times_kill": 1, "label": "前五", "team_type": 1, "award_gold": 677, "mode": 0}, { "map_id": 1, "room_id": "6575336498940384115", "team_id": 11, "dt_event_time": 1530941404, "rank_in_ds": 5, "times_kill": 2, "label": "前五", "team_type": 1, "award_gold": 632, "mode": 0}],"has_next": true}

  • 分析

  • 这个接口用after_time来进行分页,遍历获取时可以根据接口响应的has_next和next_after_time来判断是否还有下一页的数据。

  • 列表里面的room_id是每一场battle的惟一标识。

3. 获取用户战绩详情接口

  • request

API/cgi-bin/gamewap/getpubgmbattledetail方法GET参数openid、pass_ticket、room_idcookiekey pass_ticket、uin、pgv_pvid、sd_cookie_crttime、sd_userid

  • request

{"errcode": 0,"errmsg": "ok","base_info": { "nick_name": "柚茶", "head_img_url": "http://wx.qlogo.cn/mmhead/xxxx/96", "dt_event_time": 1528648165, "team_type": 4, "rank": 1, "player_count": 100, "role_sex": 1, "label": "大吉大利", "openid": "oODfo0s1w5lWjmxxxxxgQkcCljXQ"},"battle_info": { "award_gold": 622, "times_kill": 6, "times_head_shot": 0, "damage": 537, "times_assist": 3, "survival_duration": 1629, "times_save": 0, "times_reborn": 0, "vehicle_kill": 1, "forward_distance": 10140, "driving_distance": 5934, "dead_poison_circle_no": 6, "top_kill_distance": 223, "top_kill_distance_weapon_use": 2924130819, "be_kill_user": { "nick_name": "小旭", "head_img_url":"http://wx.qlogo.cn/mmhead/ibLButGMnqJNFsUtStNEV8tzlH1QpwPiaF9kxxxxx66G3ibjic6Ng2Rcg/96", "weapon_use": 20101000001, "openid": "oODfo0qrPLExxxxc0QKjFPnPxyI" }, "label": "大吉大利"},"team_info": { "user_list": [{ "nick_name": "ooo", "times_kill": 6, "assist_count": 3, "survival_duration": 1638, "award_gold": 632, "head_img_url":"http://wx.qlogo.cn/mmhead/Q3auHgzwzM4k4RXdyxavNxxxxUjcX6Tl47MNNV1dZDliazRKRg", "openid": "oODfo0xxxxf1bRAXE-q-lEezK0k" }, { "nick_name": "我吃炒肉", "times_kill": 2, "assist_count": 2, "survival_duration": 1502, "award_gold": 583, "head_img_url":"http://wx.qlogo.cn/mmhead/sTJptKvBQLKd5SAAjOF0VrwiapUxxxxFffxoDUcrVjYbDf9pNENQ", "openid": "oODfo0gIyDxxxxZpUrSrpapZSDT0" }]},"is_guest": true,"is_blocked": false}

  • 分析

  • 这个接口响应了战斗的详细信息,包括杀敌数、爆头数、救人数、跑动距离等等,足够我们分析了。

  • 这个接口还响应了是被谁杀死的以及组团成员的openid,利用这个特性我们这可无限深度的发散爬取更多用户的数据。

至于cookie中的息pass_ticket等信息肯定是用于权限认证的,在上述的几次请求中这些信息都没有变化,所以我们不需要深研其是怎么算出来的,只需要抓包提取到默认信息后填到代码里面就可以用了。

Step 2 爬取数据

接口已经确定下来了,接下来就是去抓取足够量的数据了。

使用requests请求接口获取数据

url = 'https://game.weixin.qq.com/cgi-bin/gamewap/getpubgmdatacenterindex?openid=%s&plat_id=0&uin=&key=&pass_ticket=%s' % (openid, settings.pass_ticket) r = requests.get(url=url, cookies=settings.def_cookies, headers=settings.def_headers, timeout=(5.0,5.0)) tmp = r.json() wfile = os.path.join(settings.Res_UserInfo_Dir, '%s.txt' % (rediskeys.user(openid))) withcodecs.open(wfile, 'w', 'utf-8') as wf: wf.write(simplejson.dumps(tmp, indent=2, sort_keys=True, ensure_ascii=False))

参照这种方式我们可以很快把另外两个接口写好。

使用redis来标记已经爬取过的信息

在上述接口中我们可能从用户A的入口进去找到用户B的openid,然后从用户B的入口进去又找到用户A的openid,为了避免重复采集,所以我们需要记录下哪些信息是我们采集过的。 核心代码片断:

# rediskeys.user_battle_list 根据openid获取存在redis中的key值defuser_battle_list(openid): return 'ubl_%s' % (openid)# 在提取battle list之前,首先判断这用用户的数据是否已经提取过了ifsettings.DataRedis.get(rediskeys.user_battle_list(openid)): return True# 在提取battle list之后,需要在redis中记录用户信息settings.DataRedis.set(rediskeys.user_battle_list(openid), 1)

使用celery来管理队列

celery是一个非常好用的分布式队列管理工具,我这次只打算在我自己的电脑上运行,所以并没有用到分布式的功能。 我们创建三个task和三个queue

task_queues = ( Queue('queue_get_battle_info', exchange=Exchange('priority', type='direct'), routing_key='gbi'), Queue('queue_get_battle_list', exchange=Exchange('priority', type='direct'), routing_key='gbl'), Queue('queue_get_user_info', exchange=Exchange('priority', type='direct'), routing_key='gui'),) task_routes = ([ ('get_battle_info', {'queue': 'queue_get_battle_info'}), ('get_battle_list', {'queue': 'queue_get_battle_list'}), ('get_user_info', {'queue': 'queue_get_user_info'}),],)

然后在task中控制API请求和Redis数据实现完整的任务逻辑,如:

@app.task(name='get_battle_list')def get_battle_list(openid, plat_id=None, after_time=0, update_time=None): # 判断是否已经取过用户战绩列表信息 ifsettings.DataRedis.get(rediskeys.user_battle_list(openid)): return True if not plat_id: try# 提取用户信息 us = handles.get_user_info_handles(openid) plat_id=us['plat_id'] except Exception as e: print 'can not get user plat_id', openid, traceback.format_exc() return False # 提取战绩列表 battle_list = handles.get_battle_list_handle(openid, plat_id, after_time=0, update_time=None# 为每一场战斗创建异步获取详情任务 for room_id in battle_list: if notsettings.DataRedis.get(rediskeys.user_battle(openid, room_id)): get_battle_info.delay(openid, plat_id, room_id) return True

开始抓取

因为我们是发散是爬虫,所以需要给代码一个用户的入口,所以需要手动创建一个用户的采集任务

from tasks.all import get_battle_list my_openid = 'oODfo0oIErZI2xxx9xPlVyQbRPgY'my_platid = '0' get_battle_list.delay(my_openid, my_platid, after_time=0, update_time=None)

有入口之后我们就用celery来启动worker去开始爬虫

# 启动获取用户详情workercelery -A tasks.all worker -c 5 --queue=queue_get_user_info --loglevel=info -n get_user_info@%h # 启动获取战绩列表workercelery -A tasks.all worker -c 5 --queue=queue_get_battle_list --loglevel=info -n get_battle_list@%h # 启动获取战绩详情workercelery -A tasks.all worker -c 30 --queue=queue_get_battle_info --loglevel=info -n get_battle_info@%h

这样我们的爬虫就可以愉快的跑起来了。再通过celery-flower来查看执行情况。

celery flower -A tasks.all --broker=redis://:

利用Python来爬取“吃鸡”数据,为什么别人能吃鸡?

 

REDIS_HOST:$REDIS_PORT/10

通过flower,我们可以看到运行的效率还是非常不错的。

Python教你每把轻松吃鸡_第4张图片

 

在执行过程中会发现get_battle_list跑太快,导致get_battle_info即使开了30个并发都还会积压很多,所以需要适时的去停一下这些worker。 在我们抓到20万条信息之后就可以停下来了。

Step 3 数据分析

分析方案

20万场战斗的数据已经抓取好了,全部分成json文件存在我本地磁盘上,接下来就做一些简单的分析。 python在数据分析领域也非常强大,有很多非常优秀的库,如pandas和NumPy,可惜我都没有学过,而且对于一个高考数学只考了70几分的人来说,数据分析实在是难,所以就自己写了一个非常简单的程序来做一些浅度分析。 需要进行深度分析,又不想自己爬虫的大牛可以联系我打包这些数据。

# coding=utf-8import osimport jsonimport datetimeimport math from conf import settings class UserTeamTypeData: def init(self, team_type, player_count): self.team_type = team_type self.player_count = player_count self.label = {} self.dead_poison_circle_no = {} self.count = 0 self.damage = 0 self.survival_duration = 0 # 生存时间 self.driving_distance = 0 self.forward_distance = 0 self.times_assist = 0 # 助攻 self.times_head_shot = 0 self.times_kill = 0 self.times_reborn = 0 # 被救次数 self.times_save = 0 # 救人次数self.top_kill_distance = [] self.top_kill_distance_weapon_use = {} self.vehicle_kill = 0 # 车辆杀死self.award_gold = 0 self.times_reborn_by_role_sex = {0: 0, 1: 0} # 0 男 1 女self.times_save_by_role_sex = {0: 0, 1: 0} # 0 男 1 女 defupdate_dead_poison_circle_no(self, dead_poison_circle_no): ifdead_poison_circle_no in self.dead_poison_circle_no: self.dead_poison_circle_no[dead_poison_circle_no] += 1 elseself.dead_poison_circle_no[dead_poison_circle_no] = 1 defupdate_times_reborn_and_save_by_role_sex(self, role, times_reborn, times_save): ifrole not in self.times_reborn_by_role_sex: return self.times_reborn_by_role_sex[role] += times_reborn self.times_save_by_role_sex[role] += times_save defupdate_top_kill_distance_weapon_use(self, weaponid): if weaponid notin****self.top_kill_distance_weapon_use: self.top_kill_distance_weapon_use[weaponid] = 1 elseself.top_kill_distance_weapon_use[weaponid] += 1 class UserBattleData: definit(self, openid): self.openid = openid self.team_type_res = {} self.label = {} self.hour_counter = {} self.weekday_counter = {} self.usetime = 0 self.day_record = set() self.battle_counter = 0 def get_avg_use_time_per_day(self): # print "get_avg_use_time_per_day:", self.openid, self.usetime, len(self.day_record), self.usetime / len(self.day_record) return self.usetime / len(self.day_record) defupdate_label(self, lable): if lable in self.label: self.label[lable] += 1 elseself.label[lable] = 1 def get_team_type_data(self, team_type, player_count): player_count = int(math.ceil(float(player_count) / 10)) team_type_key = '%d_%d' % (team_type, player_count) if team_type_key not in self.team_type_res: userteamtypedata = UserTeamTypeData(team_type, player_count) self.team_type_res[team_type_key] = userteamtypedata else: userteamtypedata = self.team_type_res[team_type_key] return userteamtypedata defupdate_user_time_property(self, dt_event_time): dt_event_time = datetime.datetime.fromtimestamp(dt_event_time) hour = dt_event_time.hour ifhour in self.hour_counter: self.hour_counter[hour] += 1 elseself.hour_counter[hour] = 1 weekday = dt_event_time.weekday() if weekday in****self.weekday_counter: self.weekday_counter[weekday] += 1 elseself.weekday_counter[weekday] = 1 self.day_record.add(dt_event_time.date()) defupdate_battle_info_by_room(self, roomid): # print ' load ', self.openid, roomidfile = os.path.join(settings.Res_UserBattleInfo_Dir, self.openid, '%s.txt' % roomid)with open(file, 'r') as rf: battledata = json.load(rf) self.battle_counter += 1 base_info = battledata['base_info'] self.update_user_time_property(base_info['dt_event_time']) battle_info = battledata['battle_info'] userteamtypedata = self.get_team_type_data(base_info['team_type'], base_info['player_count']) userteamtypedata.count += 1 userteamtypedata.award_gold += battle_info['award_gold'] userteamtypedata.damage += battle_info['damage'] userteamtypedata.update_dead_poison_circle_no(battle_info['dead_poison_circle_no']) userteamtypedata.driving_distance += battle_info['driving_distance'] userteamtypedata.forward_distance += battle_info['forward_distance'] self.update_label(battle_info['label']) userteamtypedata.survival_duration += battle_info['survival_duration'] self.usetime += battle_info['survival_duration']/60 userteamtypedata.times_assist += battle_info['times_assist'] userteamtypedata.times_head_shot += battle_info['times_head_shot'] userteamtypedata.times_kill += battle_info['times_kill'] userteamtypedata.times_reborn += battle_info['times_reborn'] userteamtypedata.times_save += battle_info['times_save'] userteamtypedata.damage += battle_info['damage'] userteamtypedata.top_kill_distance.append(battle_info['top_kill_distance']) userteamtypedata.update_times_reborn_and_save_by_role_sex(base_info['role_sex'],battle_info['times_reborn'], battle_info['times_save']) defget_user_battleinfo_rooms(self): user_dir = os.path.join(settings.Res_UserBattleInfo_Dir, self.openid) r = [room for room inos.listdir(user_dir)] r = [rr.replace('.txt', '') for rr in r] return r classAllUserCounter:def init(self): self.hour_counter = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0, 7: 0, 8: 0, 9: 0, 10: 0, 11: 0, 12: 0, 13: 0, 14: 0,15: 0, 16: 0, 17: 0, 18: 0, 19: 0, 20: 0, 21: 0, 22: 0, 23: 0} self.weekday_counter = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0} self.times_reborn_by_role_sex = {0: 0, 1: 0} # 0 男 1 女 self.times_save_by_role_sex = {0: 0, 1: 0} # 0 男 1 女 self.user_count = 0 self.battle_count = 0 self.every_user_use_time_per_day = [] self.top_kill_distance = 0 defavg_use_time(self): return sum(self.every_user_use_time_per_day) / len(self.every_user_use_time_per_day) def add_user_data(self, userbattledata): self.every_user_use_time_per_day.append(userbattledata.get_avg_use_time_per_day()) self.battle_count += userbattledata.battle_counter self.user_count += 1 for k inuserbattledata.hour_counter: if k in self.hour_counter: self.hour_counter[k] += userbattledata.hour_counter[k] elseself.hour_counter[k] = userbattledata.hour_counter[k] for weekday in userbattledata.weekday_counter: ifweekday in self.weekday_counter: self.weekday_counter[weekday] += userbattledata.weekday_counter[weekday] elseself.weekday_counter[weekday] = userbattledata.weekday_counter[weekday] for userteamtype inuserbattledata.team_type_res: userteamtypedata = userbattledata.team_type_res[userteamtype] for k inuserteamtypedata.times_reborn_by_role_sex: self.times_reborn_by_role_sex[k] += userteamtypedata.times_reborn_by_role_sex[k] for k inuserteamtypedata.times_save_by_role_sex: self.times_save_by_role_sex[k] += userteamtypedata.times_save_by_role_sex[k] if userteamtypedata.top_kill_distance > self.top_kill_distance: self.top_kill_distance = userteamtypedata.top_kill_distancedefstr(self): res = [] res.append('总用户数 %d' % self.user_count) res.append('总战斗数 %d' % self.battle_count) res.append('平均日耗时 %d' % self.avg_use_time()) res.append('最远击杀 %d' % max(self.top_kill_distance)) res.append('男性角色 被救%d次 救人%d次' % (self.times_reborn_by_role_sex[0],self.times_save_by_role_sex[0])) res.append('女性角色 被救%d次 救人%d次' % (self.times_reborn_by_role_sex[1],self.times_save_by_role_sex[1])) res.append('小时分布') for hour in range(0, 24): # res.append(' %d: %d' % (hour, self.hour_counter[hour])) res.append(' %d: %d %.2f%%' % (hour, self.hour_counter[hour],self.hour_counter[hour]/float(self.battle_count)100)) res.append('星期分布') # res.append(self.weekday_counter.str()) for weekday inrange(0, 7): res.append(' %d: %d %.2f%%' % (weekday+1,self.weekday_counter[weekday],(self.weekday_counter[weekday]/float(self.battle_count)100))) return ' '.join(res)def get_user_battleinfo_rooms(openid): user_dir = os.path.join(settings.Res_UserBattleInfo_Dir, openid) # files = os.listdir(user_dir) r = [room for room in os.listdir(user_dir)] r = [rr.replace('.txt', '') for rr in r] return r ifname == 'main': alluserconter = AllUserCounter() folders = os.listdir(settings.Res_UserBattleInfo_Dir) i = 0 for folder in folders: i+=1 print i, '/' , len(folders), folder userbattledata = UserBattleData(folder) for room inuserbattledata.get_user_battleinfo_rooms(): userbattledata.update_battle_info_by_room(room) alluserconter.add_user_data(userbattledata) print " " * 3 print "---------------------------------------" print alluserconter

分析结果

1. 平均用户日在线时长2小时

Python教你每把轻松吃鸡_第5张图片

 

从分布图上看大部分用户都在1小时以上,最猛的几个人超过8小时。

注:我这里统计的是每一局的存活时间,实际在线时长会比我这个更长。

2. 女性角色被救次数高于男性

Python教你每把轻松吃鸡_第6张图片

 

终于知道为什么有那么多人妖了,原来在游戏里面可以占便宜啊。

3. 女性角色救人次数高于男性

Python教你每把轻松吃鸡_第7张图片

 

给了大家一个带妹上分的好理由。

4. 周五大家最忙

Python教你每把轻松吃鸡_第8张图片

 

估计周五大家都要忙着交差和写周报了。

5. 晚上22点是游戏高峰

Python教你每把轻松吃鸡_第9张图片

 

凌晨还有那么多人玩,你们不睡觉吗?

6. 最远击杀距离639米*

我看了一下98K、SKS和AWP的有效射程,大致都在800米以内,所以这个值可信度还是可以的。 反过来看抖音上的那些超远距离击*杀应该都是摆拍的。

7. 能拿到「救死扶伤」称号才是最高荣耀

Python教你每把轻松吃鸡_第10张图片

 

从分布情况可以看出来,救死扶伤比十杀还要难。

Python教你每把轻松吃鸡_第11张图片

 

能拿到救死扶伤称号的大部分都是女性角色,再一次证明玩游戏要带妹。 回归到这个游戏的本质,那就是生存游戏,没什么比活下来更重要的了。

结尾

 

你可能感兴趣的:(Python,爬虫,Python入门)