Пишем многопоточную программу на Python
В этой статье вы узнаете, как использовать модуль threading
для разработки многопоточной программы.
Мы разработаем многопоточную программу, которая считывает цены на акции с сайта Yahoo Finance.
Создаем многопоточную программу
Для создания программы, которая считывает цены на акции с сайта Yahoo Finance, воспользуемся двумя сторонними пакетами:
- requests — для получения содержимого веб-страницы.
- lxml — для выбора определенного элемента HTML-документа.
Сначала установим модули requests
и lxml
с помощью команды pip
:
pip install request lxml
Теперь создадим новый класс Stock
, который наследуется от класса Thread
модуля threading
. Поместим класс Stock
в модуль stock.py:
import threading
class Stock(threading.Thread):
pass
Теперь реализуем метод __init__()
, который принимает символ и инициализирует переменную экземпляра url
на основе этого символа:
import threading
import requests
from lxml import html
class Stock(threading.Thread):
def __init__(self, symbol: str) -> None:
super().__init__()
self.symbol = symbol
self.url = f'https://finance.yahoo.com/quote/{symbol}'
self.price = None
Например, если передать GOOG
в метод __init__()
, URL будет выглядеть так:
https://finance.yahoo.com/quote/GOOG
Затем переопределим метод run()
класса Thread
. Метод run()
получает содержимое из self.url
и «выцепляет» цену акций:
import threading
import requests
from lxml import html
class Stock(threading.Thread):
def __init__(self, symbol: str) -> None:
super().__init__()
self.symbol = symbol
self.url = f'https://finance.yahoo.com/quote/{symbol}'
self.price = None
def run(self):
response = requests.get(self.url)
if response.status_code == 200:
# парсим HTML
tree = html.fromstring(response.text)
# получаем цену в виде текста
price_text = tree.xpath(
'//*[@id="quote-header-info"]/div[3]/div[1]/div[1]/fin-streamer[1]/text()')
if price_text:
try:
self.price = float(price_text[0].replace(',', ''))
except ValueError:
self.price = None
Как это работает
1. Выполняем запрос к URL с помощью метода requests.get()
:
response = requests.get(self.url)
2. Если запрос успешен, код состояния HTTP равен 200. В этом случае мы получаем HTML-содержимое из ответа и передаем его в функцию fromstring()
модуля html
из пакета lxml
:
if response.status_code == 200:
tree = html.fromstring(response.text)
3. Получаем цену из текста.
Любой элемент на веб-странице можно выбрать с помощью так называемого XPath.
Как получить XPath элемента страницы с помощь Google Chrome. ПКМ на странице → «Просмотреть код» → Выберите элемент → ПКМ по элементу → Copy → Copy XPath.
XPath цены акций на момент написания этого руководства выглядит следующим образом:
//*[@id="quote-header-info"]/div[3]/div[1]/div[1]/fin-streamer[1]
Чтобы получить текст элемента, нужно добавить text()
в конец XPath:
//*[@id="quote-header-info"]/div[3]/div[1]/div[1]/fin-streamer[1]/text()
Примечание. Если Yahoo изменит структуру страницы, вам необходимо будет соответствующим образом изменить XPath. В противном случае программа не будет работать так, как ожидается.
Итак, получаем цену из текста:
price_text = tree.xpath('//*[@id="quote-header-info"]/div[3]/div[1]/div[1]/fin-streamer[1]/text()')
4. Удаляем запятую и преобразуем текст в число.
if price_text:
try:
self.price = float(price_text[0].replace(',', ''))
except ValueError:
self.price = None
5. Добавляем метод __str__()
, который возвращает строковое представление объекта Stock
import threading
import requests
from lxml import html
class Stock(threading.Thread):
def __init__(self, symbol: str) -> None:
super().__init__()
self.symbol = symbol
self.url = f'https://finance.yahoo.com/quote/{symbol}'
self.price = None
def run(self):
response = requests.get(self.url)
if response.status_code == 200:
# parse the HTML
tree = html.fromstring(response.text)
# get the price in text
price_text = tree.xpath(
'//*[@id="quote-header-info"]/div[3]/div[1]/div[1]/fin-streamer[1]/text()')
if price_text:
try:
self.price = float(price_text[0].replace(',', ''))
except ValueError:
self.price = None
def __str__(self):
return f'{self.symbol}\t{self.price}'
Используем многопоточную программу
Импортируем модуль stock.py в основной модуль main.py и используем в нем класс Stock
:
from stock import Stock
symbols = ['MSFT', 'GOOGL', 'AAPL', 'META']
threads = []
for symbol in symbols:
t = Stock(symbol)
t.start()
threads.append(t)
for t in threads:
t.join()
print(t)
Вывод
MSFT 253.67
GOOGL 2280.41
AAPL 145.86
META 163.27
Как это работает
1. Импортируем класс Stock
из модуля stock.py:
from stock import Stock
2. Задаем список символов акций, которые будем считывать:
symbols = ['MSFT', 'GOOGL', 'AAPL', 'META']
3. Создаем поток для каждого символа, запускаем его и добавляем поток в список потоков:
threads = []
for symbol in symbols:
t = Stock(symbol)
t.start()
threads.append(t)
4. Дожидаемся завершения всех потоков в списке потоков и выводим на экран цену акций:
for t in threads:
t.join()
print(t)