Created
June 22, 2019 01:11
-
-
Save wadefletch/6f8e6c87e47f78be04ace5a980e8335f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import json | |
import click | |
from pathlib import Path | |
import os.path | |
import requests | |
from bs4 import BeautifulSoup | |
class Zacks(object): | |
def __init__(self, username, password): | |
self.username = username | |
self.password = password | |
self.s = self._build_session() | |
# initiates session and establishes necessary cookies | |
self.s.get('https://www.zacks.com') | |
def _build_session(self): | |
headers = requests.utils.default_headers() | |
headers[ | |
'User-Agent'] = 'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/56.0.2924.87 Safari/537.36' | |
s = requests.Session() | |
s.headers = headers | |
return s | |
def authenticate(self): | |
payload = {'force_login': True, | |
'username': self.username, | |
'password': self.password, | |
'remember_me': 'on'} | |
r = self.s.post('https://www.zacks.com', data=payload) | |
with open('failed_login.html', 'w') as f: | |
f.write(r.text) | |
soup = BeautifulSoup(r.content, 'html.parser') | |
if soup.findAll(text=["* Username or password invalid! Please try again! *", | |
"You have exceeded the number of allowed login attempts"]): | |
return False | |
else: | |
return True | |
@property | |
def portfolio_list(self): | |
r = self.s.get('https://www.zacks.com/portfolios/my-stock-portfolio/') | |
soup = BeautifulSoup(r.content, 'html.parser') | |
portfolio_select = soup.find("select", {"id": "port_id"}) | |
portfolios = [(option.text, option['value']) for option in portfolio_select.findAll('option')] | |
return portfolios | |
def _load_portfolio(self, portfolio_id): | |
self.s.get('https://www.zacks.com/portfolios/my-stock-portfolio/') | |
params = { | |
'action': 'retrieve', | |
'portfolios_id': portfolio_id, | |
'view_type': 'update' | |
} | |
r = self.s.get('https://www.zacks.com/portfolios/my-stock-portfolio/portfolio_manager.php', params=params) | |
return r | |
def export_portfolio(self, portfolio_id, filename): | |
portfolio_data = self._load_portfolio(portfolio_id) | |
headers = { | |
'accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3', | |
'content-type': 'application/x-www-form-urlencoded', | |
'upgrade-insecure-requests': '1', | |
} | |
payload = { | |
'export_data_init_tab': json.dumps(portfolio_data.json(), separators=(',', ':')), | |
'export_data_rest_tab': '', | |
'XLS_FILE': 'Excelsis' | |
} | |
portfolio_csv = self.s.post('https://www.zacks.com/portfolios/tools/ajxExportExel.php', data=payload, | |
headers=headers) | |
with open(filename, mode='w') as f: | |
f.write(portfolio_csv.text) | |
@click.group() | |
@click.pass_context | |
def cli(ctx): | |
# ensure that ctx.obj exists and is a dict (in case `cli()` is called | |
# by means other than the `if` block below | |
ctx.ensure_object(dict) | |
login_file = os.path.join(str(Path.home()), '.zacks') | |
if os.path.isfile(login_file): | |
with open(login_file, 'r') as f: | |
login_details = f.readlines() | |
username = login_details[0].strip() | |
password = login_details[1].strip() | |
else: | |
click.echo('Saved login data not found.') | |
username = click.prompt('Zacks Username', type=str) | |
password = click.prompt('Zacks Password', type=str) | |
z = Zacks(username, password) | |
if z.authenticate(): | |
click.echo('Logged in user: '+click.style(username, fg='magenta')) | |
if not os.path.isfile(login_file): | |
click.echo('Saving login details in ~/.zacks') | |
with open(login_file, 'w') as f: | |
f.write('\n'.join([username, password])) | |
ctx.obj['zacks'] = z | |
else: | |
click.secho('\tUsername and password combination not valid. Exiting...', fg='red') | |
ctx.exit() | |
@cli.command() | |
@click.pass_context | |
def list(ctx): | |
click.echo(click.style('\nPortfolios for user: ', bold=True)+click.style(ctx.obj['zacks'].username+'\n', fg='magenta')) | |
for p in ctx.obj['zacks'].portfolio_list: | |
click.echo(' • '+click.style(p[1], fg='cyan')+' '+p[0]) | |
click.echo('') | |
@cli.command() | |
@click.argument('portfolio') | |
@click.pass_context | |
def export(portfolio): | |
click.echo('export registered: '+portfolio) | |
if __name__ in '__main__': | |
cli(obj={}) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment