Веб-скраппинг финансовых рынков Google на Python

  • Что будет соскоблено
  • Полный код
    • Предварительные условия
    • Объяснение кода
  • Ссылки

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

Полный код

import requests
import json
import re
import argparse
from parsel import Selector

parser = argparse.ArgumentParser(prog="Google Finance Markets Options")
parser.add_argument('-i','--indexes', action="store_true")
parser.add_argument('-ma','--most-active', action="store_true")
parser.add_argument('-g','--gainers', action="store_true")
parser.add_argument('-l','--losers', action="store_true")
parser.add_argument('-cl','--climate-leaders', action="store_true")
parser.add_argument('-cc','--crypto', action="store_true")
parser.add_argument('-c','--currency', action="store_true")

args = parser.parse_args()

def main():

    # 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/103.0.5060.134 Safari/537.36"
    }

    if args.indexes:
        html = requests.get("https://www.google.com/finance/markets/indexes", headers=headers, timeout=30)
        return parser(html=html)

    if args.most_active:
        html = requests.get("https://www.google.com/finance/markets/most-active", headers=headers, timeout=30)
        return parser(html=html)

    if args.gainers:
        html = requests.get("https://www.google.com/finance/markets/gainers", headers=headers, timeout=30)
        return parser(html=html)

    if args.losers:
        html = requests.get("https://www.google.com/finance/markets/losers", headers=headers, timeout=30)
        return parser(html=html)

    if args.climate_leaders:
        html = requests.get("https://www.google.com/finance/markets/climate-leaders", headers=headers, timeout=30)
        return parser(html=html)

    if args.crypto:
        html = requests.get("https://www.google.com/finance/markets/cryptocurrencies", headers=headers, timeout=30)
        return parser(html=html)

    if args.currency:
        html = requests.get("https://www.google.com/finance/markets/currencies", headers=headers, timeout=30)
        return parser(html=html)


def parser(html):
    selector = Selector(text=html.text)
    stock_topic = selector.css(".Mrksgc::text").get().split("on ")[1].replace(" ", "_")

    data = {
        f"{stock_topic}_trends": [],
        f"{stock_topic}_discover_more": [],
        f"{stock_topic}_news": []
    }

    # news ressults
    for index, news_results in enumerate(selector.css(".yY3Lee"), start=1):
        data[f"{stock_topic}_news"].append({
            "position": index,
            "title": news_results.css(".mRjSYb::text").get(),
            "source": news_results.css(".sfyJob::text").get(),
            "date": news_results.css(".Adak::text").get(),
            "image": news_results.css("img::attr(src)").get(),
        })

    # stocks table
    for index, stock_results in enumerate(selector.css("li a"), start=1):
        current_percent_change_raw_value = stock_results.css("[jsname=Fe7oBc]::attr(aria-label)").get()
        current_percent_change = re.search(r"d+.d+%", stock_results.css("[jsname=Fe7oBc]::attr(aria-label)").get()).group()

        # ./quote/SNAP:NASDAQ -> SNAP:NASDAQ
        quote = stock_results.attrib["href"].replace("./quote/", "")

        data[f"{stock_topic}_trends"].append({
            "position": index,
            "title": stock_results.css(".ZvmM7::text").get(),
            "quote": stock_results.css(".COaKTb::text").get(),
            # "https://www.google.com/finance/MSFT:NASDAQ"
            "quote_link": f"https://www.google.com/finance/{quote}",
            "price_change": stock_results.css(".SEGxAb .P2Luy::text").get(),
            "percent_price_change": f"+{current_percent_change}" if "Up" in current_percent_change_raw_value else f"-{current_percent_change}"
        })

    # "you may be interested in" at the bottom of the page
    for index, interested_bottom in enumerate(selector.css(".HDXgAf .tOzDHb"), start=1):
        current_percent_change_raw_value = interested_bottom.css("[jsname=Fe7oBc]::attr(aria-label)").get()
        current_percent_change = re.search(r"d+.d+%", interested_bottom.css("[jsname=Fe7oBc]::attr(aria-label)").get()).group()

        quote = stock_results.attrib["href"].replace("./quote/", "")

        data[f"{stock_topic}_discover_more"].append({
            "position": index,
            "quote": interested_bottom.css(".COaKTb::text").get(),
            "quote_link": f"https://www.google.com/finance{quote}",
            "title": interested_bottom.css(".RwFyvf::text").get(),
            "price": interested_bottom.css(".YMlKec::text").get(),
            "percent_price_change": f"+{current_percent_change}" if "Up" in current_percent_change_raw_value else f"-{current_percent_change}"
        })

    return data



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

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

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

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

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

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

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

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

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

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

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

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

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

