Python-program til at sende dine beskeder i E-boks til din mailadresse

OPDATERING 29. maj 2019: Dette program virker ikke længere. Prøv dette i stedet.

Der er mange, der har fået den idé, automatisk at videresende beskeder i E-boks til en anden mailadresse eller gemme dem på computeren. Videresendelse kan også gøres i E-boks selv, men kun manuelt og en besked ad gangen. Her er nogle projekter:

Jeg kan kun finde ud af Python, og ved hvordan jeg sætter et job op på min webserver, der kan gøre Python-programmer regelmæssigt (det skulle jeg nemlig bruge til https://wallnot.dk).

Så – med kæmpe hjælp fra koden til Net-Eboks af Dmitry Karasik – har jeg skrevet et Python-program, der videresender nye beskeder i E-boks til min mail.

Programmet fungerer, men det er ikke gennemtestet, og tager ikke højde for fejl, fx. at brugeren indtaster forkerte oplysninger i programmet. Så det er nok en god idé også at logge ind på E-boks en gang imellem og lige se at alt bliver hentet og videresendt.

Du er velkommen til at bruge programmet, videreudvikle, og hvad du ellers kan finde på.

# -*- coding: utf-8 -*-
# Author: Morten Helmstedt. E-mail: helmstedt@gmail.com.
# Based on https://github.com/dk/Net-Eboks perl API for eboks.dk by Dmitry Karasik. Thanks!
""" This program logs on to e-boks.dk and takes new messages and sends them
to an e-mail. It requires mobile app login for e-boks (see http://www.e-boks.dk/help.aspx?pageid=db5a89a1-8530-418a-90e9-ff7f0713784a for
how to create). It also requires access to a secure (SSL) SMTP server and mail 
account for sending e-mails. """

# Necessary modules
from datetime import datetime						# Current date and time
import requests										# Communicating with E-boks
import hashlib										# Hash configuration for challenge/logon
import xml.etree.ElementTree as ET					# Parse E-boks XML responses
import smtplib										# Sending e-mails
from email.mime.multipart import MIMEMultipart		# Creating multipart e-mails
from email.mime.text import MIMEText				# Attaching text to e-mails
from email.mime.application import MIMEApplication	# Attaching pdf to e-mails
from email.mime.image import MIMEImage				# Attaching images to e-mails
from email.utils import formataddr					# Used for correct encoding of senders with special characters in name (e.g. Københavns Kommune)
import chardet										# Text message character set detection
import time											# Pause between e-mails sent

# Configuration data
data = {
'emailserver': '', 	# Your mail server hostname: host.server.dk
'emailserverport': ,					# Mail server port, e.g. 465
'emailusername': '',	# Sender mail account username
'emailpassword': '',		# Sender mail account password
'emailfrom': '',		# Sender e-mail, e.g. trump@usa.gov
'emailto': '',		# Recipient e-mail, e.g. hillary@clinton.net
'cpr': '',					# CPR number (no hyphens), e.g. 1234567890
'password': '',		# E-boks mobile account password
'activation': '',				# E-boks mobile account activation code
'numberofmessagesperfolder': '10',		# Number of messages to request (10 is usually enough)
'unreadstatusvalue': "true",			# Normally "true". If "false" also read messages are sent
'unreadmorethan': 0,					# Normally 0, only unread messages are sent. If -1 all messages are sent
'sendemails': True,						# If True, e-mails are sent, if False, they are not
'country': 'DK',
'type': 'P',
'deviceid': 'python-e-boks-000000000000',
'datetime': '',
'root': 'rest.e-boks.dk',
'nonce': '',
'sessionid': '',
'response': '3a1a51f235a8bd6bbc29b2caef986a1aeb77018d60ffdad9c5e31117e7b6ead3',
'uid': '',
'uname': '',
'challenge': ''
}

# Gets current date and time for E-boks challenge
now = datetime.now()
data['datetime'] = datetime.strftime(now, '%Y-%m-%d %H:%M:%SZ')

# Hashes parts of configuration data and sets challenge value to authenticate with E-boks
hashstring = data['activation']+":"+data['deviceid']+":"+data['type']+":"+data['cpr']+":"+data['country']+":"+data['password']+":"+data['datetime']
hashstringcoded = hashstring.encode('utf-8')
data['challenge'] = hashlib.sha256(hashstringcoded).hexdigest().encode('utf-8')
data['challenge'] = hashlib.sha256(data['challenge']).hexdigest()

# These functions are used to create sessionid, nonce and authstring values for communicating
# with E-boks throughout the program
def sessionid(authenticate):
	sessionstart = authenticate.find('sessionid="')+len('sessionid="')
	sessionend = authenticate.find('"', sessionstart)
	data['sessionid'] = authenticate[sessionstart:sessionend]

def nonce(authenticate):
	noncestart = authenticate.find('nonce="')+len('nonce="')
	nonceend = authenticate.find('"', noncestart)
	data['nonce'] = authenticate[noncestart:nonceend]

def createauthstring():
	authstr = 'deviceid="' + data['deviceid'] + '",nonce="' + data['nonce'] + ',sessionid="' + data['sessionid'] + '",response="' + data['response'] + '"'
	return authstr

# Logon to mail server
server = smtplib.SMTP_SSL(data['emailserver'], data['emailserverport'])
server.login(data['emailusername'], data['emailpassword'])
	
# First logon to e-boks
url = "https://" + data['root'] + "/mobile/1/xml.svc/en-gb/session"

content = '<Logon xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns="urn:eboks:mobile:1.0.0"><User identity="' + data['cpr'] + '" identityType="' + data['type'] + '" nationality="' + data['country'] + '" pincode="' + data['password'] + '"/></Logon>'

authstr = 'logon ' + 'deviceid="' + data['deviceid']+ '",' + 'datetime="' + data['datetime'] + '",' + 'challenge="' + data['challenge'] + '"'

headers = {
'Content-Type': 'application/xml',
'Content-Length': str(len(content)),
'X-EBOKS-AUTHENTICATE': authstr,
'Accept': '*/*',
'Accept-Language': 'en-US',
'Accept-Encoding': 'gzip,deflate',
'Host': data['root'],
}

r = requests.put(url, headers=headers, data=content)

authenticate = r.headers['X-EBOKS-AUTHENTICATE']
nonce(authenticate)
sessionid(authenticate)

xml = ET.fromstring(r.text)
# Saves username and user id
data['uname'] = xml[0].attrib['name']
data['uid'] = xml[0].attrib['userId']

# Get folder data from e-boks
url = 'https://' + data['root'] + '/mobile/1/xml.svc/en-gb/' + data['uid'] + '/0/mail/folders'
authstr = createauthstring()

headers = {
'X-EBOKS-AUTHENTICATE': authstr,
'Accept': '*/*',
'Accept-Language': 'en-US',
'Host': data['root'],
}

r = requests.get(url, headers=headers)
authenticate = r.headers['X-EBOKS-AUTHENTICATE']
nonce(authenticate)

xml = ET.fromstring(r.text)

eboks_folders = xml

# Get folder id's and numbers of unread messages
for folder in eboks_folders:
	folderid = folder.attrib['id']
	unread = folder.attrib['unread']
	
	# Get messages ONLY if any unread messages in folder
	if int(unread) > data['unreadmorethan']:		# Usually > 0. Can be changed to == 0 for debugging purposes
		
		# Get list of messages
		url = 'https://' + data['root'] + '/mobile/1/xml.svc/en-gb/' + data['uid'] + '/0/mail/folder/' + folderid

		authstr = createauthstring()

		headers = {
		'X-EBOKS-AUTHENTICATE': authstr,
		'Accept': '*/*',
		'Accept-Language': 'en-US',
		'Host': data['root'],
		}

		params = {
		'skip': '0',
		'take': data['numberofmessagesperfolder']
		}

		r = requests.get(url, headers=headers, params=params)
		authenticate = r.headers['X-EBOKS-AUTHENTICATE']
		nonce(authenticate)		
		
		xml = ET.fromstring(r.text)
		
		eboks_messages = xml
		
		i = 0
		max = int(params['take']) - 1
		
		while i  <= max:
			for child in eboks_messages:
				messageid = child[i].attrib['id']
				subject  = child[i].attrib['name']
				sender = child[i][0].text
				unreadstatus = child[i].attrib['unread']
				attachmentcount = child[i].attrib['attachmentsCount']
				format = child[i].attrib['format'].lower()
				received = child[i].attrib['receivedDateTime']
				
				i += 1
				
				# Get only messages that are unread
				if unreadstatus == data['unreadstatusvalue']:	# Usually true. Can be changed to false for debugging purposes
					
					# Start e-mail
					msg = MIMEMultipart()
					msg['From'] = formataddr((sender, data['emailfrom']))
					msg['To'] = data['emailto']
					msg['Subject'] = "E-boks: " + subject
					body = ""
					
										
					# Get message (marks it as read)
					url = 'https://' + data['root'] + '/mobile/1/xml.svc/en-gb/' + data['uid'] + '/0/mail/folder/' + folderid + '/message/' + messageid

					authstr = createauthstring()

					headers = {
					'X-EBOKS-AUTHENTICATE': authstr,
					'Accept': '*/*',
					'Accept-Language': 'en-US',
					'Host': data['root'],
					}

					r = requests.get(url, headers=headers)
					authenticate = r.headers['X-EBOKS-AUTHENTICATE']
					nonce(authenticate)
					
					# Get primary message content
					url = 'https://' + data['root'] + '/mobile/1/xml.svc/en-gb/' + data['uid'] + '/0/mail/folder/' + folderid + '/message/' + messageid + '/content'

					authstr = createauthstring()

					headers = {
					'X-EBOKS-AUTHENTICATE': authstr,
					'Accept': '*/*',
					'Accept-Language': 'en-US',
					'Host': data['root'],
					}

					r = requests.get(url, headers=headers)
					authenticate = r.headers['X-EBOKS-AUTHENTICATE']
					nonce(authenticate)
					
					# Attach primary message content to e-mail
					if format in ("txt","text","plain"):
						characterset = chardet.detect(r.content)
						r.encoding = characterset['encoding']
						body = r.text
						msg.attach(MIMEText(body, 'plain'))
					elif format in ("html","htm"):
						characterset = chardet.detect(r.content)
						r.encoding = characterset['encoding']
						body = r.text
						msg.attach(MIMEText(body, 'html'))						
					elif format == "pdf":
						filename = "".join([c for c in subject if c.isalpha() or c.isdigit() or c==' ']).rstrip() + "." + format
						part = MIMEApplication(r.content)
						part.add_header('Content-Disposition', 'attachment', filename = filename)
						msg.attach(part)
					elif format in ("gif","jpg","jpeg","tiff","tif","webp"):
						filename = "".join([c for c in subject if c.isalpha() or c.isdigit() or c==' ']).rstrip() + "." + format
						part = MIMEImage(r.content)
						part.add_header('Content-Disposition', 'attachment', filename = filename)
						msg.attach(part)
										
					# Get attachment data if message has attachments
					if int(attachmentcount) > 0:

						url = 'https://' + data['root'] + '/mobile/1/xml.svc/en-gb/' + data['uid'] + '/0/mail/folder/' + folderid + '/message/' + messageid

						authstr = createauthstring()

						headers = {
						'X-EBOKS-AUTHENTICATE': authstr,
						'Accept': '*/*',
						'Accept-Language': 'en-US',
						'Host': data['root'],
						}

						r = requests.get(url, headers=headers)
						authenticate = r.headers['X-EBOKS-AUTHENTICATE']
						nonce(authenticate)

						xml = ET.fromstring(r.text)
						
						eboks_attachment = xml
						
						# Gets if, name and format of attachment
						for child in eboks_attachment:
							for subtree in child:
								attachmentid = subtree.attrib['id']
								attachmenttitle = subtree.attrib['name']
								attachmentformat = subtree.attrib['format']
				
								# Gets the actual attachment
								url = 'https://' + data['root'] + '/mobile/1/xml.svc/en-gb/' + data['uid'] + '/0/mail/folder/' + folderid + '/message/' + attachmentid + '/content'
									
								authstr = createauthstring()

								headers = {
								'X-EBOKS-AUTHENTICATE': authstr,
								'Accept': '*/*',
								'Accept-Language': 'en-US',
								'Host': data['root'],
								}

								r = requests.get(url, headers=headers)
								authenticate = r.headers['X-EBOKS-AUTHENTICATE']
								nonce(authenticate)
								
								# Attach attachment to e-mail
								if attachmentformat in ("txt","text","html","htm","plain"):
									filename = "".join([c for c in attachmenttitle if c.isalpha() or c.isdigit() or c==' ']).rstrip() + "." + attachmentformat
									r.encoding = "utf-8"
									part = MIMEText(r.text)
									part.add_header('Content-Disposition', 'attachment', filename = filename)
									msg.attach(part)
								elif attachmentformat == "pdf":
									filename = "".join([c for c in attachmenttitle if c.isalpha() or c.isdigit() or c==' ']).rstrip() + "." + attachmentformat
									part = MIMEApplication(r.content)
									part.add_header('Content-Disposition', 'attachment', filename = filename)
									msg.attach(part)
								elif attachmentformat in ("gif","jpg","jpeg","tiff","tif","webp"):
									filename = "".join([c for c in attachmenttitle if c.isalpha() or c.isdigit() or c==' ']).rstrip() + "." + attachmentformat
									part = MIMEImage(r.content)
									part.add_header('Content-Disposition', 'attachment', filename = filename)
									msg.attach(part)

					# Send e-mail
					if data['sendemails'] == True:
						print("sending")
						msg.attach(MIMEText(body, 'plain'))
						server.sendmail(data['emailfrom'], data['emailto'], msg.as_string())
						time.sleep(2)

Sådan trækker du dine transaktioner ud fra Nordnet med Python

OPDATERING: Nordnet er ude i en ny version, som gør at man foreløbig er nødt til at ændre URLs i programmet til “classic.nordnet.dk” for at bruge den gamle version. På et tidspunkt virker det nok heller ikke længere. Jeg har opdateret koden neden for med den korrekte url.

OPDATERING: Find en opdateret udgave af programmet, der virker med det nye Nordnet, her.

Jeg kan godt lide at bruge Excel til at holde øje med min økonomi, så jeg har et ark med en pivottabel, som jeg bruger til at få overblik over min portefølje hos Nordnet. Nordnet har en funktion til at trække en CSV-fil med en oversigt over mine transaktioner, men det er lidt besværligt at skulle a) logge ind på Nordnet for derefter b) at gå ind på hver enkelt depot og c) trække en ny oversigt og copy/paste hver gang, der fx udbetales udbytte.

