Сокращение данных о котировках тикеров Google Finance в Python

  • Что будет соскабливаться
  • Предварительные условия
    • Соскабливание данных о котировках тикеров Google Finance
      • Объяснение извлечения данных о тикерах
    • Соскабливание нескольких котировок тикеров Google Finance
    • Извлечение данных временных рядов графиков Google Finance
      • Сокращение данных временных рядов Google Finance
      • Пояснения к коду извлечения временных рядов
      • Лимиты ставок NASDAQ
      • Дополнительные ресурсы API Nasdaq
  • Ссылки
  • Outro

Что будет соскоблено

Предварительные условия

Базовые знания о скраппинге с помощью селекторов CSS

CSS-селекторы определяют, к какой части разметки применяется стиль, позволяя извлекать данные из соответствующих тегов и атрибутов.

Если вы еще не занимались скраппингом с помощью CSS-селекторов, в моем блоге есть отдельная статья о том.
о том, как использовать CSS-селекторы при веб-скрапинге, в которой рассказывается о том, что это такое, плюсы и минусы, и почему они важны с точки зрения веб-скрапинга.

Отдельная виртуальная среда

Вкратце, это вещь, которая создает независимый набор установленных библиотек, включая различные версии Python, которые могут сосуществовать друг с другом в одной системе, предотвращая таким образом конфликты библиотек или версий Python.

Если вы раньше не работали с виртуальным окружением, посмотрите на
учебник по виртуальным средам Python с использованием Virtualenv и мой пост в блоге Poetry, чтобы немного освоиться.

📌Примечание: это не является строгим требованием для данного поста в блоге.

Установите библиотеки:

pip install requests parsel
Войдите в полноэкранный режим Выйти из полноэкранного режима

Снизить вероятность блокировки

Существует вероятность того, что запрос может быть заблокирован. Посмотрите
о том, как уменьшить вероятность блокировки при веб-скрапинге. Существует одиннадцать методов обхода блокировки большинства сайтов.


Скраппинг данных о котировках тикера Google Finance

