Забираем данные по ценным бумагам с finance.yahoo.com
Простой способ на Python.
Продолжаю рассматривать способы получения данных по бумагам в свой скрипт.
Из предыдущего поста где я рассказывал как можно просто буквально распарсить поисковую выдачу в гугле и вытащить текущие показатели цены я узнал по комментариям уважаемых резидентов смартлаба, что этот способ не будет хорошим решением, в силу особенности использования
html тэгов и атрибутов таких как
id класса. В конечном итоге
id поменяется и скрипт работать не будет. Лучше посмотреть в сторону чего то более долгоиграющего.
На этот раз я хочу сделать свой скрипт более универсальным. Он должен забирать данные по скормленному ему списку или словарю вот такого вида:
ticker_list = {'gazp': 'GAZP.ME',
'sber': 'SBER.ME',
'tatn': 'TATN.ME',
'moex': 'MOEX.ME',
'rosn': 'ROSN.ME',
'lkoh': 'LKOH.ME',
'yndx': 'YNDX.ME',
'nlmk': 'NLMK.ME',
'alrs': 'ALRS.ME',
'rual': 'RUAL.ME',
'magn': 'MAGN.ME'}
и
в результате своей работы скрипт должен выдавать значения, например: цена, процент изменения и объем.
Для красоты и отладки можно также вывести это всё в красивую табличку.
Я решил создать класс
Ticker отдельно и использовать его в разных скриптах, когда мне это потребуется.
Через этот класс мы реализуем создание всех отдельно запрашиваемых тикеров.
Скрипт будет состоять из двух частей.
yahooparser.py который содержит в себе класс, и
main.py, в котором я просто продемонстрирую что можно с этим классом делать.
Данный пример, о котором дальше пойдет речь, в
исходниках можно забрать ТУТ
Воспользуемся ресурсом
finance.yahoo.com и будем получать интересующие нас данные в формате
JSON. для удобства работы с JSON информацией сразу рекомендую установить расширение для браузера
JSON-handle. Это облегчит вам жизнь в поиске нужной информации.
Разберем запрос вида :
https://query2.finance.yahoo.com/v10/finance/quoteSummary/GAZP.ME?modules=price
Если установлен плагин, в браузере мы получим вот такой красивый отформатированный древовидный
JSON.
Тут мы видим в теле запроса тикер
GAZP.ME, который мы в дальнейшем можем заменить в новом запросе на любой другой и получить точно такую же информацию по данному инструменту. При этом структура запроса остается неизменной. Это как раз то, что нам нужно.
Просто меняем
GAZP.ME на
SBER.ME и получаем всю информацию по Сбербанку.
Теперь подумаем, как это можно отсюда забрать.
Я буду использовать способ из предыдущего примера и воспользуюсь библиотекой
requests для формирования
URL запроса.
Если она еще не установлена, в консоли пишем
pip install requests и устанавливаем.
Также сразу можно установить библиотеку
dpath, которая поможет нам работать с полученным
JSON, а точнее мы будем использовать её для того чтобы более удобно передвигаться внутри словарей и доставать нужные строчки без особых усилий. Аналогичным образом устанавливаем через
pip
Теперь переходим к коду.
Создадим отдельный файл, в моем примере это
yahooparser.py подключаем нужные библиотеки вначале
import requests
from dpath.util import values as path_val
Создаем класс
Ticker и внутри него сразу опишем переменные для
URL запроса
class Ticker:
# -------- URL запрос будет через открытие сессии
session = requests.session()
# -------- заголовки для URL запроса
headers = 'Mozilla/5.0 ' \
'(Windows NT 10.0; WOW64) ' \
'AppleWebKit/537.36' \
' (KHTML, like Gecko) ' \
'Chrome/91.0.4472.135 ' \
'Safari/537.36'
В отличии от моего
предыдущего парсера теперь мы устанавливаем сессию через
requests.session(), иначе ничего работать не будет. Также в запрос мы добавим заголовки. Сохраним их предварительно в переменную
headers. Ссылку с запросом мы пока объявлять не будем, а добавим её в метод, который опишем чуть позже.
Посмотрим в полученный выше
JSON в браузере и определим какие нам нужно вытащить ключи.
Я возьму оттуда
regularMarketPrice - текущая цена,
regularMarketChange - значениеизменения цены,
regularMarketChangePercent — значение изменения цены, выраженное в процентах
regularMarketVolume — текущие объемы
нам нужны ключи
raw, точнее значения по ним,
каждого из этих элементов, именно для быстрого доступа к ним, мы будем использовать
dpath в дальнейшем
Следующим шагом объявим словарь (все также внутри класса), который будет содержать в себе те поля, которые мы будем вытаскивать из полученного
JSON.
Этот список вы можете редактировать на свое усмотрение и вытаскивать именно то, что нужно по вашим задачам.
# -------- Запрашиваемые поля
value = {'price': 'regularMarketPrice',
'percent': 'regularMarketChangePercent',
'change': 'regularMarketChange',
'volume': 'regularMarketVolume'}
С полями определились, теперь приступим к описанию конструктора. Создадим эквивалентные поля, тем, которые мы собираемся получать в каждый экземпляр класса.
# -------- конструктор
def __init__(self, name):
# -------- определим первоначальные значения
self.name = name
self.price = 0.00
self.change = 0.00
self.percent = 0.0
self.volume = 0.0
Также я описал переменную
self.name значение которой экземпляр получит при его объявлении.
Например
gazp = Ticker('GAZP.ME') создаст экземпляр класса
gazp с полем
name, значение этого поля будет
GAZP.ME и
его мы будем передавать в
URL запрос, добавляя его в ссылку запроса.
Следующим шагом опишем метод
__get_update(self) который будет обслуживать
URL запросы, получать
JSON, обрабатывать его, и возвращать все искомые значения
по итогу своей работы. Здесь разместим ссылку для
URL запроса.
Переменная
link содержит ссылку запроса:
query2.finance.yahoo.com/v10/finance/quoteSummary/{self.name}?modules=price"
def __get_update(self):
"""
Отправляет URL запрос, получает JSON
возвращает список со значениями float
"""
# -------- ссылка для URL запроса
link = f"https://query2.finance.yahoo.com/v10/" \
f"finance/quoteSummary/{self.name}?modules=price"
Всё готово для того, чтобы отправить
URL запрос. используем
session.get() передаем ей ссылку и заголовки.
Ответ запишем в
response
# -------- отправляем запрос и получаем результат в response
response = self.session.get(link, headers={'User-Agent': self.headers})
response теперь содержит в себе полученный
массив данных. Для того чтобы с этим можно было работать запишем его в виде словаря
array = response.json()
Данные получены, осталось только вытащить те поля, которые нам нужны. Объявим список
return_value для наполнения его искомыми значениями.
В цикле
for мы прогоним словарь
self.value объявленный вначале класса, содержащий в себе те поля
, которые мы будем искать в
JSON.
Для каждого ключа из этого словаря мы будем добавлять в список
return_value значение, полученное в ходе работы функции
values из
dpath.util импорт которой мы определили в самом начале как
path_val. Функция принимает наш массив данных, который сейчас лежит в
array и возвращает нам каждый проход
self.value[key], то есть значение из словаря по индексу ключа
. Это значение содержит искомое поле
например, цикл доходит до
{'percent': 'regularMarketChangePercent'} и возвращает значение
regularMarketChangePercent по индексу
percent, оно подставляется в дерево запроса списка
. И далее нам нужно получить первый элемент c индексом
[0].
Полученную строку конвертируем как
float.
Возвращаем список
return_value в конце работы метода
__get_update(self)
Выглядит это менее страшно, чем я объяснил.
# ---- создадим список, в который будут помещаться возвращаемые значения
return_value = []
# -------- перебором ключей словаря получим все нужные строки,
# и сконвертируем их в числа типа float
for key in self.value:
return_value.append(float(path_val(array, f"/**/{self.value[key]}/raw")[0]))
# -------- возвращаем список из всех элементов по ключам из value
return return_value
Создадим еще один метод
update(self), который мы будем использовать для вызова
__get_update(self)
Его задача просто быть вызванным через экземпляр класса и присвоить полям обновленные значения.
например,
gazp.update() обновит все значения в этом объекте. И можно просто обратиться например к цене
print(gazp.price) которая покажет нам обновленное значение. Аналогичным образом можно получить значения других полей.
Значения обновляются через присвоение вызова
__get_update(self) для полей класса. Так как в ходе работы возвращается список, то присваиваем переменным новые значения просто указывая их через запятую.
def update(self):
"""
Устанавливает обновленные значения
полученные в результате работы _get_update
"""
# присвоим возвращенный кортеж из функции _ger_update
self.price, self.percent, self.change, self.volume = self.__get_update()
Вот и всё! наш класс готов и его можно использовать в разных ситуациях. Сохраним его отдельным файлом. В моем случае
yahooparser.py.
Для демонстрации работы класса давайте набросаем небольшой скрипт, для отображения информации по списку бумаг.
Создаем новый документ
main.py
Для этого примера я использовал словарь с тикерами по которым собираюсь получить информацию.
Для наглядности представления вывода информации можно установить очень маленькую и очень простую но удобную библиотеку
prettytable
Устанавливаем ее через
pip install prettytable
Импортируем всё что нам нужно
в первую очередь нам нужен наш созданный класс
Ticker.
И
prettytable
from yahooparser import Ticker
from prettytable import PrettyTable
Создадим словарь с тикерами которые будем скармливать в класс для создания экземпляров.
# ----------------------------------------------------
# в этот словарь можно добавить любые бумаги
# после запуска скрипта создадутся объекты с
# именами по индексам
# ----------------------------------------------------
ticker_list = {'gazp': 'GAZP.ME',
'sber': 'SBER.ME',
'tatn': 'TATN.ME',
'moex': 'MOEX.ME',
'rosn': 'ROSN.ME',
'lkoh': 'LKOH.ME',
'yndx': 'YNDX.ME',
'nlmk': 'NLMK.ME',
'alrs': 'ALRS.ME',
'rual': 'RUAL.ME',
'magn': 'MAGN.ME'}
# ----------------------------------------------------
Создадим функцию, которая будет получать этот словарь в аргументе и через
for создаст нам все объекты.
Первый
for создает новый экземпляр класса с именем индекса словаря
ticker_list и передает ему в качестве аргумента значение словаря по индексу соответственно
например, при проходе
for создается
gazp = Ticker('GAZP.ME') и так далее по словарю
Второй
for обращается к методу update для каждого вновь созданного объекта
например, при проходе for вызывается
gazp.update() а что случается при этом вызове мы уже знаем 😁
Таким образом получаем актуальную информацию по всем тикерам.
В конце работы функция возвращает список с объектами
def get_tickers(ticker_list):
"""
Создает экземпляры класса Ticker из полученного списка
:param ticker_list: получает список тикеров
:return: возвращает список с экземплярами класса Ticker
"""
tickers = []
for element in ticker_list:
name = ticker_list[element]
tickers.append(Ticker(name))
for name in range(len(tickers)):
tickers[name].update()
return tickers
теперь вызовем эту функцию и поместим результат её работы в переменную
tickers в которую и вернется список с созданными объектами.
# -------- создаем объекты тикеров
tickers = get_tickers(ticker_list)
Далее эти объекты и данные из них, вы можете использовать как вашей душе угодно. Полет фантазии не ограничен.
Я приведу простой пример и просто распечатаю табличку с полученной информацией.
Создадим функцию
show_table(tickers) и в цикле
for переберем полученный нами список из переменной
tickers, которую нужно передать в аргументе при вызове
.
При каждом проходе
for вытаскиваем значения полей из объектов и присваиваем в переменные.
(можно конечно этого и не делать и использовать обращения к полям, но это так, просто для примера).
Все результаты помещаем в список
show_list элементами которого будут словари с элементом по индексу
name и значением, состоящим из списка элементов
price,change,percent, volume
Значения переменных можно округлить до сотых для удобства представления данных. Также значение
percent умножается на 100 чтобы отобразить значение в процентах
def show_table():
"""
Демонстрация полученных данных в ходе работы скрипта
:return: ничего не возвращает
"""
# ----- считаем знрачения из класса в переменные
show_list = []
for ticker in tickers:
name = ticker.name
price = round(ticker.price, 2)
change = round(ticker.change, 2)
percent = round(ticker.percent * 100, 2)
volume = round(ticker.volume, 2)
# ----- создадим словарь со списком внутри
show_list.append({name: [price, change, percent, volume]})
Данные отформатированы как нам нужно и теперь их можно распечатать
Для этого воспользуемся нашей
prettytable создаем новую таблицу
myTable и объявляем для нее заголовки столбцов
# ----- простая табличка - очень крутая вещь
myTable = PrettyTable()
# ----- создаем заголовки таблицы
myTable.field_names = ["Name", "Price", "Change", "Perc. change", "Volume"]
Теперь просто через
for перебираем наш список
show_list, который мы сформировали парой строк выше и добавляем строки через
myTable.add_row, в
аргументах указывая список и его элементы.
Напечатаем нашу таблицу в конце
# ----- добавляем строки в таблицу
for string in show_list:
for key in string:
myTable.add_row([key, string[key][0], string[key][1], string[key][2], string[key][3]])
# печатаем таблицу с полученными значениями.
print(myTable)
Теперь чтобы всё заработало вызываем эту функцию
# -------- распечатаем для примера
show_table(tickers)
Кто дочитал — молодец! 😁Ниже полный код класса и полный код main:
yahooparser.py
import requests
from dpath.util import values as path_val
class Ticker:
# -------- URL запрос будет через открытие сессии
session = requests.session()
# -------- заголовки для URL запроса
headers = 'Mozilla/5.0 ' \
'(Windows NT 10.0; WOW64) ' \
'AppleWebKit/537.36' \
' (KHTML, like Gecko) ' \
'Chrome/91.0.4472.135 ' \
'Safari/537.36'
# -------- Запрашиваемые поля
value = {'price': 'regularMarketPrice',
'percent': 'regularMarketChangePercent',
'change': 'regularMarketChange',
'volume': 'regularMarketVolume'}
# -------- конструктор
def __init__(self, name):
# -------- определим первоначальные значения
self.name = name
self.price = 0.00
self.change = 0.00
self.percent = 0.0
self.volume = 0.0
def update(self):
"""
Устанавливает обновленные значения
полученные в результате работы _get_update
"""
# присвоим возвращенный кортеж из функции _ger_update
self.price, self.percent, self.change, self.volume = self.__get_update()
def __get_update(self):
"""
Отправляет URL запрос, получает JSON
возвращает список со значениями float
"""
# -------- ссылка для URL запроса
link = f"https://query2.finance.yahoo.com/v10/" \
f"finance/quoteSummary/{self.name}?modules=price"
# -------- отправляем запрос и получаем результат в response
response = self.session.get(link, headers={'User-Agent': self.headers})
# -------- получаем json массив
array = response.json()
# ---- создадим список, в который будут помещаться возвращаемые значения
return_value = []
# -------- перебором ключей словаря получим все нужные строки,
# и сконвертируем их в числа типа float
for key in self.value:
return_value.append(float(path_val(array, f"/**/{self.value[key]}/raw")[0]))
# -------- возвращаем список из всех элементов по ключам из value
return return_value
main.py
from yahooparser import Ticker
from prettytable import PrettyTable
def get_tickers(ticker_list):
"""
Создает экземпляры класса Ticker из полученного списка
:param ticker_list: получает список тикеров
:return: возвращает список с экземплярами класса Ticker
"""
tickers = []
for element in ticker_list:
name = ticker_list[element]
tickers.append(Ticker(name))
for name in range(len(tickers)):
tickers[name].update()
return tickers
def show_table(tickers):
"""
Демонстрация полученных данных в ходе работы скрипта
:return: ничего не возвращает
"""
# ----- считаем знрачения из класса в переменные
show_list = []
for ticker in tickers:
name = ticker.name
price = round(ticker.price, 2)
change = round(ticker.change, 2)
percent = round(ticker.percent * 100, 2)
volume = round(ticker.volume, 2)
# ----- создадим словарь со списком внутри
show_list.append({name: [price, change, percent, volume]})
# ----- эта простая табличка - очень крутая вещь
myTable = PrettyTable()
# ----- создаем заголовки таблицы
myTable.field_names = ["Name", "Price", "Change", "Perc. change", "Volume"]
# ----- добавляем строки в таблицу
for string in show_list:
for key in string:
myTable.add_row([key, string[key][0], string[key][1], string[key][2], string[key][3]])
# печатаем таблицу с полученными значениями.
print(myTable)
# ----------------------------------------------------
# в этот словарь можно добавить любые бумаги
# после запуска скрипта создадутся объекты с
# аналогичными именами, для которых будут запрошены
# значения, после чего их можно использовать или распечатать
# ----------------------------------------------------
ticker_list = {'gazp': 'GAZP.ME',
'sber': 'SBER.ME',
'tatn': 'TATN.ME',
'moex': 'MOEX.ME',
'rosn': 'ROSN.ME',
'lkoh': 'LKOH.ME',
'yndx': 'YNDX.ME',
'nlmk': 'NLMK.ME',
'alrs': 'ALRS.ME',
'rual': 'RUAL.ME',
'magn': 'MAGN.ME'}
# ----------------------------------------------------
# -------- создаем экземпляры класса
tickers = get_tickers(ticker_list)
# -------- распечатаем для примера
show_table(tickers)
response = self.session.get(link, headers={'User-Agent': self.headers})
array = response.json()
return_value = []
for key in self.value:
return_value.append(float(path_val(array, f"/**/{self.value[key]}/raw")[0]))
return return_value
Тут же я завернул это в нормальный доработанный подключаемый класс. и код по факту вырос до 40 строк. без комментов.