Объяснение кода

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

import requests
import json
import re
import argparse
from parsel import Selector
Вход в полноэкранный режим Выход из полноэкранного режима
Библиотека Назначение
requests сделать запрос к веб-сайту.
json преобразовать извлеченные данные в объект JSON.
re извлечение части данных с помощью регулярного выражения.
argparse извлечение части данных с помощью регулярного выражения.
parsel для разбора данных из документов HTML/XML. Аналогичен BeautifulSoup, но поддерживает XPath.

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

parser = argparse.ArgumentParser(prog="Google Finance Markets Options")
parser.add_argument('-i','--indexes', action="store_true")
parser.add_argument('-ma','--most-active', action="store_true")
parser.add_argument('-g','--gainers', action="store_true")
parser.add_argument('-l','--losers', action="store_true")
parser.add_argument('-cl','--climate-leaders', action="store_true")
parser.add_argument('-cc','--crypto', action="store_true")
parser.add_argument('-c','--currency', action="store_true")

args = parser.parse_args()
Войти в полноэкранный режим Выйти из полноэкранного режима

Затем мы можем запустить скрипт примерно так:

$ python main.py -cc # will parse crypto results
Войти в полноэкранный режим Выйти из полноэкранного режима

Обратите внимание, если action="store_true" не используется, результатом будет ошибка:

$ python main.py -cc

Google Finance Markets Options: error: argument -cc/--crypto: expected one argument
Войти в полноэкранный режим Выход из полноэкранного режима

action, установленное в store_true, сохранит аргумент как True, если он присутствует. Таким образом, если аргумент присутствует, будет возвращен некоторый вывод.

Мы также можем сделать параметры requried, что означает, что определенный параметр должен быть использован при работе с файлом.

Код Пояснение
add_argument Определяет, как должен быть разобран один аргумент командной строки.
parse_args Определяет, какие объекты создаются add_argument и как они назначаются. Возвращает заполненное пространство имен.

Следующий шаг — создание функции со всей логикой командной строки. Для доступа к аргументам командной строки используется точечная нотация:

def main():

    # 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/103.0.5060.134 Safari/537.36"
    }

    if args.indexes:
        html = requests.get("https://www.google.com/finance/markets/indexes", headers=headers, timeout=30)
        return parser(html=html)

    # ... other arguments logic
Войти в полноэкранный режим Выйти из полноэкранного режима
Код Пояснение
user-agent действовать как «настоящий» запрос пользователя от браузера, передавая его в заголовки запроса. Это используется для обхода блокировок от Google, так как по умолчанию requests user-agent python-requests и сайты понимают, что это бот посылает запрос и могут его заблокировать. Проверьте, какой у вас user-agent.
if args.indexes будет проверять, передается ли определенный аргумент командной строки.
timeout=30 сказать requests перестать ждать ответа через 30 секунд.
return parser(html=html) возвращать данные из функции parser() для уменьшения размера кода, так как каждый селектор идентичен для извлечения данных.

Следующий шаг — сделать функцию парсера, которая будет извлекать все необходимые данные со страницы. Функция требует аргумент html, который будет передан parsel, затем нам нужно создать структуру data после ее разбора:

def parser(html):
    selector = Selector(text=html.text)
    stock_topic = selector.css(".Mrksgc::text").get().split("on ")[1].replace(" ", "_")

    data = {
        f"{stock_topic}_trends": [],
        f"{stock_topic}_discover_more": [],
        f"{stock_topic}_news": []
    }