def scrape_google_finance(ticker: str):
    params = {
        "hl": "en" # language
        }

    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36",
        }

    html = requests.get(f"https://www.google.com/finance/quote/{ticker}", params=params, headers=headers, timeout=30)
    selector = Selector(text=html.text)

    # where all extracted data will be temporary located
    ticker_data = {
        "ticker_data": {},
        "about_panel": {},
        "news": {"items": []},
        "finance_perfomance": {"table": []}, 
        "people_also_search_for": {"items": []},
        "interested_in": {"items": []}
    }

    # current price, quote, title extraction
    ticker_data["ticker_data"]["current_price"] = selector.css(".AHmHk .fxKbKc::text").get()
    ticker_data["ticker_data"]["quote"] = selector.css(".PdOqHc::text").get().replace(" • ",":")
    ticker_data["ticker_data"]["title"] = selector.css(".zzDege::text").get()

    # about panel extraction
    about_panel_keys = selector.css(".gyFHrc .mfs7Fc::text").getall()
    about_panel_values = selector.css(".gyFHrc .P6K39c").xpath("normalize-space()").getall()

    for key, value in zip_longest(about_panel_keys, about_panel_values):
        key_value = key.lower().replace(" ", "_")
        ticker_data["about_panel"][key_value] = value

    # description "about" extraction
    ticker_data["about_panel"]["description"] = selector.css(".bLLb2d::text").get()
    ticker_data["about_panel"]["extensions"] = selector.css(".w2tnNd::text").getall()

    # news extarction
    if selector.css(".yY3Lee").get():
        for index, news in enumerate(selector.css(".yY3Lee"), start=1):
            ticker_data["news"]["items"].append({
                "position": index,
                "title": news.css(".Yfwt5::text").get(),
                "link": news.css(".z4rs2b a::attr(href)").get(),
                "source": news.css(".sfyJob::text").get(),
                "published": news.css(".Adak::text").get(),
                "thumbnail": news.css("img.Z4idke::attr(src)").get()
            })
    else: 
        ticker_data["news"]["error"] = f"No news result from a {ticker}."

    # finance perfomance table
    if selector.css(".slpEwd .roXhBd").get():
        fin_perf_col_2 = selector.css(".PFjsMe+ .yNnsfe::text").get()           # e.g. Dec 2021
        fin_perf_col_3 = selector.css(".PFjsMe~ .yNnsfe+ .yNnsfe::text").get()  # e.g. Year/year change

        for fin_perf in selector.css(".slpEwd .roXhBd"):
            if fin_perf.css(".J9Jhg::text , .jU4VAc::text").get():
                perf_key = fin_perf.css(".J9Jhg::text , .jU4VAc::text").get()   # e.g. Revenue, Net Income, Operating Income..
                perf_value_col_1 = fin_perf.css(".QXDnM::text").get()           # 60.3B, 26.40%..   
                perf_value_col_2 = fin_perf.css(".gEUVJe .JwB6zf::text").get()  # 2.39%, -21.22%..

                ticker_data["finance_perfomance"]["table"].append({
                    perf_key: {
                        fin_perf_col_2: perf_value_col_1,
                        fin_perf_col_3: perf_value_col_2
                    }
                })
    else:
        ticker_data["finance_perfomance"]["error"] = f"No 'finence perfomance table' for {ticker}."

    # "you may be interested in" results
    if selector.css(".HDXgAf .tOzDHb").get():
        for index, other_interests in enumerate(selector.css(".HDXgAf .tOzDHb"), start=1):
            ticker_data["interested_in"]["items"].append(discover_more_tickers(index, other_interests))
    else:
        ticker_data["interested_in"]["error"] = f"No 'you may be interested in` results for {ticker}"


    # "people also search for" results
    if selector.css(".HDXgAf+ div .tOzDHb").get():
        for index, other_tickers in enumerate(selector.css(".HDXgAf+ div .tOzDHb"), start=1):
            ticker_data["people_also_search_for"]["items"].append(discover_more_tickers(index, other_tickers))
    else:
        ticker_data["people_also_search_for"]["error"] = f"No 'people_also_search_for` in results for {ticker}"


    return ticker_data


def discover_more_tickers(index: int, other_data: str):
    """
    if price_change_formatted will start complaining,
    check beforehand for None values with try/except and set it to 0, in this function.

    however, re.search(r"d{1}%|d{1,10}.d{1,2}%" should make the job done.
    """
    return {
            "position": index,
            "ticker": other_data.css(".COaKTb::text").get(),
            "ticker_link": f'https://www.google.com/finance{other_data.attrib["href"].replace("./", "/")}',
            "title": other_data.css(".RwFyvf::text").get(),
            "price": other_data.css(".YMlKec::text").get(),
            "price_change": other_data.css("[jsname=Fe7oBc]::attr(aria-label)").get(),
            # https://regex101.com/r/BOFBlt/1
            # Up by 100.99% -> 100.99%
            "price_change_formatted": re.search(r"d{1}%|d{1,10}.d{1,2}%", other_data.css("[jsname=Fe7oBc]::attr(aria-label)").get()).group()
        }


scrape_google_finance(ticker="GOOGL:NASDAQ")
Войдите в полноэкранный режим Выход из полноэкранного режима

Пояснения по извлечению данных о котировках

Импорт библиотек:

import requests, json, re
from parsel import Selector
from itertools import zip_longest # https://docs.python.org/3/library/itertools.html#itertools.zip_longest
Вход в полноэкранный режим Выход из полноэкранного режима
Библиотека Назначение
requests сделать запрос к веб-сайту.
json преобразовать извлеченные данные в объект JSON.
re извлечение части данных с помощью регулярного выражения.
parsel для разбора данных из документов HTML/XML. Аналогично BeautifulSoup.
zip_longest для параллельного выполнения итераций над несколькими итерациями. Подробнее об этом ниже.

Определите функцию:

def scrape_google_finance(ticker: str): # ticker should be a string
    # further code...

scrape_google_finance(ticker="GOOGL:NASDAQ")
Войти в полноэкранный режим Выйти из полноэкранного режима

