- Что будет соскоблено
- Полный код
- Предварительные условия
- Объяснение кода
- Ссылки
Что будет соскоблено
Полный код
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
Вы также можете обратиться к команде помощи -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🐞