-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpyfitbit.py
151 lines (122 loc) · 6.71 KB
/
pyfitbit.py
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
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
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()