Создайте заголовки запроса и параметры URL:

# https://docs.python-requests.org/en/master/user/quickstart/#passing-parameters-in-urls
params = {
    "hl": "en" # language
}

# https://docs.python-requests.org/en/master/user/quickstart/#custom-headers
# https://www.whatismybrowser.com/detect/what-is-my-user-agent
headers = {
    "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.60 Safari/537.36",
}
Войти в полноэкранный режим Выход из полноэкранного режима
Библиотека Назначение
params более красивый способ передачи параметров URL в запрос.
user-agent действовать как «настоящий» пользовательский запрос от браузера, передавая его в заголовки запроса. Проверьте, что является вашим user-agent.

Передать параметры запроса и заголовки запроса, сделать запрос и передать ответ parsel:

html = requests.get(f"https://www.google.com/finance/quote/{ticker}", params=params, headers=headers, timeout=30)
selector = Selector(text=html.text)
Войти в полноэкранный режим Выйти из полноэкранного режима
Код Пояснение
f"https://www.google.com/finance/quote/{ticker}" f-строка, где {ticker} будет заменен на фактическую строку тикера, например, "GOOGL:NASDAQ".
timeout=30 прекратить ожидание ответа через 30 секунд.
Selector(text=html.text) где переданный HTML из ответа будет обработан parsel.

Создайте пустую структуру словаря, в которую будут заполнены все данные:

# where all extracted data will be temporarily located
ticker_data = {
    "ticker_data": {},
    "about_panel": {},
    "news": {"items": []},
    "finance_perfomance": {"table": []}, 
    "people_also_search_for": {"items": []},
    "interested_in": {"items": []}
}
Войти в полноэкранный режим Выход из полноэкранного режима

Извлечение текущих данных о цене, котировке и названии:

# current price, quote, title extraction
ticker_data["ticker_data"]["current_price"] = selector.css(".AHmHk .fxKbKc::text").get()
ticker_data["ticker_data"]["quote"] = selector.css(".PdOqHc::text").get().replace(" • ",":")
ticker_data["ticker_data"]["title"] = selector.css(".zzDege::text").get()
Войти в полноэкранный режим Выход из полноэкранного режима
Код Пояснение
ticker_data["ticker_data"]["current_price"] получает доступ к ключу ["ticker_data"] и создает новый ключ ["current_price"] и присваивает ему значение, которое будет извлечено parsel. То же самое для новых ключей ["quote"] и ["title"].
::text это поддержка собственного псевдоэлемента parsel, который будет переводить каждый CSS-запрос в XPath. В этом случае ::text станет /text().
get() получить фактические данные.
replace(" • ",":") заменить что-то старое на что-то новое.

Извлечение данных правой панели:

about_panel_keys = selector.css(".gyFHrc .mfs7Fc::text").getall()
about_panel_values = selector.css(".gyFHrc .P6K39c").xpath("normalize-space()").getall()

for key, value in zip_longest(about_panel_keys, about_panel_values):
    key_value = key.lower().replace(" ", "_")
    ticker_data["about_panel"][key_value] = value
Вход в полноэкранный режим Выход из полноэкранного режима
Код Пояснение
getall() получить весь список совпадений.
xpath("normalize-space()") получить также пустые текстовые узлы. По умолчанию пустые текстовые узлы будут пропущены, что приведет к неполному выводу.
lower() выводить все символы строк в нижнем регистре.
zip_longest() объединять несколько итераторов. Разница между zip() и zip_longest() в том, что zip() заканчивается на самом коротком итераторе, а zip_longest() итерирует до длины самого длинного итератора.
[key_value] динамически добавляет ключ в словарь с собственным, извлеченным значением.

Извлечение данных описания и расширений из правой панели:

# description "about" and  extensions extraction
ticker_data["about_panel"]["description"] = selector.css(".bLLb2d::text").get()
ticker_data["about_panel"]["extensions"] = selector.css(".w2tnNd::text").getall()
Вход в полноэкранный режим Выход из полноэкранного режима

Извлечение результатов новостей:

