Created
July 22, 2011 04:06
-
-
Save tmcw/1098861 to your computer and use it in GitHub Desktop.
bulk-export run data from Garmin Connect
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
#!/usr/bin/env ruby | |
## disconnect | |
# ./disconnect.rb -u yourusername | |
# | |
# This is a command-line utility for the bulk-downloading of run data from | |
# the connect.garmin.com web application, which has lackluster export | |
# capabilities. | |
# | |
# Using this code is a matter of your own relationship with Garmin Connect | |
# and their TOS. I can't imagine this being very destructive to their service, | |
# and it's just filling in a hole in their existing service. | |
# | |
# It's built against Garmin Connect as of July 22, 2011. It's a scraper: | |
# thus if Garmin changes, this **will break**. | |
# | |
# This script requires all of the utilities on the line below: install them | |
# with rubygems | |
%w{rubygems json fileutils mechanize choice highline/import}.map{|x| require x} | |
LOGIN_PAGE = "https://connect.garmin.com/signin" | |
ACTIVITIES_SEARCH = "http://connect.garmin.com/proxy/activity-search-service-1.0/json/activities?_dc=1220170621856&start=0&limit=1000" | |
GPX_EXPORT = "http://connect.garmin.com/proxy/activity-service-1.1/gpx/activity/%d?full=true" | |
KML_EXPORT = "http://connect.garmin.com/proxy/activity-service-1.0/kml/activity/%d?full=true" | |
TCX_EXPORT = "http://connect.garmin.com/proxy/activity-service-1.0/tcx/activity/%d?full=true" | |
Choice.options do | |
header '' | |
header 'Specific options:' | |
option :user, :required => true do | |
short '-u' | |
long '--user=USER' | |
desc 'connect.garmin.com username. Required' | |
end | |
option :dir do | |
short '-o' | |
long '--output-dir=OUTPUT' | |
desc 'the directory to save .tcx files' | |
default 'tcx' | |
end | |
end | |
password = ask("Enter your password: " ) { |q| q.echo = "*" } | |
def login(agent, user, password) | |
page = agent.get(LOGIN_PAGE) | |
login_form = page.form('login') | |
login_form['login:loginUsernameField'] = user | |
login_form['login:password'] = password | |
page = agent.submit(login_form, login_form.buttons.first) | |
raise "Login incorrect!" if page.title().match('Sign In') | |
page | |
end | |
def download_run(agent, id) | |
print "." | |
# This downloads TCX files: you can swap out the constant, or add | |
# more lines that download the different kinds of exports. I prefer TCX, | |
# because despite being a 'private standard,' it includes all data, | |
# including heart rate data. | |
agent.get(TCX_EXPORT % (id).to_i).save_as(File.join(Choice[:dir], "%d.tcx" % id)) | |
end | |
def activities(agent) | |
j = agent.get(ACTIVITIES_SEARCH) | |
search = JSON.parse(j.content) | |
runs = search['results']['activities'].map {|r| | |
# Get each activity id to insert into the download URL | |
r['activity']['activityId'] | |
}.map {|id| | |
# Download a run. | |
download_run(agent, id) | |
} | |
end | |
agent = Mechanize.new | |
# One needs to log in to get access to private runs. Mechanize will store | |
# the session data for the API call that cames next. | |
home_page = login(agent, Choice[:user], password) | |
FileUtils.mkdir_p(Choice[:dir]) if not File.directory?(Choice[:dir]) | |
puts "Downloading runs..." | |
activities(agent) |
Garmin Connect UI has changed and this script no longer works for me.
If you're interested in a Python solution, I have one that does pretty much the same thing (in fact, this script was inspiration for it) and also contains a fix for the new Garmin Connect UI (basically just needs a redirect after login):
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Yes, thanks a lot for sharing. I also experienced the 400 => Net::HTTPBadRequest problem. Instead of limiting ACTIVITIES_SEARCH to 100, I limited it to 1 and changed the activities function to look like the following:
def activities(agent)
$activityIdx = 0
$numActivities = 624
while $activityIdx <= $numActivities do
puts("Downloading activity = #$activityIdx")
as = ACTIVITIES_SEARCH % $activityIdx
print "\tas: ", as, "\n"
j = agent.get(as)
search = JSON.parse(j.content)
runs = search['results']['activities'].map {|r|
# Get each activity id to insert into the download URL
r['activity']['activityId']
}.map {|id|
# Download an activity.
download_run(agent, id)
}
$activityIdx += 1
end
end
In my case I wanted all of my history, 624.
Thanks a lot for sharing @tmcw!