Skip to content

Commit

Permalink
First commit
Browse files Browse the repository at this point in the history
  • Loading branch information
jgysland committed Aug 17, 2015
0 parents commit 71af3d4
Showing 1 changed file with 151 additions and 0 deletions.
151 changes: 151 additions & 0 deletions pyfitbit.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
"""pyfitbit gets Fitbit intraday data from fitbit.com."""
# coding=utf-8

__author__ = 'jgysland'

from robobrowser import RoboBrowser
import json
from datetime import datetime
import pandas as pd


class FitbitData(object):
"""FibitData contains functions to log into fitbit.com and pull data from the api that feeds charts on the
dashboard and return them as a pandas.DataFrame with a pandas.DatetimeIndex at 15-minute intervals (the minimum
interval for which all metrics are available; heart-rate is available at 5-minute intervals.
:param email: The email address used to log in to fitbit.com
:param passwd: The password associated with the email address. Defaults to None, which results in getpass.getpass
being called to prompt the user for the password without echoing.
:param metrics: A list of metrics from ['steps', 'distance', 'floors', 'active-minutes', 'calories-burned',
'heart-rate']. Defaults to all metrics.
:param dt1: The first date for which to retrieve data (as ISO 8601, datetime.datetime, numpy.datetime64, or other
pandas.date_range()-compliant date-like object). Defaults to None, in which case dt1 is seven days prior to dt2.
:param dt2: The last date for which to retrieve data (with the same formatting considerations as dt1). Defaults
to None, in which case dt2 is the current date.
:param write_out: Write out a .csv of the pandas.DataFrame generated by FitbitData.get_data(). Default False.
:param filename: The filename to write to. Defaults to 'fitbit_export_%Y-%m-%d.csv'
"""

def __init__(self, email, passwd=None, metrics=None, dt1=None, dt2=None, write_out=False, filename=None):
self.email = email
if passwd is None:
from getpass import getpass
self.passwd = getpass('Password for %s: ' % self.email)
else:
self.passwd = passwd
if metrics is None:
self.metrics = ['steps', 'distance', 'floors', 'active-minutes', 'calories-burned', 'heart-rate']
else:
self.metrics = metrics
if dt2 is None:
self.dt2 = datetime.now()
else:
self.dt2 = dt2
if dt1 is None:
from datetime import timedelta

self.dt1 = self.dt2 - timedelta(days=7)
else:
self.dt1 = dt1
self.browser = RoboBrowser(parser='lxml')
self.write_out = write_out
self.filename = filename
self.date, self.metric, self.data = (None, None, None)

def login(self):
"""Login to fitbit.com and return a RoboBrowser instance."""

self.browser.open('https://www.fitbit.com/login')

login = self.browser.get_form('loginForm')
login['email'] = self.email
login['password'] = self.passwd
self.browser.submit_form(login)

def get_daily_data(self):
"""Get data on specified metric for specified date."""

id_data = {'template': '/ajaxTemplate.jsp',
'serviceCalls': [{'name': 'activityTileData',
'args': {'date': self.date,
'dataTypes': self.metric},
'method': 'getIntradayData'}]}
csrf_token = {'csrfToken': self.browser.session.cookies['u'].split('|')[2]}

self.browser.open('https://www.fitbit.com/ajaxapi', method='post',
data={'request': json.dumps(id_data),
'csrfToken': json.dumps(csrf_token)})
response = json.loads(self.browser.parsed.text)

return response[0]['dataSets']['activity']['dataPoints']

def scrape(self):
"""Get data on specified metric for all dates in FitbitData instance."""

date_range = pd.to_datetime(pd.date_range(self.dt1, self.dt2))
if len(date_range) == 0:
from warnings import warn

warn("dt1 is greater than dt2 resulting in 0 dates for which to pull data; dt1 and dt2 are being reversed.",
UserWarning)
date_range = pd.to_datetime(pd.date_range(self.dt2, self.dt1))
dates = [d.strftime('%Y-%m-%d') for d in date_range]
data = []
for d in dates:
self.date = d
data.append(self.get_daily_data())

return data

def make_df(self):
"""Return a pandas.DataFrame of the specified data,
optionally write to .csv file as specified in FitbitData instance."""

df = pd.concat([pd.DataFrame(d) for d in self.data], ignore_index=True)
df.dateTime = pd.to_datetime(df.dateTime)
df.set_index('dateTime', inplace=True)

return df

def get_data(self):
"""Get data for all metrics and dates specified in FitbitData instance and return pandas.DataFrame of
:rtype : pandas.DataFrame
15-minute observations."""

dfs = {}
self.login()

for metric in self.metrics:
self.metric = metric
self.data = self.scrape()
df = self.make_df()
dfs[metric] = df

df = pd.concat([dfs[k].groupby(pd.TimeGrouper('15Min')).bpm.median() if k == 'heart-rate' else
dfs[k].rename(columns={'value': k}) for k in dfs.keys()],
axis=1)
if self.filename is None:
self.filename = 'fitbit_export_%s.csv' % datetime.now().strftime('%Y-%m-%d')
if self.write_out is True:
df.to_csv(self.filename)

return df


def get_fitbit_data(email, passwd=None, metrics=None, dt1=None, dt2=None, write_out=False, filename=None):
"""Create a FitbitData instance and return pandas.DataFrame using FitbitData.get_data().
:param email: The email address used to log in to fitbit.com
:param passwd: The password associated with the email address. Defaults to None, which results in getpass.getpass
being called to prompt the user for the password without echoing.
:param metrics: A list of metrics from ['steps', 'distance', 'floors', 'active-minutes', 'calories-burned',
'heart-rate']. Defaults to all metrics.
:param dt1: The first date for which to retrieve data (as ISO 8601, datetime.datetime, numpy.datetime64, or other
pandas.date_range()-compliant date-like object). Defaults to None, in which case dt1 is seven days prior to dt2.
:param dt2: The last date for which to retrieve data (with the same formatting considerations as dt1). Defaults
to None, in which case dt2 is the current date.
:param write_out: Write out a .csv of the pandas.DataFrame generated by FitbitData.get_data(). Default False.
:param filename: The filename to write to. Defaults to 'fitbit_export_%Y-%m-%d.csv'"""
fitbit = FitbitData(email, passwd, metrics, dt1, dt2, write_out, filename)
return fitbit.get_data()

0 comments on commit 71af3d4

Please sign in to comment.