CodeWorlds
Powrót do kolekcji
Przewodnik14 min czytaniaZespół CodeWorlds

Browser-use, agent LLM sterujący przeglądarką

Browser-use pozwala modelowi klikać i wypełniać formularze na stronach bez API. Wersja 0.13.8, licencja MIT, koszt tokenów i sposoby jego cięcia.

Browser-use, agent LLM sterujący przeglądarką

Browser-use to biblioteka Pythona, która oddaje modelowi językowemu kontrolę nad przeglądarką: model klika, wypełnia formularze i czyta strony, dla których nie ma oficjalnego API. Bieżąca wersja to 0.13.8 z 16 sierpnia 2026 roku, licencja jest MIT, a repozytorium browser-use/browser-use ma około 109,9 tysiąca gwiazdek. Za tę wygodę płaci się tokenami i zawodnością.

Co browser-use właściwie robi

Sercem biblioteki jest pętla. Agent zbiera stan strony, wysyła go do modelu razem z treścią zadania i skróconą historią poprzednich kroków, dostaje w odpowiedzi listę akcji do wykonania, wykonuje je w przeglądarce i zaczyna od nowa. Pętla kończy się, gdy model wywoła akcję done, gdy skończy się limit nieudanych prób albo gdy upłynie czas na krok.

Interfejs jest krótki. Agent(task=..., llm=...) oraz await agent.run() to cały minimalny program. Reszta parametrów, a jest ich w konstruktorze grubo ponad pięćdziesiąt, służy do przycinania tej pętli: ile akcji na krok, ile historii, czy dołączać zrzut ekranu, jak długo czekać na sieć, które domeny są dozwolone.

Zestaw akcji, które model ma do dyspozycji, jest zamknięty i zdefiniowany w rejestrze narzędzi. Są tam czynności oczywiste, czyli navigate, click, input, scroll, send_keys, go_back, switch, close, select_dropdown i dropdown_options. Są operacje odczytu: extract przepuszcza markdown strony przez model, search_page szuka wzorca w tekście jak grep, find_elements odpytuje DOM selektorem CSS. Są też akcje pracujące na plikach, write_file, read_file, replace_file, save_as_pdf i screenshot, oraz evaluate uruchamiające dowolny JavaScript na stronie. Na końcu jest done, która zamyka bieg i zwraca wynik. Rejestr jest zamknięty w tym sensie, że model nie wymyśli sobie nowej akcji poza listą, ale pozostaje otwarty dla programisty, bo własną akcję dopisuje się jedną adnotacją nad zwykłą funkcją.

W wersji 0.13 sterowanie przeglądarką idzie bezpośrednio przez Chrome DevTools Protocol. Wśród zależności pakietu nie ma już Playwrighta, jest za to cdp-use i browser-harness. Nazwy pól w BrowserProfile pozostały bliskie tym z Playwrighta, bo profil dziedziczy po klasach opisujących argumenty uruchomienia i kontekstu, ale warstwa wykonawcza jest własna.

Czego browser-use nie robi, jest równie istotne. Nie jest frameworkiem do zbierania treści na dużą skalę, od tego jest Firecrawl. Nie jest środowiskiem testowym, tę rolę pełni Playwright. Nie izoluje wykonywanego kodu, czym zajmuje się E2B. Nie jest też frameworkiem agentowym ogólnego przeznaczenia w rodzaju LangChain, choć da się go w takim frameworku osadzić jako jedno narzędzie.

Wersja, licencja i stan projektu

Licencja nie budzi wątpliwości i to rzadka wygoda w tej kategorii narzędzi. Plik LICENSE w opublikowanej paczce zawiera pełny tekst MIT z notą „Copyright (c) 2024 Gregor Zunic”. Klasyfikator na PyPI mówi License :: OSI Approved :: MIT License, a interfejs programistyczny GitHuba zwraca dla repozytorium identyfikator SPDX MIT. Trzy niezależne źródła zgadzają się ze sobą, nie ma osobnego katalogu z warunkami komercyjnymi ani klauzuli ograniczającej konkurencyjny hosting.