Войти в полноэкранный режим Выход из полноэкранного режима
Код Пояснение
Selector(text=html.text) где переданный HTML из ответа будет обработан parsel.
text= аргумент parsel, принимающий объект str, из которого будут извлечены HTML-узлы.
css() для разбора данных из переданного селектора(ов) CSS. Каждый CSS-запрос траслируется в XPath с помощью пакета csselect под капотом.
::text или ::attr(<attribute>) для извлечения текстовых или атрибутивных данных из узла.
get() для получения фактических данных, возвращенных из parsel
split() разделить строку на список, где каждое слово является элементом списка
replace("<something>", "<with_something>") заменить что-то старое на что-то новое в строке.

После создания пустой структуры словаря нам нужно заполнить его новостями, акциями и другими данными, appending, как dict в данном случае:

# news ressults
for index, news_results in enumerate(selector.css(".yY3Lee"), start=1):
    data[f"{stock_topic}_news"].append({
        "position": index,
        "title": news_results.css(".mRjSYb::text").get(),
        "source": news_results.css(".sfyJob::text").get(),
        "date": news_results.css(".Adak::text").get(),
        "image": news_results.css("img::attr(src)").get(),
    })

# stocks table
for index, stock_results in enumerate(selector.css("li a"), start=1):
    current_percent_change_raw_value = stock_results.css("[jsname=Fe7oBc]::attr(aria-label)").get()
    current_percent_change = re.search(r"d+.d+%", stock_results.css("[jsname=Fe7oBc]::attr(aria-label)").get()).group()

    # ./quote/SNAP:NASDAQ -> SNAP:NASDAQ
    quote = stock_results.attrib["href"].replace("./quote/", "")

    data[f"{stock_topic}_trends"].append({
        "position": index,
        "title": stock_results.css(".ZvmM7::text").get(),
        "quote": stock_results.css(".COaKTb::text").get(),
        # "https://www.google.com/finance/MSFT:NASDAQ"
        "quote_link": f"https://www.google.com/finance/{quote}",
        "price_change": stock_results.css(".SEGxAb .P2Luy::text").get(),
        "percent_price_change": f"+{current_percent_change}" if "Up" in current_percent_change_raw_value else f"-{current_percent_change}"
    })

# "you may be interested in" at the bottom of the page
for index, interested_bottom in enumerate(selector.css(".HDXgAf .tOzDHb"), start=1):
    current_percent_change_raw_value = interested_bottom.css("[jsname=Fe7oBc]::attr(aria-label)").get()
    current_percent_change = re.search(r"d+.d+%", interested_bottom.css("[jsname=Fe7oBc]::attr(aria-label)").get()).group()

    # ./quote/SNAP:NASDAQ -> SNAP:NASDAQ
    quote = stock_results.attrib["href"].replace("./quote/", "")

    data[f"{stock_topic}_discover_more"].append({
        "position": index,
        "quote": interested_bottom.css(".COaKTb::text").get(),
        "quote_link": f"https://www.google.com/finance{quote}",
        "title": interested_bottom.css(".RwFyvf::text").get(),
        "price": interested_bottom.css(".YMlKec::text").get(),
        "percent_price_change": f"+{current_percent_change}" if "Up" in current_percent_change_raw_value else f"-{current_percent_change}"
    })
Войти в полноэкранный режим Выход из полноэкранного режима
Код Пояснение
enumerate() добавить счетчик в итерабельную переменную и вернуть его. start=1 начнет отсчет с 1, вместо значения по умолчанию 0.
::text или ::attr(<attribute>) для извлечения текстовых или атрибутивных данных из узла.
data[f"{stock_topic}_news"] динамически добавляет данные в виде dict к любому значению, извлекаемому переменной stock_topic.
append() добавлять извлеченные данные в list в виде словаря.
css() разобрать данные из переданного селектора(ов) CSS. Каждый CSS-запрос траслируется в XPath с помощью пакета csselect под капотом.
get() получить фактические данные.
getall() для получения всего списка совпадений.
[jsname=Fe7oBc] это CSS-селектор, который используется для выбора элементов с указанным атрибутом и значением, например, [attribute=value].
attrib["href"] это метод parsel для доступа к атрибуту узла. Он возвращает dict для первого найденного элемента. None, если dict пуст.
replace("<something>", "<with_something>") заменить что-то старое на что-то новое в строке.
re.search() для сопоставления частей строки и захвата только цифровых значений. group() для возврата строки, сопоставленной регулярному выражению.

