用Python分析了20万场吃鸡数据,有不少有趣的发现

640?wx_fmt=gif


首先,神枪镇楼:


640?


背景


最近老板爱上了吃鸡(手游:全军出击),经常拉着我们开黑,只能放弃午休的时间,陪老板在沙漠里奔波。 


640?wx_fmt=png上周在在微信游戏频道看战绩的时候突发奇想,是不是可以通过这个方式抓取到很多战斗数据,然后分析看看有什么规律。


640?


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


640?wx_fmt=png简单评估了一下,觉得可行,咱就开始。


Step 1  分析数据接口


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


使用Charles抓包


抓包实现


在Mac下推荐使用工具Charles来从协议层抓取手机上的流量。


原理就是在Mac上开启一个代*理*服务器,然后将手机的网络代*理设置为Mac,这样手机上的所有流量都会经过我们的代*理*服务器了。 


大致流程如下:


640?wx_fmt=jpeg


https加密流量的处理


640?wx_fmt=png在实际操作的时候发现微信所有的流量都走了HTTPS,导致我们的抓到的都是加密数据,对我们没有任何参考意义。


 经过研究,可以通过在手机和电脑都安装Charles根证书的方式来实现对Https流量的分析,具体操作可以参考:


  • charles mac下https抓包和iphone https抓包


  • 解决Charles无法正常抓包iOS 11中的Https请求


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


640?

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

如下图所示:

640?


  • windows下用findler可以实现相同的功能


  • 其实这就是一个非常典型的中间人场景


数据接口


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


  • 获取用户信息接口


  • 获取用户战绩列表接口


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


下面我们一个一个看:


1. 获取用户信息接口


  • request



API/cgi-bin/gamewap/getpubgmdatacenterindex
方法GET
参数openid、pass_ticket
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": "枪战 | 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
    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来进行分页,遍历获取时可以根据接口响应的has_next和next_after_time来判断是否还有下一页的数据。


  • 列表里面的room_id是每一场battle的惟一标识。


3. 获取用户战绩详情接口

  • request

API/cgi-bin/gamewap/getpubgmbattledetail
方法GET
参数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
}


  • 分析


  • 这个接口响应了战斗的详细信息,包括杀*敌数、爆*头数、救人数、跑动距离等等,足够我们分析了。


  • 这个接口还响应了是被谁杀死的以及组团成员的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)


# 在提取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,我们可以看到运行的效率还是非常不错的。


640?

在执行过程中会发现get_battle_list跑太快,导致get_battle_info即使开了30个并发都还会积压很多。


所以需要适时的去停一下这些worker, 在我们抓到20万条信息之后就可以停下来了。


Step 3  数据分析


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


640?


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


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


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

640?


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


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

640?

640?wx_fmt=png给了大家一个带妹上分的好理由。


4. 周五大家最忙


640?

640?wx_fmt=png估计周五大家都要忙着交差和写周报了。


5. 晚上22点是游戏高峰


640?

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


6. 最远击*杀距离639米

我看了一下98K、SKS和AWP的有效射程,大致都在800米以内,所以这个值可信度还是可以的。 


反过来看抖音上的那些超远距离击*杀应该都是摆拍的。


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


640?

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


640?wx_fmt=jpeg


能拿到救死扶伤称号的大部分都是女性角色,再一次证明玩游戏要带妹。 


回归到这个游戏的本质,那就是生存游戏,没什么比活下来更重要的了


结尾

这次爬虫主要是利用了微信游戏频道可以查看陌生人数据的场景才能提取到这么多数据。


我们可以通过同样的手段来分析王者荣耀和其它游戏的数据,有兴趣的同学可以尝试一下。 


640?wx_fmt=png最后再说一下,UMP9是把好枪,配2倍镜非常爽。


作者:张波

来源:

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

版权归原作者所有,转载仅供学习使用,不用于任何商业用途,如有侵权请留言联系删除,感谢合作。


数据与算法之美

用数据解决不可能


640?wx_fmt=jpeg

长按扫码关注


640?wx_fmt=gif

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/303130.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

万字长文,带你彻底理解EF Core5的运行机制,让你成为团队中的EF Core专家

在EF Core 5中,有很多方式可以窥察工作流程中发生的事情,并与该信息进行交互。这些功能点包括日志记录,拦截,事件处理程序和一些超酷的最新出现的调试功能。EF团队甚至从Entity Framework的第一个版本中恢复了一些有用的旧的功能。…

重学数据结构004——栈的基本操作及实现(数组实现)

上文提到过栈以及栈的基本操作。上文中是基于链表做的实现。但是这种方法会出现大量的malloc()和free()操作,这种开销是非常昂贵的。 另外一种实现方式是基于数组的实现。这种实现方式需要预先制定一个栈的大小,此外还需要一个Top来记录栈顶元素下一个位…

C#使用线程窗口调试多线程程序

调试多线程程序一般有以下几种办法1、在日志的某个地方写日志文件。优点:不会干扰程序的执行,特别是对网络的多线程通信。缺点:每次都需要打开日志文件以查看进程运行的信息。2、利用断点进行调试。优点:直观,可以直接…

窥探渣男天才爱因斯坦的一生

本文授权转自微信公众号超级数学建模(ID:supermodeling)----------------------------------提起爱因斯坦,你最先想到什么?相对论?原子弹?物理天才?Emc?然而,2017年由美…

在 Azure App Service 上运行 .NET 6 预览版