Her klikker du hos Nordnet for at generere en CSV-fil, du kan bruge til Excel

Derfor tænkte jeg: Kan jeg automatisere dette udtræk, sådan jeg altid har opdateret data i mit Excelark? Ja, det kan jeg. Med Python. Her fortæller jeg om hvordan og deler min kode.

Hvordan snakker man http med Nordnet?

Det første jeg gjorde, var at undersøge hvad der egentlig sker, når jeg beder Nordnet om en transaktionsfil. Det gør jeg i Chrome ved at trykke F12, vælge Network og undersøge hvad min browser sender af sted for at få en CSV-fil tilbage. Jeg kan se, at der ryger en cookie af sted og nogle parametre, der handler om bl.a. sortering og periode for de transaktioner, jeg vil have ud:

I Python bruger jeg modulet Requests til at snakke med Nordnet og forsøger at konstruere noget der ligner det, min browser smider af sted. Efter at have prøvet mig frem, finder jeg ud af, at det er den cookie, der hedder NOW, der er afgørende for, at modtage noget fra Nordnet. Jeg laver en cookie-ordbog, der foreløbig indeholder min NOW-værdi fra Chrome:

cookies = {'NOW': '63261fc324153bd1632006105c5b4444d97fc72a'}

Min forespørgsel, der giver mig data tilbage, ser sådan ud:

payload = {
'year': 'all',
'month': 'all',
'trtyp': 'all',
'vp': 'all',
'curr': 'all',
'sorteringsordning': 'fallande',
'sortera': 'datum',
'startperiod': startdate,
'endperiod': enddate
}
url = "https://www.nordnet.dk/mux/laddaner/transaktionsfil.html"
data = requests.get(url, params=payload, cookies=cookies)
result = data.text

Så langt, så godt.

Men da jeg logger ud af Nordnet, virker min forespørgsel ikke længere. For at få en gyldig cookie, er jeg altså nødt til at logge ind på Nordnet.

Tilbage i Chrome kigger jeg på, hvad der sendes af sted, og modtages, ved indlogningsproceduren. Indlogningsproceduren foregår i 3 trin:

  1. https://www.nordnet.dk/mux/login/start.html?cmpi=start-loggain&state=signin sættes to cookies: LOL og TUX-COOKIE.
  2. Når brugeren har indtastet brugernavn og password, forespørges https://www.nordnet.dk/api/2/login/anonymous og returnerer en cookie: NOW.
  3. Til sidst sendes de 3 cookie-værdier sammen med brugernavn og password til https://www.nordnet.dk/api/2/authentication/basic/login. Der returneres en ny NOW-værdi og en anden cookie, der hedder xrsf.

Login på siden ser sådan her ud:

# LOGIN TO NORDNET #
	
# First part of cookie setting prior to login
url = 'https://www.nordnet.dk/mux/login/start.html?cmpi=start-loggain&state=signin'
r = requests.get(url)

cookies['LOL'] = r.cookies['LOL']
cookies['TUX-COOKIE'] = r.cookies['TUX-COOKIE']

# Second part of cookie setting prior to login
url = 'https://www.nordnet.dk/api/2/login/anonymous'
r = requests.post(url, cookies=cookies)
cookies['NOW'] = r.cookies['NOW']

# Actual login that gets us cookies required for primary account extraction
url = "https://www.nordnet.dk/api/2/authentication/basic/login"

r = requests.post(url,cookies=cookies, data = {'username': user, 'password': password})

cookies['NOW'] = r.cookies['NOW']
cookies['xsrf'] = r.cookies['xsrf']

Når denne procedure er gennemført, kan jeg med den genererede NOW-værdi trække transaktioner ud af mit primære depot (det jeg oprettede først, da jeg fik en Nordnet-konto).

For at trække transaktioner ud fra andre depoter, undersøger jeg hvad der sker, når jeg vælger et andet depot i Nordnet. Det sker ved at forespørge https://www.nordnet.dk/mux/ajax/session/bytdepa.html med mine gemte cookies og værdien fra den cookie, der hedder xrsf i headeren. Tilbage får jeg en ny NOW-værdi, som jeg kan bruge til at hente transaktioner på det andet depot, og en ny xrsf-værdi, som jeg kan bruge, hvis jeg har endnu flere depoter, jeg får brug for at skifte til:

		# Switch to secondary account and set new cookies
		url = 'https://www.nordnet.dk/mux/ajax/session/bytdepa.html'
		headers = {'X-XSRF-TOKEN': cookies['xsrf']}	
		
		r = requests.post(url,cookies=cookies, headers=headers, data = {'portfolio': item['id']})

		cookies['NOW'] = r.cookies['NOW']
		cookies['xsrf'] = r.cookies['xsrf']

Til sidst skal jeg finde en fornuftig struktur for mit program, finde ud af hvordan jeg får lavet en god struktur i min CSV-fil (nogle gange returnerer Nordnet en CSV-fil med en kolonne for meget). Og så har jeg brug for en mulighed for at tilføje manuelle linjer til min CSV-fil (fordi jeg gerne vil have historisk data med fra et gammelt depot hos en anden bank).

Det færdige program i Python

Her er det færdige program. Du er velkommen til at bruge det, videreudvikle, osv.

<pre class="wp-block-syntaxhighlighter-code"><p># -*- coding: utf-8 -*-
# Author: Morten Helmstedt. E-mail: helmstedt@gmail.com
""" This program logs into a Nordnet account and extracts transactions as a csv file.
Handy for exporting to Excel with as few manual steps as possible """

import requests 
from datetime import datetime
from datetime import date
import os

# USER ACCOUNT, PORTFOLIO AND PERIOD DATA. SHOULD BE EDITED FOR YOUR NEEDS #

# Nordnet user account credentials and name of primary portfolio (first one listed in Nordnet)
user = ''
password = ''
primaryportfolioname = "Frie midler"

# Names and portfolio ids for all any all secondary portfolios. The id is listed in 
# Nordnet when selecting a portfolio. If no secondary portfolios the variable
# secondaryportfolioexists should be set to False.
secondaryportfolioexists = True
secondaryportfolios = [
{'name': 'Ratepension', 'id': ''},
]

# Start date (start of period for transactions) and date today used for extraction of transactions
startdate = '01.01.2013'
today = date.today()
enddate = datetime.strftime(today, '%d.%m.%Y')


