Проанализировано 200 000 данных о поедании цыплят с помощью Python

Python рептилия API игра

Во-первых, здание оружейного городка

15310635540917

задний план

Недавно босс полюбил есть курицу (мобильная игра: All-Army Attack) и часто таскал нас погонять в темноте, ему оставалось только отказаться от обеденного перерыва и сопровождать босса, чтобы побегать по пустыне. На прошлой неделе, когда я смотрел запись на игровом канале WeChat, у меня возникла прихоть, смогу ли я таким образом зафиксировать много данных о битвах, а затем проанализировать их, чтобы увидеть, каковы правила.15311076652700

Покажите волну рекордов, наша команда имеет очень высокий показатель поедания цыплят в случае с черными, 51 раз в почти 100 играх.

После краткой оценки, если это представляется возможным, давайте начнем.

Шаг 1 Анализ интерфейса данных

Первым шагом, конечно же, является сбор этих данных записи, прежде всего нам нужно понять историю, стоящую за страницей. Зайдите и посмотрите, как на странице появляется боевая статистика.

Используйте Чарльза для захвата пакетов

Реализация захвата пакетов

Рекомендуется использовать инструмент Charles для захвата трафика на мобильном телефоне с уровня протокола под Mac.Принцип заключается в том, чтобы открыть прокси-сервер на Mac, а затем установить сетевой прокси мобильного телефона на Mac, чтобы весь трафик на мобильном телефоне проходил через наш прокси-сервер. . Общий процесс выглядит следующим образом:1

Обработка зашифрованного трафика https

В ходе фактической операции было обнаружено, что весь трафик WeChat проходил через HTTPS, в результате чего все захваченные нами зашифрованные данные не имели к нам никакого отношения. После исследования можно провести анализ HTTPS-трафика, установив корневой сертификат Charles как на мобильный телефон, так и на компьютер.Подробнее см.:

После установки сертификата наш трафик примерно такой1После приведенной выше конфигурации мы уже можем читать данные запроса и ответа https, как показано на следующем рисунке.15310659449947

  • Та же функция может быть достигнута с помощью искателя под окнами.
  • На самом деле, это очень типичный сценарий «человек посередине».

Интерфейс данных

Далее на основе этих данных узнаем нужные нам интерфейсы.После анализа в основном задействованы три интерфейса.

  • Получить интерфейс информации о пользователе
  • Получить интерфейс списка записей пользователя
  • Получить указанный пользователем интерфейс сведений о записи

Давайте посмотрим один за другим

1. Получить интерфейс информации о пользователе

  • request
API /cgi-bin/gamewap/getpubgmbattlelist
метод GET
параметр openid, pass_ticket, plat_id, after_time, лимит
cookie ключ 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"
}

2. Получить интерфейс списка пользовательских записей

  • анализировать

openid — уникальный идентификатор пользователя.

2. Получить интерфейс списка пользовательских записей

  • request
    API /cgi-bin/gamewap/getpubgmbattlelist
    метод GET
    параметр openid, pass_ticket, plat_id, after_time, лимит
    cookie ключ 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 в списке — это уникальный идентификатор каждой битвы.

3. Получить интерфейс сведений о записи пользователя

  • request
API /cgi-bin/gamewap/getpubgmbattledetail
метод GET
параметр openid, pass_ticket, room_id
cookie ключ 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 членов группы.Используя эту функцию, мы можем сканировать больше данных пользователей в бесконечной глубине расхождения.

Что касается такой информации, как pass_ticket в куке, то она должна использоваться для аутентификации авторизации.Эта информация не изменилась в вышеупомянутых запросах, поэтому нам не нужно глубоко изучать, как она рассчитывается, а нужно только перехватывать пакеты. чтобы извлечь информацию по умолчанию.Затем введите код, и вы можете использовать его.

Шаг 2. Сканирование данных

Интерфейс определен, и следующим шагом будет получение достаточного количества данных.