Projekt powstał 31 października 2024 roku i rósł szybko. Dziś ma około 109,9 tysiąca gwiazdek, 12 084 rozgałęzienia i 363 otwarte zgłoszenia, nie jest zarchiwizowany, a ostatnia zmiana w gałęzi głównej pochodzi z 21 sierpnia 2026 roku. Tempo wydań jest wysokie, a interfejs zmienia się między wydaniami pobocznymi: Controller bywa aliasem Tools, Browser aliasem BrowserSession, a parametry konstruktora przybywają i zmieniają domyślne wartości. Przypnij dokładną wersję i traktuj każdą aktualizację jak zmianę wymagającą przetestowania scenariuszy. Sama numeracja bywa przy tym myląca: mimo numeru zaczynającego się od zera projekt ma bardzo szeroką bazę użytkowników, a zmiany łamiące zgodność trafiają do wydań pobocznych, bo dokładnie na to pozwala wersjonowanie semantyczne przed jedynką.

Osobnym problemem operacyjnym jest sposób deklarowania zależności. Pakiet przypina je znakiem równości, nie zakresem: openai==2.16.0, anthropic==0.76.0, pydantic==2.12.5, mcp==1.26.0, httpx==0.28.1 i kilkadziesiąt kolejnych. Jeśli Twoja aplikacja używa własnego klienta OpenAI albo Claude w innej wersji, instalacja w jednym środowisku wirtualnym skończy się konfliktem. Osobne środowisko albo osobny proces to nie przesada, tylko wymóg praktyczny. Minimalna wersja Pythona to 3.11.

Wokół biblioteki działa komercyjna chmura pod adresem browser-use.com. Rozliczenie jest kredytowe: jest miesięczna pula kredytów przypisana do planu, plany roczne dostają kredyty z góry, a kredyty wydaje się na dowolny produkt według stawek zużycia. Osobna ścieżka obejmuje roczną pulę kredytów, umowy o poziomie usług i warunki przechowywania danych. Konkretnych kwot nie podaję, bo cennik ładowany jest po stronie klienta i nie da się go rzetelnie odczytać z surowej odpowiedzi serwera.

Dwie rzeczy z pogranicza otwartego kodu i chmury zasługują na wyraźne nazwanie. Po pierwsze, jeśli utworzysz agenta bez podania modelu, biblioteka sięgnie po ChatBrowserUse, czyli własnego dostawcę projektu, który wymaga klucza BROWSER_USE_API_KEY. Domyślne zachowanie prowadzi więc do usługi płatnej, a nie do modelu, który już masz. Po drugie, telemetria jest włączona domyślnie: zmienna ANONYMIZED_TELEMETRY ma wartość true, a BROWSER_USE_CLOUD_SYNC domyślnie powtarza tę wartość. Wśród zależności siedzi klient PostHoga. Jeżeli agent chodzi po systemach wewnętrznych firmy, ustaw obie zmienne na false przed pierwszym uruchomieniem.

Jak stan strony trafia do modelu

To pytanie decyduje o rachunku za tokeny, więc odpowiedź warto znać dokładnie. Browser-use nie wysyła modelowi surowego HTML i nie opiera się wyłącznie na obrazie. Buduje własną, uproszczoną reprezentację tekstową, łącząc trzy źródła z Chrome DevTools Protocol: DOM.getDocument daje strukturę drzewa, DOMSnapshot.captureSnapshot dokłada geometrię i style wyliczone, a Accessibility.getFullAXTree wnosi role i nazwy dostępnościowe.

Z połączonego drzewa serializator zostawia tylko węzły, które mają znaczenie dla agenta: elementy interaktywne, elementy przewijalne oraz ramki. Każdy interaktywny węzeł dostaje numer porządkowy i trafia do tekstu w postaci zbliżonej do HTML, poprzedzony indeksem w nawiasie kwadratowym. Gwiazdka przed indeksem oznacza element, który pojawił się od poprzedniego kroku, co pozwala modelowi zauważyć, że po kliknięciu otworzyło się okno modalne.

Code
TEXT
[12]<button type=submit aria-label=Szukaj>Szukaj</button>
[13]<input type=text name=q placeholder=Wpisz frazę />
*[14]<div role=dialog aria-label=Zgody>Ustawienia prywatności</div>

Zestaw atrybutów przepisywanych do tej reprezentacji jest ustalony i ma znaczenie kosztowe. Domyślnie są to title, type, checked, id, name, role, value, placeholder, data-date-format, alt, aria-label, aria-expanded, data-state, aria-checked, aria-valuemin, aria-valuemax, aria-valuenow i aria-placeholder. Atrybut class jest w kodzie źródłowym zakomentowany, czyli świadomie pominięty, bo na stronach z Tailwindem potrafiłby sam zjeść większość okna kontekstowego. Listę można nadpisać parametrem include_attributes.

Serializacja ma twardy limit. Parametr max_clickable_elements_length domyślnie wynosi 40 000 znaków i ucina reprezentację powyżej tego progu. Na stronie z długą listą wyników oznacza to, że model widzi jej początek, a nie całość, i to jest najczęstsza przyczyna zdania „nie znalazłem tego elementu”, choć element na stronie jest.

Zrzut ekranu to warstwa dodatkowa, sterowana parametrem use_vision, domyślnie włączona. Obraz jedzie do modelu jako dane w treści wiadomości, a jego rozdzielczość kontrolują vision_detail_level z wartościami auto, low i high oraz llm_screenshot_size przyjmujące krotkę szerokości i wysokości. Dla modeli z rodziny Claude Sonnet biblioteka sama ustawia rozmiar 1400 na 850 pikseli, bo powyżej tego progu obraz jest skalowany po stronie dostawcy i płacisz za piksele, których model i tak nie zobaczy w pełnej rozdzielczości.

Pierwszy agent i kontrola nad przebiegiem

Instalacja i uruchomienie wyglądają tak.

Code
Bash
# osobne środowisko, bo zależności są przypięte sztywno
python -m venv .venv && source .venv/bin/activate

# przypięcie dokładnej wersji
pip install "browser-use==0.13.8"

# wyłączenie telemetrii przed pierwszym uruchomieniem
export ANONYMIZED_TELEMETRY=false
export BROWSER_USE_CLOUD_SYNC=false

# klucz własnego dostawcy, nie chmury browser-use
export OPENAI_API_KEY=sk-...

Program z realną konfiguracją, a nie z przykładu marketingowego, wygląda następująco.

Code
Python
import asyncio

from browser_use import Agent, BrowserProfile, ChatOpenAI

profile = BrowserProfile(
    headless=True,
    allowed_domains=['*.example.com'],
    block_ip_addresses=True,
    wait_between_actions=0.2,
    wait_for_network_idle_page_load_time=1.0,
    highlight_elements=False,
    user_data_dir=None,
)

async def main():
    agent = Agent(
        task='Znajdź w cenniku plan z limitem 50 użytkowników i zapisz jego nazwę.',
        llm=ChatOpenAI(model='gpt-5.5'),
        browser_profile=profile,
        sensitive_data={'https://*.example.com': {'haslo': 'tajne'}},
        initial_actions=[{'navigate': {'url': 'https://example.com/pricing'}}],
        max_actions_per_step=3,
        max_failures=3,
        step_timeout=120,
        calculate_cost=True,
    )
    history = await agent.run(max_steps=20)
    print(history.final_result())
    print(history.number_of_steps(), history.urls())

asyncio.run(main())

Trzy parametry z tego przykładu zasługują na komentarz. allowed_domains to jedyna realna bariera przed tym, żeby agent poszedł za odnośnikiem w stopce i zaczął działać poza Twoim serwisem. sensitive_data podmienia wartości dopiero w przeglądarce, więc do modelu jedzie nazwa symboliczna zamiast hasła. max_steps przekazane do run jest ostatnim bezpiecznikiem kosztowym i nie ma sensownej wartości domyślnej, którą można by zostawić bez zastanowienia.

Obiekt historii zwracany z run daje final_result, is_done, errors, urls, model_actions, number_of_steps i structured_output. Jeśli podasz output_model_schema z modelem Pydantica, wynik wróci jako obiekt, a nie jako tekst do parsowania wyrażeniem regularnym.

Koszt tokenów i sposoby jego ograniczania

Mechanizm kosztu jest prosty i nieprzyjemny. Każdy krok to osobne wywołanie modelu, w którym leci systemowa instrukcja, treść zadania, historia i pełna reprezentacja bieżącej strony, a przy włączonym use_vision także obraz. Dziesięciokrokowe zadanie to dziesięć takich wywołań. Strona z rozbudowaną nawigacją potrafi wygenerować kilkanaście tysięcy tokenów samego opisu stanu, i to na każdym kroku od nowa.

Biblioteka daje kilka dźwigni i wszystkie są realnymi parametrami konstruktora. flash_mode ustawiony na true usuwa ze schematu odpowiedzi pola z rozumowaniem i wyłącza planowanie, przez co odpowiedź modelu jest krótsza. use_thinking ustawione na false robi wariant łagodniejszy. use_vision ustawione na false usuwa obraz, co zwykle jest największą pojedynczą oszczędnością. max_history_items przycina liczbę poprzednich kroków w kontekście, a message_compaction włącza zwijanie starszej historii. max_clickable_elements_length obniżony do kilku tysięcy znaków ogranicza opis strony, kosztem widoczności dalszych elementów. include_attributes zawężone do trzech czy czterech pozycji wycina resztę. Kolejność strojenia ma znaczenie praktyczne: najpierw zmierz koszt bazowy, potem wyłącz obraz, a dopiero na końcu przycinaj opis strony, bo ten ostatni krok najszybciej obniża skuteczność wykonania zadania.

Code
Python
from browser_use import Agent, ChatOpenAI

agent = Agent(
    task='Zbierz nazwy i ceny pierwszych dziesięciu produktów z listy.',
    llm=ChatOpenAI(model='gpt-5.5'),
    page_extraction_llm=ChatOpenAI(model='gpt-5.5-mini'),
    use_vision=False,
    use_thinking=False,
    use_judge=False,
    flash_mode=True,
    max_history_items=8,
    max_actions_per_step=5,
    max_clickable_elements_length=8000,
    include_attributes=['id', 'name', 'role', 'aria-label'],
    calculate_cost=True,
)

Osobno działają dwie akcje opisane w kodzie źródłowym jako darmowe pod względem modelu. search_page przeszukuje tekst strony wzorcem, opcjonalnie wyrażeniem regularnym i w obrębie selektora CSS, a find_elements zwraca elementy pasujące do selektora wraz z wybranymi atrybutami. Obie wykonują się w przeglądarce i zwracają krótki wynik zamiast całej strony. Jeżeli zadanie sprowadza się do sprawdzenia, czy na stronie jest określony napis, podpowiedz agentowi w treści zadania, żeby użył search_page, zamiast pozwolić mu przewijać stronę na oślep.

Akcja extract jest kosztowna z natury, bo przepuszcza markdown strony przez model. Dlatego istnieje page_extraction_llm: możesz do samego wyciągania danych podstawić model tańszy niż ten, który prowadzi agenta. Domyślnie oba są tym samym modelem, co jest najdroższą możliwą konfiguracją. Warto też wiedzieć, że use_judge jest domyślnie włączone i dokłada wywołanie oceniające wynik, a calculate_cost jest domyślnie wyłączone, więc bez jego włączenia nie zobaczysz w podsumowaniu, ile bieg naprawdę kosztował.

Własne akcje zamiast klikania

Najskuteczniejszym sposobem cięcia kosztu i podnoszenia niezawodności jest odebranie modelowi tych fragmentów zadania, które nie wymagają rozumienia. Rejestr narzędzi pozwala dopisać własną akcję, którą model wywoła po nazwie i opisie, a której ciało jest zwykłym kodem Pythona.

Code
Python
from pydantic import BaseModel
from browser_use import Agent, ChatOpenAI, Tools

tools = Tools()

class OrderQuery(BaseModel):
    order_id: str

@tools.action(
    'Pobierz status zamówienia z wewnętrznego API po numerze zamówienia.',
    param_model=OrderQuery,
    domains=['*.example.com'],
)
async def order_status(params: OrderQuery) -> str:
    import httpx
    async with httpx.AsyncClient() as client:
        r = await client.get(f'https://api.example.com/orders/{params.order_id}')
        return r.json()['status']

agent = Agent(
    task='Sprawdź status zamówienia 88421 i opisz go jednym zdaniem.',
    llm=ChatOpenAI(model='gpt-5.5'),
    tools=tools,
)

Dekorator przyjmuje opis, opcjonalny model parametrów Pydantica, listę domen ograniczającą dostępność akcji oraz terminates_sequence, które przerywa dalsze akcje w tym samym kroku. Sześć kliknięć zamienionych na jedno wywołanie HTTP to sześć wywołań modelu mniej i sześć okazji do pomyłki mniej. Ta sama zasada dotyczy akcji evaluate, która uruchamia JavaScript na stronie: jeśli znasz selektor, deterministyczny skrypt jest tańszy i pewniejszy niż opisywanie modelowi, w co ma kliknąć.

Browser-use a alternatywy

NarzędzieSposób działaniaKiedy wybrać
browser-usemodel steruje przeglądarką przez CDP, stan jako drzewo tekstowezadania jednorazowe, strony bez API, zmienny układ
Playwrightdeterministyczny skrypt z selektoramipowtarzalny przepływ, testy, potok CI
Firecrawlpobranie strony i konwersja na markdownzbieranie treści na dużą skalę, bez klikania
computer use w OpenAI i Claudemodel steruje kursorem po współrzędnych na obrazieaplikacje bez DOM, pulpit, przypadki skrajne
oficjalne API serwisuwywołanie HTTP z kontraktemzawsze, gdy takie API istnieje

Ostatni wiersz nie jest żartem. Agent przeglądarkowy jest rozwiązaniem dla sytuacji, w której API nie ma albo jest niedostępne. Jeśli istnieje, przegrywa z nim na każdym wymiarze: kosztu, czasu, powtarzalności i możliwości testowania. Browser-use bywa też dobrym narzędziem etapu przejściowego, kiedy najpierw pozwalasz agentowi wykonać zadanie kilka razy, obserwujesz, jakie akcje wybiera, a potem przepisujesz stabilny przepływ na skrypt.

Między tymi dwoma skrajnościami mieści się Stagehand, który pozwala mieszać jedno z drugim w tym samym skrypcie: tam, gdzie selektor jest znany, piszesz go wprost, a polecenie w języku naturalnym zostawiasz na fragmenty zmienne. To rozwiązuje dokładnie ten problem etapu przejściowego opisany wyżej, bo nie musisz przepisywać całości naraz. Dwie rzeczy trzeba jednak wiedzieć: buforowanie akcji, które ogranicza liczbę wywołań modelu, działa wyłącznie z przeglądarką hostowaną u dostawcy, a wersja czwarta porzuciła Playwrighta na rzecz własnego klienta CDP i rozszerzenia do Chrome, więc kod pisany pod wersję trzecią nie przeniesie się bez zmian.

Typowe błędy

Traktowanie agenta jak funkcji. Ten sam model, to samo zadanie i ta sama strona mogą dać trzy różne przebiegi. Jeśli wynik ma trafić do systemu produkcyjnego, potrzebujesz schematu wyjścia, weryfikacji wyniku i polityki ponawiania, a nie założenia, że tym razem się uda.

Uruchamianie bez allowed_domains. Strona może zawierać tekst adresowany do modelu, który każe mu przejść gdzie indziej albo wykonać inną czynność. Przy agencie mającym dostęp do zalogowanej sesji to jest realny wektor ataku, a lista dozwolonych domen razem z prohibited_domains i block_ip_addresses jest pierwszą i najtańszą barierą.

Wklejanie haseł w treść zadania. Wszystko, co znajdzie się w polu task, jedzie do dostawcy modelu i ląduje w logach. Do danych logowania służy sensitive_data, gdzie wartości podstawiane są dopiero w przeglądarce.

Instalacja obok kodu aplikacji. Sztywno przypięte wersje openai, anthropic, pydantic i httpx prędzej czy później zderzą się z tym, czego używa reszta projektu.

Zostawianie domyślnych ustawień kosztowych. Domyślnie masz włączony obraz, włączone rozumowanie, włączonego sędziego i wyłączone liczenie kosztu. To konfiguracja pod jakość wyniku w demonstracji, nie pod rachunek na koniec miesiąca.

Brak limitu kroków. Bez max_steps agent, który wpadnie w pętlę, potrafi generować wywołania modelu tak długo, jak pozwoli mu na to limit u dostawcy.

FAQ

Czy browser-use zastąpi Playwrighta?

Nie w przypadkach, w których Playwright jest właściwym narzędziem. Do testów i do przepływów, których układ się nie zmienia, deterministyczny skrypt jest szybszy, tańszy i daje powtarzalne wyniki. Browser-use wygrywa tam, gdzie strona jest nieznana, zmienna albo tak rozbudowana, że napisanie selektorów zajęłoby więcej czasu niż wykonanie zadania raz.

Czy indeksy elementów są stabilne między krokami?

Nie. Indeksy nadawane są przy serializacji bieżącego stanu i po każdej akcji budowane od nowa. Dlatego akcja kliknięcia w nieistniejący już indeks zwraca komunikat o tym, że strona mogła się zmienić, i zachęca model do odświeżenia stanu. Nie buduj własnej logiki opartej na tym, że indeks 12 zawsze wskazuje ten sam przycisk.

Ile kosztuje jeden krok agenta?

Zależy od strony i konfiguracji, więc podanie kwoty byłoby zgadywaniem. Możesz to jednak zmierzyć: ustaw calculate_cost=True, uruchom zadanie na docelowej stronie i porównaj wynik z tym samym biegiem przy use_vision=False oraz obniżonym max_clickable_elements_length. Ten pomiar zajmuje kilkanaście minut i daje liczbę dla Twojego przypadku.

Czy mogę uruchomić browser-use bez klucza do chmury browser-use?

Tak. Podaj własny model, na przykład ChatOpenAI, ChatAnthropic, ChatGoogle, ChatOllama albo ChatOpenRouter. Klucz BROWSER_USE_API_KEY jest potrzebny wyłącznie wtedy, gdy zostawisz domyślnego dostawcę ChatBrowserUse, co dzieje się automatycznie przy pominięciu parametru llm.

Czy agent jest bezpieczny na stronach z treścią od użytkowników?

Sam z siebie nie. Treść strony trafia do modelu jako część kontekstu, więc komentarz na forum może zawierać polecenie skierowane do agenta. Ograniczaj domeny, ograniczaj zestaw akcji do niezbędnych, nie dawaj agentowi sesji z uprawnieniami, których zadanie nie wymaga, i nie pozwalaj mu na akcje nieodwracalne bez potwierdzenia po stronie Twojego kodu.

Czy da się to uruchomić w kontenerze bez ekranu?

Tak, BrowserProfile(headless=True) uruchamia przeglądarkę bez okna, a cdp_url pozwala podłączyć się do przeglądarki działającej gdzie indziej. Pełną listę parametrów profilu opisuje dokumentacja projektu, a kod źródłowy rejestru akcji znajdziesz w repozytorium na GitHubie.

Czytaj dalej

Używamy cookies, żeby zwiększyć Twoje doświadczenia na stronie