Вернуть данные:

# data = {
#     f"{stock_topic}_trends": [],
#     f"{stock_topic}_discover_more": [],
#     f"{stock_topic}_news": []
# }

# extraction code...

return data
Войти в полноэкранный режим Выйти из полноэкранного режима

И, наконец, нам нужно указать, что это запускаемый скрипт для считывателей кода:

if __name__ == "__main__":
    print(json.dumps(main(), indent=2, ensure_ascii=False))
Войти в полноэкранный режим Выйти из полноэкранного режима

Теперь вы можете запустить свой скрипт из командной строки:

$ python main.py -ma # most-active
Enter fullscreen mode Выйти из полноэкранного режима

Вы также можете обратиться к команде помощи -h, чтобы увидеть доступные аргументы, как показано ниже:

$ python main.py -h
usage: Google Finance Markets Options [-h] [-i] [-ma] [-g] [-l]
                                      [-cl] [-cc] [-c]

optional arguments:
  -h, --help            show this help message and exit
  -i, --indexes
  -ma, --most-active
  -g, --gainers
  -l, --losers
  -cl, --climate-leaders
  -cc, --crypto
  -c, --currency
Ввести полноэкранный режим Выйти из полноэкранного режима

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

{
  "most_active_trends": [
    {
      "position": 1,
      "title": "Advanced Micro Devices, Inc.",
      "quote": "AMD",
      "quote_link": "https://www.google.com/finance/AMD:NASDAQ",
      "price_change": "+$3.04",
      "percent_price_change": "+3.22%"
    }, ... other results
    {
      "position": 50,
      "title": "Freeport-McMoRan Inc",
      "quote": "FCX",
      "quote_link": "https://www.google.com/finance/FCX:NYSE",
      "price_change": "-$1.15",
      "percent_price_change": "-3.66%"
    }
  ],
  "most_active_discover_more": [
    {
      "position": 1,
      "quote": "Index",
      "quote_link": "https://www.google.com/financeFCX:NYSE",
      "title": "Dow Jones Industrial Average",
      "price": "32,772.36",
      "percent_price_change": "-0.22%"
    }, ... other results
    {
      "position": 18,
      "quote": "NFLX",
      "quote_link": "https://www.google.com/financeFCX:NYSE",
      "title": "Netflix Inc",
      "price": "$226.14",
      "percent_price_change": "+0.55%"
    }
  ],
  "most_active_news": [
    {
      "position": 1,
      "title": "Alibaba says will work to keep trading in U.S., Hong Kong after being added nto SEC delisting risk list",
      "source": "CNBC",
      "date": "7 hours ago",
      "image": "https://encrypted-tbn3.gstatic.com/images?q=tbn:ANd9GcRMBjVDpAgK8AJP6gxfd89Kb5rz7th_s3ntTLA_WYWnVWT3Q05aQJTWpMpjcOg"
    }, ... other news results
    {
      "position": 6,
      "title": "Intel CEO: 'This is a time for a bit of austerity'",
      "source": "Yahoo Finance",
      "date": "4 hours ago",
      "image": "https://encrypted-tbn2.gstatic.com/images?q=tbn:ANd9GcTxkwNmmHmcXqkF3-pa2Bl0SsCzdIJyB0jPdutL0vw9pV4sRkgy8BKemYIkEeg"
    }
  ]
}
Войти в полноэкранный режим Выход из полноэкранного режима

Ссылки

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

Первоначально опубликовано на SerpApi: https://serpapi.com/blog/scrape-google-finance-markets-in-python/

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

Add a Feature Request💫 or a Bug🐞

Оцените статью
devanswers.ru
Добавить комментарий