#python #python-3.x #python-asyncio
#python #python-3.x #python-asyncio
Вопрос:
в моем классе у меня есть метод, который извлекает веб-сайт (виден ниже).
Я заметил, что другие методы, использующие этот метод, могут привести к открытию нескольких запросов к одному сайту (когда один запрос ожидает self._page по-прежнему отсутствует).
Как я могу этого избежать?
Я имею в виду, когда есть еще один вызов _get_page, но он находится в ожидании, просто верните future из первого и не повторяйте запрос страницы
async def _get_page(self) -> HtmlElement:
if self._page is None:
async with self._get_session().get(self._url) as page:
self._page = lxml.html.document_fromstring(await page.text())
return self._page
Ответ №1:
Как я могу избежать [множественных запросов]?
Вы могли бы использовать asyncio.Lock
:
saync def __init__(self, ...):
...
self._page_lock = asyncio.Lock()
async def _get_page(self) -> HtmlElement:
async with self._page_lock:
if self._page is None:
async with self._get_session().get(self._url) as page:
self._page = lxml.html.document_fromstring(await page.text())
return self._page
Комментарии:
1. Это, безусловно, более простое решение, чем мое. Кстати, это генерирует:
AttributeError: module 'lxml' has no attribute 'html'
(исходный код OP).2. Я принял запрос OP за чистую монету и вернул Будущее, которое не блокируется.
3. @Booboo Я не уверен, что вы подразумеваете под «возвращением будущего» — ваш
_get_page
, безусловно, ожидает будущего и возвращает фактический результат, как и мой. (Обратите внимание, что вы могли бы простоreturn await future
потому, что ожидание будущего немедленно оценивается по его результату.) Ваш код дублирует функциональность, уже используемую в устройствах синхронизации asyncio, таких какLock
orEvent
, которые отслеживают список официантов очень похожим образом.
Ответ №2:
Обновление для Python 3.8 и jupyter notebook
import asyncio
import aiohttp
from lxml import html
class MyClass:
def __init__(self):
self._url = 'https://www.google.com'
self._page = None
self._futures = []
self._working = False
self._session = aiohttp.ClientSession()
async def _close(self):
if self._session:
session = self._session
self._session = None
await session.close()
def _get_session(self):
return self._session
async def _get_page(self):
if self._page is None:
if self._working:
print('will await current page request')
loop = asyncio.get_event_loop()
future = loop.create_future()
self._futures.append(future)
return await future
else:
self._working = True
session = self._get_session()
print('making url request')
async with session.get(self._url) as page:
print('status =', page.status)
print('making page request')
self._page = html.document_fromstring(await page.text())
print('Got page text')
for future in self._futures:
print('setting result to awaiting request')
future.set_result(self._page)
self._futures = []
self._working = False
return self._page
async def main():
futures = []
m = MyClass()
futures.append(asyncio.ensure_future(m._get_page()))
futures.append(asyncio.ensure_future(m._get_page()))
futures.append(asyncio.ensure_future(m._get_page()))
results = await asyncio.gather(*futures)
for result in results:
print(result[0:80])
await m._close()
if __name__ == '__main__':
asyncio.run(main())
#await main() # In jupyter notebook and iPython
Обратите внимание, что в Windows 10 я видел при завершении:
RuntimeError: Event loop is closed
Комментарии:
1. Я обновил ответ, чтобы рассмотреть Python 3.8, Jupyter Notebook и некоторые ложные ошибки, которые вы могли видеть в Windows 10. Небольшая обратная связь была бы оценена.