Как преобразовать код, использующий генераторы, в асинхронный

У меня есть часть кода, связанного с вводом-выводом, который в основном занимается веб-скрапингом для моего исследовательского проекта.

Код начинался с императива, затем превратился в списки, которые теперь в основном превратились в генераторы:

if __name__ == '__main__':
    while True:
        with suppress(Exception):
            page = requests.get(baseUrl).content
        urls = (baseUrl + link['href'] for link in BeautifulSoup(page,'html.parser').select('.tournament a'))
        resources = (scrape_host(url) for url in urls)
        keywords = ((keywords_for_resource(referer, site_id), rid) for
                          referer, site_id, rid in resources)
        output = (scrape(years, animals) for years, animals in keywords)
        responses = (post_data_with_exception_handling(list(data)) for data in output)
        for response in responses:
            print(response.status_code)

Такой код действительно хорошо укладывается в моей голове, и, поскольку он основан на генераторах без сохранения большого количества состояния, я решил, что могу довольно легко превратить его в код на основе asyncio:

async def fetch(session, url):
    with async_timeout.timeout(10):
        async with session.get(url) as response:
            return await response.text()             
async def main(loop):
    async with aiohttp.ClientSession(loop=loop) as session: 
        page = await fetch(session,baseUrl)
        urls = (baseUrl + link['href'] for link in BeautifulSoup(page,'html.parser').select('.tournament a'))
        subpages = (await fetch(session,url) for url in urls)

Однако в Python 3.5 это просто возвращает Syntax error, поскольку выражение await не разрешено внутри понимания.

Python 3.6 обещает реализовать асинхронные генераторы в pep 530.

Позволит ли эта функция легко преобразовать код на основе генератора в код asyncio, или его также потребуется полностью переписать?


person Sebastian Wozny    schedule 23.11.2016    source источник
comment
вышел python 3.6 :-)   -  person Udi    schedule 25.12.2016


Ответы (1)


Здесь лучшим решением может быть asyncio.as_completed():

# pip install beautifulsoup4 aiohttp
import asyncio
from urllib.parse import urljoin

import aiohttp
import async_timeout
from bs4 import BeautifulSoup

BASE_URL = "http://www.thewebsiteyouarescraping.com/"
SELECTOR = ".tournament a"

async def fetch(session, url):
    with async_timeout.timeout(10):
        async with session.get(url) as response:
            return url, await response.text()


async def main(base_url, selector, loop):
    async with aiohttp.ClientSession(loop=loop) as session:
        _, page = await fetch(session, base_url)
        urls = (urljoin(base_url, link['href']) for link in
                BeautifulSoup(page, 'html.parser').select(selector))
        tasks = {fetch(session, url): url for url in urls}
        for fut in asyncio.as_completed(tasks, loop=loop):
            process(*await fut)
        # Compare with:
        # for fut in tasks:
        #     process(*await fut)


def process(url, page):
    print(url, len(page))


loop = asyncio.get_event_loop()
loop.run_until_complete(main(BASE_URL, SELECTOR, loop))
loop.close()
person Udi    schedule 25.12.2016