Adapt to new design of bob homepage.
[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 argparse
21 import warnings
22 from urllib.parse import urljoin
23 import requests # pip install requests
24 from bs4 import BeautifulSoup # pip install beautifulsoup4
25 from requests.packages.urllib3.exceptions import SubjectAltNameWarning
26
27 # SubjectAltNameWarning's should go off 0 times per host
28 warnings.simplefilter('ignore', SubjectAltNameWarning)
29
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="logout") is not None
56
57     # Download files
58     links = html.findAll('a', class_="table-bill__link--pdf")
59     for link in links:
60         url_pdf = link['href']
61         date_range = link.parent.parent.parent.find(class_='table-bills__header').find(class_='text-copy').text  # 26.02.2019 - 25.03.2019
62         match = re.match(r'\d\d\.\d\d\.\d\d\d\d - (\d\d)\.(\d\d)\.(\d\d\d\d)', date_range)
63
64         # Download PDF
65         filename_pdf = '{}-{}-{}_Rechnung.pdf'.format(*match.groups()[::-1])  # '2019-03-25_Rechnung.pdf'
66         filepath_pdf = os.path.join(destdir, filename_pdf)
67         if not os.path.exists(filepath_pdf):
68             response = session.get(urljoin(response.url, url_pdf))
69             assert response.ok
70             with open(filepath_pdf, 'wb') as file:
71                 file.write(response.content)
72
73         # Download CSV
74         # https://ppp.bob.at/bobstart/invoiceDetailsCSV.sp?bsn=103
75         filename_csv = '{}-{}-{}_EVN.csv'.format(*match.groups()[::-1])  # '2019-03-25_EVN.pdf'
76         filepath_csv = os.path.join(destdir, filename_csv)
77         if not os.path.exists(filepath_csv):
78             url_csv = url_pdf.replace('invoicePdf', 'invoiceDetailsCSV')
79             response = session.get(urljoin(response.url, url_csv))
80             assert response.ok
81             with open(filepath_csv, 'wb') as file:
82                 file.write(response.content)
83
84
85 if __name__ == '__main__':
86     parser = argparse.ArgumentParser(description='Downloads invoices from BOB.')
87     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)')
88     parser.add_argument('username', help="login user name is phone number (e.g. '4369911111111')")
89     parser.add_argument('password', help="login password")
90     parser.add_argument('destdir', help="directory where the invoices should be saved to")
91     args = parser.parse_args()
92     main(args.username, args.password, args.destdir, args.csv_format)