-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
0 parents
commit 71af3d4
Showing
1 changed file
with
151 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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() |