Skip to content

Instantly share code, notes, and snippets.

@wadefletch
Created June 22, 2019 01:11
Show Gist options
  • Save wadefletch/6f8e6c87e47f78be04ace5a980e8335f to your computer and use it in GitHub Desktop.
Save wadefletch/6f8e6c87e47f78be04ace5a980e8335f to your computer and use it in GitHub Desktop.
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