Читать книгу Практическая реализация AI-агента в Chrome с LangChain - - Страница 2

Глава 2. Основы работы с Chromium и Playwright

Оглавление

В этом разделе рассматривается полное построение браузерного AI‑агента, который использует возможности Chromium (через Playwright) и легко интегрируется в цепочки обработки данных, управляемые LangChain. Мы подробно опишем архитектуру, подготовку окружения, написание кода, обработку ошибок, оптимизацию производительности и механизмы масштабируемости, а также продемонстрируем несколько практических сценариев применения.


### 1. Почему комбинировать Chromium, Playwright и LangChain


Chromium предоставляет полностью программируемый движок рендеринга, позволяющий запускать браузер в «головном» или «фоновом» режиме без GUI, а Playwright добавляет к этому удобный API для контроля над страницами, элементами и сетью. Вместе они позволяют создавать автоматизированные скрипты, которые могут открывать веб‑страницы, извлекать Structured Data, взаимодействовать с динамическим контентом и выполнять действия, какие в обычном браузере делал человек.


LangChain же предоставляет фреймворк для построения цепочек (chains) и агентов, которые могут использовать внешние инструменты (tools) в качестве «действий». Подключив Playwright как инструмент к LangChain, мы получаем возможность формировать цепочки, где каждый шаг – это вызов браузера, а решение о дальнейшем движении агента принимается на основе LLM‑модели. Таким образом, LLM автоматически генерирует запрос к браузеру, а браузер возвращает результат, который снова передаётся в LLM для дальнейшего планирования.


Эта синергия делает возможным Creation of AI‑агентов, способных выполнять поиск, сбор информации, проверку форм, визуализацию и даже взаимодействие с API без прямого написания скриптов под каждый конкретный случай.


### 2. Установка и настройка окружения


Для начала требуется установить Python 3.11+ и несколько системных зависимостей.


```bash

# Обновление пакетов

sudo apt-get update && sudo apt-get upgrade -y


# Установка Chromium в режиме headless

sudo apt-get install -y chromium-browser


# Установка Playwright и его зависимостей

python -m pip install –upgrade pip

python -m pip install playwright


# Инициализация браузерных бинарников Playwright

python -m playwright install chromium


# Установка LangChain и вспомогательных пакетов

python -m pip install langchain[all] langchain-community chromadb unstructured tiktoken

```


После установки необходимо убедиться, что путь к исполняемому файлу Chromium доступен в переменной окружения `CHROME_PATH`.


```python

import os

os.environ["CHROME_PATH"] = "/usr/bin/chromium-browser"

```


Если вы используете контейнер Docker, в Dockerfile обычно добавляют:


```Dockerfile

FROM python:3.11-slim


# Установка Chromium и зависимостей

RUN apt-get update && apt-get install -y chromium-browser wget gnupg && \

wget -q -O – https://dl.google.com/linux/linux_signing_key.pub | apt-key add – && \

echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" > /etc/apt/sources.list.d/google-chrome.list && \

apt-get update && apt-get install -y google-chrome-stable && \

rm -rf /var/lib/apt/lists/*


# Установка Python‑зависимостей

COPY requirements.txt .

RUN pip install –no-cache-dir -r requirements.txt


# Установка браузерных бинарников Playwright

RUN python -m playwright install chromium


WORKDIR /app

COPY . .

CMD ["python", "main.py"]

```


### 3. Интеграция Playwright как инструмента в LangChain


LangChain позволяет объявлять кастомные инструменты, реализующие интерфейс `BaseTool`. Мы создадим класс `PlaywrightBrowserTool`, который будет инкапсулировать все операции с браузером.


