thecave


My hideout on the Internet

Download iTunes Connect Sales Reports Using Python

Apple publishes daily sales figures on their iTunes Connect website for software vendors who have applications for sale in the iPhone App Store. I’ve been making a point to download the daily file each morning but I know I cannot continue doing this manually. What I need is an automated way to download the daily file.

I searched the net for script code that will download the iTunes Connect daily sales report but I came up empty. So I decided to write my own.

I’ve been interested in getting into Python programming and I thought what better way to learn Python programming than to write a script that will download the daily sales report from the iTunes Connect website. It turned out to be a fairly easy task to complete using Python. Guess it goes to show how powerful Python really is.

So here is my very first Python script. I’m sure there are things I could do better in the script, but hey, it’s my first script. Enjoy.

UPDATE: The original code had a problem with retaining cookies set by the iTunes Connect website. The code below has been updated with the correct, working code.

UPDATE 2: The Python script below is now hosted over at Google. Just click here or visit http://code.google.com/p/appdailysales/ for the latest information and version.

<pre>
#!/usr/bin/python
#
# appdailysales.py
#
# iTune Connect Daily Sales Reports Downloader
# Copyright 2008 Kirby Turner
#
#
# This script will download yesterday’s daily sales report from
# the iTunes Connect web site. The downloaded file is stored
# in the same directory containing the script file. Note: if
# the download file already exists then it will be overwritten.
#
# The iTunes Connect web site has dynamic urls and form field
# names. In other words, these values change from session to
# session. So to get to the download file we must navigate
# the site and webscrape the pages. Joy, joy.
#
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the “Software”), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.


# – Change the following to match your credentials –
appleId = ‘Your Apple Id’
password = ‘Your Password’
# —————————————————-


import urllib
import urllib2
import cookielib
import datetime
import re

urlWebsite = ‘https://itunesconnect.apple.com/WebObjects/iTunesConnect.woa’
urlActionLogin = ‘https://itunesconnect.apple.com%s’
urlActionSalesReport = ‘https://itunesconnect.apple.com%s’

print ‘– begin script –‘

# There is an issue with Python 2.5 where it assumes the ‘version’
# cookie value is always interger. However, itunesconnect.apple.com
# returns this value as a string, i.e., “1” instead of 1. Because
# of this we need a workaround that “fixes” the version field.
#
# More information at: http://bugs.python.org/issue3924
class MyCookieJar(cookielib.CookieJar):
def _cookie_from_cookie_tuple(self, tup, request):
name, value, standard, rest = tup
version = standard.get(‘version’, None)
if version is not None:
version = version.replace(‘”’, ‘’)
standard[“version”] = version
return cookielib.CookieJar._cookie_from_cookie_tuple(self, tup, request)


def showCookies(cj):
for index, cookie in enumerate(cj):
print index, ‘ : ‘, cookie


cj = MyCookieJar();
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))



# Go to the iTunes Connect website and retrieve the
# form action for logging into the site.
urlHandle = opener.open(urlWebsite)
html = urlHandle.read()
match = re.search(‘action=”(.)”’, html)
urlActionLogin = urlActionLogin % match.group(1)


# Login to iTunes Connect web site and retrieve the
# link to the sales report page.
webFormLoginData = urllib.urlencode({‘theAccountName’:appleId, ‘theAccountPW’:password})
urlHandle = opener.open(urlActionLogin, webFormLoginData)
html = urlHandle.read()
match = re.search(‘href=”(/WebObjects/iTunesConnect.woa/wo/.
)”>
urlActionSalesReport = urlActionSalesReport % match.group(1)


# Go to the sales report page, get the form action url and
# form fields. Note the sales report page will actually
# load a blank page that redirects to the static URL. Best
# guess here is that the server is setting some session
# variables or something.
urlHandle = opener.open(urlActionSalesReport)
urlHandle = opener.open(‘https://itts.apple.com/cgi-bin/WebObjects/Piano.woa’)
html = urlHandle.read()
match = re.findall(‘action=”(.)”’, html)
urlDownload = “https://itts.apple.com%s” % match[1]


# Get the form field names needed to download the report.
match = re.findall(‘name=”(.
?)”’, html)
fieldNameReportType = match[3]
fieldNameReportPeriod = match[4]
fieldNameDayOrWeekSelection = match[6]
fieldNameSubmitTypeName = match[7]


# Ah…more fun. We need to post the page with the form
# fields collected so far. This will give us the remaining
# form fields needed to get the download file.
webFormSalesReportData = urllib.urlencode({fieldNameReportType:’Summary’, fieldNameReportPeriod:’Daily’, fieldNameDayOrWeekSelection:’Daily’, fieldNameSubmitTypeName:’ShowDropDown’})
urlHandle = opener.open(urlDownload, webFormSalesReportData)
html = urlHandle.read()
match = re.findall(‘action=”(.)”’, html)
urlDownload = “https://itts.apple.com%s” % match[1]
match = re.findall(‘name=”(.
?)”’, html)
fieldNameDayOrWeekDropdown = match[5]


# Set report date to yesterday’s date. This will be the most
# recent daily report available. Another option would be to
# webscrape the dropdown list of available report dates and
# select the first item but setting the date to yesterday’s
# date is easier.
today = datetime.date.today() - datetime.timedelta(1)
reportDate = ‘%i/%i/%i’ % (today.month, today.day, today.year)


# And finally…we’re ready to download yesterday’s sales report.
webFormSalesReportData = urllib.urlencode({fieldNameReportType:’Summary’, fieldNameReportPeriod:’Daily’, fieldNameDayOrWeekDropdown:reportDate, fieldNameDayOrWeekSelection:’Daily’, fieldNameSubmitTypeName:’Download’})
urlHandle = opener.open(urlDownload, webFormSalesReportData)
filename = urlHandle.info().getheader(‘content-disposition’).split(‘=’)[1]
filebuffer = urlHandle.read()
urlHandle.close()


print ‘ saving download file:’, filename
d ownloadFile = open(filename, ‘w’)
downloadFile.write(filebuffer)
downloadFile.close()

print ‘– end of script –‘
</pre>

Continue reading →


Pictures from Recent Memphis Trip

Continue reading →


Lake Champlain Trip

Continue reading →


Wedding Pictures

Continue reading →


Pictures from Dave, Lexi, and Lucy's Visit

Continue reading →


Playing with Xbox Again

Today was the first time in over 6 months that I played on my Xbox 360.

Continue reading →


ImportError: No module named svn

Recently I moved my CVS and Subversion repositories to a VPS that is running the server edition of Ubuntu Hardy. As part of the move I installed ViewCV enabling me to view my repositories via a web browser. Problem was when I viewed a Subversion repository I got the following error message:


An Exception Has Occurred
Python Traceback
Traceback (most recent call last):
File "/usr/local/viewcvs-1.0-dev/lib/viewcvs.py", line 3194, in main
request.run_viewcvs()
File "/usr/local/viewcvs-1.0-dev/lib/viewcvs.py", line 258, in run_viewcvs
import vclib.svn
File "/usr/local/viewcvs-1.0-dev/lib/vclib/svn/__init__.py", line 27, in ?
from svn import fs, repos, core, delta
ImportError: No module named svn


I could view CVS repository just not Subversion repositories. This left me scratching my head. I did a couple of google searches but no solution found. Then I thought to myself, “I wonder if there is anything Python specific for Subversion.” This prompted me to try searching for packages using the aptitude command.

Sure enough the command aptitude search subversion returned a few items including python-subversion, which is the Python binding for Subversion. Bingo! I ran the command aptitude install python-subversion then checked ViewCV again in my browser. Error gone. I’m now able to view both CVS and SVN repositories.

Continue reading →


Salem Bloggers Unite

The fine folks at The Salem Insider are compiling a list of Salem-based bloggers. If you live in Salem Mass and have a blog, you should add your blog site to the list. And no, your blog site doesn’t have to be about Salem. They just ask that you be a Salem resident. It’s a neat way to see what others in Salem are writing about.

Continue reading →


Waiting on a Response?

I know there are family and friends waiting for me to response to emails, IM, txt, facebook wall posting, tweets, etc. I’m sorry to say you will have to wait another day or two. I’ll be catching up on personal emails, etc starting tomorrow. Meanwhile, check this out if you need entertaining while you wait.

Continue reading →


New Blog for White Peak Software

I decided the time has come to separate blog postings specific to White Peak Software from my personal posting found here at thecave.com. If you are interested in following product news, tips, tricks, and all other things related to White Peak Software please visit or subscribe to blog.whitepeaksoftware.com.

Continue reading →