Python获取暗黑破坏神3战网前1000命位玩家的英雄技能统计


Posted in Python onJuly 04, 2016

说实在的个人对游戏并没有多大的兴趣,但唯独对暴雪的Diablo系列很有感情,去年年初开始玩Diablo3,断断续续,感觉最麻烦的是选择技能,每次版本更新可能都有更优的build,这对于我这样的业余玩家来说可不是件好事,好在宏伟秘境后有了天梯,借鉴排名在前的高级玩家们build总没错,于是花了点时间写了这个脚本。

Python获取暗黑破坏神3战网前1000命位玩家的英雄技能统计

脚本只是统计了主动技能、被动技能和传奇宝石的使用情况,理论上统计其它如装备等信息也是一样简单可行的,但Diablo装备的生成机制使得统计这个没有多大意义,相同的装备属性可能各有优劣,难以比较,而且某些装备坑爹的掉率也不是你想要就能有的。

题外话,不得不说Python太适合写这类功能相对简单的脚本了,一个字:快。

# -*- coding: utf-8 -*-
"""
Diablo3 排名前1000玩家英雄使用技能统计

python diablo.py help
python diablo.py [barbarian|crusader|demon-hunter|monk'|witch-doctor|wizard]

默认使用的是亚服的数据,如果需要美服或欧服,更改`_rank_page`和`_api`变量地址即可

Copyright (c) 2015 JinnLynn <eatfishlin@gmail.com>
Released under the terms of the MIT license.
"""
from __future__ import unicode_literals, print_function, absolute_import
import os
import sys
import urllib2
import json
import re

__version__ = '1.0.0'
__author__ = 'JinnLynn <eatfishlin@gmail.com>'
__license__ = 'The MIT License'
__copyright__ = 'Copyright 2015 JinnLynn'

# 排名页面
_rank_page = 'http://tw.battle.net/d3/zh/rankings/'
# api
_api = 'http://tw.battle.net/api/d3/'
_api_profile = os.path.join(_api, 'profile')
_api_data = os.path.join(_api, 'data')

_hero_classes = {
  'barbarian': '野?人', 'crusader': '?教?', 'demon-hunter': '狩魔?人',
  'monk': '武僧', 'witch-doctor': '巫?', 'wizard': '秘???'}

_retry = 5

_hero_class = ''
_active_skills = {}
_passive_skills = {}
_unique_gems = {}


def _clear_output(msg=''):
  sys.stdout.write('\r{:30}'.format(' '))
  sys.stdout.write('\r{}'.format(msg))
  sys.stdout.flush()


def _process(stated, total):
  msg = '英雄数据分析中... {}/{}'.format(stated, total)
  _clear_output(msg)


def _get(url, is_json=True):
  # print('GET: ', url)
  retry = 5 if _retry < 1 else _retry
  while retry > 0:
    try:
      req = urllib2.urlopen(url.encode('utf8'), timeout=10)
      return json.load(req) if is_json else req.read()
    except KeyboardInterrupt, e:
      raise e
    except Exception, e:
      retry -= 1
      # print('retry', retry, e)
      # raise e


def _api_url(*args, **kwargs):
  slash = kwargs.get('slash', False)
  args = [unicode(arg) for arg in args]
  url = os.path.join(*args).rstrip('/')
  return url + '/' if slash else url


def get_era():
  req = urllib2.urlopen(_rank_page)
  return req.geturl().split('/')[-2]


def get_rank_page_url(era):
  url_part = 'rift-'
  if _hero_class == 'demon-hunter':
    url_part += 'dh'
  elif _hero_class == 'witch-doctor':
    url_part += 'wd'
  else:
    url_part += _hero_class
  return os.path.join(_rank_page, 'era', era, url_part)


def fetch_rank_list():
  tags = []
  try:
    _clear_output('获取当前游戏纪元...')
    era = get_era()
    _clear_output('获取当前排名前1000的玩家...')
    url = get_rank_page_url(era)
    html = _get(url, is_json=False)
    # re parse
    lst = re.findall(
      r"a href=\"(.*)\" title=.*class=\"icon-profile link-first\">",
      html.decode('utf8'),
      re.UNICODE)
    # BeautifulSoup parse
    # import bs4
    # soup = bs4.BeautifulSoup(html)
    # lst = soup.select('#ladders-table tbody tr .battletag a')['href']
    for item in lst:
      try:
        tags.append(item.split('/')[-2])
      except:
        pass
  except Exception, e:
    print('fetch rank list fail. {}'.format(_rank_page))
    raise e
  return tags