```python

from langchain.tools import BaseTool

from playwright.sync_api import sync_playwright

import json

import time


class PlaywrightBrowserTool(BaseTool):

name = "playwright_browser"

description = """

Инструмент для управления headless‑Chromium через Playwright.

При необходимости вызывается с JSON‑параметрами:

– url: строка с адресом страницы

– query_selector: CSS‑селектор (по желанию)

– fill: словарь с полями для заполнения (по желанию)

– click: CSS‑селектор элемента для клика (по желанию)

– wait_until: строка, например 'networkidle' или 'domcontentloaded'

– screenshot: true/false, сохранить ли скриншот

– extract_text: true/false, выполнить извлечение текста

Возвращает JSON с результатом (HTML, скриншот в base64, ошибки).

"""

def __init__(self, headless: bool = True):

self.headless = headless


def _run(self, **kwargs) -> dict:

# Доступ к Playwright через контекст

with sync_playwright() as p:

browser = p.chromium.launch(headless=self.headless)

page = browser.new_page()

try:

url = kwargs.get("url")

if url:

page.goto(url, wait_until=kwargs.get("wait_until", "domcontentloaded"))

# Заполнение полей

fill_data = kwargs.get("fill")

if fill_data:

for selector, value in fill_data.items():

page.fill(selector, value)

# Клик по элементу

click_selector = kwargs.get("click")

if click_selector:

page.click(click_selector)

# Возможный скриншот

screenshot = kwargs.get("screenshot")

if screenshot:

screenshot_bytes = page.screenshot()

screenshot_b64 = base64.b64encode(screenshot_bytes).decode()

else:

screenshot_b64 = None

# Извлечение текста или HTML

extract_text = kwargs.get("extract_text")

if extract_text:

result = page.content()

else:

result = None

return {

"status": "ok",

"url": url,

"page_source": result,

"screenshot_b64": screenshot_b64,

}

except Exception as e:

return {"status": "error", "error": str(e)}

finally:

browser.close()

def _serialize(self):

return json.dumps({"headless": self.headless})

@classmethod

def _deserialize(cls, data):

return cls()

```


Данный инструмент полностью написан на синхронном API Playwright (для простоты), но его можно адаптировать под асинхронный режим с `asyncio`. Ключевой момент – передача JSON‑параметров через метод `_run`, который возвращает словарь, пригодный для последующей обработки LLM.


### 4. Создание агента с помощью LangChain Agent


В LangChain есть несколько реализаций агентов: `ZeroShotAgent`, `Chains` с `ToolCallingAgent` и более современный `AgentType`/`AgentExecutor`. Для нашего сценария удобно использовать `AgentType.CONVERSATIONAL_REACT_DECOMPOSE`.


```python

from langchain_experimental.agents import create_react_agent

from langchain_community.tools import Tool

from langchain_openai import ChatOpenAI

from langchain.prompts import PromptTemplate


# 1️⃣ Описание инструмента для LangChain

browse_tool = Tool(

name="Browse",

func=lambda input: PlaywrightBrowserTool()._run(**json.loads(input)),

description="Используйте для открытия веб‑страницы, навигации и извлечения данных."

)


# 2️⃣ Создание LLM‑модели

llm = ChatOpenAI(model="gpt-4o", temperature=0.2, streaming=True)


# 3️⃣ Формирование промпта для агента

agent_prompt = PromptTemplate.from_template("""

Ты – AI‑агент, способный управлять браузером через инструмент Browse.

Твоя задача – выполнить запрос пользователя, используя инструменты, доступные тебе.

Когда тебе нужно выполнить действие в браузере, выводи команду в формате JSON,

соответствующее полям инструмента (url, fill, click, wait_until, extract_text, screenshot).


Вопрос пользователя: {input}

Ответ:

""")

# 4️⃣ Создание агента

agent = create_react_agent(

llm=llm,

tools=[browse_tool],

prompt=agent_prompt,

verbose=True,

memory=None

)


# 5️⃣ Обёртка Executor

from langchain_experimental.agents import AgentExecutor

agent_executor = AgentExecutor(agent=agent, tools=[browse_tool], verbose=True)

```


После запуска `agent_executor.invoke({"input": "Открой https://ru.wikipedia.org/wiki/Искусственный_интеллект и извлеки первый абзац текста."})` агент сгенерирует JSON‑команду, передаст её в `PlaywrightBrowserTool`, получит HTML‑страницу, извлечёт абзац и вернёт его в виде окончательного ответа пользователю.


### 5. Практическая реализация: сценарии использования


#### 5.1. Скрапинг динамических страниц с подгрузкой контента


Многие сайты используют Infinite Scroll или React‑router, поэтому обычный `page.content()` не содержит полностью загруженного контента. В таком случае нужно выполнить скрипт прокрутки до тех пор, пока количество элементов не изменится.


```python

def infinite_scroll_and_extract(page, target_selector=".article-item"):

previous_height = 0

while True:

page.evaluate("window.scrollBy(0, document.body.scrollHeight)")

time.sleep(2)

new_height = page.evaluate("document.body.scrollHeight")

if new_height == previous_height:

break

previous_height = new_height

# После завершения скролла извлекаем содержимое

items = page.query_selector_all(target_selector)

texts = [item.inner_text() for item in items]

return "\n".join(texts)

```


Эту функцию можно добавить в `PlaywrightBrowserTool` как отдельный метод `scroll_and_extract`. При отработке агента запрос «Собери 10 последних статей из раздела новостей» будет автоматически преобразован в цепочку вызовов: открытие URL, скролл, извлечение текста, возврат результата в LLM.


