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中的元类(metaclass)
Feb 14 Python
Python实现合并字典的方法
Jul 07 Python
合并百度影音的离线数据( with python 2.3)
Aug 04 Python
python对象及面向对象技术详解
Jul 19 Python
Python实现将HTML转换成doc格式文件的方法示例
Nov 20 Python
Python cookbook(数据结构与算法)保存最后N个元素的方法
Feb 13 Python
tensorflow更改变量的值实例
Jul 30 Python
python自动化测试之异常及日志操作实例分析
Nov 09 Python
解决django中form表单设置action后无法回到原页面的问题
Mar 13 Python
Python使用shutil模块实现文件拷贝
Jul 31 Python
OpenCV-Python实现怀旧滤镜与连环画滤镜
Jun 09 Python
解析python中的jsonpath 提取器
Jan 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出错界面
2006/10/09 PHP
php利用腾讯ip分享计划获取地理位置示例分享
2014/01/20 PHP
php+js实现异步图片上传实例分享
2014/06/02 PHP
PHP中Fatal error session_start()错误解决步骤
2014/08/05 PHP
Laravel 4 初级教程之Pages、表单验证
2014/10/30 PHP
laravel项目利用twemproxy部署redis集群的完整步骤
2018/05/11 PHP
JavaScript 开发中规范性的一点感想
2009/06/23 Javascript
extjs 学习笔记(三) 最基本的grid
2009/10/15 Javascript
js继承的实现代码
2010/08/05 Javascript
JQuery与JSon实现的无刷新分页代码
2011/09/13 Javascript
javascript椭圆旋转相册实现代码
2012/01/16 Javascript
JS localStorage实现本地缓存的方法
2013/06/22 Javascript
jQuery验证插件 Validate详解
2014/11/20 Javascript
jQuery.prop() 使用详解
2015/07/19 Javascript
jQuery地图map悬停显示省市代码分享
2015/08/20 Javascript
[02:16]卖萌的僵尸 DOTA2神话信使飞僵小宝来袭
2014/03/24 DOTA
[06:07]刀塔密之二:攻之吾命受之吾幸
2014/07/03 DOTA
[02:41]DOTA2亚洲邀请赛小组赛第三日 赛事回顾
2015/02/01 DOTA
[06:43]DAC2018 4.5 SOLO赛 Maybe vs Paparazi
2018/04/06 DOTA
python文件操作整理汇总
2014/10/21 Python
Python中类的定义、继承及使用对象实例详解
2015/04/30 Python
一步步解析Python斗牛游戏的概率
2016/02/12 Python
Python基本数据结构之字典类型dict用法分析
2019/06/08 Python
python实现串口自动触发工作的示例
2019/07/02 Python
Python中拆分字符串的操作方法
2019/07/23 Python
python颜色随机生成器的实例代码
2020/01/10 Python
解决使用python print打印函数返回值多一个None的问题
2020/04/09 Python
Python opencv相机标定实现原理及步骤详解
2020/04/09 Python
jupyter notebook读取/导出文件/图片实例
2020/04/16 Python
python 如何实现遗传算法
2020/09/22 Python
使用OpenCV校准鱼眼镜头的方法
2020/11/26 Python
家长写给老师的建议书
2014/03/13 职场文书
2015年妇联工作总结范文
2015/04/22 职场文书
nginx配置proxy_pass中url末尾带/与不带/的区别详解
2021/03/31 Servers
Python爬虫基础之爬虫的分类知识总结
2021/05/13 Python
Elasticsearch 批量操作
2022/04/19 Python