# news extarction
if selector.css(".yY3Lee").get():
    for index, news in enumerate(selector.css(".yY3Lee"), start=1):
        ticker_data["news"]["items"].append({
            "position": index,
            "title": news.css(".Yfwt5::text").get(),
            "link": news.css(".z4rs2b a::attr(href)").get(),
            "source": news.css(".sfyJob::text").get(),
            "published": news.css(".Adak::text").get(),
            "thumbnail": news.css("img.Z4idke::attr(src)").get()
        })
else: 
    ticker_data["news"]["error"] = f"No news result from a {ticker}."
Вход в полноэкранный режим Выход из полноэкранного режима
Код Пояснение
if selector.css(".yY3Lee").get() чтобы проверить, присутствуют ли результаты новостей. Нет необходимости проверять if <element> is not None.
enumerate() добавить счетчик к итерабельной таблице и вернуть его. start=1 начнет отсчет с 1, а не со значения по умолчанию 0.
ticker_data["news"].append({}) append извлеченные данные в list в виде словаря.
::attr(src) также поддерживает псевдоэлемент parsel для получения атрибута src из узла. Эквивалентно XPath /@src.
ticker_data["news"]["error"] создание нового ключа "error" и сообщения при возникновении ошибки.

Извлечение данных таблицы Financial Perfomance:

# finance perfomance table
# checks if finance table exists
if selector.css(".slpEwd .roXhBd").get():
    fin_perf_col_2 = selector.css(".PFjsMe+ .yNnsfe::text").get()           # e.g. Dec 2021
    fin_perf_col_3 = selector.css(".PFjsMe~ .yNnsfe+ .yNnsfe::text").get()  # e.g. Year/year change

    for fin_perf in selector.css(".slpEwd .roXhBd"):
        if fin_perf.css(".J9Jhg::text , .jU4VAc::text").get():

            """
            if fin_perf.css().get() statement is needed, otherwise first dict key and sub dict values would be None:

            "finance_perfomance": {
            "table": [
                {
                "null": {
                    "Dec 2021": null,
                    "Year/year change": null
                }
            }
            """             

            perf_key = fin_perf.css(".J9Jhg::text , .jU4VAc::text").get()   # e.g. Revenue, Net Income, Operating Income..
            perf_value_col_1 = fin_perf.css(".QXDnM::text").get()           # 60.3B, 26.40%..   
            perf_value_col_2 = fin_perf.css(".gEUVJe .JwB6zf::text").get()  # 2.39%, -21.22%..

            ticker_data["finance_perfomance"]["table"].append({
                perf_key: {
                    fin_perf_col_2: perf_value_col_1, # dynamically add key and value from the second (2) column
                    fin_perf_col_3: perf_value_col_2  # dynamically add key and value from the third (3) column
                }
            })
else:
    ticker_data["finance_perfomance"]["error"] = f"No 'finence perfomance table' for {ticker}."
Вход в полноэкранный режим Выход из полноэкранного режима

Извлечение результатов, которые могут вас "заинтересовать"/"люди также ищут":

    # "you may be interested in" results
    if selector.css(".HDXgAf .tOzDHb").get():
        for index, other_interests in enumerate(selector.css(".HDXgAf .tOzDHb"), start=1):
            ticker_data["interested_in"]["items"].append(discover_more_tickers(index, other_interests))
    else:
        ticker_data["interested_in"]["error"] = f"No 'you may be interested in` results for {ticker}"


    # "people also search for" results
    if selector.css(".HDXgAf+ div .tOzDHb").get():
        for index, other_tickers in enumerate(selector.css(".HDXgAf+ div .tOzDHb"), start=1):
            ticker_data["people_also_search_for"]["items"].append(discover_more_tickers(index, other_tickers))
    else:
        ticker_data["people_also_search_for"]["error"] = f"No 'people_also_search_for` in results for {ticker}"

# ....

