|
|
|
#!/usr/bin/env python3
|
|
|
|
# stockquotes - Python module to pull stock quotes from Yahoo! Finance
|
|
|
|
#
|
|
|
|
# Copyright 2020-2023 Samuel Sloniker
|
|
|
|
#
|
|
|
|
# Permission to use, copy, modify, and/or distribute this software for any
|
|
|
|
# purpose with or without fee is hereby granted.
|
|
|
|
#
|
|
|
|
# THE SOFTWARE IS PROVIDED “AS IS” AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
|
|
# REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
|
|
# AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
|
|
# INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
|
|
# LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
|
# OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
|
|
# PERFORMANCE OF THIS SOFTWARE.
|
|
|
|
|
|
|
|
from bs4 import BeautifulSoup as bs
|
|
|
|
import requests
|
|
|
|
import datetime
|
|
|
|
|
|
|
|
|
|
|
|
class StockDoesNotExistError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class NetworkError(Exception):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Stock:
|
|
|
|
def __init__(self, ticker):
|
|
|
|
try:
|
|
|
|
r = requests.get(
|
|
|
|
f"https://finance.yahoo.com/quote/{ticker}/history",
|
|
|
|
headers={
|
|
|
|
"User-Agent": "Mozilla/5.0 (X11; Linux x86_64; rv:92.0) Gecko/20100101 Firefox/92.0"
|
|
|
|
},
|
|
|
|
)
|
|
|
|
except:
|
|
|
|
raise NetworkError()
|
|
|
|
if r.status_code == 302:
|
|
|
|
raise StockDoesNotExistError(ticker)
|
|
|
|
try:
|
|
|
|
soup = bs(r.text, features="lxml")
|
|
|
|
self.symbol = soup.h1.string.split("(")[1].split(")")[0]
|
|
|
|
self.name = soup.h1.string.split("(")[0].strip()
|
|
|
|
rows = soup.table.tbody.find_all("tr")
|
|
|
|
self.historical = []
|
|
|
|
for i in rows:
|
|
|
|
row = i.find_all("td")
|
|
|
|
try:
|
|
|
|
parsed = {
|
|
|
|
"date": datetime.datetime.strptime(
|
|
|
|
row[0].span.string, "%b %d, %Y"
|
|
|
|
),
|
|
|
|
"open": float(row[1].span.string.replace(",", "")),
|
|
|
|
"high": float(row[2].span.string.replace(",", "")),
|
|
|
|
"low": float(row[3].span.string.replace(",", "")),
|
|
|
|
"close": float(row[4].span.string.replace(",", "")),
|
|
|
|
"adjusted_close": float(
|
|
|
|
row[5].span.string.replace(",", "")
|
|
|
|
),
|
|
|
|
"volume": int(row[6].span.string.replace(",", ""))
|
|
|
|
if row[6].string != "-"
|
|
|
|
else None,
|
|
|
|
}
|
|
|
|
except:
|
|
|
|
continue
|
|
|
|
|
|
|
|
self.historical.append(parsed)
|
|
|
|
|
|
|
|
price_selector = f'fin-streamer[data-field="regularMarketPrice"][data-symbol="{self.symbol}"]'
|
|
|
|
price_element = soup.select_one(price_selector)
|
|
|
|
self.current_price = float(price_element.text)
|
|
|
|
|
|
|
|
change_selector = f'fin-streamer[data-field="regularMarketChange"][data-symbol="{self.symbol}"]'
|
|
|
|
change_element = soup.select_one(change_selector)
|
|
|
|
self.increase_dollars = float(change_element.text)
|
|
|
|
|
|
|
|
change_percent_selector = f'fin-streamer[data-field="regularMarketChangePercent"][data-symbol="{self.symbol}"]'
|
|
|
|
change_percent_element = soup.select_one(change_percent_selector)
|
|
|
|
change_percent_text = "".join(
|
|
|
|
[
|
|
|
|
char
|
|
|
|
for char in change_percent_element.text
|
|
|
|
if char in "-.0123456789"
|
|
|
|
]
|
|
|
|
)
|
|
|
|
self.increase_percent = float(change_percent_text)
|
|
|
|
except AttributeError as error:
|
|
|
|
raise StockDoesNotExistError(ticker) from error
|