# Manual date lines. These can be used if you have portfolios elsewhere that you would
# like to add manually to the data set. If no manual data the variable manualdataexists
# should be set to False
manualdataexists = True
manualdata = """
Id;Bogføringsdag;Handelsdag;Valørdag;Transaktionstype;Værdipapirer;Instrumenttyp;ISIN;Antal;Kurs;Rente;Afgifter;Beløb;Valuta;Indkøbsværdi;Resultat;Totalt antal;Saldo;Vekslingskurs;Transaktionstekst;Makuleringsdato;Verifikations-/Notanummer;Depot
;30-09-2013;30-09-2013;30-09-2013;KØBT;Obligationer 3,5%;Obligationer;;72000;;;;-69.891,54;DKK;;;;;;;;;Frie midler
"""


# CREATE OUTPUT FOLDER AND VARIABLES FOR LATER USE. #

# Checking that we have an output folder to save our csv file
if not os.path.exists("./output"):
	os.makedirs("./output")

# Creates a dictionary to use with cookies	
cookies = {}

# A variable to store transactions before saving to csv
transactions = ""

# Payload for transaction requests
payload = {
'year': 'all',
'month': 'all',
'trtyp': 'all',
'vp': 'all',
'curr': 'all',
'sorteringsordning': 'fallande',
'sortera': 'datum',
'startperiod': startdate,
'endperiod': enddate
}

# LOGIN TO NORDNET #
	
# First part of cookie setting prior to login
url = 'https://classic.nordnet.dk/mux/login/start.html?cmpi=start-loggain&state=signin'
r = requests.get(url)

cookies['LOL'] = r.cookies['LOL']
cookies['TUX-COOKIE'] = r.cookies['TUX-COOKIE']

# Second part of cookie setting prior to login
url = 'https://classic.nordnet.dk/api/2/login/anonymous'
r = requests.post(url, cookies=cookies)
cookies['NOW'] = r.cookies['NOW']

# Actual login that gets us cookies required for primary account extraction
url = "https://classic.nordnet.dk/api/2/authentication/basic/login"

r = requests.post(url,cookies=cookies, data = {'username': user, 'password': password})

cookies['NOW'] = r.cookies['NOW']
cookies['xsrf'] = r.cookies['xsrf']


# GET PRIMARY ACCOUNT TRANSACTION DATA #

# Get CSV for primary account
url = "https://classic.nordnet.dk/mux/laddaner/transaktionsfil.html"
data = requests.get(url, params=payload, cookies=cookies)
result = data.text
result = result.splitlines()
firstline = 0

for line in result:
	if line and firstline == 0:
		transactions += line + ';' + "Depot" + "\n"
		firstline = 1
	elif line:
		# Sometimes Nordnet inserts one semicolon too many in the file. This removes the additional semicolon
		if line.count(';') == 22:
			position = line.rfind(';')
			line = line [:position] + line[position+1:]
		transactions += line + ';' + primaryportfolioname + "\n"

		
# GET TRANSACTION DATA FOR ALL/ANY SECONDARY ACCOUNTS #

if secondaryportfolioexists == True:
	for item in secondaryportfolios:
		# Switch to secondary account and set new cookies
		url = 'https://classic.nordnet.dk/mux/ajax/session/bytdepa.html'
		headers = {'X-XSRF-TOKEN': cookies['xsrf']}	
		
		r = requests.post(url,cookies=cookies, headers=headers, data = {'portfolio': item['id']})

		cookies['NOW'] = r.cookies['NOW']
		cookies['xsrf'] = r.cookies['xsrf']

		# Get CSV for secondary account
		url = "https://classic.nordnet.dk/mux/laddaner/transaktionsfil.html"
		data = requests.get(url, params=payload, cookies=cookies)
		result = data.text
		result = result.split("\n",1)[1]
		result = result.splitlines()

		for line in result:
			if line:
				# Sometimes Nordnet inserts one semicolon too many in the file. This removes the additional semicolon
				if line.count(';') == 22:
					position = line.rfind(';')
					line = line [:position] + line[position+1:]
				transactions += line + ';' + item['name'] + "\n"


if manualdataexists == True:
	manualdata = manualdata.split("\n",2)[2]
	transactions += manualdata				


# WRITE CSV OUTPUT TO FILE #
		
with open("./output/trans.csv", "w", encoding='utf8') as fout:
	fout.write(transactions)</p></pre>