使用Python'获取数据;按顺序同步异步IO
我有一个Python2.7程序,它从网站中提取数据,并将结果转储到数据库中。它遵循消费者-生产者模型,并使用线程模块编写 只是为了好玩,我想用新的asyncio模块(3.4版)重写这个程序,但我不知道如何正确地完成 最关键的要求是程序必须按顺序从同一网站获取数据。例如,对于url“”,它应该首先获取“”,然后获取“”,然后获取“”。。。 如果未按顺序获取页面,则网站将完全停止交付页面,您必须从0开始 但是,另一个网站(“”)的另一个获取可以(也应该)同时运行(其他网站也有相应的限制) 线程模块非常适合这种情况,因为我可以为每个网站创建单独的线程,在每个线程中,它可以等到一个页面加载完毕后再获取另一个页面 下面是线程版本(Python2.7)的一段非常简化的代码片段: 下面是我如何尝试使用asyncio(在3.4.1中)实现的: 它以非连续的顺序获取并打印所有内容。嗯,我想这就是这些合作的全部想法。我是否应该不使用aiohttp而只使用urllib进行获取?但是,第一家餐馆的回迁会阻止其他餐馆的回迁吗?我是不是觉得这完全错了?使用Python'获取数据;按顺序同步异步IO,python,asynchronous,python-asyncio,Python,Asynchronous,Python Asyncio,我有一个Python2.7程序,它从网站中提取数据,并将结果转储到数据库中。它遵循消费者-生产者模型,并使用线程模块编写 只是为了好玩,我想用新的asyncio模块(3.4版)重写这个程序,但我不知道如何正确地完成 最关键的要求是程序必须按顺序从同一网站获取数据。例如,对于url“”,它应该首先获取“”,然后获取“”,然后获取“”。。。 如果未按顺序获取页面,则网站将完全停止交付页面,您必须从0开始 但是,另一个网站(“”)的另一个获取可以(也应该)同时运行(其他网站也有相应的限制) 线程模块非
(这只是一个尝试按顺序取东西的测试。还没有到队列部分。)对于不关心请求顺序的餐厅,您当前的代码可以正常工作。对菜单的所有十个请求将同时运行,并在完成后立即打印到标准输出 显然,这对需要连续请求的餐厅不起作用。您需要进行一些重构才能使其正常工作:
@asyncio.coroutine
def fetch(url):
response = yield from aiohttp.request('GET', url)
response = yield from response.read_and_close()
return response.decode('utf-8')
@asyncio.coroutine
def print_page(url):
page = yield from fetch(url)
print(page)
@syncio.coroutine
def print_pages_sequential(url, num_pages):
for food in range(num_pages):
menu_url = url + '/' + str(food)
yield from print_page(menu_url)
l = [print_pages_sequential('http://a-restaurant.com/menu', 10)]
conc_url = 'http://another-restaurant.com/menu'
for food in range(10):
menu_url = conc_url + '/' + str(food)
l.append(print_page(menu_url))
loop.run_until_complete(asyncio.wait(l))
我们没有将顺序餐厅的所有十个请求都添加到列表中,而是向列表中添加一个协同路由,它将按顺序迭代所有十个页面。其工作方式是,yield from print\u page
将停止执行print\u pages\u sequential
,直到print\u page
请求完成,但它不会阻止同时运行的任何其他协同程序(就像附加到l
的所有print\u page
调用)
通过这种方式,您的所有“另一家餐馆”请求都可以完全并发运行,就像您希望的那样,您的“a-restaurant”请求将按顺序运行,但不会阻止任何“另一家餐馆”请求
编辑:
如果所有站点都有相同的顺序抓取要求,则可以进一步简化逻辑:
l = []
urls = ["http://a-restaurant.com/menu", "http://another-restaurant.com/menu"]
for url in urls:
menu_url = url + '/' + str(food)
l.append(print_page_sequential(menu_url, 10))
loop.run_until_complete(asyncio.wait(l))
asyncio.Task
是asyncio世界中threading.Thread
的替代品。
asyncio.async
也会创建新任务
asyncio.gather
是等待多个协同进程的非常方便的方法,我更喜欢它而不是asyncio.wait
@asyncio.coroutine
def fetch(url):
response = yield from aiohttp.request('GET', url)
response = yield from response.read_and_close()
return response.decode('utf-8')
@asyncio.coroutine
def print_page(url):
page = yield from fetch(url)
print(page)
@asyncio.coroutine
def process_restaurant(url):
for food in range(10):
menu_url = url + '/' + str(food)
yield from print_page(menu_url)
urls = ('http://a-restaurant.com/menu', 'http://another-restaurant.com/menu')
coros = []
for url in urls:
coros.append(asyncio.Task(process_restaurant(url)))
loop.run_until_complete(asyncio.gather(*coros))
谢谢你,达诺。需要明确的是:所有餐厅都需要在其菜单中按顺序获取数据,但我希望同时从第一家餐厅和第二家餐厅获取数据(仅他们各自的菜单获取需要按顺序)。所以我想解决办法是
l=[打印页面]http://a-restaurant.com/menu,10),打印页面http://another-restaurant.com/menu“,10)]
然后运行循环。运行直到完成(asyncio.wait(l))
(现在无法测试)。@user3313978啊,对不起,我误解了这个要求。考虑到该约束,您对解决方案的假设是正确的。我更新了我的答案以反映新的约束。这仍然无法按顺序启动请求,@dano。不幸的是,collect
和wait
以不确定的顺序安排任何传入的协程,因为它将它们包装在Task
s中。看见解决方法是在将每个协同路由对象传递到gather
或wait
之前,手动为它们分配一个循环任务。e、 g.l.append(循环。创建任务(打印页面\u顺序(菜单\u url,10))
很高兴知道<代码>异步IO似乎比我预期的要复杂一些。顺便说一句,def process_restaurant(url)
缺少一个缩进级别。process_restaurant
的标记是固定的。谢谢你的报告。
l = []
urls = ["http://a-restaurant.com/menu", "http://another-restaurant.com/menu"]
for url in urls:
menu_url = url + '/' + str(food)
l.append(print_page_sequential(menu_url, 10))
loop.run_until_complete(asyncio.wait(l))
@asyncio.coroutine
def fetch(url):
response = yield from aiohttp.request('GET', url)
response = yield from response.read_and_close()
return response.decode('utf-8')
@asyncio.coroutine
def print_page(url):
page = yield from fetch(url)
print(page)
@asyncio.coroutine
def process_restaurant(url):
for food in range(10):
menu_url = url + '/' + str(food)
yield from print_page(menu_url)
urls = ('http://a-restaurant.com/menu', 'http://another-restaurant.com/menu')
coros = []
for url in urls:
coros.append(asyncio.Task(process_restaurant(url)))
loop.run_until_complete(asyncio.gather(*coros))