Debug code to track down error in finding link_csv_download.
[toast/webscraper/bob.git] / bob_download.py
1 #!/usr/bin/python3
2 # (c) 2015-2017 Gregor Herrmann und Philipp Spitzer
3 # License: CC0 1.0 Universal (CC0 1.0) Public Domain Dedication
4 # (https://creativecommons.org/publicdomain/zero/1.0/)
5 """
6 usage: bob_download.py [-h] user_name password dest_dir
7
8 Downloads invoices from BOB.
9
10 positional arguments:
11   username    login user name is phone number (e.g. '4369911111111')
12   password    login password
13   destdir     directory where the invoices should be saved to
14
15 optional arguments:
16   -h, --help  show this help message and exit
17 """
18 import os
19 import re
20 import time
21 import argparse
22 import warnings
23 from urllib.parse import urljoin
24 import requests # pip install requests
25 from bs4 import BeautifulSoup # pip install beautifulsoup4
26 from requests.packages.urllib3.exceptions import SubjectAltNameWarning
27
28 # SubjectAltNameWarning's should go off 0 times per host
29 warnings.simplefilter('ignore', SubjectAltNameWarning)
30
31 def main(username, password, destdir, csv_format):
32     session = requests.Session()
33     session.headers.update({
34         'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8', # otherwise site with content '<HTML></HTML>' is returned
35         })
36
37     # load login page
38     response = session.get('https://rechnung.bob.at/')
39     assert response.ok
40     html = BeautifulSoup(response.text, 'html.parser')
41
42     # fill out login form (name='asmpform') with username=<phone number> and password
43     form = html.find(attrs={'name': 'asmpform'})
44     fields = {e['name']: e.get('value', '') for e in form.find_all('input', {'name': True}) if e['name'] != 'submit'}
45     assert 'loginMsisdn' in fields # user name
46     fields['loginMsisdn'] = username # e.g. '4369911111111'
47     assert 'kkw' in fields # password
48     fields['kkw'] = password
49
50     # load overview page
51     response = session.post(form['action'], data=fields)
52     assert response.ok
53     assert 'invalid KKW response' not in response.text
54     html = BeautifulSoup(response.text, 'html.parser')
55     assert html.find('a', title="ausloggen") is not None
56
57     # Download PDFs
58     # Links look like:
59     # https://rechnung.bob.at/bill/download/pdf/sync/Rechnung_1703_523260091_1.pdf?ban=523260091&ben=1&bsn=79&original=true
60     # https://rechnung.bob.at/bill/download/pdf/sync/Rechnung_1702_523260091_1.pdf?ban=523260091&ben=1&bsn=78&original=true
61     # https://rechnung.bob.at/bill/download/pdf/sync/Rechnung_1701_523260091_1.pdf?ban=523260091&ben=1&bsn=77&original=true
62     regexp = re.compile(r'\/(Rechnung_.*)\?')
63     links = html.findAll('a', href=regexp)
64     for link in links:
65         url = link['href']
66         filename = regexp.search(url).group(1)
67         assert filename.startswith('Rechnung_')
68         filepath = os.path.join(destdir, filename)
69         if not os.path.exists(filepath):
70             response = session.get(urljoin(response.url, url))
71             assert response.ok
72             with open(filepath, 'wb') as file:
73                 file.write(response.content)
74
75     # Download CSVs
76     # e.g. https://rechnung.bob.at/bill.ctn.cdr.obp?bsn=79
77     regexp = re.compile(r'\/bill.ctn.cdr.obp\?')
78     links = html.findAll('a', href=regexp)
79     for link in links:
80         url = link['href']
81         response = session.get(urljoin(response.url, url))  # e.g. 'https://rechnung.bob.at/bill.ctn.cdr.obp?bsn=79'
82         assert response.ok
83         html = BeautifulSoup(response.text, 'html.parser')
84         if not html.find('a', id='link_csv_download'):
85             filepath = os.path.join(destdir, 'debug_no_link_csv_download.txt')
86             with open(filepath, 'wb') as file:
87                 file.write(response.content)
88         assert html.find('a', id='link_csv_download')
89         response = session.get('https://rechnung.bob.at/obp/download.obp?fmt={}&table=obp.calls.table'.format(csv_format))
90         assert response.ok
91         filename = response.headers['Content-Disposition'].split('=')[1] # e.g. 'EVN_1509_523260091_1_069911934859.txt'
92         assert filename.startswith('EVN_')
93         filepath = os.path.join(destdir, filename)
94         if not os.path.exists(filepath):
95             with open(filepath, 'wb') as file:
96                 file.write(response.content)
97
98
99 if __name__ == '__main__':
100     parser = argparse.ArgumentParser(description='Downloads invoices from BOB.')
101     parser.add_argument('--csv-format', choices=['CSV-DE', 'CSV', 'TAB'], default='TAB', help='Which CSV variant to use (CSV-DE is semicolon, CSV is comma and TAB is tab separated)')
102     parser.add_argument('username', help="login user name is phone number (e.g. '4369911111111')")
103     parser.add_argument('password', help="login password")
104     parser.add_argument('destdir', help="directory where the invoices should be saved to")
105     args = parser.parse_args()
106     main(args.username, args.password, args.destdir, args.csv_format)