Приготовление асинхронного супа. Часть 2



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

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

import aiohttp
import asyncio
import requests
from bs4 import BeautifulSoup

category_lst = []
pagen_lst = []
domain = 'https://mob25.com/'
def get_soup(url):
    resp = requests.get(url=url)
    return BeautifulSoup(resp.text, 'lxml')
def get_urls_categories(soup):
    all_link = soup.find('div', class_='nav_menu').find_all('a')
    for cat in all_link:
        category_lst.append(domain + cat['href'])
def get_urls_pages(category_lst):
    for cat in category_lst:
        resp = requests.get(url=cat)
        soup = BeautifulSoup(resp.text, 'lxml')
        for pagen in soup.find('div', class_='pagen').find_all('a'):
            pagen_lst.append(domain + pagen['href'])
async def get_data(session, link):
    async with session.get(url=link) as response:
        resp = await response.text()
        soup = BeautifulSoup(resp, 'lxml')
        item_card = [x['href'] for x in soup.find_all('a', class_='name_item')]
        for x in item_card:
            url2 = domain + x
            async with session.get(url=url2) as response2:
                resp2 = await response2.text()
                soup2 = BeautifulSoup(resp2, 'lxml')
                article = soup2.find('p', class_='article').text
                name = soup2.find('p', id='p_header').text
                price = soup2.find('span', id='price').text
                print(url2, price, article, name)
async def main():
    async with aiohttp.ClientSession() as session:
        tasks = []
        for link in pagen_lst:
            task = asyncio.create_task(get_data(session, link))
            tasks.append(task)
        await asyncio.gather(*tasks)
url = 'https://mob25.com/'
soup = get_soup(url)
get_urls_categories(soup)
get_urls_pages(category_lst)
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
asyncio.run(main())

 Итак, давайте подробнее поговорим про каждую из функций, сверху-вниз.

Функция def get_soup(url):

def get_soup(url):
    resp = requests.get(url=url)
    return BeautifulSoup(resp.text, 'lxml')

Эта функция максимально проста, она извлекает суп из переданной в нее первой ссылки на сайте.

Функция get_urls_categories(soup):

def get_urls_categories(soup):
    all_link = soup.find('div', class_='nav_menu').find_all('a')
    for cat in all_link:
        category_lst.append(domain + cat['href'])

Эта функция извлекает все ссылки из правого списка с навигацией, в которой находятся все категории товаров, также она наполняет глобальный список category_lst = [] ссылками всех категорий. На нашем сайте-тренажере всего пять категорий, и все пять ссылок попадают в этот глобальный список. Он нам потребуется для извлечения длины пагинации в каждой из этих категорий.

Функция def get_urls_pages(category_lst):

Эта функция собирает все ссылки пагинации, которые находятся в каждой из категорий. Также эта функция формирует глобальный список pagen_lst = [], на каждой из этих ссылок находится по восемь карточек с товаром, из которых мы будем извлекать данные в асинхронных функциях.

Асинхронная функция async def main():

В этой функции происходит создание session при помощи библиотеки aiohttp, также в этой функции мы создаем таски(задачи) task = asyncio.create_task(get_data(session, link)),в каждую задачу мы заворачиваем корутину get_data(session, link) и передаем в нее созданную ранее session и ссылку, полученную из глобального списка pagen_lst. Когда все задачи сформированы и список tasks наполнен, мы передаем распакованный список tasks в функцию .gather(*tasks), чтобы она собрала все задачи и передала их в цикл событий. Каждый из тасков (задач) имеет тип  <class ‘_asyncio.Task’>. Каждый task является awaitable-объектом который подходит для их передачи в функцию .gather(*tasks) для дальнейшей отправки в цикл событий.

Функция async def get_data(session, link):

В этой функции происходит извлечение данных с каждой страницы. Эта функция получает на вход session и link из глобального списка со ссылками pagen_lst=[].  В этой функции мы работаем с BeautifulSoup точно так же, как и работали бы при написании синхронного парсера. Здесь нам пришлось использовать session дважды, для более глубокого извлечения данных из карточек. Аналогичный код мы писали в задачах по сбору данных со всех 160 карточек с товаром. В асинхронном коде такой подход тоже допустим, т.к. мы используем одну session для всех наших запросов. Пример ниже наглядно продемонстрирует множественное переиспользование сессии для многоуровневого извлечения данных. Пример синтетический, но хорошо демонстрирует возможности глубины парсинга. Попробуйте понять логику этого кода, она не очень сложная, но понимание этого облегчит написание собственных парсеров.

import aiohttp
import asyncio
from bs4 import BeautifulSoup

async def main(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url=url) as response:
            resp = await response.text()
            soup = BeautifulSoup(resp, 'lxml')
            item_card = [x['href'] for x in soup.find_all('a', class_='link')]
            for url2 in item_card:
                async with session.get(url=url2) as response2:
                    resp2 = await response2.text()
                    soup2 = BeautifulSoup(resp2, 'lxml')
                    item_card2 = [x['href'] for x in soup2.find_all('a', class_='link2')]
                    for url3 in item_card2:
                        async with session.get(url=url3) as response3:
                            resp3 = await response3.text()
                            soup3 = BeautifulSoup(resp3, 'lxml')
                            item_card3 = [x['href'] for x in soup3.find_all('a', class_='link3')]
                            for url4 in item_card3:
                                async with session.get(url=url4) as response4:
                                    resp4 = await response4.text()
                                    soup4 = BeautifulSoup(resp4, 'lxml')
                                    data = [x['href'] for x in soup4.find_all('div', class_='data')]
                                    print(data)
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
asyncio.run(main('http://example.com'))


Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: