用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 |
| cookie | key 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、limit cookie key 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
| 方法 | GET |
| 參數 | openid、pass_ticket、room_id |
| cookie | key 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)))
? ?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。
為了避免重復采集,所以我們需要記錄下哪些信息是我們采集過的。?
核心代碼片斷:
# rediskeys.user_battle_list 根據openid獲取存在redis中的key值def user_battle_list(openid):
? ?return 'ubl_%s' % (openid)
if settings.DataRedis.get(rediskeys.user_battle_list(openid)):
? ?return True
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):
? ?# 判斷是否已經取過用戶戰績列表信息
? ?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
開始抓取
因為我們是發散是爬蟲,所以需要給代碼一個用戶的入口,需要手動創建一個用戶的采集任務。
from tasks.all import get_battle_listmy_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
# 啟動獲取戰績列表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來查看執行情況。
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? 數據分析
1. 平均用戶日在線時長2小時
從分布圖上看大部分用戶都在1小時以上,最猛的幾個人超過8小時。
注:我這里統計的是每一局的存活時間,實際在線時長會比我這個更長。
2. 女性角色被救次數高于男性
終于知道為什么有那么多人妖了,原來在游戲里面可以占便宜啊。
3. 女性角色救人次數高于男性
給了大家一個帶妹上分的好理由。
4. 周五大家最忙
估計周五大家都要忙著交差和寫周報了。
5. 晚上22點是游戲高峰
凌晨還有那么多人玩,你們不睡覺嗎?
6. 最遠擊*殺距離639米
我看了一下98K、SKS和AWP的有效射程,大致都在800米以內,所以這個值可信度還是可以的。?
反過來看抖音上的那些超遠距離擊*殺應該都是擺拍的。
7. 能拿到「救死扶傷」稱號才是最高榮耀
從分布情況可以看出來,救死扶傷比十殺還要難。
能拿到救死扶傷稱號的大部分都是女性角色,再一次證明玩游戲要帶妹。?
回歸到這個游戲的本質,那就是生存游戲,沒什么比活下來更重要的了。
結尾
這次爬蟲主要是利用了微信游戲頻道可以查看陌生人數據的場景才能提取到這么多數據。
我們可以通過同樣的手段來分析王者榮耀和其它游戲的數據,有興趣的同學可以嘗試一下。?
最后再說一下,UMP9是把好槍,配2倍鏡非常爽。
作者:張波
來源:
http://blog.codingroad.com/python-get-and-analysis-pubg-mobile-data.html
版權歸原作者所有,轉載僅供學習使用,不用于任何商業用途,如有侵權請留言聯系刪除,感謝合作。
數據與算法之美
用數據解決不可能
長按掃碼關注
總結
以上是生活随笔為你收集整理的用Python分析了20万场吃鸡数据,有不少有趣的发现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 趣图:脸部识别最快的实现
- 下一篇: 我用Python爬取了14年所有的福彩3