Используйте интерфейс запроса запросов для получения данных

 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, чтобы пометить просканированную информацию

В приведенном выше интерфейсе мы можем найти openid пользователя B по входу пользователя A, а затем найти openid пользователя A по входу пользователя B. Чтобы избежать повторного сбора, нам нужно записать, какая информация у нас есть. собрал. фрагмент основного кода

# 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 — очень полезный инструмент для управления распределенными очередями, в этот раз я планирую запустить его только на своем компьютере, поэтому я не использую распределенные функции. Создаем три задачи и три очереди

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'}),
],)

Затем управляйте запросами 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)

После того, как есть запись, используем сельдерей для запуска воркера для запуска краулера

# 启动获取用户详情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

Через цветок мы видим, что эффективность работы по-прежнему очень высока.15308449650665В процессе выполнения обнаружится, что get_battle_list работает слишком быстро, что приводит к отставанию get_battle_info даже при открытии 30 одновременных операций, поэтому необходимо своевременно остановить этих воркеров. Остановитесь после того, как мы получили 200 000 сообщений.

Шаг 3 Анализ данных

Программа анализа

Захвачены данные 200 000 боев, все они разбиты на json файлы и сохранены на моем локальном диске.Далее я проведу несложный анализ. Python также очень силен в области анализа данных.Есть много отличных библиотек, таких как pandas и NumPy.К сожалению, я никогда не изучал его, и для человека, который набрал только 70 баллов на вступительном экзамене в колледж по математике, анализ данных Это действительно сложно, поэтому я сам написал очень простую программу для поверхностного анализа. Крупные коровы, которым нужен глубокий анализ и которые не хотят сами ползать, могут связаться со мной, чтобы упаковать эти данные.

# 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 часа.

15311020770066Судя по карте распространения, большинство пользователей тратят более 1 часа, а самые агрессивные — более 8 часов.

Примечание: здесь я считаю время выживания в каждом раунде, и фактическое время онлайн будет больше, чем у меня.

2. Женских персонажей спасают чаще, чем мужских.

15311012585793Наконец-то я понял, почему так много транссексуалов, оказывается, они могут воспользоваться игрой.

3. Женские персонажи спасают больше жизней, чем мужские.

15311012987492Дал всем вескую причину поднять твою сестру.

4. Пятница — самый загруженный день для всех15310699545478

Предполагается, что в пятницу все будут заняты делами и написанием еженедельных отчетов.

5. 22 часа вечера — пик игры

15311024917841Ранним утром так много людей играет, ты не спишь?

6. Самая длинная дистанция поражения — 639 метров.

Посмотрел эффективную дальность 98К, СКС и АРМ, а они все в пределах 800 метров, так что достоверность этого значения еще приемлемая. С другой стороны, те сверхдальние убийства Доуина должны быть инсценированы.

7. Высшая честь – получить звание «Исцеление раненых».

15311041446783

Из раздачи видно, что спасти мертвого сложнее, чем убить десять человек.

15311063423105Большинство людей, которые могут получить звание спасателей жизней и помощи раненым, являются персонажами женского пола, что еще раз доказывает, что для игр нужно приводить сестер. Возвращаясь к сути этой игры, которая является игрой на выживание, нет ничего важнее выживания.

конец

На этот раз краулер в основном использует игровой канал WeChat для просмотра данных незнакомцев, чтобы извлечь так много данных. Мы можем анализировать данные King of Glory и других игр с помощью того же метода, и заинтересованные студенты могут попробовать его. И последнее, но не менее важное: UMP9 — хороший пистолет, и он очень крут с двукратным прицелом.

Об авторе:Чжан Бо

Он успешно занимается тестированием, эксплуатацией и обслуживанием, разработкой и анализом спроса.Основной язык разработки — Python.В настоящее время он является руководителем технической группы небольшой компании. Идентификатор WeChat: zhangbo_369029696Домашняя страница · моя статья ·