aiohttp с соединителем. Сеанс закрыт и исключения для незакрытого соединителя
Моя программа несколько раз отправляет запросы на сайт черезaiohttp
. Еще мне нужно использовать прокси, поэтому я использую коннектор отaiohttp-socks
библиотека. Но после реализации функционала я столкнулся с исключением.
Я привел наглядный пример, который можно использовать для воспроизведения исключения.
требования.txt
aiohttp==3.8.4
aiohttp-socks==0.8.0
PySocks==1.7.1
main.py
import asyncio
from typing import Optional
import aiohttp
from aiohttp_socks import ProxyConnector
async def async_get(url: str, connector: Optional[ProxyConnector] = None, **kwargs) -> str:
async with aiohttp.ClientSession(connector=connector) as session:
print(session)
async with session.get(url=url, **kwargs) as response:
status_code = response.status
if status_code <= 201:
return await response.text()
raise Exception(f'Unsuccessful request: {status_code}')
async def main():
proxy = 'socks5://username:password@0.0.0.0:1111'
connector = ProxyConnector.from_url(url=proxy, rdns=True)
for _ in range(3):
await async_get(url='https://www.google.com/', connector=connector)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Выход
<aiohttp.client.ClientSession object at 0x0000011A5F468940>
Traceback (most recent call last):
File "...\program\main.py", line 28, in <module>
loop.run_until_complete(main())
File "...\Python\Python38\lib\asyncio\base_events.py", line 616, in run_until_complete
return future.result()
File "...\program\main.py", line 23, in main
await async_get(url='https://www.google.com/', connector=connector)
File "...\program\main.py", line 11, in async_get
async with session.get(url=url, **kwargs) as response:
File "...\program\venv\lib\site-packages\aiohttp\client.py", line 1141, in __aenter__
self._resp = await self._coro
File "...\program\venv\lib\site-packages\aiohttp\client.py", line 400, in _request
raise RuntimeError("Session is closed")
RuntimeError: Session is closed
<aiohttp.client.ClientSession object at 0x0000011A5F48FE50>
После поиска чужих решений я понял, что после закрытия сессии и коннектора он продолжает указывать на одну и ту же сессию. Чтобы этого не произошло, мне нужно сделать одно из следующих действий:
- Инициализируйте новый соединитель для каждого сеанса;
- Пройти
connector_owner=False
аргумент сеанса, чтобы он не закрывал соединитель.
Я решил использовать второй метод:
async with aiohttp.ClientSession(connector=connector, connector_owner=False) as session:
Но когда я запустил программу с этим исправлением, появилось новое исключение:
<aiohttp.client.ClientSession object at 0x0000024DD07D9940>
<aiohttp.client.ClientSession object at 0x0000024DD07FFE50>
<aiohttp.client.ClientSession object at 0x0000024DD07FF550>
Unclosed connector
connections: ['[(<aiohttp.client_proto.ResponseHandler object at 0x0000024DD08066A0>, 69074.984)]']
connector: <aiohttp_socks.connector.ProxyConnector object at 0x0000024DD07D9820>
1 ответ
После безуспешных попыток найти какое-то решение, я решил разобраться во всем самостоятельно.
Поскольку исключение говорит, что коннектор не закрыт, то логично, что я должен закрыть его после завершения работы программы. Я нашел два способа сделать это.
соединитель.закрыть()
Самый очевидный способ сделать это — использовать специальную функцию.
main.py
import asyncio
from typing import Optional
import aiohttp
from aiohttp_socks import ProxyConnector
async def async_get(url: str, connector: Optional[ProxyConnector] = None, **kwargs) -> str:
async with aiohttp.ClientSession(connector=connector, connector_owner=False) as session:
print(session)
async with session.get(url=url, **kwargs) as response:
status_code = response.status
if status_code <= 201:
return await response.text()
raise Exception(f'Unsuccessful request: {status_code}')
async def main():
proxy = 'socks5://username:password@0.0.0.0:1111'
connector = ProxyConnector.from_url(url=proxy, rdns=True)
for _ in range(3):
await async_get(url='https://www.google.com/', connector=connector)
connector.close() # Close connector
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())
Этот метод подходит для простой программы, такой как пример. Моя программа гораздо сложнее, поэтому реализовать этот метод будет сложно. Кроме того, после нескольких запусков я заметил, что функция печати в некоторых случаях печатает две одинаковые сессии. Вы можете использовать print(id(session)) чтобы убедиться, что они идентичны.
<aiohttp.client.ClientSession object at 0x0000027BC8ED8940>
<aiohttp.client.ClientSession object at 0x0000027BC8EFFE50>
<aiohttp.client.ClientSession object at 0x0000027BC8EFFE50>
Для моей программы такое поведение нежелательно. Поэтому я продолжал искать решения и придумал еще одно.
аргумент соединителя
Если я хочу, чтобы для каждого запроса использовался новый сеанс, мне нужно найти, как это сделать. Исследуя разъем базы, я нашел
Итог: окончательное решение, которое я использую, выглядит следующим образом.
main.py
import asyncio
from typing import Optional
import aiohttp
from aiohttp_socks import ProxyConnector
async def async_get(url: str, connector: Optional[ProxyConnector] = None, **kwargs) -> str:
async with aiohttp.ClientSession(connector=connector, connector_owner=False) as session:
print(session)
async with session.get(url=url, **kwargs) as response:
status_code = response.status
if status_code <= 201:
return await response.text()
raise Exception(f'Unsuccessful request: {status_code}')
async def main():
proxy = 'socks5://username:password@0.0.0.0:1111'
connector = ProxyConnector.from_url(url=proxy, rdns=True, force_close=True)
for _ in range(3):
await async_get(url='https://www.google.com/', connector=connector)
if __name__ == '__main__':
loop = asyncio.get_event_loop()
loop.run_until_complete(main())