def get_hero(player_tag):
  url = _api_url(_api_profile, player_tag, slash=True)
  data = _get(url)
  hero_selected = None
  for hero in data.get('heroes', []):
    if hero['class'] != _hero_class:
      continue
    last_updated = hero_selected['last-updated']
    # 最近使用的英雄
    if hero_selected is None or last_updated < hero['last-updated']:
      hero_selected = hero
  if not hero_selected:
    raise Exception('{} hero missing.'.format(player_tag))
  url = _api_url(_api_profile, player_tag, 'hero', hero_selected['id'])
  return _get(url)


# 主动技能符文
def stat_active_skill_rune(skill_slug, rune):
  global _active_skills
  if not rune:
    return
  slug = rune.get('slug')
  if slug in _active_skills[skill_slug]['rune']:
    _active_skills[skill_slug]['rune'][slug]['count'] += 1
  else:
    _active_skills[skill_slug]['rune'][slug] = {
      'count': 1,
      'name': rune.get('name')
    }


# 主动技能
def stat_active_skill(active):
  global _active_skills
  slug = active.get('skill', {}).get('slug')
  # d3 API 返回的数据中可能存在空的数据
  if not slug:
    return
  if slug in _active_skills:
    _active_skills[slug]['count'] += 1
  else:
    _active_skills[slug] = {
      'count': 1,
      'name': active.get('skill').get('name'),
      'rune': {}
    }
  stat_active_skill_rune(slug, active.get('rune'))


# 被动技能
def stat_passive_skill(passive):
  global _passive_skills
  slug = passive.get('skill', {}).get('slug')
  # d3 API 返回的数据中可能存在空的数据
  if not slug:
    return
  if slug in _passive_skills:
    _passive_skills[slug]['count'] += 1
  else:
    _passive_skills[slug] = {
      'count': 1,
      'name': passive.get('skill').get('name')
    }


def stat_unique_gem(items):
  global _unique_gems

  def get_gem(tooltip):
    if not tooltip:
      return None, None
    url = _api_url(_api_data, tooltip)
    data = _get(url)
    gems = data.get('gems')
    if not gems:
      return None, None
    gem = gems[0].get('item', {})
    return gem.get('id'), gem.get('name')

  if not items:
    return

  lst = [items.get(s, {}) for s in ['leftFinger', 'rightFinger', 'neck']]
  for tooltip in [d.get('tooltipParams', None) for d in lst]:
    id_, name = get_gem(tooltip)
    if not id_:
      continue
    if id_ in _unique_gems:
      _unique_gems[id_]['count'] += 1
    else:
      _unique_gems[id_] = {
        'count': 1,
        'name': name
      }


def stat(hero):
  global _active_skills, _passive_skills

  map(stat_active_skill, hero.get('skills', {}).get('active', []))
  map(stat_passive_skill, hero.get('skills', {}).get('passive', []))

  items = hero.get('items', {})
  stat_unique_gem(items)


def output(hero_stated, hero_stat_failed):
  def sort(data, count=10):
    d = sorted(data.items(), key=lambda d: d[1]['count'], reverse=True)
    return d if count <= 0 else d[0:count]

  _clear_output()

  # print('======')
  # print(hero_stated, hero_stat_failed)
  # print('======')
  # pprint(_active_skills)
  # print('======')
  # pprint(_passive_skills)
  # print('======')
  # pprint(_unique_gems)
  # pprint(_active_skills.items())
  # print('======')

  print('\n=== RESULT ===\n')
  print('统计英雄数\n')
  print(' 成功: {} 失败: {}\n'.format(hero_stated, hero_stat_failed))

  print('主动技能使用排名: ')
  for _, d in sort(_active_skills):
    runes = []
    for _, r in sort(d.get('rune', {})):
      runes.append('{name}[{count}]'.format(**r))
    d.update({'rune_rank': ', '.join(runes)})
    print(' {name}[{count}]: {rune_rank}'.format(**d))
  print()

  print('被动技能使用排名: ')
  for _, d in sort(_passive_skills):
    print(' {name}[{count}]'.format(**d))
  print()

  print('传奇宝石使用排名: ')
  for _, d in sort(_unique_gems):
    print(' {name}[{count}]'.format(**d))
  print()


def prepare():
  global _hero_class

  def print_hc():
    print('仅支持以下英雄类型, 默认 demon-hunter:\n')
    for c, n in _hero_classes.items():
      print(c, ':', n)

  if len(sys.argv) == 1:
    _hero_class = 'demon-hunter'
  elif len(sys.argv) > 2:
    sys.exit('参数错误')
  else:
    arg = sys.argv[1]
    if arg == 'help':
      print_hc()
      print('\nTips: 运行中可随时Ctrl+C终止以获得已统计的数据结果')
      sys.exit()
    elif arg not in _hero_classes:
      print_hc()
      sys.exit()
    else:
      _hero_class = arg


