Aiohttp — работа с SOCKS



В aiohttp работа с SOCKS отличается от работы с простыми HTTP/HTTPS-прокси, и в этом разделе мы подробно об этом поговорим.

Для работы с SOCKS4 и SOCKS5 нам необходимо использовать класс ProxyConnector из модуля aiohttp_socks. Для этого его нужно импортировать. Заодно импортируем ProxyType и ChainProxyConnector. Они немного сложнее в работе и для написания парсера с множеством прокси подходят не совсем хорошо, но знать про них нужно.

from aiohttp_socks import ProxyType, ProxyConnector, ChainProxyConnector

ProxyConnector

ProxyConnector — это самый простой способ передать SOCKS4 и SOCKS5 в асинхронную сессию, для этого нужно передать прокси как продемонстрировано в коде ниже.

async def main(url):
    connector = ProxyConnector.from_url('socks5://user:password@127.0.0.1:1080')
    async with aiohttp.ClientSession(connector=connector, trust_env=True) as session:
        async with session.get(url) as response:
            return await response.text()

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

connector = ProxyConnector(
    proxy_type=ProxyType.SOCKS5,
    host='127.0.0.1',
    port=1080,
    username='user',
    password='password',
    rdns=True
)

Самая распространенная ситуация — когда у вас есть список рабочих прокси. Вы хотите поочередно передать каждый прокси в сессию, тем самым увеличивая время жизни вашего парсера. Для этого нам понадобится код с прошлого раздела, который мы немного изменили.

Для запуска этого кода вам потребуется файл proxy_list_SOCKS.txt, который вам необходимо создать самостоятельно. Поместите свои SOCKS в этот файл. Обратите внимание, что мы передаем в сессию только connector, в котором на каждой итерации цикла будет храниться новый SOCKS.

Этот код создает на каждой итерации новую ClientSession(), в которую мы передаем параметры ProxyConnector, в котором на каждой итерации мы передаем новый прокси. Пересоздавать на каждой итерации новую сессию считается не очень хорошей идеей, но для небольших парсеров вполне сгодится. Сильная сторона такого подхода в том, что при плохом прокси в списке код продолжит работать со следующим, и так до тех пор, пока прокси не закончатся. Как делать повторные запросы через прокси, которые были забанены, мы поговорим в следующем разделе курса.

import aiohttp
import asyncio
import aiofiles
from aiohttp_socks import ProxyConnector

async def main():
    # Установка timeout с помощью класса aiohttp.ClientTimeout
    timeout = aiohttp.ClientTimeout(total=.5)
    async with aiofiles.open('proxy_list_SOCKS.txt', mode='r') as f:
        for prx in await f.readlines():
            url = 'http://httpbin.org/ip'
            connector = ProxyConnector.from_url(f'socks4://{prx}')
            try:
                async with aiohttp.ClientSession(connector=connector, timeout=timeout) as session:
                    async with session.get(url=url, timeout=1) as response:
                        if response.ok:
                            print(f'good proxy, status_code -{response.status}-', prx, end='')
            except Exception as _ex:
                continue
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
asyncio.run(main())

В ранних версиях aiohttp допустимо было использовать числовое значение для установки тайм-аута. Однако это вызывало определенные ограничения и сложности, когда необходимо было гибко управлять разными типами тайм-аутов (например, время на подключение, чтение, общее время выполнения запроса и т.д.). Для решения этих проблем, начиная с версии aiohttp 3.3.0 и далее, был введен класс ClientTimeout. Этот класс позволяет более гибко и точно управлять всеми аспектами тайм-аутов, связанными с HTTP-запросами.

class aiohttp.ClientTimeout(*, total=None, connect=None, sock_connect=None, sock_read=None) — представляет собой структуру данных для настроек тайм-аутов клиента.

Описание параметров

total — общее количество секунд на выполнение всего запроса. Тип: float, по умолчанию None.

connect — максимальное количество секунд для получения соединения из пула. Это время включает в себя установление нового соединения или ожидание свободного соединения из пула, если пределы соединений в пуле превышены. Для времени чистого установления сокет-соединения используйте sock_connect. Тип: float, по умолчанию None.

sock_connect — максимальное количество секунд для подключения к удаленному узлу для нового соединения, которое не было получено из пула. Также см. connect. Тип: float, по умолчанию None.

sock_read — максимальное количество секунд для чтения части данных от удаленного узла. Тип: float, по умолчанию None.

Используя ClientTimeout можно настроить любой из этих параметров по своему усмотрению и ваш код будет правильно создавать сессию с установленным тайм-аутом.

timeout = aiohttp.ClientTimeout(
    total=0.5,
    connect=0.1,
    sock_connect=0.1,
    sock_read=0.3
)

