From 6b5843b7e5aef6c05e84a01d4a43246d44ee3fdb Mon Sep 17 00:00:00 2001 From: Igor Gakhov Date: Mon, 27 Nov 2023 22:59:01 +0300 Subject: [PATCH 1/2] =?UTF-8?q?=D0=9F=D0=B5=D1=80=D0=B5=D0=BF=D0=B8=D1=81?= =?UTF-8?q?=D1=8B=D0=B2=D0=B0=D0=BD=D0=B8=D0=B5=20=D0=BD=D0=B0=20=D0=B0?= =?UTF-8?q?=D1=81=D0=B8=D0=BD=D1=85=D1=80=D0=BE=D0=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 3 + README.md | 18 ++-- coffee_metro.xlsx | Bin 27971 -> 28775 bytes config.py | 17 ++++ database.py | 76 ++++++++++++++++ main.py | 218 ++++++++++++++++++++++++++-------------------- requirements.txt | 24 +++-- 7 files changed, 247 insertions(+), 109 deletions(-) create mode 100644 config.py create mode 100644 database.py diff --git a/.gitignore b/.gitignore index 7ceacdd..2f4e960 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,5 @@ /venv .idea +.python-version +*.db +__pycache__/* diff --git a/README.md b/README.md index feb2310..b9d3613 100644 --- a/README.md +++ b/README.md @@ -3,27 +3,33 @@ Этот парсер предназначен для сбора информации о продуктах категории кофе на сайте магазина [Метро](https://online.metro-cc.ru/). Он извлекает информацию о продуктах, включая их идентификатор, название, ссылку, обычную цену, промо-цену и бренд (если доступен). Парсер реализован с использованием библиотеки Python requests для HTTP-запросов, bs4 для парсинга HTML и pandas для создания и сохранения данных в файл Excel ### Инструкции по использованию + 1. Убедитесь, что у вас установлены все необходимые библиотеки, перечисленные в файле requirements.txt. Установите их, выполнив команду: + ``` pip install -r requirements.txt ``` + 2. Запустите файл main.py: ``` python main.py ``` -3. Парсер начнет сбор данных о продуктах категории кофе на сайте "Метро". Информация будет выводиться в консоль, а также сохраняется в файл Excel с именем coffee_metro.xlsx +3. Парсер начнет сбор данных о продуктах категории кофе на сайте "Метро". Информация будет выводиться в консоль, а также сохраняется в файл Excel с именем coffee_metro.xlsx 4. По завершении работы парсера в консоли вы увидите сообщение о времени выполнения -## Настройки -Измените переменную link_to_coffee в файле main.py, чтобы указать другую категорию продуктов +## Использование + +При старте программы в консоле возникнет поле ввода. Впишите туда ссылку категории продуктов, которую хотите распарсить. ``` -link_to_coffee = 'https://online.metro-cc.ru/category/chaj-kofe-kakao/kofe?from=under_search&in_stock=1' +Привет! Введи URL категории товара для парсинга: +https://online.metro-cc.ru/category/chaj-kofe-kakao/kofe?from=under_search&in_stock=1 ``` -Вы также можете настроить другие параметры, такие как классы элементов HTML для поиска продуктов и кнопки "Показать еще" в коде main.py, если сайт "Метро" изменится + +Вы также можете настроить другие параметры, такие как классы элементов HTML для поиска продуктов и кнопки "Показать еще" в коде main.py, если сайт "Метро" изменится. ## Дополнения -Для ускорения работы парсера можно использовать асинхронный подход (asyncio, aiohttp). Буду рада :sparkles: pull-request :sparkles: c предложениями по оптимизацией работы кода +Буду рада ✨ pull-request ✨ c предложениями по оптимизацией работы кода. diff --git a/coffee_metro.xlsx b/coffee_metro.xlsx index ec8a270c44602f937577906fd52aa0583364925a..4bea084d83c41fea99bb66f0c097acbe34bc1761 100644 GIT binary patch delta 25036 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 &CITg~NJ0tUU);A3r3aFfk92{)o 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 PnZWBribL-FxBveGa(4G* delta 24153 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 AsyncSession: + async with async_session() as session: + yield session + + +async def connect(): + async with engine.begin() as conn: + await conn.run_sync(Base.metadata.drop_all) + await conn.run_sync(Base.metadata.create_all) + + +class Base(DeclarativeBase): + pass + + +class Product(Base): + __tablename__ = "products" + id = Column(Integer, primary_key=True, autoincrement=True) + product_id = Column(String(255)) + name = Column(String(500)) + link = Column(String(255)) + regular_price = Column(String(255)) + promo_price = Column(String(255)) + brand = Column(String(255)) + + @staticmethod + async def add(**kwargs): + session: AsyncSession = [i async for i in get_session()][0] + async with session: + query = insert(Product).values(**kwargs) + await session.execute(query) + await session.commit() + + @staticmethod + async def export(): + print('Начинаем экспорт данных') + session: AsyncSession = [i async for i in get_session()][0] + async with session: + query = select(Product) + execute = await session.execute(query) + result = execute.unique().scalars().all() + df = pd.DataFrame([{ + 'id': row.product_id, + 'name': row.name, + 'link': row.link, + 'regular_price': row.regular_price, + 'promo_price': row.promo_price, + 'brand': row.brand + } for row in result]) + df.to_excel( + os.path.join(settings.BASE_DIR, 'coffee_metro.xlsx'), + index=False + ) diff --git a/main.py b/main.py index a0746b0..9aebc14 100644 --- a/main.py +++ b/main.py @@ -1,106 +1,134 @@ -import requests +import datetime as dt +import asyncio + +import aiohttp +import aiolimiter import bs4 -import pandas -import datetime +import prompt + +from database import connect, Product + + +PRODUCT_CLASS = 'catalog-2-level-product-card' +SHOW_MORE_BUTTON_CLASS = 'subcategory-or-type__load-more' + + +async def scrape(html: str, session: aiohttp.ClientSession): + # Создаем объект BeautifulSoup для парсинга HTML текущей страницы + soup = bs4.BeautifulSoup(html, 'html.parser') + + # Находим все продукты на текущей странице + products = soup.find_all('div', PRODUCT_CLASS) + + # Проверяем, есть ли продукты на текущей странице + if not products: + return + + # Итерируемся по каждому продукту на текущей странице + for product in products: + product_id = product['data-sku'] + name = product.find('span', 'product-card-name__text').text.strip() + link = 'https://online.metro-cc.ru' + product.find('a', 'product-card-photo__link')['href'] + + # Отправляем GET-запрос к странице продукта + response: aiohttp.ClientResponse = await session.get(link) + + # Проверяем успешность запроса + if not response.ok: + break + + # Считываем контент страницы + html = await response.text() + + # Создаем объект BeautifulSoup для парсинга HTML страницы продукта + link_soup = bs4.BeautifulSoup(html, 'html.parser') + + # Извлекаем информацию о бренде продукта + brand_elem = link_soup.find('meta', {'itemprop': 'brand'}) + brand = brand_elem.get('content') if brand_elem else None + # Извлекаем информацию о цене продукта + regular_price_element = product.find('div', 'product-unit-prices__old-wrapper') + if regular_price_element: + regular_price = regular_price_element.find('span', 'product-price__sum-rubles') + regular_price = regular_price.text.strip().replace(" ", "") if regular_price else None + else: + regular_price = None -link_to_coffee = 'https://online.metro-cc.ru/category/chaj-kofe-kakao/kofe?from=under_search&in_stock=1' -product_class = 'catalog-2-level-product-card' -show_more_button_class = 'subcategory-or-type__load-more' + # Извлекаем информацию о промо-цене продукта + promo_price_element = product.find('div', 'product-unit-prices__actual-wrapper') + if promo_price_element: + promo_price = promo_price_element.find('span', 'product-price__sum-rubles') + promo_price = promo_price.text.strip() if promo_price else None + else: + promo_price = None + # Добавляем информацию о продукте в БД + await Product.add( + product_id = product_id, + name = name, + link = link, + regular_price = regular_price, + promo_price = promo_price, + brand = brand + ) -def parse_metro_categories(category_link): - page_number = 1 - data_list = [] + print(f'Продукт {product_id} добавлен в базу данных.') + + +async def main(): + throttler = aiolimiter.AsyncLimiter(max_rate=1000, time_period=1) # 1000 задач в секунду + + category_link = prompt.string('Привет! Введи URL категории товара для парсинга:\n') + + await connect() # Используем менеджер сеансов для повторного использования соединения - with requests.Session() as session: - while True: - # Формируем URL текущей страницы - current_page_url = f"{category_link}&page={page_number}" - - # Отправляем GET-запрос к текущей странице - request = session.get(current_page_url) - - # Проверяем успешность запроса - if request.status_code != 200: - break - - # Создаем объект BeautifulSoup для парсинга HTML текущей страницы - soup = bs4.BeautifulSoup(request.text, 'html.parser') - - # Находим все продукты на текущей странице - products = soup.find_all('div', product_class) - - # Проверяем, есть ли продукты на текущей странице - if not products: - break - - # Итерируемся по каждому продукту на текущей странице - for product in products: - product_id = product['data-sku'] - name = product.find('span', 'product-card-name__text').text.strip() - link = 'https://online.metro-cc.ru' + product.find('a', 'product-card-photo__link')['href'] - - # Отправляем GET-запрос к странице продукта - link_request = session.get(link) - - # Проверяем успешность запроса - if link_request.status_code == 200: - # Создаем объект BeautifulSoup для парсинга HTML страницы продукта - link_soup = bs4.BeautifulSoup(link_request.text, 'html.parser') - - # Извлекаем информацию о бренде продукта - brand_elem = link_soup.find('meta', {'itemprop': 'brand'}) - brand = brand_elem.get('content') if brand_elem else None - - # Извлекаем информацию о цене продукта - regular_price_element = product.find('div', 'product-unit-prices__old-wrapper') - if regular_price_element: - regular_price = regular_price_element.find('span', 'product-price__sum-rubles') - regular_price = regular_price.text.strip().replace(" ", "") if regular_price else None - else: - regular_price = None - - # Извлекаем информацию о промо-цене продукта - promo_price_element = product.find('div', 'product-unit-prices__actual-wrapper') - if promo_price_element: - promo_price = promo_price_element.find('span', 'product-price__sum-rubles') - promo_price = promo_price.text.strip() if promo_price else None - else: - promo_price = None - - # Добавляем информацию о продукте в список - data_list.append({ - 'id': product_id, - 'name': name, - 'link': link, - 'regular_price': regular_price, - 'promo_price': promo_price, - 'brand': brand - }) - # Выводим последний добавленный продукт и общее количество продуктов (для отладки) - print(data_list[-1]) - print(len(data_list)) - - # Увеличиваем номер страницы для следующего запроса - page_number += 1 - - # Проверяем наличие кнопки "Показать еще" - show_more_button = soup.find('button', {'class': show_more_button_class}) - if not show_more_button: - print("No 'Show More' button found. Exiting.") - break - - # Создаем DataFrame из списка продуктов и сохраняем в Excel-файл - df = pandas.DataFrame(data_list) - df.to_excel('coffee_metro.xlsx', index=False) - return data_list + async with aiohttp.ClientSession() as session: + page_number = 1 + tasks = [] + + async with throttler: + while True: + # Формируем URL текущей страницы + current_page_url = f'{category_link}&page={page_number}' + print(f'Парсим страницу {page_number}.') + + # async with throttler: + # Отправляем GET-запрос к текущей странице + async with session.get(current_page_url) as response: + + # Проверяем успешность запроса + if not response.ok: + break + + # Считываем контент страницы + html = await response.text() + + # Создаем задачу извлечения данных + task = asyncio.create_task(scrape(html, session)) + tasks.append(task) + print(f'Задачу на страницу {page_number} поставили.') + + soup = bs4.BeautifulSoup(html, 'html.parser') + # Проверяем наличие кнопки "Показать еще" + show_more_button = soup.find('button', {'class': SHOW_MORE_BUTTON_CLASS}) + if not show_more_button: + print('Кнопка «Показать ещё» не найдена. Выход.') + break + + # Инкрементируем счетчик + page_number += 1 + # break + + await asyncio.gather(*tasks) + + await Product.export() if __name__ == "__main__": # Замеряем время выполнения - start_time = datetime.datetime.now() - parse_metro_categories(link_to_coffee) - end_time = datetime.datetime.now() - print(f'Время выполнения: {end_time-start_time}') \ No newline at end of file + start_time = dt.datetime.now() + asyncio.run(main()) + end_time = dt.datetime.now() + print(f'Время выполнения: {end_time-start_time}') diff --git a/requirements.txt b/requirements.txt index 01d9d19..d25fa9b 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,16 +1,24 @@ +aiohttp==3.9.1 +aiolimiter==1.1.0 +aiosignal==1.3.1 +aiosqlite==0.19.0 +attrs==23.1.0 beautifulsoup4==4.12.2 -certifi==2023.7.22 -charset-normalizer==3.3.2 et-xmlfile==1.1.0 -idna==3.4 -numpy==1.24.4 +frozenlist==1.4.0 +greenlet==3.0.1 +idna==3.6 +multidict==6.0.4 +numpy==1.26.2 openpyxl==3.1.2 -pandas==2.0.3 +pandas==2.1.3 +prompt==0.4.1 python-dateutil==2.8.2 +python-dotenv==1.0.0 pytz==2023.3.post1 -requests==2.31.0 six==1.16.0 -sortedcontainers==2.4.0 soupsieve==2.5 +SQLAlchemy==2.0.23 +typing_extensions==4.8.0 tzdata==2023.3 -urllib3==2.1.0 +yarl==1.9.3 From 22a3de12f1395643a37c74fa94561e20ff637638 Mon Sep 17 00:00:00 2001 From: Igor Gakhov Date: Tue, 28 Nov 2023 22:00:35 +0300 Subject: [PATCH 2/2] =?UTF-8?q?=D0=A3=D0=B4=D0=B0=D0=BB=D0=B5=D0=BD=D0=B8?= =?UTF-8?q?=D0=B5=20=D0=BB=D0=B8=D1=88=D0=BD=D0=B8=D1=85=20=D0=BA=D0=BE?= =?UTF-8?q?=D0=BC=D0=BC=D0=B5=D0=BD=D1=82=D0=B0=D1=80=D0=B8=D0=B5=D0=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- main.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/main.py b/main.py index 9aebc14..c3c1496 100644 --- a/main.py +++ b/main.py @@ -94,7 +94,6 @@ async def main(): current_page_url = f'{category_link}&page={page_number}' print(f'Парсим страницу {page_number}.') - # async with throttler: # Отправляем GET-запрос к текущей странице async with session.get(current_page_url) as response: @@ -119,7 +118,6 @@ async def main(): # Инкрементируем счетчик page_number += 1 - # break await asyncio.gather(*tasks)