Многопоточный парсинг на Python
В этой статье я расскажу, как значительно ускорить парсинг сайта. Те, кто изучил парсинг и начал применять его на практике приходят к такому моменту как ожидание окончания работы парсера, в особенности, когда объем данных высок. На скорость работы влияет множество параметров, один из них — это время ответа сервера, т.к. серверу нужно время чтобы обработать информацию.
В статье я опишу один из способов борьбы с этой проблемой.
Использоваться будет библиотека «concurrent.futures». Использовать «concurrent» нужно умеренно, не стоит устраивать «DDoS» атаку на сайт.
Для создания многопотока ничего мудрёного делать не придётся, код очень прост и понятен. После установки импортируем библиотеки. Добавлены 2 дополнительные библиотеки, это «tqdm» индикатор процесса и «pandas» для создания датафрейма.
import requests import pandas as pd from tqdm import tqdm import concurrent.futures
Прописываем «headers» в request запросы, создаем сессию, объявляем функцию отправки запросов
headers = < 'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_1_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.146 Safari/537.36' >TIMEOUT = 10 session = requests.Session() session.headers = headers def load_url(url): answer = requests.get(url, headers=headers, timeout=TIMEOUT) return [url, answer.text]
Создаем датафрейм со списком «url» из файла «test.xlsx». Для тестирования берем только первые 2000 записей
df = pd.read_excel('test.xlsx') df.head()
| Index | source.objectHrefTerm |
| 0 | https://test.ru/PurchaseView/43/0/0/705675 |
| 1 | https://test.ru/PurchaseView/30/0/0/705663 |
| 2 | https://test.ru/PurchaseView/43/0/0/705661 |
| 3 | https://test.ru/PurchaseView/30/0/0/705653 |
| 4 | https://test.ru/PurchaseView/20/0/0/705300 |
urls = list(df['source.objectHrefTerm'])[:2000]
Вначале протестируем время выгрузки одним потоком
out = [] for url in tqdm(urls): out.append(load_url(url))

Прогресс-бар показывает скорость выполнения — 2 минуты и 21 секунду. Среднее количество запросов в секунду — 14.15.
Теперь сделаем те же 2000 запросов, используя concurrent.futures и 2 потока. Изменяется только параметр CONNECTIONS
out = [] CONNECTIONS = 2 with concurrent.futures.ThreadPoolExecutor(max_workers=CONNECTIONS) as executor: future_to_url = (executor.submit(load_url, url) for url in urls) for future in tqdm(concurrent.futures.as_completed(future_to_url), total=len(urls)): try: data = future.result() except Exception as exc: data = str(type(exc)) finally: out.append(data)

Прогресс-бар показал скорость выполнения 1 минута и 16 секунд. В среднем 26.06 запроса в секунду.
Наблюдается уже значительное увеличение скорости. Попробуем использовать 5 потоков.
out = [] CONNECTIONS = 5

Скорость парсера увеличилась почти в 5 раз.
При использовании большего количества потоков не на все запросы приходили ответы либо на время блокировался ip адрес.
Многопоточный парсинг позволяет нам выполнять работу быстрее, в нашем случае ускорение работы достигло 4,85 раз, что дает существенную экономию времени в больших пулах запросов.
Как ускорить парсинг python
Всем привет. Я пишу бота для тг. И суть его в том, что присылаются породы кроликов и потом присылаются выбранные (например, выбрал породу минилоп, и все представители этой породы берутся с сайта). Берется картинка, порода, цена и есть ли скидка. Присылается первый кролик, потом по кнопке второй и т.д. Я заметил, что сбор данных очень долгий (1.67 сек). При нажатии на кнопку у меня заново все парсится, и поэтому все работает не очень плавно. Как можно его ускорить?
from bs4 import BeautifulSoup from datetime import timedelta, datetime from collections import namedtuple import requests import time class Parser: """ Class Parser use for parsing info about rabbits General use - take info from div tag with class 'product-wrapper' Note: Web-site: https://tsarskiykrolik.com/ Methods: -------------------- get_hours_from_td(data: datetime.timedelta) - Returns hours from timedelta convert_hours_to_word(hour: int) - Changes the ending of a word for correct form convert_days_to_word(day: int) - Changes the ending of a word for correct form parse_date(str_date: str) - Converts str date to datetime type format_datetime(date: datetime.timedelta) - Returns a string with the number of days and hours until the end of the discount parse(breed: str) - Returns a list of namedtuple with information about rabbits """ def __init__(self): pass @staticmethod def get_hours_from_td(date: timedelta) -> int: """Returns hours from timedelta""" return date.seconds // 3600 @staticmethod def convert_hours_to_word(hour: int) -> str: """Returns correct ending of a word(час/часа/часов)""" if hour str: """Returns correct ending of a word(день, дня, дней)""" if day == 1: return 'день' elif 2 datetime: """Returns date at datetime type""" return datetime.strptime(str_date, '%Y-%m-%d %H:%M:%S') def format_datetime(self, date: timedelta) -> str: """Returns a string with the number of days and hours until the end of the discount. If days until the end of discount less 0, only the hours are returned""" time_end = (date.days, self.get_hours_from_td(date)) if time_end[0] == 0 and time_end[1] > 0: # if days < 0 return f'' else: return f' и ' @staticmethod def get_url(breed: str) -> str: """Takes breed of the rabbit and returns url of them""" return f'https://tsarskiykrolik.com/product-category/kroliki/?filter_poroda=' def parse(self, breed: str) -> list[namedtuple]: """Takes breed from user(inline-button) and parse info about it from site. The Exception is raised if no discount in block""" url = self.get_url(breed) page = requests.get(url) soup = BeautifulSoup(page.text, 'html.parser') products = soup.find_all('div', class_='product-wrapper') res = [] for block in products: breed = block.find('h3').text.strip() price = block.find('span', class_='price').text.split() img_url = block.find('img').get('data-srcset').split(', ')[0][:-4] more_info = block.find('h3').find('a')['href'] end_discount = timedelta(days=0, hours=0) time_to_end = timedelta(days=0, hours=0) try: end_discount = ( self.parse_date( block.find( 'div', class_='wd-product-countdown wd-timer woodmart-product-countdown woodmart-timer').attrs[ 'data-end-date']) ) current_date = datetime.now() time_to_end = end_discount - current_date + timedelta(hours=3) except AttributeError: pass old_price = ''.join(price[:price.index('₽') + 1]) discount_price = ''.join(price[price.index('₽') + 1:]) Rabbit = namedtuple('Rabbit', 'breed, img_url, old_price, discount_price, time_to_disc_end, more_info') rabbit = Rabbit(breed, img_url, old_price, discount_price, self.format_datetime(time_to_end), more_info) res.append(rabbit) return res if __name__ == '__main__': a = Parser() start = time.monotonic() print(*a.parse('germelin'), sep='\n') print(time.monotonic() - start)
Как ускорить парсинг на Python?
Добрый вечер, тостерчане! Захотелось в научных целях пропарсить башорг, ithappens и задолбали. Написал банальный парсер на bs4, скорость при 4 потоках 10 постов в секунду. Сеть 100Мбит (Монреаль). Пинг до башорга 82ms. Time показывает скорость загрузки страницы в одном потоке за 365ms. При таком раскладе, только на башорг уйдет около суток. Есть ли способы ускорить процесс?
- Вопрос задан более трёх лет назад
- 2358 просмотров
Комментировать
Решения вопроса 1

Парсинг вам не нужно ускорять
Вам нужно ускорить граббинг
А ускорить его можно если сделать кеширующий днс поближе, сам граббер разместить поближе к хосту донору
И если парсинг сделать на lxml, то и ускорять там уже нечего — он и так на Си написан
Ответ написан более трёх лет назад
Комментировать
Нравится 3 Комментировать
Ответы на вопрос 3
если используете только многопоточность, то можно запускать по 20-40 потоков или больше если процессор позволяет.
увеличивайте число потоков пока не нагрузите 100Мбит или процессор
Ответ написан более трёх лет назад
Комментировать
Нравится 1 Комментировать
Скачайте сайты многопоточной качалкой (типа winhttrack) и парсите уже со своего диска
Ответ написан более трёх лет назад
Комментировать
Нравится Комментировать
башорг 1300+ страниц, выкачивается достаточно быстро, а дальше простой простой разбор. Также и с остальными сайтами.
Ответ написан более трёх лет назад
Комментировать
Нравится Комментировать
Ваш ответ на вопрос
Войдите, чтобы написать ответ

- Python
- +2 ещё
TypeError: event_list() missing 1 required positional argument: ‘call_data» aiogram при использовании CallbackFactory?
- 1 подписчик
- 12 часов назад
- 25 просмотров
