|
|
|
|
移动端

用Python分析了20万场吃鸡数据

最近老板爱上了吃鸡(手游:全军出击),经常拉着我们开黑,只能放弃午休的时间,陪老板在沙漠里奔波。 上周在在微信游戏频道看战绩的时候突发奇想,是不是可以通过这个方式抓取到很多战斗数据,然后分析看看有什么规律。

作者:张波来源:Python开发者|2018-07-10 15:28

【新品产上线啦】51CTO播客,随时随地,碎片化学习

首先,神枪镇楼

背景

最近老板爱上了吃鸡(手游:全军出击),经常拉着我们开黑,只能放弃午休的时间,陪老板在沙漠里奔波。 上周在在微信游戏频道看战绩的时候突发奇想,是不是可以通过这个方式抓取到很多战斗数据,然后分析看看有什么规律。

秀一波战绩,开黑情况下我们团队吃鸡率非常高,近100场吃鸡次数51次

简单评估了一下,觉得可行,咱就开始。

Step 1 分析数据接口

第一步当然是把这些战绩数据采集下来,首先我们需要了解页面背后的故事。去看看页面是如何获取战斗数据的。

使用Charles抓包

抓包实现

在Mac下推荐使用工具Charles来从协议层抓取手机上的流量,原理就是在Mac上开启一个代*理*服务器,然后将手机的网络代*理设置为Mac,这样手机上的所有流量都会经过我们的代*理*服务器了。 大致流程如下:

https加密流量的处理

在实际操作的时候发现微信所有的流量都走了HTTPS,导致我们的抓到的都是加密数据,对我们没有任何参考意义。 经过研究,可以通过在手机和电脑都安装Charles根证书的方式来实现对Https流量的分析,具体操作可以参考:

  • charles mac下https抓包和iphone https抓包
  • 解决Charles无法正常抓包iOS 11中的Https请求

安装证书后,我们的流量大致是这样子的

经过上述的配置,我们已经可以读取到https的请求和响应数据了,如下图所示。

  • windows下用findler可以实现相同的功能
  • 其实这就是一个非常典型的中间人场景

数据接口

接下来就根据这些数据来找出我们需要的接口了,经过分析,主要涉及三个接口

  • 获取用户信息接口
  • 获取用户战绩列表接口
  • 获取用户指定战绩详细信息接口

下面我们一个一个看

1. 获取用户信息接口

  • request
API /cgi-bin/gamewap/getpubgmdatacenterindex
方法 GET
参数 openid、pass_ticket
cookie key pass_ticket、uin、pgv_pvid、sd_cookie_crttime、sd_userid
  • response 
  1.  
  2.     "user_info": {  
  3.         "openid""oODfo0pjBQkcNuR4XLTQ321xFVws" 
  4.         "head_img_url""http://wx.qlogo.cn/mmhead/Q3auHgzwzM5hSWxxxxxUQPwW9ibxxxx9DlxLTsKWk97oWpDI0rg/96" 
  5.         "nick_name""望" 
  6.         "role_name""xxxx" 
  7.         "zone_area_id": 0,  
  8.         "plat_id": 1  
  9.     },  
  10.     "battle_info": {  
  11.         "total_1": 75,  
  12.         "total_10": 336,  
  13.         "total_game": 745,  
  14.         "total_kill": 1669  
  15.     },  
  16.     "battle_list": [{  
  17.         "map_id": 1,  
  18.         "room_id""6575389198189071197" 
  19.         "team_id": 57,  
  20.         "dt_event_time": 1530953799,  
  21.         "rank_in_ds": 3,  
  22.         "times_kill": 1,  
  23.         "label""前五" 
  24.         "team_type": 1,  
  25.         "award_gold": 677,  
  26.         "mode": 0  
  27.     }],  
  28.     "appitem": {  
  29.         "AppID""wx13051697527efc45" 
  30.         "IconURL""https://mmocgame.qpic.cn/wechatgame/mEMdfrX5RU0dZFfNEdCsMJpfsof1HE0TP3cfZiboX0ZPxqh5aZnHjxPFXUGgsXmibe/0" 
  31.         "Name""绝地求生 全军出击" 
  32.         "BriefName""绝地求生 全军出击" 
  33.         "Desc""官方正版绝地求生手游" 
  34.         "Brief""枪战 | 808.2M" 
  35.         "WebURL""https://game.weixin.qq.com/cgi-bin/h5/static/detail_v2/index.html?wechat_pkgid=detail_v2&appid=wx13051697527efc45&show_bubble=0" 
  36.         "DownloadInfo": {  
  37.             "DownloadURL""https://itunes.apple.com/cn/app/id1304987143" 
  38.             "DownloadFlag": 5  
  39.         },  
  40.         "Status": 0,  
  41.         "AppInfoFlag": 45,  
  42.         "Label": [],  
  43.         "AppStorePopUpDialogConfig": {  
  44.             "Duration": 1500,  
  45.             "Interval": 172800,  
  46.             "ServerTimestamp": 1531066098  
  47.         },  
  48.         "HasEnabledChatGroup"false 
  49.         "AppType": 0,  
  50.         "game_tag_list": ["绝地求生""正版还原""好友开黑""百人对战""超大地图"],  
  51.         "recommend_reason""正版绝地求生,荒野射击" 
  52.         "size_desc""808.2M"  
  53.     },  
  54.     "is_guest"true 
  55.     "is_blocked"false 
  56.     "errcode": 0,  
  57.     "errmsg""ok"  
  58.  
  • 分析

