用 Python 分析了 20 万场吃鸡数据,看看玩家群体是怎么样的
首先,神槍鎮樓
背景
最近老板愛上了吃雞(手游:全軍出擊),經常拉著我們開黑,只能放棄午休的時間,陪老板在沙漠里奔波。 上周在在微信游戲頻道看戰績的時候突發奇想,是不是可以通過這個方式抓取到很多戰斗數據,然后分析看看有什么規律。
?
秀一波戰績,開黑情況下我們團隊吃雞率非常高,近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
| 方法 | GET |
| 參數 | openid、pass_ticket、plat_id、after_time、limit |
| cookie | key pass_ticket、uin、pgv_pvid、sd_cookie_crttime、sd_userid |
- response
?
Python| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 | { ????"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" } |
?
2. 獲取用戶戰績列表接口
- 分析
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
?
Python| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 | { "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
| 方法 | GET |
| 參數 | openid、pass_ticket、room_id |
| cookie | key pass_ticket、uin、pgv_pvid、sd_cookie_crttime、sd_userid |
- request
?
Python| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 | { "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請求接口獲取數據
?
Python| 1 2 3 4 5 6 7 | 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))) ????with codecs.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,為了避免重復采集,所以我們需要記錄下哪些信息是我們采集過的。 核心代碼片斷
Python| 1 2 3 4 5 6 7 8 | # rediskeys.user_battle_list 根據openid獲取存在redis中的key值 def user_battle_list(openid): ????return 'ubl_%s' % (openid) # 在提取battle list之前,首先判斷這用用戶的數據是否已經提取過了 if settings.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
Python| 1 2 3 4 5 6 7 8 9 10 11 | 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數據實現完整的任務邏輯,如:
Python| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | @app.task(name='get_battle_list') def get_battle_list(openid, plat_id=None, after_time=0, update_time=None): ????# 判斷是否已經取過用戶戰績列表信息 ????if settings.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 not settings.DataRedis.get(rediskeys.user_battle(openid, room_id)): ????????????get_battle_info.delay(openid, plat_id, room_id) ????return True |
?
開始抓取
因為我們是發散是爬蟲,所以需要給代碼一個用戶的入口,所以需要手動創建一個用戶的采集任務
Python| 1 2 3 4 5 6 | 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去開始爬蟲
Python| 1 2 3 4 5 6 7 8 | # 啟動獲取用戶詳情worker celery -A tasks.all worker -c 5 --queue=queue_get_user_info --loglevel=info -n get_user_info@%h # 啟動獲取戰績列表worker celery -A tasks.all worker -c 5 --queue=queue_get_battle_list --loglevel=info -n get_battle_list@%h # 啟動獲取戰績詳情worker celery -A tasks.all worker -c 30 --queue=queue_get_battle_info --loglevel=info -n get_battle_info@%h |
這樣我們的爬蟲就可以愉快的跑起來了。再通過celery-flower來查看執行情況。
Python| 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幾分的人來說,數據分析實在是難,所以就自己寫了一個非常簡單的程序來做一些淺度分析。 需要進行深度分析,又不想自己爬蟲的大牛可以聯系我打包這些數據。
Python| 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 | # coding=utf-8 import os import json import datetime import 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 女 ????def update_dead_poison_circle_no(self, dead_poison_circle_no): ????????if dead_poison_circle_no in self.dead_poison_circle_no: ????????????self.dead_poison_circle_no[dead_poison_circle_no] += 1 ????????else: ????????????self.dead_poison_circle_no[dead_poison_circle_no] = 1 ????def update_times_reborn_and_save_by_role_sex(self, role, times_reborn, times_save): ????????if role 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 ????def update_top_kill_distance_weapon_use(self, weaponid): ????????if weaponid not in self.top_kill_distance_weapon_use: ????????????self.top_kill_distance_weapon_use[weaponid] = 1 ????????else: ????????????self.top_kill_distance_weapon_use[weaponid] += 1 class UserBattleData: ????def __init__(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) ????def update_label(self, lable): ????????if lable in self.label: ????????????self.label[lable] += 1 ????????else: ????????????self.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 ????def update_user_time_property(self, dt_event_time): ????????dt_event_time = datetime.datetime.fromtimestamp(dt_event_time) ????????hour = dt_event_time.hour ????????if hour in self.hour_counter: ????????????self.hour_counter[hour] += 1 ????????else: ????????????self.hour_counter[hour] = 1 ????????weekday = dt_event_time.weekday() ????????if weekday in self.weekday_counter: ????????????self.weekday_counter[weekday] += 1 ????????else: ????????????self.weekday_counter[weekday] = 1 ????????self.day_record.add(dt_event_time.date()) ????def update_battle_info_by_room(self, roomid): ????????# print '??load ', self.openid, roomid ????????file = 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']) ????def get_user_battleinfo_rooms(self): ????????user_dir = os.path.join(settings.Res_UserBattleInfo_Dir, self.openid) ????????r = [room for room in os.listdir(user_dir)] ????????r = [rr.replace('.txt', '') for rr in r] ????????return r class AllUserCounter: ????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 ????def avg_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 in userbattledata.hour_counter: ????????????if k in self.hour_counter: ????????????????self.hour_counter[k] += userbattledata.hour_counter[k] ????????????else: ????????????????self.hour_counter[k] = userbattledata.hour_counter[k] ????????for weekday in userbattledata.weekday_counter: ????????????if weekday in self.weekday_counter: ????????????????self.weekday_counter[weekday] += userbattledata.weekday_counter[weekday] ????????????else: ????????????????self.weekday_counter[weekday] = userbattledata.weekday_counter[weekday] ????????for userteamtype in userbattledata.team_type_res: ????????????userteamtypedata = userbattledata.team_type_res[userteamtype] ????????????for k in userteamtypedata.times_reborn_by_role_sex: ????????????????self.times_reborn_by_role_sex[k] += userteamtypedata.times_reborn_by_role_sex[k] ????????????for k in userteamtypedata.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_distance ????def __str__(self): ????????res = [] ????????res.append('總用戶數\t%d' % self.user_count) ????????res.append('總戰斗數\t%d' % self.battle_count) ????????res.append('平均日耗時\t%d' % self.avg_use_time()) ????????res.append('最遠擊殺\t%d' % max(self.top_kill_distance)) ????????res.append('男性角色\t被救%d次\t救人%d次' % (self.times_reborn_by_role_sex[0], self.times_save_by_role_sex[0])) ????????res.append('女性角色\t被救%d次\t救人%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('\t%d: %d' % (hour, self.hour_counter[hour])) ????????????res.append('\t%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 in range(0, 7): ????????????res.append('\t%d: %d %.2f%%' % (weekday+1, self.weekday_counter[weekday], (self.weekday_counter[weekday]/float(self.battle_count)*100))) ????????return '\n'.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 if __name__ == '__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 in userbattledata.get_user_battleinfo_rooms(): ????????????userbattledata.update_battle_info_by_room(room) ????????alluserconter.add_user_data(userbattledata) ????print "\n" * 3 ????print "---------------------------------------" ????print alluserconter |
?
分析結果
1. 平均用戶日在線時長2小時
從分布圖上看大部分用戶都在1小時以上,最猛的幾個人超過8小時。
注:我這里統計的是每一局的存活時間,實際在線時長會比我這個更長。
2. 女性角色被救次數高于男性
終于知道為什么有那么多人妖了,原來在游戲里面可以占便宜啊。
3. 女性角色救人次數高于男性
給了大家一個帶妹上分的好理由。
4. 周五大家最忙
估計周五大家都要忙著交差和寫周報了。
5. 晚上22點是游戲高峰
凌晨還有那么多人玩,你們不睡覺嗎?
6. 最遠擊殺距離639米
我看了一下98K、SKS 和 AWP 的有效射程,大致都在 800 米以內,所以這個值可信度還是可以的。 反過來看抖音上的那些超遠距離擊殺應該都是擺拍的。
7. 能拿到「救死扶傷」稱號才是最高榮耀
從分布情況可以看出來,救死扶傷比十殺還要難。
能拿到救死扶傷稱號的大部分都是女性角色,再一次證明玩游戲要帶妹。 回歸到這個游戲的本質,那就是生存游戲,沒什么比活下來更重要的了。
結尾
這次爬蟲主要是利用了微信游戲頻道可以查看陌生人數據的場景才能提取到這么多數據。我們可以通過同樣的手段來分析王者榮耀和其它游戲的數據,有興趣的同學可以嘗試一下。 最后再說一下,UMP9 是把好槍,配 2 倍鏡非常爽。
轉載于:https://www.cnblogs.com/paisenpython/p/10303463.html
總結
以上是生活随笔為你收集整理的用 Python 分析了 20 万场吃鸡数据,看看玩家群体是怎么样的的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 我向面试官讲解了单例模式,他对我竖起了大
- 下一篇: 大话集群和负载均衡