点击上方蓝字关注“汪宇杰博客”原文:Jeff Martinez翻译:Edi Wang导语.NET 6 是最新的 .NET 版本,它最终将.NET Core,Framework,Xamarin和Mono的精华带入以 .NET 5 开始的统一平台。该版本目前为预览版,用于…

R还能这样玩!

R作为一种统计分析软件,广泛应用于生物、医学、电商、新闻等数据相关行业,是目前主流数据应用软件之一。那么,R到底有哪些特别之处呢?实际上,R是统计领域广泛使用的S语言的一个分支,两者在程序语法上几乎一样&#xf…

百度网页移动端html,百度移动端开始用网站品牌名代替网址显示

最近,有站长发现,百度移动端最近做了部分改版:移动端部分网站域名开始逐渐被网站相关名称代替,PC端还是用域名展示,卢松松博客网站域名也被替换成网站品牌名显示!不知道站长们,最近有没有注意到&#xff0c…

每天6亿人在看《延禧攻略》?大数据告诉你哪家视频网站VIP值得买(附代码)

导读:随着《延禧攻略》的播出,魏璎珞、富察皇后等各位后宫小主的命运时刻牵动着各位观众的心。同时爱奇艺也因为该剧的大火,收获了单日超过6亿的播放量。我们此次将对比各大视频网站2018年截止到8月18号的电视剧和综艺节目的播放情况&#xf…

大厂面试都爱问这4个问题,.NET开发必看!

金三银四已进入尾声,身边不少从事.NET开发的朋友有了更好的去处!大家日常在交流群里也常有分享经验。在面试时,大厂面试官都喜欢问什么;提问的形式和特点,大家又该如何应对等问题格外引起注意。今天就以腾讯公司的招聘…

参加计算机竞赛需要学什么知识,数学和计算机专业,我应该参加那些比赛?

该楼层疑似违规已被系统折叠 隐藏此楼查看此楼美国数学竞赛美国数学竞赛 AmericanMathematical Competition,简称 AMC,原是于 1950 年起由美国数学协会 (MathematicsAssociation of America ,简称 MAA) 开始举办的美国高中数学考试 (AHSME)。…

程序员年入50万,我们该如何努力达到这个目标?

先说明,这里是指税后。年收入50万在上海算多吗?比上不足,比下有余。不过在上海达到这个水平,至少可以说是能达到财务自由了吧。况且,根据马太效应的说法,和年入20万到50万这个增加阶段相比,年入…

昆明学院计算机二级报名时间,2017年3月昆明学院计算机等级考试报名时间(云南)...

考试时间:2017年3月25日至3月27日。网上报名及费用支付时间:2016年12月19日至12月28日。报名及准考证打印网址:http://222.221.5.208/NCRE_EMS/StudentLogin.aspx请各位考生注意:考生只能选择一个考点报名;本校考生只能…

OrchardCore 如何动态加载模块?

【导读】今天,我们再次讨论下OrchardCore,通过初期调研,我们项目采用OrchardCore底层设施支持模块化,同时根据业务场景,额外还需支持二次开发,于是有了本文,若有不同解决方案,欢迎留…

sed之G、H、g、h使用

前言 作者不善言谈,如有错误请指正!!! 转载请注明出处!!! sed之G、H、g、h使用 什么是sed? sed是面向流的行编辑器,所谓面向流,是指接受标准输入的输入&#…

人工智能路上,怎么能少了它!

目前,人工智能的应用日渐广泛。而作为人工智能核心的机器学习,是一门多领域的交叉学科,专门研究计算机模拟或实现人类学习行为的方法,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。简单来说&a…

墙裂推荐:这可能是CAP理论的最好解释

> 英文蓝本:http://ksat.me/a-plain-english-introduction-to-cap-theorem 经过小码甲意译、原创配图, 建议收藏。你可能经常听到CAP定理, 这个定理描述了在设计分布式系统时的天然约束。就像其他文章一样, 本文以现实场景对比理解CAP定理…

台湾 计算机术语,快取,陣列,程式,这些台湾的计算机术语,你知道几个?|冷知识...

原标题:快取,陣列,程式,这些台湾的计算机术语,你知道几个?|冷知识作者 | 楼下小黑哥来源 | 程序通事今天就不写技术文了,写点轻松的,带大家涨点知识。最近闲来无聊的时候&#xff0c…

计算机原理试题b,计算机组成原理试题B答案

计算机组成原理试题B答案 (3页)本资源提供全文预览,点击全文预览即可全文预览,如果喜欢文档就下载吧,查找使用更方便哦!9.9 积分计算机组成原理试题B答案一、 选择题1. D 2. A 3. A,C 4. B 5. B 6. B 7. B 8. B 9. A 10. D二、 填…

趣图:各种程序员的键盘热力图

图0:不保存不舒服斯基型图1:复制粘贴型 图2:复制粘贴型专用键盘图3:Vim 党新手(内心OS:门在哪里,我要退出)图4:Windows 程序员图5:01 党终极程序员 来源&am…

创建支持依赖注入、Serilog 日志和 AppSettings 的 .NET 5 控制台应用

翻译自 Mohamad Lawand 2021年3月24日的文章 《.NET 5 Console App with Dependency Injection, Serilog Logging, and AppSettings》 [1]在本文中,我们将构建一个 .NET 5 控制台应用程序,该应用程序支持依赖注入、日志记录和 appsettings 配置。你也可以…