首先,神槍鎮(zhèn)樓
背景
最近老板愛上了吃雞(手游:全軍出擊)动猬,經(jīng)常拉著我們開黑啤斗,只能放棄午休的時間,陪老板在沙漠里奔波枣察。 上周在在微信游戲頻道看戰(zhàn)績的時候突發(fā)奇想争占,是不是可以通過這個方式抓取到很多戰(zhàn)斗數(shù)據(jù)燃逻,然后分析看看有什么規(guī)律。
秀一波戰(zhàn)績臂痕,開黑情況下我們團(tuán)隊吃雞率非常高伯襟,近100場吃雞次數(shù)51次
簡單評估了一下,覺得可行握童,咱就開始姆怪。
Step 1 分析數(shù)據(jù)接口
第一步當(dāng)然是把這些戰(zhàn)績數(shù)據(jù)采集下來,首先我們需要了解頁面背后的故事澡绩。去看看頁面是如何獲取戰(zhàn)斗數(shù)據(jù)的稽揭。
使用Charles抓包
抓包實(shí)現(xiàn)
在Mac下推薦使用工具Charles來從協(xié)議層抓取手機(jī)上的流量,原理就是在Mac上開啟一個代理服務(wù)器肥卡,然后將手機(jī)的網(wǎng)絡(luò)代理設(shè)置為Mac溪掀,這樣手機(jī)上的所有流量都會經(jīng)過我們的代理服務(wù)器了。 大致流程如下:
https加密流量的處理
在實(shí)際操作的時候發(fā)現(xiàn)微信所有的流量都走了HTTPS步鉴,導(dǎo)致我們的抓到的都是加密數(shù)據(jù)揪胃,對我們沒有任何參考意義。 經(jīng)過研究氛琢,可以通過在手機(jī)和電腦都安裝Charles根證書的方式來實(shí)現(xiàn)對Https流量的分析喊递,具體操作可以參考:
charles mac下https抓包和iphone https抓包
解決Charles無法正常抓包iOS 11中的Https請求
安裝證書后,我們的流量大致是這樣子的經(jīng)過上述的配置阳似,我們已經(jīng)可以讀取到https的請求和響應(yīng)數(shù)據(jù)了骚勘,如下圖所示。
windows下用findler可以實(shí)現(xiàn)相同的功能
其實(shí)這就是一個非常典型的中間人場景
數(shù)據(jù)接口
接下來就根據(jù)這些數(shù)據(jù)來找出我們需要的接口了撮奏,經(jīng)過分析俏讹,主要涉及三個接口
獲取用戶信息接口
獲取用戶戰(zhàn)績列表接口
獲取用戶指定戰(zhàn)績詳細(xì)信息接口
下面我們一個一個看
1. 獲取用戶信息接口
request
API/cgi-bin/gamewap/getpubgmbattlelist
方法GET
參數(shù)openid、pass_ticket挽荡、plat_id藐石、after_time、limit
cookiekey 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": "槍戰(zhàn) | 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": ["絕地求生", "正版還原", "好友開黑", "百人對戰(zhàn)", "超大地圖"],
????????"recommend_reason": "正版絕地求生株依,荒野射擊",
????????"size_desc": "808.2M"
????},
????"is_guest": true,
????"is_blocked": false,
????"errcode": 0,
????"errmsg": "ok"
}
2. 獲取用戶戰(zhàn)績列表接口
分析
openid是用戶的惟一標(biāo)識。
2. 獲取用戶戰(zhàn)績列表接口
request
API/cgi-bin/gamewap/getpubgmbattlelist
方法GET
參數(shù)openid延窜、pass_ticket恋腕、plat_id、after_time逆瑞、limit
cookiekey 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來進(jìn)行分頁吻育,遍歷獲取時可以根據(jù)接口響應(yīng)的has_next和next_after_time來判斷是否還有下一頁的數(shù)據(jù)。
列表里面的room_id是每一場battle的惟一標(biāo)識淤井。
3. 獲取用戶戰(zhàn)績詳情接口
request
API/cgi-bin/gamewap/getpubgmbattledetail
方法GET
參數(shù)openid布疼、pass_ticket、room_id
cookiekey 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
}
分析
這個接口響應(yīng)了戰(zhàn)斗的詳細(xì)信息贱案,包括殺人數(shù)、爆頭數(shù)渐行、救人數(shù)轰坊、跑動距離等等,足夠我們分析了祟印。
這個接口還響應(yīng)了是被誰殺死的以及組團(tuán)成員的openid,利用這個特性我們這可無限深度的發(fā)散爬取更多用戶的數(shù)據(jù)粟害。
至于cookie中的息pass_ticket等信息肯定是用于權(quán)限認(rèn)證的蕴忆,在上述的幾次請求中這些信息都沒有變化,所以我們不需要深研其是怎么算出來的悲幅,只需要抓包提取到默認(rèn)信息后填到代碼里面就可以用了套鹅。
Step 2 爬取數(shù)據(jù)
接口已經(jīng)確定下來了,接下來就是去抓取足夠量的數(shù)據(jù)了汰具。
使用requests請求接口獲取數(shù)據(jù)
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來標(biāo)記已經(jīng)爬取過的信息
在上述接口中我們可能從用戶A的入口進(jìn)去找到用戶B的openid,然后從用戶B的入口進(jìn)去又找到用戶A的openid留荔,為了避免重復(fù)采集吟孙,所以我們需要記錄下哪些信息是我們采集過的。 核心代碼片斷
# rediskeys.user_battle_list 根據(jù)openid獲取存在redis中的key值
def user_battle_list(openid):
????return 'ubl_%s' % (openid)
# 在提取battle list之前聚蝶,首先判斷這用用戶的數(shù)據(jù)是否已經(jīng)提取過了
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是一個非常好用的分布式隊列管理工具,我這次只打算在我自己的電腦上運(yùn)行碘勉,所以并沒有用到分布式的功能巷挥。 我們創(chuàng)建三個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數(shù)據(jù)實(shí)現(xiàn)完整的任務(wù)邏輯,如:
@app.task(name='get_battle_list')
def get_battle_list(openid, plat_id=None, after_time=0, update_time=None):
????# 判斷是否已經(jīng)取過用戶戰(zhàn)績列表信息
????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
????# 提取戰(zhàn)績列表
????battle_list = handles.get_battle_list_handle(openid, plat_id, after_time=0, update_time=None)
????# 為每一場戰(zhàn)斗創(chuàng)建異步獲取詳情任務(wù)
????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
開始抓取
因?yàn)槲覀兪前l(fā)散是爬蟲验靡,所以需要給代碼一個用戶的入口倍宾,所以需要手動創(chuàng)建一個用戶的采集任務(wù)
# 啟動獲取用戶詳情worker
celery -A tasks.all worker -c 5 --queue=queue_get_user_info --loglevel=info -n get_user_info@%h
# 啟動獲取戰(zhàn)績列表worker
celery -A tasks.all worker -c 5 --queue=queue_get_battle_list --loglevel=info -n get_battle_list@%h
# 啟動獲取戰(zhàn)績詳情worker
celery -A tasks.all worker -c 30 --queue=queue_get_battle_info --loglevel=info -n get_battle_info@%h
這樣我們的爬蟲就可以愉快的跑起來了雏节。再通過celery-flower來查看執(zhí)行情況。
Python
1celery flower -A tasks.all --broker=redis://:$REDIS_PASS@$REDIS_HOST:$REDIS_PORT/10
通過flower高职,我們可以看到運(yùn)行的效率還是非常不錯的矾屯。在執(zhí)行過程中會發(fā)現(xiàn)get_battle_list跑太快,導(dǎo)致get_battle_info即使開了30個并發(fā)都還會積壓很多初厚,所以需要適時的去停一下這些worker件蚕。 在我們抓到20萬條信息之后就可以停下來了。
Step 3 數(shù)據(jù)分析
分析方案
20萬場戰(zhàn)斗的數(shù)據(jù)已經(jīng)抓取好了产禾,全部分成json文件存在我本地磁盤上排作,接下來就做一些簡單的分析。 python在數(shù)據(jù)分析領(lǐng)域也非常強(qiáng)大亚情,有很多非常優(yōu)秀的庫妄痪,如pandas和NumPy,可惜我都沒有學(xué)過楞件,而且對于一個高考數(shù)學(xué)只考了70幾分的人來說衫生,數(shù)據(jù)分析實(shí)在是難,所以就自己寫了一個非常簡單的程序來做一些淺度分析土浸。 需要進(jìn)行深度分析罪针,又不想自己爬蟲的大牛可以聯(lián)系我打包這些數(shù)據(jù)黄伊。
# 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??# 被救次數(shù)
????????self.times_save = 0??# 救人次數(shù)
????????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('總用戶數(shù)\t%d' % self.user_count)
????????res.append('總戰(zhàn)斗數(shù)\t%d' % self.battle_count)
????????res.append('平均日耗時\t%d' % self.avg_use_time())
????????res.append('最遠(yuǎn)擊殺\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
分析結(jié)果
1. 平均用戶日在線時長2小時
從分布圖上看大部分用戶都在1小時以上泪酱,最猛的幾個人超過8小時。
注:我這里統(tǒng)計的是每一局的存活時間还最,實(shí)際在線時長會比我這個更長墓阀。
2. 女性角色被救次數(shù)高于男性
終于知道為什么有那么多人妖了,原來在游戲里面可以占便宜啊拓轻。
3. 女性角色救人次數(shù)高于男性
給了大家一個帶妹上分的好理由斯撮。
4. 周五大家最忙
估計周五大家都要忙著交差和寫周報了。
5. 晚上22點(diǎn)是游戲高峰
凌晨還有那么多人玩扶叉,你們不睡覺嗎勿锅?
6. 最遠(yuǎn)擊殺距離639米
我看了一下98K、SKS 和 AWP 的有效射程辜梳,大致都在 800 米以內(nèi)粱甫,所以這個值可信度還是可以的。 反過來看抖音上的那些超遠(yuǎn)距離擊殺應(yīng)該都是擺拍的作瞄。
7. 能拿到「救死扶傷」稱號才是最高榮耀
從分布情況可以看出來茶宵,救死扶傷比十殺還要難。
能拿到救死扶傷稱號的大部分都是女性角色宗挥,再一次證明玩游戲要帶妹乌庶。 回歸到這個游戲的本質(zhì)种蝶,那就是生存游戲,沒什么比活下來更重要的了瞒大。
結(jié)尾
這次爬蟲主要是利用了微信游戲頻道可以查看陌生人數(shù)據(jù)的場景才能提取到這么多數(shù)據(jù)螃征。我們可以通過同樣的手段來分析王者榮耀和其它游戲的數(shù)據(jù),有興趣的同學(xué)可以嘗試一下透敌。 最后再說一下盯滚,UMP9 是把好槍,配 2 倍鏡非常爽酗电。