Скрипт работает очень медленно, даже если он работает асинхронно - PullRequest
0 голосов
/ 11 декабря 2018

Я написал скрипт в asyncio в сочетании с aiohttp для анализа содержимогосайт асинхронно.Я попытался применить логику в следующем скрипте так, как это обычно применяется в scrapy.

Однако, когда я выполняю свой скрипт, ондействует так же, как синхронные библиотеки, такие как requests или urllib.request.Поэтому он очень медленный и не служит цели.

Я знаю, что могу обойти это, определив все ссылки на следующей странице в пределах link переменная.Но разве я не выполняю задачу с моим существующим сценарием уже правильно?

Внутри сценария функция processing_docs() собирает все ссылки разных постов и передает уточненные ссылки в fetch_again() функция для извлечения заголовка с его целевой страницы.В функции processing_docs() применяется логика, которая собирает ссылку на следующую страницу и передает ее в функцию fetch(), чтобы повторить то же самое.This next_page call is making the script slower whereas we usually do the same in scrapy and get expected performance.

Мой вопрос: как мне добиться того же самого, сохранив существующую логику?

import aiohttp
import asyncio
from lxml.html import fromstring
from urllib.parse import urljoin

link = "https://stackoverflow.com/questions/tagged/web-scraping"

async def fetch(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as response:
            text = await response.text()
            result = await processing_docs(session, text)
        return result

async def processing_docs(session, html):
        tree = fromstring(html)
        titles = [urljoin(link,title.attrib['href']) for title in tree.cssselect(".summary .question-hyperlink")]
        for title in titles:
            await fetch_again(session,title)

        next_page = tree.cssselect("div.pager a[rel='next']")
        if next_page:
            page_link = urljoin(link,next_page[0].attrib['href'])
            await fetch(page_link)

async def fetch_again(session,url):
    async with session.get(url) as response:
        text = await response.text()
        tree = fromstring(text)
        title = tree.cssselect("h1[itemprop='name'] a")[0].text
        print(title)

if __name__ == '__main__':
    loop = asyncio.get_event_loop()
    loop.run_until_complete(asyncio.gather(*(fetch(url) for url in [link])))
    loop.close()

1 Ответ

0 голосов
/ 11 декабря 2018

Весь смысл использования asyncio заключается в том, что вы можете запускать несколько выборок одновременно (параллельно друг другу).Давайте посмотрим на ваш код:

for title in titles:
    await fetch_again(session,title)

Эта часть означает, что каждый новый fetch_again будет запускаться только после того, как ожидался предыдущий (законченный).Если вы делаете это таким образом, да, нет никакой разницы с использованием подхода синхронизации.

Чтобы задействовать всю мощь асинхронного запуска, запускайте несколько выборок одновременно, используя asyncio.gather:

await asyncio.gather(*[
    fetch_again(session,title)
    for title 
    in titles
])

.увидеть значительное ускорение.


Вы можете перейти к событию и начать fetch для следующей страницы одновременно с fetch_again для заголовков:

async def processing_docs(session, html):
        coros = []

        tree = fromstring(html)

        # titles:
        titles = [
            urljoin(link,title.attrib['href']) 
            for title 
            in tree.cssselect(".summary .question-hyperlink")
        ]

        for title in titles:
            coros.append(
                fetch_again(session,title)
            )

        # next_page:
        next_page = tree.cssselect("div.pager a[rel='next']")
        if next_page:
            page_link = urljoin(link,next_page[0].attrib['href'])

            coros.append(
                fetch(page_link)
            )

        # await:
        await asyncio.gather(*coros)

Важноnote

Хотя такой подход позволяет вам делать вещи намного быстрее, вы можете ограничить количество одновременных запросов одновременно, чтобы избежать значительного использования ресурсов как на вашей машине, так и на сервере.

Вы можете использовать asyncio.Semaphore для этой цели:

semaphore = asyncio.Semaphore(10)

async def fetch(url):
    async with semaphore:
        async with aiohttp.ClientSession() as session:
            async with session.get(url) as response:
                text = await response.text()
                result = await processing_docs(session, text)
            return result
...