#### 5.2. Автозаполнение форм и отправка данных


Для автоматизации взаимодействия с веб‑формами часто требуется ввести данные, выбрать вариант из выпадающего списка и отправить форму. Пример: заполнить форму заявки на вакансию на hh.ru.


```python

def fill_and_submit_form(page, form_data: dict, submit_selector=".submit-button"):

# Заполнение полей

for selector, value in form_data.items():

page.fill(selector, value)

# Ожидание элемента отправки

page.wait_for_selector(submit_selector)

page.click(submit_selector)

# Ожидание перехода к результату

page.wait_for_url("**/thanks*", timeout=10000)

```


Эту функцию можно добавить в `PlaywrightBrowserTool` под именем `fill_form`. Агент получив запрос «Заполнить форму заявки на hh.ru и отправить её», сформирует JSON‑структуру вида:


```json

{

"url": "https://hh.ru/vacancy/123456",

"fill": {

"input[name='name']": "Иван Петров",

"input[name='email']": "ivan@example.com",

"textarea[name='cover_letter']": "Уверен в своих силах…"

},

"click": ".submit-button",

"wait_until": "networkidle"

}

```


После выполнения агент получит подтверждение отправки, что можно передать дальше для анализа ответа сервера (например, наличие сообщения «Спасибо за заявку»).


#### 5.3. Сбор ценовых данных с динамических таблиц


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


```python

def extract_price_table(page, table_css=".price-table"):

# Выполняем скрипт, который возвращает массив цен

prices = page.evaluate("""

() => {

const rows = document.querySelectorAll('.price-table tbody tr');

return Array.from(rows).map(r => r.innerText.trim());

}

""")

return prices

```


Эта функция может быть использована как часть цепочки «получить список цен», после чего LLM может проанализировать их, сравнить с историческими значениями и принять решение о покупке.


### 6. Управление ресурсами и масштабирование


#### 6.1. Параллельные запросы


Playwright позволяет открывать несколько контекстов в рамках одного процесса, но каждый контекст требует отдельного браузерного процесс‑потока. Чтобы эффективно обслуживать множество запросов одновременно, удобно использовать пул контекстов или отдельный сервис, который принимает задачи через очередь (Redis, RabbitMQ).


```python

from concurrent.futures import ThreadPoolExecutor


class BrowserPool:

def __init__(self, max_workers=4):

self.executor = ThreadPoolExecutor(max_workers=max_workers)


def run_task(self, func, **kwargs):

return self.executor.submit(func, **kwargs)


# Пример использования:

pool = BrowserPool(max_workers=8)


def browse_task(url, **params):

return PlaywrightBrowserTool()._run(url=url, **params)


result = pool.run_task(browse_task, url="https://example.com")

```


Таким образом, каждый запрос получает собственный браузерный контекст, а количество одновременных задач ограничено конфигурируемым параметром `max_workers`.


#### 6.2. Управление памятью и временем жизни


Одним из типичных проблем при длительной работе браузера является утечка памяти из‑за открытых страниц и контекстов. Регулярно закрывайте браузерные экземпляры и пере‑создавайте их. Для долгосрочных сервисов рекомендуется использовать `process`‑level restart каждые N запросов или при достижении определённого объёма потребления RAM (пример – 500 МБ).


#### 6.3. Отказоустойчивость


Идеальная архитектура подразумевает распределённые агенты, где каждый узел может «умереть» и автоматически восстановиться. Для этого храните состояние задачи (например, URL, параметры) в durable store (PostgreSQL или DynamoDB) и при восстановлении вызывайте очередной запрос.


### 7. Лучшие практики в написании AI‑агентов на основе LangChain и Playwright


1. **Сохраняйте запросы в лог** – храните входной запрос, сгенерированный JSON‑команду и полученный ответ. Это упрощает отладку и позволяет построить датасет для улучшения промптов.

2. **Избегайте «бесконечных» циклов** – в инструментах вроде скроллинга устанавливайте лимит количества итераций (например, 10 раз) и таймаут.

3. **Ограничивайте размер получения данных** – если требуется собрать большой объём текста, стройте пагинацию (offset/limit) вместо полного сканирования страницы.

4. **Контролируйте уровень привилегий** – запускайте браузер от имени ограниченного пользователя, чтобы минимизировать влияние потенциальных уязвимостей.

5. **Проверяйте политику сайта** – убедитесь, что ваш скрейпинг не нарушает `robots.txt` и условия использования.

