Настройка прокси в LangChain

Прокси в LangChain: параметр proxies в WebBaseLoader, requests_kwargs для таймаута, переменные HTTP_PROXY и trust_env для асинхронной загрузки страниц.

Обновлено · Редакция Prokso

Данные для подключения
Хост
gw.prokso.com
Порт
823 HTTP · 824 SOCKS5 — новый IP на каждый запрос · 10000–20000 фиксированный IP (sticky-сессия), HTTP(S) и SOCKS5
Логин и пароль
в кабинете, на странице плана

Готовые строки в нужном формате генерирует кабинет: выберите страну, тип IP и протокол.

Коротко
  • Хост — gw.prokso.com, порты 823 (HTTP), 824 (SOCKS5), 10000–20000 (фиксированный IP, HTTP или SOCKS5)
  • Логин и пароль — в кабинете на странице плана
  • Проверяйте IP в ответе самого запроса через прокси: страница «Мой IP» в браузере ваш скрипт не проверяет

Пошаговая инструкция, как подключить прокси Prokso в LangChain. Займёт несколько минут.

Шаг 1. Возьмите данные прокси в кабинете

Откройте план и скопируйте хост gw.prokso.com, порт, логин и пароль. Порт 823 — HTTP, 824 — SOCKS5 (оба дают новый IP на каждый запрос); порты 10000–20000 — фиксированный IP до 120 минут, работают и как HTTP, и как SOCKS5 (каждый порт — отдельный адрес). Страну задают в логине: логин__cr.ua.

Шаг 2. Установите пакеты

pip install -U langchain-community beautifulsoup4. Загрузчик страниц WebBaseLoader находится в пакете langchain-community, а beautifulsoup4 нужен ему, чтобы разобрать HTML. Обычная загрузка идёт через библиотеку requests, асинхронная — через aiohttp, и прокси для них задаётся по-разному.

Шаг 3. Передайте прокси загрузчику

Для методов load() и lazy_load() передайте параметр proxies — словарь с адресом прокси для http и https. Логин и пароль впишите прямо в адрес. Параметр requests_kwargs передаёт дополнительные настройки в каждый запрос, например timeout, чтобы скрипт не висел на медленной странице.

from langchain_community.document_loaders import WebBaseLoader

PROXY = "http://ЛОГИН:ПАРОЛЬ@gw.prokso.com:823"

loader = WebBaseLoader(
    "https://api.ipify.org?format=json",
    proxies={"http": PROXY, "https": PROXY},
    requests_kwargs={"timeout": 30},
)
docs = loader.load()
print(docs[0].page_content)

Шаг 4. Асинхронная загрузка — через переменные окружения

Методы alazy_load() и fetch_all() (а также устаревший aload()) работают через aiohttp и параметр proxies не учитывают. Для них задайте переменные HTTP_PROXY и HTTPS_PROXY и создайте загрузчик с trust_env=True: без этого aiohttp переменные игнорирует. Так же настраивается AsyncHtmlLoader.

import asyncio
import os

os.environ["HTTP_PROXY"] = "http://ЛОГИН:ПАРОЛЬ@gw.prokso.com:823"
os.environ["HTTPS_PROXY"] = "http://ЛОГИН:ПАРОЛЬ@gw.prokso.com:823"

from langchain_community.document_loaders import WebBaseLoader


async def main():
    loader = WebBaseLoader(
        web_paths=["https://api.ipify.org?format=json"] * 3,
        trust_env=True,
        requests_per_second=2,
    )
    async for doc in loader.alazy_load():
        print(doc.page_content)


asyncio.run(main())

Шаг 5. Переменные окружения для других загрузчиков

Библиотека requests по умолчанию читает HTTP_PROXY и HTTPS_PROXY, поэтому переменные, заданные до запуска скрипта, действуют и для загрузчиков на requests без собственного параметра прокси. Но учтите: по документации requests переменные окружения перекрывают прокси, заданный на уровне сессии, то есть параметр proxies в WebBaseLoader. Выбирайте один способ на скрипт.

Шаг 6. Выберите порт и темп

Порт 823 даёт новый IP на каждую страницу — удобно для сбора многих страниц. Чтобы серия страниц загружалась с одного IP, возьмите фиксированный порт 10000–20000. В асинхронном режиме параметр requests_per_second ограничивает, сколько страниц загружается одновременно; по умолчанию — 2.

Шаг 7. Проверьте вывод

Запустите пример: загрузчик открывает https://api.ipify.org и печатает содержимое страницы — IP, который видит сайт. Это должен быть адрес прокси, а не ваш; в асинхронном примере на порту 823 три строки покажут разные адреса. Ошибка 407 означает неверный логин или пароль либо закончившийся трафик на плане. Страница «Мой IP» в браузере здесь не поможет: браузер идёт напрямую.

Отдельный загрузчик для каждой страны и сессия на источник

Когда базе знаний нужны локальные версии страниц (цены, наличие, местные новости), создайте отдельный WebBaseLoader для каждой страны. Страна идёт в логин, пароль берётся из переменной окружения, а оба проходят через urllib.parse.quote(..., safe=""), чтобы символ ; и спецсимволы не сломали адрес прокси. Перед отправкой requests сама декодирует их обратно.

import os
from urllib.parse import quote
from langchain_community.document_loaders import WebBaseLoader

USER = "ЛОГИН"
PASSWORD = os.environ["PROKSO_PASSWORD"]  # пароль не храним в коде