openid是用户的惟一标识。

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

  • request
API /cgi-bin/gamewap/getpubgmbattlelist
方法 GET
参数 openid、pass_ticket、plat_id、after_time、limit
cookie key pass_ticket、uin、pgv_pvid、sd_cookie_crttime、sd_userid
  • response 
  1.  
  2. "errcode": 0,  
  3. "errmsg""ok" 
  4. "next_after_time": 1528120556,  
  5. "battle_list": [{  
  6.     "map_id": 1,  
  7.     "room_id""6575389198111172597" 
  8.     "team_id": 57,  
  9.     "dt_event_time": 1530953799,  
  10.     "rank_in_ds": 3,  
  11.     "times_kill": 1,  
  12.     "label""前五" 
  13.     "team_type": 1,  
  14.     "award_gold": 677,  
  15.     "mode": 0  
  16. }, {  
  17.     "map_id": 1,  
  18.     "room_id""6575336498940384115" 
  19.     "team_id": 11,  
  20.     "dt_event_time": 1530941404,  
  21.     "rank_in_ds": 5,  
  22.     "times_kill": 2,  
  23.     "label""前五" 
  24.     "team_type": 1,  
  25.     "award_gold": 632,  
  26.     "mode": 0  
  27. }],  
  28. "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_id
cookie key pass_ticket、uin、pgv_pvid、sd_cookie_crttime、sd_userid
  • request 
  1.  
  2. "errcode": 0,  
  3. "errmsg""ok" 
  4. "base_info": {  
  5.     "nick_name""柚茶" 
  6.     "head_img_url""http://wx.qlogo.cn/mmhead/xxxx/96" 
  7.     "dt_event_time": 1528648165,  
  8.     "team_type": 4,  
  9.     "rank": 1,  
  10.     "player_count": 100,  
  11.     "role_sex": 1,  
  12.     "label""大吉大利" 
  13.     "openid""oODfo0s1w5lWjmxxxxxgQkcCljXQ"  
  14. },  
  15. "battle_info": {  
  16.     "award_gold": 622,  
  17.     "times_kill": 6,  
  18.     "times_head_shot": 0,  
  19.     "damage": 537,  
  20.     "times_assist": 3,  
  21.     "survival_duration": 1629,  
  22.     "times_save": 0,  
  23.     "times_reborn": 0,  
  24.     "vehicle_kill": 1,  
  25.     "forward_distance": 10140,  
  26.     "driving_distance": 5934,  
  27.     "dead_poison_circle_no": 6,  
  28.     "top_kill_distance": 223,  
  29.     "top_kill_distance_weapon_use": 2924130819,  
  30.     "be_kill_user": {  
  31.         "nick_name""小旭" 
  32.         "head_img_url""http://wx.qlogo.cn/mmhead/ibLButGMnqJNFsUtStNEV8tzlH1QpwPiaF9kxxxxx66G3ibjic6Ng2Rcg/96" 
  33.         "weapon_use": 20101000001,  
  34.         "openid""oODfo0qrPLExxxxc0QKjFPnPxyI"  
  35.     },  
  36.     "label""大吉大利"  
  37. },  
  38. "team_info": {  
  39.     "user_list": [{  
  40.         "nick_name""ooo" 
  41.         "times_kill": 6,  
  42.         "assist_count": 3,  
  43.         "survival_duration": 1638,  
  44.         "award_gold": 632,  
  45.         "head_img_url""http://wx.qlogo.cn/mmhead/Q3auHgzwzM4k4RXdyxavNxxxxUjcX6Tl47MNNV1dZDliazRKRg" 
  46.         "openid""oODfo0xxxxf1bRAXE-q-lEezK0k"  
  47.     }, {  
  48.         "nick_name""我吃炒肉" 
  49.         "times_kill": 2,  
  50.         "assist_count": 2,  
  51.         "survival_duration": 1502,  
  52.         "award_gold": 583, 
  53.         "head_img_url""http://wx.qlogo.cn/mmhead/sTJptKvBQLKd5SAAjOF0VrwiapUxxxxFffxoDUcrVjYbDf9pNENQ" 
  54.         "openid""oODfo0gIyDxxxxZpUrSrpapZSDT0"  
  55.     }]  
  56. },  
  57. "is_guest"true 
  58. "is_blocked"false  
  • 分析
  • 这个接口响应了战斗的详细信息,包括杀*敌数、爆*头数、救人数、跑动距离等等,足够我们分析了。
  • 这个接口还响应了是被谁杀死的以及组团成员的openid,利用这个特性我们这可无限深度的发散爬取更多用户的数据。

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

Step 2 爬取数据

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

使用requests请求接口获取数据

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

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

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

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

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

使用celery来管理队列

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

  1. task_queues = (  
  2.     Queue('queue_get_battle_info', exchange=Exchange('priority', type='direct'), routing_key='gbi'),  
  3.     Queue('queue_get_battle_list', exchange=Exchange('priority', type='direct'), routing_key='gbl'), 
  4.     Queue('queue_get_user_info', exchange=Exchange('priority', type='direct'), routing_key='gui'),  
  5.    
  6.  
  7. task_routes = ([  
  8.     ('get_battle_info', {'queue''queue_get_battle_info'}),  
  9.     ('get_battle_list', {'queue''queue_get_battle_list'}),  
  10.     ('get_user_info', {'queue''queue_get_user_info'}),  
  11. ],) 

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

  1. @app.task(name='get_battle_list' 
  2. def get_battle_list(openid, plat_id=None, after_time=0, update_time=None):  
  3.     # 判断是否已经取过用户战绩列表信息 
  4.     if settings.DataRedis.get(rediskeys.user_battle_list(openid)):  
  5.         return True   
  6.  
  7.     if not plat_id:  
  8.         try:  
  9.             # 提取用户信息  
  10.             us = handles.get_user_info_handles(openid)  
  11.             plat_id=us['plat_id' 
  12.         except Exception as e:  
  13.             print 'can not get user plat_id', openid, traceback.format_exc()  
  14.             return False  
  15.     # 提取战绩列表  
  16.     battle_list = handles.get_battle_list_handle(openid, plat_id, after_time=0, update_time=None)      
  17.  
  18.     # 为每一场战斗创建异步获取详情任务  
  19.     for room_id in battle_list:  
  20.         if not settings.DataRedis.get(rediskeys.user_battle(openid, room_id)):  
  21.             get_battle_info.delay(openid, plat_id, room_id) 
  22.  
  23.  
  24.     return True 

开始抓取

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

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

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

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

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

  1. celery flower -A tasks.all --broker=redis://:$REDIS_PASS@$REDIS_HOST:$REDIS_PORT/10 

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

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

Step 3 数据分析

分析方案

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

  1. # coding=utf-8  
  2. import os 
  3. import json  
  4. import datetime  
  5. import math   
  6.  
  7. from conf import settings    
  8.  
  9. class UserTeamTypeData:  
  10.     def __init__(self, team_type, player_count):  
  11.         self.team_type = team_type 
  12.         self.player_count = player_count  
  13.         self.label = {}  
  14.         self.dead_poison_circle_no = {}  
  15.         self.count = 0  
  16.         self.damage = 0  
  17.         self.survival_duration = 0  # 生存时间  
  18.         self.driving_distance = 0  
  19.         self.forward_distance = 0  
  20.         self.times_assist = 0  # 助攻  
  21.         self.times_head_shot = 0  
  22.         self.times_kill = 0  
  23.         self.times_reborn = 0  # 被救次数  
  24.         self.times_save = 0  # 救人次数  
  25.         self.top_kill_distance = []  
  26.         self.top_kill_distance_weapon_use = {}  
  27.         self.vehicle_kill = 0  # 车辆杀死  
  28.         self.award_gold = 0  
  29.         self.times_reborn_by_role_sex = {0: 0, 1: 0}  # 0 男 1 女  
  30.         self.times_save_by_role_sex = {0: 0, 1: 0}  # 0 男 1 女   
  31.  
  32.     def update_dead_poison_circle_no(self, dead_poison_circle_no):  
  33.         if dead_poison_circle_no in self.dead_poison_circle_no:  
  34.             self.dead_poison_circle_no[dead_poison_circle_no] += 1  
  35.         else 
  36.             self.dead_poison_circle_no[dead_poison_circle_no] = 1   
  37.  
  38.     def update_times_reborn_and_save_by_role_sex(self, role, times_reborn, times_save):  
  39.         if role not in self.times_reborn_by_role_sex:  
  40.             return   
  41.  
  42.         self.times_reborn_by_role_sex[role] += times_reborn  
  43.         self.times_save_by_role_sex[role] += times_save   
  44.  
  45.     def update_top_kill_distance_weapon_use(self, weaponid):  
  46.         if weaponid not in self.top_kill_distance_weapon_use:  
  47.             self.top_kill_distance_weapon_use[weaponid] = 1  
  48.         else 
  49.             self.top_kill_distance_weapon_use[weaponid] += 1  
  50.   
  51.  
  52. class UserBattleData:   
  53.  
  54.     def __init__(self, openid):  
  55.         self.openid = openid  
  56.         self.team_type_res = {}  
  57.         self.label = {}  
  58.         self.hour_counter = {}  
  59.         self.weekday_counter = {}  
  60.         self.usetime = 0  
  61.         self.day_record = set()  
  62.         self.battle_counter = 0   
  63.  
  64.     def get_avg_use_time_per_day(self):  
  65.         # print "get_avg_use_time_per_day:", self.openid, self.usetime, len(self.day_record), self.usetime / len(self.day_record)  
  66.         return self.usetime / len(self.day_record)   
  67.  
  68.     def update_label(self, lable):  
  69.         if lable in self.label:  
  70.             self.label[lable] += 1 
  71.          else 
  72.             self.label[lable] = 1   
  73.  
  74.     def get_team_type_data(self, team_type, player_count):  
  75.         player_count = int(math.ceil(float(player_count) / 10))  
  76.         team_type_key = '%d_%d' % (team_type, player_count)   
  77.  
  78.         if team_type_key not in self.team_type_res:  
  79.             userteamtypedata = UserTeamTypeData(team_type, player_count)  
  80.             self.team_type_res[team_type_key] = userteamtypedata  
  81.         else 
  82.             userteamtypedata = self.team_type_res[team_type_key]   
  83.  
  84.         return userteamtypedata   
  85.  
  86.     def update_user_time_property(self, dt_event_time):  
  87.         dt_event_time = datetime.datetime.fromtimestamp(dt_event_time)  
  88.         hour = dt_event_time.hour  
  89.         if hour in self.hour_counter:  
  90.             self.hour_counter[hour] += 1  
  91.         else 
  92.             self.hour_counter[hour] = 1   
  93.  
  94.         weekday = dt_event_time.weekday()  
  95.         if weekday in self.weekday_counter:  
  96.             self.weekday_counter[weekday] += 1  
  97.         else 
  98.             self.weekday_counter[weekday] = 1   
  99.  
  100.         self.day_record.add(dt_event_time.date())  
  101.   
  102.  
  103.     def update_battle_info_by_room(self, roomid):  
  104.         # print '  load ', self.openid, roomid  
  105.         file = os.path.join(settings.Res_UserBattleInfo_Dir, self.openid, '%s.txt' % roomid)   
  106.  
  107.         with open(file, 'r'as rf:  
  108.             battledata = json.load(rf)   
  109.  
  110.         self.battle_counter += 1  
  111.         base_info = battledata['base_info' 
  112.         self.update_user_time_property(base_info['dt_event_time'])  
  113.         battle_info = battledata['battle_info'  
  114.  
  115.         userteamtypedata = self.get_team_type_data(base_info['team_type'], base_info['player_count'])  
  116.         userteamtypedata.count += 1  
  117.         userteamtypedata.award_gold += battle_info['award_gold' 
  118.         userteamtypedata.damage += battle_info['damage' 
  119.         userteamtypedata.update_dead_poison_circle_no(battle_info['dead_poison_circle_no'])  
  120.         userteamtypedata.driving_distance += battle_info['driving_distance' 
  121.         userteamtypedata.forward_distance += battle_info['forward_distance' 
  122.         self.update_label(battle_info['label'])  
  123.         userteamtypedata.survival_duration += battle_info['survival_duration' 
  124.         self.usetime += battle_info['survival_duration']/60  
  125.         userteamtypedata.times_assist += battle_info['times_assist'
  126.         userteamtypedata.times_head_shot += battle_info['times_head_shot' 
  127.         userteamtypedata.times_kill += battle_info['times_kill' 
  128.         userteamtypedata.times_reborn += battle_info['times_reborn' 
  129.         userteamtypedata.times_save += battle_info['times_save' 
  130.         userteamtypedata.damage += battle_info['damage' 
  131.         userteamtypedata.top_kill_distance.append(battle_info['top_kill_distance']) 
  132.          userteamtypedata.update_times_reborn_and_save_by_role_sex(base_info['role_sex'], battle_info['times_reborn'],  
  133.                                                                   battle_info['times_save'])  
  134.   
  135.  
  136.     def get_user_battleinfo_rooms(self):  
  137.         user_dir = os.path.join(settings.Res_UserBattleInfo_Dir, self.openid)  
  138.         r = [room for room in os.listdir(user_dir)]  
  139.         r = [rr.replace('.txt'''for rr in r]  
  140.         return r    
  141.  
  142. class AllUserCounter:   
  143.  
  144.     def __init__(self):  
  145.         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} 
  146.  
  147.         self.weekday_counter = {0: 0, 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 6: 0} 
  148.         self.times_reborn_by_role_sex = {0: 0, 1: 0}  # 0 男 1 女  
  149.         self.times_save_by_role_sex = {0: 0, 1: 0}  # 0 男 1 女  
  150.         self.user_count = 0  
  151.         self.battle_count = 0  
  152.         self.every_user_use_time_per_day = []  
  153.         self.top_kill_distance = 0   
  154.  
  155.     def avg_use_time(self):  
  156.         return sum(self.every_user_use_time_per_day) / len(self.every_user_use_time_per_day)   
  157.  
  158.     def add_user_data(self, userbattledata):  
  159.         self.every_user_use_time_per_day.append(userbattledata.get_avg_use_time_per_day())  
  160.         self.battle_count += userbattledata.battle_counter  
  161.         self.user_count += 1   
  162.  
  163.         for k in userbattledata.hour_counter:  
  164.             if k in self.hour_counter:  
  165.                 self.hour_counter[k] += userbattledata.hour_counter[k]  
  166.             else 
  167.                 self.hour_counter[k] = userbattledata.hour_counter[k] 
  168.   
  169.  
  170.         for weekday in userbattledata.weekday_counter:  
  171.             if weekday in self.weekday_counter:  
  172.                 self.weekday_counter[weekday] += userbattledata.weekday_counter[weekday]  
  173.             else 
  174.                 self.weekday_counter[weekday] = userbattledata.weekday_counter[weekday]   
  175.  
  176.         for userteamtype in userbattledata.team_type_res:  
  177.             userteamtypedata = userbattledata.team_type_res[userteamtype] 
  178.             for k in userteamtypedata.times_reborn_by_role_sex:  
  179.                 self.times_reborn_by_role_sex[k] += userteamtypedata.times_reborn_by_role_sex[k]   
  180.  
  181.             for k in userteamtypedata.times_save_by_role_sex:  
  182.                 self.times_save_by_role_sex[k] += userteamtypedata.times_save_by_role_sex[k]   
  183.  
  184.             if userteamtypedata.top_kill_distance > self.top_kill_distance:  
  185.                 self.top_kill_distance = userteamtypedata.top_kill_distance  
  186.   
  187.  
  188.     def __str__(self):  
  189.         res = []  
  190.         res.append('总用户数\t%d' % self.user_count)  
  191.         res.append('总战斗数\t%d' % self.battle_count)  
  192.         res.append('平均日耗时\t%d' % self.avg_use_time())  
  193.         res.append('最远击杀\t%d' % max(self.top_kill_distance))  
  194.         res.append('男性角色\t被救%d次\t救人%d次' % (self.times_reborn_by_role_sex[0], self.times_save_by_role_sex[0]))  
  195.         res.append('女性角色\t被救%d次\t救人%d次' % (self.times_reborn_by_role_sex[1], self.times_save_by_role_sex[1])) 
  196.   
  197.  
  198.         res.append('小时分布' 
  199.         for hour in range(0, 24):  
  200.             # res.append('\t%d: %d' % (hour, self.hour_counter[hour]))  
  201.             res.append('\t%d: %d %.2f%%' % (hour, self.hour_counter[hour], self.hour_counter[hour]/float(self.battle_count)*100))  
  202.         res.append('星期分布' 
  203.         # res.append(self.weekday_counter.__str__())  
  204.         for weekday in range(0, 7):  
  205.             res.append('\t%d: %d %.2f%%' % (weekday+1, self.weekday_counter[weekday], (self.weekday_counter[weekday]/float(self.battle_count)*100)))   
  206.  
  207.         return '\n'.join(res)   
  208.  
  209. def get_user_battleinfo_rooms(openid):  
  210.     user_dir = os.path.join(settings.Res_UserBattleInfo_Dir, openid)  
  211.   
  212.  
  213.     # files = os.listdir(user_dir)  
  214.     r = [room for room in os.listdir(user_dir)]  
  215.     r = [rr.replace('.txt'''for rr in r]  
  216.     return r  
  217.  
  218. if __name__ == '__main__' 
  219.     alluserconter = AllUserCounter()      
  220.  
  221.     folders = os.listdir(settings.Res_UserBattleInfo_Dir)  
  222.     i = 0  
  223.     for folder in folders:  
  224.         i+=1  
  225.         print i, '/' , len(folders), folder  
  226.         userbattledata = UserBattleData(folder)  
  227.         for room in userbattledata.get_user_battleinfo_rooms():  
  228.             userbattledata.update_battle_info_by_room(room)  
  229.         alluserconter.add_user_data(userbattledata)   
  230.  
  231.     print "\n" * 3  
  232.     print "---------------------------------------" 
  233.     print alluserconter 

分析结果

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

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

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

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

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

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

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

4. 周五大家最忙

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

5. 晚上22点是游戏高峰

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

6. 最远击*杀距离639米

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

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

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

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

结尾

这次爬虫主要是利用了微信游戏频道可以查看陌生人数据的场景才能提取到这么多数据。我们可以通过同样的手段来分析王者荣耀和其它游戏的数据,有兴趣的同学可以尝试一下。 最后再说一下,UMP9是把好枪,配2倍镜非常爽。

【编辑推荐】

  1. 自从会了Python之后,我就没用过PS了!带你将照片变成卡通图片!
  2. 学习PHP好,还是Python好呢?
  3. 为什么Python开发人员应该使用Pipenv
  4. 用Python来看3天破10亿的《我不是药神》到底神在哪?
  5. 用Python抓取分析十年彩票中奖结果
【责任编辑:庞桂玉 TEL:(010)68476606】

点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

网络渗透测试——保护网络安全的技术、工具和过程

网络和计算机安全问题已经成为政府、企业必须面对的现实问题。应对安全威胁的途径之一就是采用渗透测试的方法模拟黑客的攻击,找出网络和计...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