6. **Тестируйте в headless‑режиме и в режиме с GUI** – в некоторых случаях UI‑элементы отображаются только в обычном режиме, тогда как в headless‑режиме могут возникать различия в размерах и координатах.

7. **Тестируйте резистивные изменения** – сайты часто меняют структуру CSS‑классов. Для устойчивого селектора используйте XPath с атрибутами или даже собственный парсер, который проверяет наличие нужного текста.

8. **Сохраняйте скриншоты при ошибках** – это упрощает диагностику проблем с загрузкой страницы или с отсутствием элементов.


### 8. Полный пример: «Собрать текущую стоимость биткоина в USD и вывести топ‑5 новостей из крупных финансовых источников»


```python

def ask_agent(task: str):

response = agent_executor.invoke({"input": task})

return response["output"]


# Пример запроса:

task = ("Открой https://coinmarketcap.com/, извлеки текущую цену биткоина в USD, "

"запиши её в переменную price, затем открой https://cointelegraph.com/ и "

"выведи первые 5 заголовков новостей, связанных с биткоином.")

answer = ask_agent(task)

print(answer)

```


В результате агент вернёт отформатированный ответ примерно такого вида:


```

Price of Bitcoin (USD): $27,845.12

Top 5 news headlines about Bitcoin:

1. "Institutional adoption of Bitcoin accelerates in 2025"

2. "Regulators propose new framework for crypto exchanges"

3. "Bitcoin mining becomes greener with renewable energy"

4. "Analysts predict bullish trend for Q4 2025"

5. "New layer‑2 solutions boost Bitcoin scalability"

```


Этот пример демонстрирует, как одна цепочка вызовов браузера может обслуживать несколько шагов, а каждое действие легко комбинировать с последующей обработкой в LLM.


### 9. Развёртывание в продакшн‑окружении


Для реального продукта необходимо Packaging‑упаковать весь код в Docker‑контейнер и разместить за reverse‑proxy (NGINX) с поддержкой HTTP/2.


**Dockerfile** (минимальная конфигурация)


```Dockerfile

FROM python:3.11-slim


# Установка системных зависимостей

RUN apt-get update && apt-get install -y –no-install-recommends \

chromium-browser \

wget \

ca-certificates \

&& rm -rf /var/lib/apt/lists/*


# Копирование зависимостей и кода

WORKDIR /app

COPY requirements.txt .

RUN pip install –no-cache-dir -r requirements.txt


# Установка браузерных бинарников Playwright

RUN python -m playwright install chromium


# Копирование проекта

COPY . .


# Параметры окружения

ENV CHROME_PATH=/usr/bin/chromium-browser


# Запуск приложения

CMD ["python", "main.py"]

```


В `docker-compose.yml` добавляем ограничения ресурсов и стратегии перезапуска:


```yaml

services:

browser-agent:

build: .

restart: unless-stopped

deploy:

resources:

limits:

cpus: "2"

memory: "2g"

environment:

– OPENAI_API_KEY=${OPENAI_API_KEY}

– CHROME_PATH=/usr/bin/chromium-browser

ports:

– "8000:8000"

```


После деплоя каждый запрос от клиента попадает в FastAPI‑эндпоинт, который вызывает `agent_executor.invoke`. Это позволяет масштабировать сервис горизонтально, добавляя новые реплики в Kubernetes.


### 10. Заключение


В результате этой главы мы вывели полную картину того, как собрать AI‑агент на базе LangChain, который использует Chromium через Playwright для взаимодействия с веб‑страницами. Мы начали с установки окружения, продвинулись до создания кастомного инструмента, построили агента, продемонстрировали несколько реальных сценариев (скроллинг, заполнение форм, извлечение динамических данных) и завершили описанием подходов к масштабированию и развертыванию в продакшн.


Ключевые идеи, которые следует помнить:


* LangChain предоставляет гибкую модель агента, позволяющую интерпретировать естественный язык как команду для браузера.

* Playwright дает мощный и надёжный способ контроля над Chromium, включая работу в headless‑режиме, управление сетью и скриншоты.

* Интеграция инструмента в виде JSON‑команды делает процесс полностью детерминированным и удобным для отладки.

* Продвинутая архитектура (пул контекстов, очередь задач, долговечное хранилище) позволяет построить масштабируемый сервис, который будет работать в условиях высокой нагрузки.


С этой базой вы можете переходить к более сложным проектам: от автоматизации поиска вакансий и составления резюме до построения аналитических панелей, собирающих данные из десятков источников в реальном времени. Основы уже заложены, а дальше – лишь творчество и уточнение требований к вашему конкретному сценарию.


Практическая реализация AI-агента в Chrome с LangChain

Подняться наверх