歡迎光臨
每天分享高質量文章

用Python分析了20萬場吃雞資料

作者:張波

鏈接:http://blog.codingroad.com/python-get-and-analysis-pubg-mobile-data.html

已獲作者轉載授權

首先,神槍鎮樓

背景

最近老闆愛上了吃雞(手游:全軍出擊),經常拉著我們開黑,只能放棄午休的時間,陪老闆在沙漠里奔波。 上周在在微信游戲頻道看戰績的時候突發奇想,是不是可以通過這個方式抓取到很多戰鬥資料,然後分析看看有什麼規律。

秀一波戰績,開黑情況下我們團隊吃雞率非常高,近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

{

    “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

API /cgi-bin/gamewap/getpubgmbattledetail
方法 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, essay-headers=settings.def_essay-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)

# 在提取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

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_list

 

my_openid = ‘oODfo0oIErZI2xxx9xPlVyQbRPgY’

my_platid = ‘0’

 

get_battle_list.delay(my_openid, my_platid, after_time=0, update_time=None)


有入口之後我們就用celery來啟動worker去開始爬蟲


# 啟動獲取用戶詳情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來查看執行情況。


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倍鏡非常爽。

作者:張波:先後從事過測試、運維、開發和需求分析工作,主要開發語言是 Python,現任一家小型公司技術團隊負責人。


●編號477,輸入編號直達本文

●輸入m獲取文章目錄

推薦↓↓↓

 

演算法與資料結構

更多推薦18個技術類微信公眾號

涵蓋:程式人生、演算法與資料結構、黑客技術與網絡安全、大資料技術、前端開發、Java、Python、Web開發、安卓開發、iOS開發、C/C++、.NET、Linux、資料庫、運維等。

赞(0)

分享創造快樂