Блог им. tomas_b88

Забираем данные по ценным бумагам с finance.yahoo.com Python класс в подарок.

 

Забираем данные по ценным бумагам с 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'}


и в результате своей работы скрипт должен выдавать значения, например: цена, процент изменения и объем.
Для красоты и отладки можно также вывести это всё в красивую табличку.

Забираем данные по ценным бумагам с finance.yahoo.com Python класс в подарок.



Я решил создать класс 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

Забираем данные по ценным бумагам с finance.yahoo.com Python класс в подарок. 


Тут мы видим в теле запроса тикер 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 в браузере и определим какие нам нужно вытащить ключи. 

Забираем данные по ценным бумагам с finance.yahoo.com Python класс в подарок.


Я возьму оттуда 
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)
★13
8 комментариев
А что скринеров штатных сайтовских мало, или там не передашь данные в свой скрипт, опять таки зачем это. Раз взглянул визуально.
avatar
the Rolling Stones, вопрос не в скринерах а в обработке значений, визуализация если не понятно, тут не преследуется. Преследуется обработка.
avatar
Что-то как-то сложно. Не пробовали использовать библиотеку yfinance?
avatar
Riskplayer, и что же тут сложного? если подумать головой и внимательно смотерть. (вот что сложно) то весь механизм закрыт в пяти строчках

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 строк. без комментов. 

avatar
Riskplayer, щас попробовал рекомендуемую вами библиотеку. Крутая штука попробую на её основе сделать несколько скриптов в следующие разы :) Норм тема
avatar
Riskplayer, вот тут попробовал :) в принципе норм.
avatar
Не то же самое? github.com/ranaroussi/yfinance
avatar
Денис Г., без понятия. попробую использовать в следующих экспериментах сначала нужно было пощупать своими руками так сказать что да как. Глубина понимания не помешает. 
avatar

теги блога tomas_b88

....все тэги



UPDONW
Новый дизайн