def discover_more_tickers(index: int, other_data: str):
    """
    if price_change_formatted will start complaining,
    check beforehand for None values with try/except or if statement and set it to 0.

    however, re.search(r"d{1}%|d{1,10}.d{1,2}%" should get the job done.
    """
    return {
            "position": index,
            "ticker": other_data.css(".COaKTb::text").get(),
            "ticker_link": f'https://www.google.com/finance{other_data.attrib["href"].replace("./", "/")}',
            "title": other_data.css(".RwFyvf::text").get(),
            "price": other_data.css(".YMlKec::text").get(),
            "price_change": other_data.css("[jsname=Fe7oBc]::attr(aria-label)").get(),
            # https://regex101.com/r/BOFBlt/1
            # Up by 100.99% -> 100.99%
            "price_change_formatted": re.search(r"d{1}%|d{1,10}.d{1,2}%", other_data.css("[jsname=Fe7oBc]::attr(aria-label)").get()).group()
        }
Войти в полноэкранный режим Выйти из полноэкранного режима
Код Пояснение
discover_more_tickers() созданная функция используется для объединения двух одинаковых кодов в одну функцию. Таким образом, код нужно изменить только в одном месте.
attrib["attribute_name"] для получения атрибута узла.
[jsname=Fe7oBc] это CSS-селектор, который используется для выбора элементов с указанным атрибутом и значением, например, [attribute=value].
re.search() для сопоставления частей строки и захвата только цифр и значений %. И group() для возврата строки, сопоставленной регулярному выражению.

Верните и распечатайте данные:

# def scrape_google_finance(ticker: str):
    # ticker_data = {
    #     "ticker_data": {},
    #     "about_panel": {},
    #     "news": {"items": []},
    #     "finance_perfomance": {"table": []}, 
    #     "people_also_search_for": {"items": []},
    #     "interested_in": {"items": []}
    # }

    # extraction code...

    return ticker_data

print(json.dumps(data_1, indent=2, ensure_ascii=False))
Войти в полноэкранный режим Выход из полноэкранного режима

Полный вывод:

