SoFunction
Updated on 2024-12-13

Python to get the hero skill stats of the top 1000 hit players on Diablo 3 Battle.net

To be honest personally do not have much interest in the game, but the only Blizzard's Diablo series is very affectionate, early last year began to play Diablo3, on and off, I feel that the most troublesome is to choose the skills, each version update may have a better build, which is not a good thing for amateur players like me, well after the grand secret of the ladder, draw on the ranking of the top of the high-level The players build is always right, so spent some time to write this script.

The script only statistics active skills, passive skills and the use of legendary gems, theoretically, statistics on other information such as equipment is just as simple and feasible, but Diablo equipment generation mechanism makes the statistics of this does not make much sense, the same equipment attributes may have their own strengths and weaknesses, it is difficult to compare, and some of the equipment of the pitfalls of the drop rate is not something you want to be able to have.

As an aside, I have to say that Python is too good for writing these kinds of scripts with relatively simple functions, in a word: fast.

# -*- coding: utf-8 -*-
"""
Diablo3 top of the list1000Player Heroes Using Skills Statistics

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

The default is to use the data from the Asian service,If you need American or European service,variation`_rank_page`respond in singing`_api`The address of the variable is sufficient

Copyright (c) 2015 JinnLynn <eatfishlin@>
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@>'
__license__ = 'The MIT License'
__copyright__ = 'Copyright 2015 JinnLynn'

# Ranking Page
_rank_page = '/d3/zh/rankings/'
# api
_api = '/api/d3/'
_api_profile = (_api, 'profile')
_api_data = (_api, 'data')

_hero_classes = {
  'barbarian': "The Barbarians, 'crusader': "Holy Army, 'demon-hunter': "Demon Hunter.,
  'monk': 'Martial Monk', 'witch-doctor': 'Witch Doctor', 'wizard': "Secretaries}

_retry = 5

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


def _clear_output(msg=''):
  ('\r{:30}'.format(' '))
  ('\r{}'.format(msg))
  ()


def _process(stated, total):
  msg = 'Heroic Data Analysis in Progress... {}/{}'.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 = (('utf8'), timeout=10)
      return (req) if is_json else ()
    except KeyboardInterrupt, e:
      raise e
    except Exception, e:
      retry -= 1
      # print('retry', retry, e)
      # raise e


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


def get_era():
  req = (_rank_page)
  return ().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 (_rank_page, 'era', era, url_part)


def fetch_rank_list():
  tags = []
  try:
    _clear_output('Getting the current game epoch...')
    era = get_era()
    _clear_output('Get the current top 1000 players...')
    url = get_rank_page_url(era)
    html = _get(url, is_json=False)
    # re parse
    lst = (
      r"a href=\"(.*)\" title=.*class=\"icon-profile link-first\">",
      ('utf8'),
      )
    # BeautifulSoup parse
    # import bs4
    # soup = (html)
    # lst = ('#ladders-table tbody tr .battletag a')['href']
    for item in lst:
      try:
        (('/')[-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 ('heroes', []):
    if hero['class'] != _hero_class:
      continue
    last_updated = hero_selected['last-updated']
    # Recently used heroes
    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)


# Active skill runes
def stat_active_skill_rune(skill_slug, rune):
  global _active_skills
  if not rune:
    return
  slug = ('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': ('name')
    }


# Active skills
def stat_active_skill(active):
  global _active_skills
  slug = ('skill', {}).get('slug')
  # d3 API returns data that may have nulls in it
  if not slug:
    return
  if slug in _active_skills:
    _active_skills[slug]['count'] += 1
  else:
    _active_skills[slug] = {
      'count': 1,
      'name': ('skill').get('name'),
      'rune': {}
    }
  stat_active_skill_rune(slug, ('rune'))


# Passive skills
def stat_passive_skill(passive):
  global _passive_skills
  slug = ('skill', {}).get('slug')
  # d3 API returns data that may have nulls in it
  if not slug:
    return
  if slug in _passive_skills:
    _passive_skills[slug]['count'] += 1
  else:
    _passive_skills[slug] = {
      'count': 1,
      'name': ('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 = ('gems')
    if not gems:
      return None, None
    gem = gems[0].get('item', {})
    return ('id'), ('name')

  if not items:
    return

  lst = [(s, {}) for s in ['leftFinger', 'rightFinger', 'neck']]
  for tooltip in [('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, ('skills', {}).get('active', []))
  map(stat_passive_skill, ('skills', {}).get('passive', []))

  items = ('items', {})
  stat_unique_gem(items)


def output(hero_stated, hero_stat_failed):
  def sort(data, count=10):
    d = sorted((), 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('Counting the number of heroes \n')
  print(' successes: {} fail (e.g. experiments): {}\n'.format(hero_stated, hero_stat_failed))

  print('Ranking of Active Skill Usage: ')
  for _, d in sort(_active_skills):
    runes = []
    for _, r in sort(('rune', {})):
      ('{name}[{count}]'.format(**r))
    ({'rune_rank': ', '.join(runes)})
    print(' {name}[{count}]: {rune_rank}'.format(**d))
  print()

  print('Ranking of Passive Skill Usage: ')
  for _, d in sort(_passive_skills):
    print(' {name}[{count}]'.format(**d))
  print()

  print(' Ranking of Legendary Gem Usage: ')
  for _, d in sort(_unique_gems):
    print(' {name}[{count}]'.format(**d))
  print()


def prepare():
  global _hero_class

  def print_hc():
    print('Only the following hero types are supported, default demon-hunter:\n')
    for c, n in _hero_classes.items():
      print(c, ':', n)

  if len() == 1:
    _hero_class = 'demon-hunter'
  elif len() > 2:
    ('Parameter error')
  else:
    arg = [1]
    if arg == 'help':
      print_hc()
      print('\nTips: Ctrl+C can be terminated at any time during the run to get the results of the data that has been counted')
      ()
    elif arg not in _hero_classes:
      print_hc()
      ()
    else:
      _hero_class = arg


def main():
  prepare()
  print('Type of hero to be analyzed:', _hero_classes[_hero_class])

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

  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()