def main():
  prepare()
  print('待分析的英雄类型:', _hero_classes[_hero_class])

  hero_stated = 0
  hero_stat_failed = 0
  try:
    tags = fetch_rank_list()
    if not tags:
      raise Exception('parse battle.net rank page fail.')
  except Exception, e:
    print('error,', e)
    sys.exit()

  total = len(tags)

  for tag in tags:
    try:
      hero = get_hero(tag)
      if not hero:
        raise Exception('no hero data')
      stat(hero)
      hero_stated += 1
      _process(hero_stated, total)
    except KeyboardInterrupt:
      break
    except Exception, e:
      # print('Fail: ', tag, e, hero)
      hero_stat_failed += 1

  output(hero_stated, hero_stat_failed)


if __name__ == '__main__':
  main()
Python 相关文章推荐
Python常用的文件及文件路径、目录操作方法汇总介绍
May 21 Python
Python批量修改文本文件内容的方法
Apr 29 Python
Python 搭建Web站点之Web服务器与Web框架
Nov 06 Python
解决Python网页爬虫之中文乱码问题
May 11 Python
分析python请求数据
Aug 19 Python
Python模拟自动存取款机的查询、存取款、修改密码等操作
Sep 02 Python
python整小时 整天时间戳获取算法示例
Feb 20 Python
Django Rest framework频率原理与限制
Jul 26 Python
Python学习笔记之函数的定义和作用域实例详解
Aug 13 Python
一行Python代码制作动态二维码的实现
Sep 09 Python
pandas 对group进行聚合的例子
Dec 27 Python
python如何从键盘获取输入实例
Jun 18 Python
Python实现代码统计工具(终极篇)
Jul 04 #Python
在win和Linux系统中python命令行运行的不同
Jul 03 #Python
win10系统中安装scrapy-1.1
Jul 03 #Python
使用Python从有道词典网页获取单词翻译
Jul 03 #Python
python中函数传参详解
Jul 03 #Python
Python使用Srapy框架爬虫模拟登陆并抓取知乎内容
Jul 02 #Python
Ruby元编程基础学习笔记整理
Jul 02 #Python
You might like
php HtmlReplace输入过滤安全函数
2010/07/03 PHP
php curl 伪造IP来源的实例代码
2012/11/01 PHP
PHP autoload与spl_autoload自动加载机制的深入理解
2013/06/05 PHP
PHP+jQuery实现滚屏无刷新动态加载数据功能详解
2017/05/04 PHP
PHP有序表查找之二分查找(折半查找)算法示例
2018/02/09 PHP
Yii框架核心组件类实例详解
2019/08/06 PHP
按下回车键指向下一个位置的一个函数代码
2014/03/10 Javascript
使用js操作css实现js改变背景图片示例
2014/03/10 Javascript
JavaScript获得表单target属性的方法
2015/04/02 Javascript
jquery+ajax实现注册实时验证实例详解
2015/12/08 Javascript
JavaScript实现多种排序算法
2016/02/24 Javascript
AngularJs concepts详解及示例代码
2016/09/01 Javascript
详解JS获取HTML DOM元素的8种方法
2017/06/17 Javascript
Vue中的Vux配置指南
2017/12/08 Javascript
深入浅析Vue.js计算属性和侦听器
2018/05/05 Javascript
Vue自定义属性实例分析
2019/02/23 Javascript
使用taro开发微信小程序遇到的坑总结
2019/04/08 Javascript
javascript实现的字符串转换成数组操作示例
2019/06/13 Javascript
JS合并两个数组的3种方法详解
2019/10/24 Javascript
Vue router传递参数并解决刷新页面参数丢失问题
2020/12/02 Vue.js
[01:56]生活中的妖精之七夕特别档
2016/08/09 DOTA
Python通过DOM和SAX方式解析XML的应用实例分享
2015/11/16 Python
Django学习笔记之Class-Based-View
2017/02/15 Python
利用python操作SQLite数据库及文件操作详解
2017/09/22 Python
python实现单链表的方法示例
2019/09/03 Python
python Django 反向访问器的外键冲突解决
2020/05/20 Python
Python绘制动态水球图过程详解
2020/06/03 Python
python中append函数用法讲解
2020/12/11 Python
农业大学毕业生的个人自我评价
2013/10/11 职场文书
个人工作作风整改措施思想汇报
2014/10/13 职场文书
2015年社区综治工作总结
2015/04/21 职场文书
不同意离婚代理词
2015/05/23 职场文书
教师考核鉴定意见
2015/06/05 职场文书
小英雄雨来观后感
2015/06/09 职场文书
社区干部培训心得体会
2016/01/06 职场文书
浅谈音视频 pts dts基本概念及理解
2022/08/05 数码科技