{
  "ticker_data": {
    "current_price": "$2,665.75",
    "quote": "GOOGL:NASDAQ",
    "title": "Alphabet Inc Class A"
  },
  "about_panel": {
    "previous_close": "$2,717.77",
    "day_range": "$2,659.31 - $2,713.40",
    "year_range": "$2,193.62 - $3,030.93",
    "market_cap": "1.80T USD",
    "volume": "1.56M",
    "p/e_ratio": "23.76",
    "dividend_yield": "-",
    "primary_exchange": "NASDAQ",
    "ceo": "Sundar Pichai",
    "founded": "Oct 2, 2015",
    "headquarters": "Mountain View, CaliforniaUnited States",
    "website": "abc.xyz",
    "employees": "156,500",
    "description": "Alphabet Inc. is an American multinational technology conglomerate holding company headquartered in Mountain View, California. It was created through a restructuring of Google on October 2, 2015, and became the parent company of Google and several former Google subsidiaries. The two co-founders of Google remained as controlling shareholders, board members, and employees at Alphabet. Alphabet is the world's third-largest technology company by revenue and one of the world's most valuable companies. It is one of the Big Five American information technology companies, alongside Amazon, Apple, Meta and Microsoft.nThe establishment of Alphabet Inc. was prompted by a desire to make the core Google business "cleaner and more accountable" while allowing greater autonomy to group companies that operate in businesses other than Internet services. Founders Larry Page and Sergey Brin announced their resignation from their executive posts in December 2019, with the CEO role to be filled by Sundar Pichai, also the CEO of Google. Page and Brin remain co-founders, employees, board members, and controlling shareholders of Alphabet Inc. ",
    "extensions": [
      "Stock",
      "US listed security",
      "US headquartered"
    ]
  },
  "news": [
    {
      "position": 1,
      "title": "Amazon Splitting Stock, Alphabet Too. Which Joins the Dow First?",
      "link": "https://www.barrons.com/articles/amazon-stock-split-dow-jones-51646912881?tesla=y",
      "source": "Barron's",
      "published": "1 month ago",
      "thumbnail": "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcRlf6wb63KP9lMPsOheYDvvANIfevHp17lzZ-Y0d0aQO1-pRCIDX8POXGtZBQk"
    },
    {
      "position": 2,
      "title": "Alphabet's quantum tech group Sandbox spins off into an independent company",
      "link": "https://www.cnbc.com/2022/03/22/alphabets-quantum-tech-group-sandbox-spins-off-into-an-independent-company.html",
      "source": "CNBC",
      "published": "2 weeks ago",
      "thumbnail": "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSIyv1WZJgDvwtMW8e3RAs9ImXtTZSmo2rfmCKIASk4B_XofZfZ8AbDLAMolhk"
    },
    {
      "position": 3,
      "title": "Cash-Rich Berkshire Hathaway, Apple, and Alphabet Should Gain From Higher nRates",
      "link": "https://www.barrons.com/articles/cash-rich-berkshire-hathaway-apple-and-alphabet-should-gain-from-higher-rates-51647614268",
      "source": "Barron's",
      "published": "3 weeks ago",
      "thumbnail": "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcSZ6dJ9h9vXlKrWlTmHiHxlfYVbViP5DAr9a_xV4LhNUOaNS01RuPmt-5sjh4c"
    },
    {
      "position": 4,
      "title": "Amazon's Stock Split Follows Alphabet's. Here's Who's Next.",
      "link": "https://www.barrons.com/articles/amazon-stock-split-who-next-51646944161",
      "source": "Barron's",
      "published": "1 month ago",
      "thumbnail": "https://encrypted-tbn1.gstatic.com/images?q=tbn:ANd9GcSJGKk2i1kLT_YToKJlJnhWaaj_ujLvhhZ5Obw_suZcu_YyaDD6O_Llsm1aqt8"
    },
    {
      "position": 5,
      "title": "Amazon, Alphabet, and 8 Other Beaten-Up Growth Stocks Set to Soar",
      "link": "https://www.barrons.com/articles/amazon-stock-growth-buy-51647372422",
      "source": "Barron's",
      "published": "3 weeks ago",
      "thumbnail": "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcTxotkd3p81U7xhmCTJ6IO0tMf_yVKv3Z40bafvtp9XCyosyB4WAuX7Qt-t7Ds"
    },
    {
      "position": 6,
      "title": "Is It Too Late to Buy Alphabet Stock?",
      "link": "https://www.fool.com/investing/2022/03/14/is-it-too-late-to-buy-alphabet-stock/",
      "source": "The Motley Fool",
      "published": "3 weeks ago",
      "thumbnail": "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcQv5D9GFKMNUPvMd91aRvi83p12y91Oau1mh_4FBPj6LCNK3cH1vEZ3_gFU4kI"
    }
  ],
  "finance_perfomance": [
    {
      "Revenue": {
        "Dec 2021": "75.32B",
        "Year/year change": "32.39%"
      }
    },
    {
      "Net income": {
        "Dec 2021": "20.64B",
        "Year/year change": "35.56%"
      }
    },
    {
      "Diluted EPS": {
        "Dec 2021": "30.69",
        "Year/year change": "37.62%"
      }
    },
    {
      "Net profit margin": {
        "Dec 2021": "27.40%",
        "Year/year change": "2.39%"
      }
    },
    {
      "Operating income": {
        "Dec 2021": "21.88B",
        "Year/year change": "39.83%"
      }
    },
    {
      "Net change in cash": {
        "Dec 2021": "-2.77B",
        "Year/year change": "-143.78%"
      }
    },
    {
      "Cash and equivalents": {
        "Dec 2021": "20.94B",
        "Year/year change": "-20.86%"
      }
    },
    {
      "Cost of revenue": {
        "Dec 2021": "32.99B",
        "Year/year change": "26.49%"
      }
    }
  ],
  "people_also_search_for": [
    {
      "position": 1,
      "ticker": "GOOG",
      "ticker_link": "https://www.google.com/finance/quote/GOOG:NASDAQ",
      "title": "Alphabet Inc Class C",
      "price": "$2,680.21",
      "price_change": "Down by 1.80%",
      "price_change_formatted": "1.80%"
    }, ... other results
    {
      "position": 18,
      "ticker": "SQ",
      "ticker_link": "https://www.google.com/finance/quote/SQ:NYSE",
      "title": "Block Inc",
      "price": "$123.22",
      "price_change": "Down by 2.15%",
      "price_change_formatted": "2.15%"
    }
  ],
  "interested_in": [
    {
      "position": 1,
      "ticker": "Index",
      "ticker_link": "https://www.google.com/finance/quote/.INX:INDEXSP",
      "title": "S&P 500",
      "price": "4,488.28",
      "price_change": "Down by 0.27%",
      "price_change_formatted": "0.27%"
    }, ... other results
    {
      "position": 18,
      "ticker": "NFLX",
      "ticker_link": "https://www.google.com/finance/quote/NFLX:NASDAQ",
      "title": "Netflix Inc",
      "price": "$355.88",
      "price_change": "Down by 1.73%",
      "price_change_formatted": "1.73%"
    }
  ]
}
Войти в полноэкранный режим Выход из полноэкранного режима