def proxy_url(country, session=None, port=823):
    login = f"{USER}__cr.{country}"
    if session:
        login += f";sessid.{session}"  # один IP примерно на 30 минут
    return f"http://{quote(login, safe='')}:{quote(PASSWORD, safe='')}@gw.prokso.com:{port}"


SOURCES = {
    "de": ["https://example.de/catalog?page=1", "https://example.de/catalog?page=2"],
    "pl": ["https://example.pl/catalog?page=1", "https://example.pl/catalog?page=2"],
}

docs = []
for country, urls in SOURCES.items():
    proxy = proxy_url(country, session=f"src{country}")
    loader = WebBaseLoader(
        web_paths=urls,
        proxies={"http": proxy, "https": proxy},
        requests_kwargs={"timeout": 30},
        raise_for_status=True,
    )
    for doc in loader.lazy_load():  # по одной странице, без всего списка в памяти
        doc.metadata["country"] = country
        docs.append(doc)

print(len(docs))

Номер сессии в логине (;sessid.srcde) держит один IP примерно 30 минут на порту 823, поэтому все страницы одного источника, например пагинация каталога, идут с одного адреса. Каждая страна получает свою сессию. lazy_load() возвращает документы по одному: с длинным списком каждый можно сразу записывать в векторное хранилище, а поле country в метаданных поможет потом фильтровать ответы.

Ошибки загрузчиков LangChain через прокси и как их исправить

  • Страница 403 или 429 попадает в базу знаний. По умолчанию WebBaseLoader не проверяет код ответа, поэтому страница «Access denied» разбирается как обычный документ. Задайте raise_for_status=True: тогда load() и lazy_load() бросают requests.HTTPError, и страницу можно пропустить, а не индексировать.
  • InvalidProxyURL: Please check proxy URL. It is malformed and could be missing the host. requests не нашла хост в адресе прокси. Обычно незакодированный @ или / в пароле сдвинул границу между частями адреса. Собирайте адрес через quote(), как в примере выше.
  • aiohttp.ClientHttpProxyError со статусом 407 в alazy_load() или fetch_all(). Прокси не принял логин или пароль из HTTPS_PROXY. Асинхронный загрузчик эту ошибку не повторяет, а падает сразу. Закодированные символы здесь тоже работают: aiohttp декодирует их сам.
  • aiohttp.ClientProxyConnectionError. aiohttp вообще не добрался до gw.prokso.com: проверьте порт и то, что исходящие соединения на него разрешены на вашем сервере. Загрузчик делает 3 попытки с растущей паузой, а потом сдаётся.
  • Одна неудачная страница останавливает всю асинхронную загрузку. По умолчанию ошибка на одном адресе прерывает работу. С continue_on_failure=True загрузчик пишет предупреждение и возвращает для этой страницы пустой документ: отфильтруйте пустые документы перед индексацией.

Темп и трафик в пайплайне LangChain

В асинхронном режиме requests_per_second — не скорость в секунду: в коде библиотеки это размер семафора, то есть сколько страниц грузится одновременно. На порту 823 каждая из них идёт со своего IP, но собственные ограничения сайта никто не отменял. Для одного сайта держите значение низким и поднимайте его только для списков разных сайтов.

Трафик считается в обе стороны. WebBaseLoader загружает только HTML страницы, без картинок и скриптов, поэтому стоит меньше загрузчика на браузере, если странице не нужен JavaScript. Уберите дубликаты адресов перед загрузкой и храните уже загруженные URL, чтобы следующий запуск их пропускал. Купленные гигабайты не сгорают, так что пауза между запусками ничего не стоит. Собирайте только то, что разрешают правила сайта, и в рамках наших правил использования.

Если прокси не работает

Вопросы и ответы

Можно ли подключить SOCKS5?

Для load() — да: установите pip install "requests[socks]" и укажите в proxies адрес socks5h://ЛОГИН:ПАРОЛЬ@gw.prokso.com:824, тогда и DNS-запросы идут через прокси. Асинхронные методы работают через aiohttp, а он поддерживает только HTTP-прокси.

Прокси указан, а сайт видит мой IP. Почему?

Чаще всего одна из двух причин. Либо вы вызываете асинхронный метод, который не видит параметр proxies, — задайте переменные окружения и trust_env=True. Либо в системе задан HTTP_PROXY или HTTPS_PROXY с другим адресом, и они перекрыли параметр.

Что значит ошибка ProxyError: 407?

Прокси не принял логин или пароль. Скопируйте строку из генератора в кабинете ещё раз и проверьте, остался ли трафик на плане.

Почему асинхронный загрузчик не повторяет 403 или 429?

Он повторяет только ошибки соединения aiohttp. Коды от сайта возвращаются как текст страницы или сразу как ошибка, если задано raise_for_status=True. Мгновенный повтор 429 и так не поможет: уменьшите requests_per_second и вернитесь позже.

Можно ли держать один IP для многостраничного источника без фиксированного порта?

Да. Добавьте ;sessid.<название> к логину на порту 823, и адрес будет держаться примерно 30 минут. Для более долгих задач возьмите фиксированный порт 10000–20000: там время удержания задаётся в кабинете, до 120 минут.

Стоит ли выбор страны в логине дополнительно?

Нет, страна бесплатна. Вдвойне считается только трафик с точным гео: регион, город, ZIP или оператор. Цены за ГБ — на странице <a href="/ru/pricing">тарифов</a>.