Забираем данные по ценным бумагам с 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 строк. без комментов.