Соскабливание нескольких котировок тикеров Google Finance

for ticker in ["DAX:INDEXDB", "GOOGL:NASDAQ", "MSFT:NASDAQ"]:
    data = scrape_google_finance(ticker=ticker)
    print(json.dumps(data["ticker_data"], indent=2, ensure_ascii=False))
Войти в полноэкранный режим Выход из полноэкранного режима

Выходы:

{
  "current_price": "14,178.23",
  "quote": "DAX:Index",
  "title": "DAX PERFORMANCE-INDEX"
}
{
  "current_price": "$2,665.75",
  "quote": "GOOGL:NASDAQ",
  "title": "Alphabet Inc Class A"
}
{
  "current_price": "$296.97",
  "quote": "MSFT:NASDAQ",
  "title": "Microsoft Corporation"
}
Войти в полноэкранный режим Выход из полноэкранного режима

Извлечение данных временных рядов графиков Google Finance

Извлечение данных временных рядов — не самая лучшая идея, поэтому лучше использовать специальный API для выполнения этой работы.

Как узнать, какой API использует Google для построения графиков временных рядов?

Мы можем подтвердить, что Google использует API NASDAQ для получения данных временных рядов, просто проверив график Nasdaq с котировкой GOOGL:

В данном случае я использовал Nasdaq Data Link API, который имеет поддержку Python, R и Excel. Я полагаю, что другие платформы также обеспечивают интеграцию с Python.

Я предполагаю, что вы уже установили пакет nasdaq-data-link, но если нет, то вот как вы можете это сделать. Если вы установили версию Python по умолчанию:

# WSL
$ pip install nasdaq-data-link
Войдите в полноэкранный режим Выйти из полноэкранного режима

Если вы не установили версию Python по умолчанию:

# WSL
$ python3.9 -m pip install nasdaq-data-link # change python to your version: python3.X
Войдите в полноэкранный режим Выйти из полноэкранного режима

Получите ключ API на data.nasdaq.com/account/profile:

Создайте файл .env, чтобы сохранить в нем свой ключ API:

touch .nasdaq_api_key # change the file name to yours 

# paste API key inside the created file
Войдите в полноэкранный режим Выход из полноэкранного режима

Сбор данных временных рядов Google Finance

import nasdaqdatalink

def nasdaq_get_timeseries_data():
    nasdaqdatalink.read_key(filename=".nasdaq_api_key")
    # print(nasdaqdatalink.ApiConfig.api_key) # prints api key from the .nasdaq_api_key file

    timeseries_data = nasdaqdatalink.get("WIKI/GOOGL", collapse="monthly") # not sure what "WIKI" stands for
    print(timeseries_data)

nasdaq_get_timeseries_data()
Вход в полноэкранный режим Выход из полноэкранного режима

Извлечение временных рядов Объяснение кода

Код Пояснение
nasdaqdatalink.read_key(filename=".nasdaq_api_key") для считывания вашего API-ключа.
".nasdaq_api_key" это ваша переменная .env с секретным ключом API. Все секретные переменные (поправьте меня, если я ошибаюсь) начинаются с символа ., чтобы показать его.
nasdaqdatalink.ApiConfig.api_key чтобы проверить, распознается ли ваш API пакетом nasdaq-data-link. Пример вывода: 2adA_avd12CXauv_1zxs.
nasdaqdatalink.get() для получения данных временных рядов, представляющих собой структуру набора данных.