Убедитесь, что вы используете объект ClientTimeout при настройке параметра timeout для aiohttp.ClientSession, иначе вы столкнетесь с исключением:

ValueError: timeout parameter cannot be of <class 'float'> type, please use 'timeout=ClientTimeout(...)'

Cтруктура настроек ClientTimeout: по умолчанию общий таймаут (total) составляет 300 секунд (5 минут).

ChainProxyConnector

ChainProxyConnector полезен тем, что позволяет использовать сразу большой список прокси. Ваш список прокси может быть любой длины, и вы можете поместить его в память программы, записав в переменную. Однако это не совсем хорошая практика, потому что при большом количестве прокси ваш код будет выглядеть ужасно. Метод from_urls([list]) принимает на вход список, который может быть извлечён напрямую из файла. В следующих примерах мы рассмотрим, как это сделать.

# Передаём список с прокси напрямую в from_urls()
connector = ChainProxyConnector.from_urls([
    'socks5://user:password@127.0.0.1:1080',
    'socks4://127.0.0.1:1081',
    'http://user:password@127.0.0.1:3128',
])
async with aiohttp.ClientSession(connector=connector) as session:
    async with session.get(url) as response:
        return await response.text()

У такого подхода есть одна очень важная особенность, которая портит всю картину. Вам нужно указывать схему, по которой работают ваши прокси: socks4://, socks5:// или http://. Прокси socks4 не будут работать по схеме socks5. По этой причине нужно заранее знать, какие прокси вы используете. Для парсинга хорошо подходят socks4 и socks5.

В этом коде мы открываем файл SOCKS5.txt, который содержит прокси вида.

Читаем построчно прокси из файла, обрабатывая каждую и добавляя к ней схему, по которой она работает, формируем список proxy для передачи в ChainProxyConnector.

import aiohttp
import asyncio
import aiofiles
from aiohttp_socks import ChainProxyConnector

async def main():
    url = 'http://httpbin.org/ip'
    proxy = []
    async with aiofiles.open('SOCKS5.txt', mode='r') as socks5:
        for s5_line in await socks5.readlines():
            proxy.append(f'socks5://{s5_line}')
    connector = ChainProxyConnector.from_urls(proxy)
    async with aiohttp.ClientSession(connector=connector) as session:
        async with session.get(url=url) as response:
            if response.ok:
                print(f'good proxy, status_code -{response.status}-', s5_line, end='\n')
            elif response.status >= 400:
                print(f'bad proxy, status_code -{response.status}-', s5_line, end='\n')
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
asyncio.run(main())

Ниже, тот же самый код, только теперь он берет прокси непосредственно из списка proxy = []. Такой код будет работать, если все прокси в списке живые, этот код упадет при первом нерабочем прокси и вы получите ошибку =>

[Errno 11001] Could not connect to proxy 194.28.210.392:9867 [getaddrinfo failed]
import aiohttp
import asyncio
from aiohttp_socks import ChainProxyConnector

async def main():
    timeout = aiohttp.ClientTimeout(total=.5)
    url = 'http://httpbin.org/ip'
    proxy = [
       'socks5://D2Frs6:75JjrW@194.28.210.39:9867',
       'socks5://D2Frs6:75JjrW@194.28.209.68:9925'
    ]
    connector = ChainProxyConnector.from_urls(proxy)
    try:
        async with aiohttp.ClientSession(connector=connector, timeout=timeout, trust_env=True) as session:
            async with session.get(url=url, timeout=1) as response:
                if response.ok:
                    print(f'good proxy, status_code -{response.status}-', end='')
                elif response.status >= 400:
                    print(f'bad proxy, status_code -{response.status}-', end='')
    except Exception as _ex:
        print(_ex)
asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())
asyncio.run(main())

ProxyType

ProxyType — используется для указания типа прокси, что в практике используется редко, т.к ChainProxyConnector проще и удобнее в использовании.

connector = ProxyConnector(
    proxy_type=ProxyType.SOCKS5,
    host='127.0.0.1',
    port=1080,
    username='user',
    password='password',
    rdns=True
    )
    async with aiohttp.ClientSession(connector=connector) as session:
        async with session.get(url) as response:
            return await response.text()

Код ниже выполняет ту же самую задачу, что и код выше, только теперь у нас есть специальные поля для параметров прокси. В ProxyType появилось поле rdns(Reverse DNS).



Добавить комментарий

;-) :| :x :twisted: :smile: :shock: :sad: :roll: :razz: :oops: :o :mrgreen: :lol: :idea: :grin: :evil: :cry: :cool: :arrow: :???: :?: :!: