forked from interesse/web_bank
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathweb_bank.py
268 lines (234 loc) · 9.47 KB
/
web_bank.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
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
#!/usr/bin/python
# -*- coding: UTF-8 -*-
'''
Holt Kreditkarten-Umsätze per Web-Scraping vom Webfrontend der DKB
(Deutsche Kreditbank), die für diese Daten kein HCBI anbietet.
Die Umsätze werden im Quicken Exchange Format (.qif) ausgegeben
oder gespeichert und können somit in eine Buchhaltungssoftware
importiert werden.
Geschrieben 2007 von Jens Herrmann <[email protected]> http://qoli.de
Benutzung des Programms ohne Gewähr. Speichern Sie nicht ihr Passwort
in der Kommandozeilen-History!
7.9.2008: Anstelle von xml.xpath, das von Ubuntu 8.04 nicht mehr unterstützt
wird, wird jetzt lxml.etree benutzt.
25.10.2008: Kleiner Fix wegen einer HTML-Änderung der DKB (table wird nicht mehr
gebraucht), dafür wird jetzt exportiertes CSV ausgewertet, das nicht auf eine
Seite Ausgabe beschränkt ist. Da kein HTML mehr geparsed wird, entfällt auch die
Abhängigkeit zu tidy und xpath.
27.12.2009: Auswahl der Kartennummer
23.11.2011: Fix des Session-Handlings durch eine Änderung der DKB
12.05.2012: Fix des Session-Handlings durch erneute Änderung der DKB (Akzeptieren von
Cookies ist erforderlich) Danke an Robert Steffens!
Benutzung: web_bank.py [OPTIONEN]
-a, --account=ACCOUNT Kontonummer des Hauptkontos. Angabe notwendig
-c, --card=NUMBER Die letzten 4 Stellen der Kartennummer, falls
mehrere Karten vorhanden sind.
-p, --password=PASSWORD Passwort (Benutzung nicht empfohlen,
geben Sie das Passwort ein, wenn Sie danach
gefragt werden)
-f, --from=DD.MM.YYYY Buchungen ab diesem Datum abfragen
-t, --till=DD.MM.YYYY Buchungen bis zu diesem Datum abfragen
Default: Heute
-o, --outfile=FILE Dateiname für die Ausgabedatei
Default: Standardausgabe (Fenster)
-v, --verbose Gibt zusätzliche Debug-Informationen aus
'''
import sys, getopt
from datetime import datetime
from getpass import getpass
import urllib2, urllib, cookielib, re
def group(lst, n):
return zip(*[lst[i::n] for i in range(n)])
debug=False
def log(msg):
if debug:
print msg
# Parser for new style banking pages
class NewParser:
URL = "https://banking.dkb.de"
BETRAG = 'frmBuchungsbetrag'
ZWECK = 'frmVerwendungszweck'
TAG = 'frmBuchungstag'
PLUSMINUS = 'frmSollHabenKennzeichen'
MINUS_CHAR='S'
DATUM = 'frmBelegdatum'
def get_cc_index(self, card, data):
log('Finde Kreditkartenindex für Karte ***%s...'%card)
pattern= r'<option value="(.)" id=".*"( selected="selected" )?>.{12}%s / Kreditkarte'%card
index= re.findall(pattern, data)
if len(index)>0:
return index[0][0]
else:
return '0'
def get_cc_csv(self, account, card, password, fromdate, till):
log('Hole sessionID und Token...')
# retrieve sessionid and token
url= self.URL+"/dkb/-?$javascript=disabled"
cj = cookielib.LWPCookieJar()
opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cj))
urllib2.install_opener(opener)
page= urllib2.urlopen(url,).read()
session= re.findall(';dkbsessid=.*?["\?]',page)[0][:-1]
token= re.findall('<input type="hidden" name="token" value="(.*)" id=',page)[0]
log('SessionID: %s Token: %s'%(session,token))
# login
url= self.URL+'/dkb/-'+session
request=urllib2.Request(url, data= urllib.urlencode({
'$$event_login.x': '0',
'$$event_login.y': '0',
'token': token,
'j_username': account,
'j_password': password,
'$part': 'Welcome.login',
'$$$event_login': 'login',
}))
page=urllib2.urlopen(request).read()
referer = url
url= self.URL+'/dkb/-'
# init search
request=urllib2.Request(url+'?$part=DkbTransactionBanking.content.creditcard.CreditcardTransactionSearch&$event=init',
headers={'Referer':urllib.quote_plus(referer)})
throwaway=urllib2.urlopen(request).read()
referer = url
# retrieve data
request=urllib2.Request(url, data= urllib.urlencode({
'slCreditCard': '0',
'searchPeriod': '0',
'postingDate': fromdate,
'toPostingDate': till,
'$$event_search': 'Umsätze+anzeigen',
'$part': 'DkbTransactionBanking.content.creditcard.CreditcardTransactionSearch',
'$$$event_search': 'search',
}), headers={'Referer':urllib.quote_plus(referer)})
data= ''.join(urllib2.urlopen(request).readlines())
# find card index
if not card=='':
cc_index= self.get_cc_index(card, data)
# again retrieve data for correct card
request=urllib2.Request(url, data= urllib.urlencode({
'slCreditCard': cc_index,
'searchPeriod': '0',
'postingDate': fromdate,
'toPostingDate': till,
'$$event_search': 'Umsätze+anzeigen',
'$part': 'DkbTransactionBanking.content.creditcard.CreditcardTransactionSearch',
'$$$event_search': 'search',
}), headers={'Referer':urllib.quote_plus(url+"?$part=DkbTransactionBanking.content.banking.FinancialStatus.FinancialStatus&$event=paymentTransaction&row=1&table=cashTable")})
throwaway= ''.join(urllib2.urlopen(request).readlines())
#CSV abrufen
request=urllib2.Request(url+'?$part=DkbTransactionBanking.content.creditcard.CreditcardTransactionSearch&$event=csvExport',
headers={'Referer':urllib.quote_plus(url)})
req = urllib2.urlopen(request)
antwort= req.read()
log('Daten empfangen. Länge: %s'%len(antwort))
encoding = req.headers['content-type'].split('charset=')[-1]
return unicode(antwort, encoding)
def parse_csv(self, cc_csv):
result=[]
for line in cc_csv.split('\n')[8:]: # Liste beginnt in Zeile 9 des CSV
g= line.split(';')
if len(g)==7: #Jede Zeile hat 7 Elemente
act={}
act[self.ZWECK]=g[3][1:-1]
act[self.TAG]=g[1][1:-1]
act[self.DATUM]=g[2][1:-1]
act[self.PLUSMINUS]=''
act[self.BETRAG]=g[4][1:-1]
result.append(act)
return result
CC_NAME= 'VISA'
CC_NUMBER= ''
LOGIN_ACCOUNT=''
LOGIN_PASSWORD=''
PARSER= NewParser()
GUESSES=[
(PARSER.BETRAG,'-150.0',u'Aktiva:Barvermögen:Bargeld'),
]
def guessCategories(f):
for g in GUESSES:
if g[1] in f[g[0]].upper():
return g[2]
def render_qif(cc_data):
cc_qif=[]
cc_qif.append('!Account')
cc_qif.append('N'+CC_NAME)
cc_qif.append('^')
cc_qif.append('!Type:Bank')
log('Für Ausgabe vorbereiten:')
for f in cc_data:
log(str(f))
if PARSER.TAG in f.keys():
f[PARSER.BETRAG]= float(f[PARSER.BETRAG].replace('.','').replace(',','.'))
if PARSER.MINUS_CHAR in f[PARSER.PLUSMINUS]:
f[PARSER.BETRAG]= -f[PARSER.BETRAG]
f[PARSER.BETRAG]=str(f[PARSER.BETRAG])
datum=f[PARSER.DATUM].split('.')
cc_qif.append('D'+datum[1]+'/'+datum[0]+'/'+datum[2])
cc_qif.append('T'+f[PARSER.BETRAG])
if PARSER.ZWECK+"1" in f:
for n in range(1,8):
if f[PARSER.ZWECK+str(n)].strip():
cc_qif.append('M'+f[PARSER.ZWECK+str(n)])
else:
cc_qif.append('M'+f[PARSER.ZWECK])
c= guessCategories(f)
if c:
cc_qif.append('L'+c)
cc_qif.append('^')
return u'\n'.join(cc_qif)
class Usage(Exception):
def __init__(self, msg):
self.msg = msg
def main(argv=None):
account=LOGIN_ACCOUNT
password= LOGIN_PASSWORD
card_no= CC_NUMBER
fromdate=''
till=datetime.now().strftime('%d.%m.%Y')
outfile= sys.stdout
if argv is None:
argv = sys.argv
try:
try:
opts, args = getopt.getopt(argv[1:], "ha:c:p:f:t:o:v", ['help','account=','card=','password=','from=','till=','outfile=','verbose'])
except getopt.error, msg:
raise Usage(msg)
for o, a in opts:
if o in ("-h", "--help"):
print __doc__
return 0
if o in ('-a','--account'):
account= a
if o in ('-c','--card'):
card_no= a
if o in ('-p','--password'):
password= a
if o in ('-f','--from'):
fromdate= a
if o in ('-t','--till'):
till= a
if o in ('-o','--outfile'):
try:
outfile=open(a,'w')
except IOError, msg:
raise Usage(msg)
if o in ('-v','--verbose'):
print 'Mit Debug-Ausgaben'
global debug
debug=True
if not account or not fromdate:
raise Usage('Anfangsdatum und Kontonummer müssen angegeben sein.')
if not password:
try:
password=getpass('Geben Sie das Passwort für das Konto '+account+' ein: ')
except KeyboardInterrupt:
raise Usage('Sie müssen ein Passwort eingeben!')
cc_csv = PARSER.get_cc_csv(account, card_no, password, fromdate, till)
cc_data = PARSER.parse_csv(cc_csv)
print >>outfile, render_qif(cc_data).encode('utf-8')
except Usage, err:
print >>sys.stderr, __doc__
print >>sys.stderr, err.msg
return 2
if __name__ == '__main__':
sys.exit(main())