Выводит объект pandas DataFrame:

                Open     High      Low    Close      Volume  Ex-Dividend  Split Ratio    Adj. Open    Adj. High     Adj. Low   Adj. Close  Adj. Volume
Date                                                                                                                                                  
2004-08-31   102.320   103.71   102.16   102.37   4917800.0          0.0          1.0    51.318415    52.015567    51.238167    51.343492    4917800.0
2004-09-30   129.899   132.30   129.00   129.60  13758000.0          0.0          1.0    65.150614    66.354831    64.699722    65.000651   13758000.0
2004-10-31   198.870   199.95   190.60   190.64  42282600.0          0.0          1.0    99.742897   100.284569    95.595093    95.615155   42282600.0
2004-11-30   180.700   183.00   180.25   181.98  15384600.0          0.0          1.0    90.629765    91.783326    90.404069    91.271747   15384600.0
2004-12-31   199.230   199.88   192.56   192.79  15321600.0          0.0          1.0    99.923454   100.249460    96.578127    96.693484   15321600.0
...              ...      ...      ...      ...         ...          ...          ...          ...          ...          ...          ...          ...
2017-11-30  1039.940  1044.14  1030.07  1036.17   2190379.0          0.0          1.0  1039.940000  1044.140000  1030.070000  1036.170000    2190379.0
2017-12-31  1055.490  1058.05  1052.70  1053.40   1156357.0          0.0          1.0  1055.490000  1058.050000  1052.700000  1053.400000    1156357.0
2018-01-31  1183.810  1186.32  1172.10  1182.22   1643877.0          0.0          1.0  1183.810000  1186.320000  1172.100000  1182.220000    1643877.0
2018-02-28  1122.000  1127.65  1103.00  1103.92   2431023.0          0.0          1.0  1122.000000  1127.650000  1103.000000  1103.920000    2431023.0
2018-03-31  1063.900  1064.54   997.62  1006.94   2940957.0          0.0          1.0  1063.900000  1064.540000   997.620000  1006.940000    2940957.0

[164 rows x 12 columns]
Войти в полноэкранный режим Выход из полноэкранного режима

Как вы можете видеть, здесь нет данных о 2019-2022 годах. Это потому, что я использую версию Free, которая подходит для экспериментов и исследований, как говорит Nasdaq.

Лимиты ставок Nasdaq

Аутентифицированные пользователи Премиум-пользователи
300 звонков за 10 секунд.
2 000 звонков за 10 минут. 5 000 звонков за 10 минут.
ограничение 50 000 звонков в день. ограничение 720 000 звонков в день.

Дополнительные ресурсы API Nasdaq

Ресурс Объяснение
Параметры временных рядов для настройки (манипулирования) набора данных временных рядов путем добавления дополнительных параметров к запросу. Преобразование данных временных рядов
Составление запроса с помощью curl чтобы легко составить запрос с помощью curl.
Доступные форматы сохранения данных сохранение данных в форматах CSV, XML, JSON.
Форматы данных для преобразования данных в доступные форматы.
Загрузка в большом количестве для загрузки всех данных в базе данных за один вызов.
Подробное руководство по доступным методам чтобы понять, как использовать пакет data-link-python более подробно.
data-link-python на GitHub чтобы прочитать полную документацию.
quandl-python на GitHub Что data-link-python использует под капотом. Вы можете найти немного больше документации здесь.

Ссылки

  • Код в онлайн IDE
  • Репозиторий на GitHub

Outro

Если вам есть чем поделиться, есть вопросы, предложения или что-то работает неправильно, свяжитесь с нами через Twitter по адресу @dimitryzub или @serp_api.

Ваши,
Дмитрий и остальная команда SerpApi Team.

Присоединяйтесь к нам в Twitter | YouTube

Добавить запрос на улучшение💫 или ошибку🐞

Оставьте комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *