Hackeando mis datos en NikeRunning.com

Tipos de terreno para todas mis carreras, desde NikeRunning, mostrado con JavaFX
Desde que comencé a correr he utilizado a NikeRunning.com de una u otra manera; Primero comencé con mi Itouch y ahora lo utilizó con el NikeWatch.
Sin embargo, el sitio de Nike es un poco fastidioso; El sitio web está escrito en Flash (lo cual lo hace pesado), tiene gráficos limitados pero lo mas inconveniente es que mis datos están cautivos en el sitio web. Según ellos ya llevan tiempo trabajando en una migración que eliminará Flash:
Hello Everyone,
Thank you all for your feedback.
To let everyone know, we are in the process of revamping our website. We are in transition to move away from flash to HTML. The goal is to be faster and more efficient.
We thank you for your patience as we update our sites. There is not an estimated time of when the transition will be fixed, but I can assure you that I’ve seen parts of the site and it’s looks and feels amazing.
Stay tuned!
Buscando un poco en Google me conseguí conque se puede obtener toda la información de las carreras (siempre y cuando estas esten marcadas como públicas) usando el siguiente URL:
1 | http://nikerunning.nike.com/nikeplus/v1/services/widget/get_public_run_list.jsp?userID=IDENTIFICADOR_DE_USUARIO |
El archivo resultante está en formato XML.
Pero, ¿y de donde obtenemos el identificador del usuario? Es sencillo, simplemente vaya a nikerunning.nike.com y una vez que entre con su usuario y clave al sitio web haga click sobre una de sus corridas. El URL resultante se verá como esto:
1 | http://nikerunning.nike.com/nikeos/p/nikeplus/en_US/plus/#//runs/gps/IDENTIFICADOR_DE_USUARIO/299193444/ |
En donde IDENTIFICADOR_DE_USUARIO es el número mágico que queremos usar.
Se me ocurrió por ejemplo escribir un pequeño programa en Jython y JavaFX el cual muestra la distribución de tipos de terreno encontrados mientras corrí:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 | #!/usr/bin/env jython # Author: Jose Vicente Nunez Zuleta, josevnz@kodegeek.com # This script parses the user run data and creates a simple distribtion by terrain types # See: http://nikerunning.nike.com/nikeos/p/nikeplus/en_EMEA/plus/#//dashboard/ # Nike and Nikeplus/Nike+ are trademarks owned by Nike. # License: BSD import sys from javafx.application import Application from javafx.collections import FXCollections from javafx.collections import ObservableList from javafx.scene import Scene from javafx.stage import Stage from javafx.scene.chart import PieChart from javafx.scene import Group from com.kodegeek.fitness.nikerun.query import CannedQuery from com.kodegeek.fitness.nikerun.public import PlusPublicService class TerrainTypesChart(Application): def __getData__(self): args = self.getParameters().getRaw() pServ = PlusPublicService(args[0]) data = pServ.getUserData() dp = CannedQuery('string', data) return dp.getTypesOfTerrain() def start(self, stage): scene = Scene(Group()) stage.setTitle("Terrain types found in all races") stage.setWidth(500) stage.setHeight(500) count = self.__getData__() data = [PieChart.Data(key, value) for (key, value) in count.iteritems()] pieChartData = FXCollections.observableArrayList(data) chart = PieChart(pieChartData) chart.setTitle("Terrain types found") scene.getRoot().getChildren().add(chart) stage.setScene(scene) stage.show() if __name__ == "__main__": Application.launch(TerrainTypesChart().class, sys.argv[1:]) |
La clase ‘PlusPublicService’ no es más que un simple cliente GET el cual se baja los datos del sitio de Nike:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | !/usr/bin/env jython # # Author: Jose Vicente Nunez Zuleta, josevnz@kodegeek.com # See: http://nikerunning.nike.com/nikeos/p/nikeplus/en_EMEA/plus/#//dashboard/ # Nike and Nikeplus/Nike+ are trademarks owned by Nike. # License: BSD # from httplib import HTTPConnection, HTTP_PORT import sys __URL__ = 'nikerunning.nike.com' __SERVICE__ = '/nikeplus/v1/services/widget/get_public_run_list.jsp?userID=' class PlusPublicService: def __init__(self, userId, port = HTTP_PORT): self.service = __SERVICE__ + userId self.userId = userId self.port = port def getUserData(self, debug=0): data = None con = HTTPConnection(__URL__, self.port) con.set_debuglevel(debug) con.request('GET', self.service) response = con.getresponse() if response.status == 200: data = response.read() else: raise Exception("Error. Status = %s, reason = %s" % (response.status, response.reason)) con.close() return data if __name__ == "__main__": argv = sys.argv[1:] if len(argv) > 0: pServ = PlusPublicService(argv[0]) print "%s" % pServ.getUserData(8) else: print "NikeRunning user id is required!" sys.exit(192) |
Y para procesar mis datos utilizo un poco de XPATH en la clase ‘CannedQuery’ la cual tiene un método el cual se encarga de crear la distribución para los distintos tipos de terreno:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 | /usr/bin/env jython # Author: Jose Vicente Nunez Zuleta, josevnz@kodegeek.com # See: http://nikerunning.nike.com/nikeos/p/nikeplus/en_EMEA/plus/#//dashboard/ # Nike and Nikeplus/Nike+ are trademarks owned by Nike. # License: BSD from xml.dom.minidom import parse, parseString from xml.etree import ElementTree import sys class DataParser(object): def __init__(self, type='string'): self.type = type def __parseDomFile__(self, file): return parse(open(file, 'r')) def __parseDomString__(self, string): return parseString(string) def __parseXpathFile__(self, file): return ElementTree.parse(file) def __parseXpathString__(self, string): return ElementTree.fromstring(string) def __getDom__(self, source): if self.type == 'string': return self.__parseDomString__(source) else: return self.__parseDomFile__(source) def __getTree__(self, source): if self.type == 'string': return self.__parseXpathString__(source) else: return self.__parseXpathFile__(source) class CannedQuery(DataParser): def __init__(self, type, source): super(CannedQuery, self).__init__(type) self.source = source self.terrainMap = { '0': 'Not defined', '1': 'Road', '2': 'Trail', '3': 'Treadmill', '4': 'Track' } def getTypesOfTerrain(self): tree = self.__getTree__(self.source) count = {} for elem in tree.findall(".//terrain"): key = 'Never collected' if elem.text in self.terrainMap: key = self.terrainMap[elem.text] if key in count: count[key] += 1 else: count[key] = 1 return count if __name__ == "__main__": argv = sys.argv[1:] if len(argv) > 0: dp = CannedQuery('file', argv[0]) count = dp.getTypesOfTerrain() print "%s" % count |
Pienso abrir un proyecto con todas estas clases de Python (Jython) en unos pocos días. Quiero agregarle un par de cosas más antes de soltarlas al aire libre
Comentarios recientes