├── README.md ├── examples └── list-xp-notas.py ├── ibapi ├── __init__.py └── xp.py └── setup.py /README.md: -------------------------------------------------------------------------------- 1 | Internet Banking API 2 | ==================== 3 | 4 | Este módulo de python provê uma API para alguns *Internet Bankings* 5 | brasileiros. 6 | 7 | # Instalação 8 | 9 | Para instalar faça o clone deste repositório e execute o comando abaixo 10 | na pasta do projeto: 11 | 12 | pip install . 13 | 14 | # Exêmplos de uso 15 | 16 | ``` python 17 | from ibapi import xp 18 | from getpass import getpass 19 | 20 | account = input('Account: ') 21 | passwd = getpass() 22 | with xp.Session(account, passwd) as sess: 23 | sess.start() 24 | dates = sess.list_notas() 25 | for date in dates: 26 | print(sess.get_nota(date)) 27 | ``` 28 | 29 | # Objetivos 30 | 31 | ## XP Investimentos 32 | 33 | - [x] Sessão de login 34 | - [x] Listar datas em que houveram negociações no *homebroker* 35 | - [x] Recuperar notas de corretagem em JSON 36 | - [ ] Recuperar extratos da conta corrente 37 | 38 | ## Santander 39 | 40 | - [ ] Sessão de login 41 | - [ ] Recuperar extratos da conta corrente 42 | - [ ] Recuperar extratos da conta poupança 43 | - [ ] Recuperar faturas de cartão de crédito 44 | 45 | 47 | -------------------------------------------------------------------------------- /examples/list-xp-notas.py: -------------------------------------------------------------------------------- 1 | from ibapi import xp 2 | from getpass import getpass 3 | 4 | def print_passwd_menu(numbers): 5 | print('Password buttons:') 6 | for i, num in enumerate(numbers): 7 | print(' {}. {}'.format(i + 1, num)) 8 | 9 | def passwd_cb(numbers): 10 | order = [] 11 | for i in range(6): 12 | print_passwd_menu(numbers) 13 | k = int(input('Choice: ')) - 1 14 | order.append(k) 15 | print(order) 16 | return order 17 | 18 | def main(): 19 | account = input('Account: ') 20 | #passwd = getpass() 21 | #with xp.Session(account, passwd) as sess: 22 | with xp.Session(account, passwd_cb=passwd_cb) as sess: 23 | sess.start() 24 | dates = sess.list_notas() 25 | for date in dates: 26 | print(sess.get_nota(date)) 27 | 28 | if __name__ == '__main__': 29 | main() 30 | -------------------------------------------------------------------------------- /ibapi/__init__.py: -------------------------------------------------------------------------------- 1 | from . import xp 2 | #from . import santander 3 | 4 | import logging 5 | 6 | formatter = logging.Formatter('%(asctime)s %(levelname)s %(message)s') 7 | handler = logging.StreamHandler() 8 | handler.setFormatter(formatter) 9 | logger = logging.getLogger('ibapi') 10 | logger.setLevel(logging.INFO) 11 | logger.addHandler(handler) 12 | 13 | all = [xp] 14 | -------------------------------------------------------------------------------- /ibapi/xp.py: -------------------------------------------------------------------------------- 1 | import os 2 | import time 3 | import logging 4 | import requests 5 | from datetime import date 6 | from selenium import webdriver 7 | from selenium.webdriver.common.by import By 8 | from selenium.webdriver.common.keys import Keys 9 | from selenium.webdriver.support import expected_conditions as EC 10 | from selenium.webdriver.support.ui import Select 11 | from selenium.webdriver.support.ui import WebDriverWait 12 | from lxml import etree 13 | 14 | 15 | default_headers = { 16 | 'Accept-Language': 'en-US,en;q=0.8,pt-BR;q=0.6,pt;q=0.4', 17 | 'Connection': 'keep-alive', 18 | 'Host': 'portal.xpi.com.br', 19 | 'User-Agent': 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.81 Safari/537.36', 20 | } 21 | 22 | 23 | logger = logging.getLogger('ibapi') 24 | 25 | class Session(object): 26 | def __init__(self, account, passwd=None, passwd_cb=None, url=None): 27 | ''' 28 | account: the XP login number 29 | pwmethod: the authentication method (either 'plain', or 'shuffle') 30 | 31 | When authenticating on XP website, one has to press the button 32 | containing the digits of its password in the correct order. There are a 33 | total of 5 buttons containing 3 digits each. 34 | ''' 35 | self.account = account 36 | self.driver = None 37 | self.passwd = passwd 38 | self.passwd_cb = passwd_cb 39 | self.url = url or 'https://portal.xpi.com.br' 40 | 41 | def __enter__(self): 42 | return self 43 | 44 | def start(self): 45 | logger.info('starting XP session for account {}'.format(self.account)) 46 | self.driver = webdriver.PhantomJS(service_log_path=os.path.devnull) 47 | logger.info('navigating to {}'.format(self.url)) 48 | self.driver.get(self.url) 49 | el = self.get('txtLogin') 50 | el.clear() 51 | el.send_keys(self.account) 52 | el = self.get('btnOkLogin') 53 | el.click() 54 | el = self.get('lnkNomeUsuario', timeout=120) 55 | el.click() 56 | logger.info('feeding password') 57 | ids = ['btnPass0', 'btnPass1', 'btnPass2', 'btnPass3', 'btnPass4'] 58 | btns = [self.get(id) for id in ids] 59 | numbers = self._parse_numbers([b.text for b in btns]) 60 | logger.info('XP password buttons are {}'.format(' '.join(numbers))) 61 | if self.passwd_cb: 62 | order = self.passwd_cb(numbers) 63 | else: 64 | order = [] 65 | for x in self.passwd: 66 | for i, num in enumerate(numbers): 67 | if x in num: 68 | order.append(i) 69 | break 70 | for i in order: 71 | btns[i].click() 72 | time.sleep(0.5) 73 | el = self.get('btnEntrar') 74 | el.click() 75 | el = self.get('cphMinhaConta_wcDefault1_wcLateralInfo1_lblNomeCliente') 76 | logger.info('logging successful, welcome {}'.format(el.text)) 77 | 78 | def list_notas(self): 79 | headers = default_headers.copy() 80 | headers.update({ 81 | 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,*/*;q=0.8', 82 | 'Accept-Encoding': 'gzip, deflate, sdch, br', 83 | 'Referer': 'https://portal.xpi.com.br/acoes-opcoes-futuros/posicao-geral', 84 | 'Upgrade-Insecure-Requests': '1', 85 | }) 86 | cookies = self.get_cookies() 87 | url = 'https://portal.xpi.com.br/acoes-opcoes-futuros/notas-corretagem' 88 | req = requests.get(url, cookies=cookies, headers=headers) 89 | parser = etree.HTMLParser() 90 | tree = etree.fromstring(req.text, parser) 91 | return [date(*map(int, reversed(x.split('/')))) 92 | for x in tree.xpath('//*[@id="Data"]/option/text()')] 93 | 94 | def get_nota(self, date): 95 | date_str = date.strftime('%d/%m/%Y') 96 | headers = default_headers.copy() 97 | headers.update({ 98 | 'Accept': '*/*', 99 | 'Accept-Encoding': 'gzip, deflate, br', 100 | 'Content-Length': '51', 101 | 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8', 102 | 'Origin': 'https://portal.xpi.com.br', 103 | 'Referer': 'https://portal.xpi.com.br/acoes-opcoes-futuros/notas-corretagem', 104 | 'X-Requested-With': 'XMLHttpRequest', 105 | }) 106 | data = { 107 | 'Data': date_str, 108 | 'X-Requested-With': 'XMLHttpRequest', 109 | } 110 | xpnotaurl = 'https://portal.xpi.com.br/acoes-opcoes-futuros/NotasCorretagem/ExibirExtrato' 111 | cookies = self.get_cookies() 112 | req = requests.post(xpnotaurl, 113 | data=data, 114 | cookies=cookies, 115 | headers=headers) 116 | return req.json() 117 | 118 | def get_cookies(self): 119 | return {c['name']: c['value'] for c in self.driver.get_cookies()} 120 | 121 | def __exit__(self, e_typ, e_val, trcbak): 122 | if self.driver: 123 | self.driver.close() 124 | 125 | def _parse_numbers(self, txts): 126 | return [''.join(txt.split(' ou ')) for txt in txts] 127 | 128 | def get(self, id, timeout=60, by=By.ID): 129 | return WebDriverWait(self.driver, timeout).until( 130 | EC.presence_of_element_located((by, id))) 131 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | 3 | from distutils.core import setup 4 | 5 | setup( 6 | name='ibapi', 7 | version='0.1.0', 8 | description='Python library for interfacing with internet banking websites', 9 | packages=['ibapi'], 10 | install_requires=[ 11 | 'requests', 12 | 'selenium', 13 | 'lxml', 14 | ], 15 | ) 16 | --------------------------------------------------------------------------------