Kurs
Das Gemini-Team hat kürzlich Gemini 2.5 Gemini 2.5 Computer Use, ein spezielles Modell, das einen Live-Bildschirm sehen und darauf reagieren kann, indem es wie ein Mensch klickt, tippt, scrollt und im Internet surft.
In dieser Anleitung lassen wir abstrakte Benchmarks mal beiseite und machen was Praktisches. Eine Streamlit-App, die Computer Use nutzt, um einen echten Browser zu steuern, bei Google nach Stellenangeboten zu suchen, einen Filter anzuwenden und die Ergebnisse ohne Such-APIs von Drittanbietern als CSV-Datei zu speichern.
In diesem Tutorial lernst du, wie du:
- Gemini 2.5 Computer-Nutzungsschleife einrichten
- Lass den Agenten mit Playwright (Chromium) laufen, mit einer Whitelist und optionaler menschlicher Bestätigung für riskante Schritte.
- Erstell eine Streamlit-Benutzeroberfläche mit Live-Aktionsprotokollen und Screenshots nach jedem Zug.
- Kratz organische Ergebnisse zusammen und lade eine CSV-Datei runter.
Am Ende hast du einen Job-Suchagenten, der dir passende Stellenanzeigen zusammenstellt.

Wenn du mehr über Gemini 2.5 erfahren möchtest, schau dir doch mal unser Tutorial zu Gemini 2.5 Pro an. Gemini 2.5 Pro-Tutorial, das Funktionen, Tests, Zugriff, Benchmarks und mehr abdeckt.
Was ist Gemini 2.5 Computer Use?
Gemini 2.5 Computer Use ist ein spezielles (Vorschau-)Modell und Tool in der Gemini API, mit dem du Browser-Steuerungsagenten erstellen kannst. Anstatt bestimmte APIs aufzurufen, nutzt das Modell Screenshots. Es kann sehen, was auf der Seite ist, und dann UI-Aktionen wie navigate, click_at, scroll_document usw. ausführen.
Der Client-Code kriegt die vorgeschlagenen Aktionen, macht sie und schickt dann eine neue Kombination aus Screenshot und URL zurück, damit das Modell den nächsten Schritt entscheiden kann.
Wie die Computernutzungsschleife funktioniert

Quelle: Gemini-Dokumentation
So läuft die Agentenschleife von Anfang bis Ende ab:
- Eine Anfrage senden: Schick zuerst eine Anfrage mit dem Tool „Computernutzung“, einer Zielvorgabe und einem ersten Screenshot der Benutzeroberfläche.
- Eine Antwort bekommen: Das Modell antwortet mit einer Beschreibung und einem oder mehreren Funktionsaufrufen (wie z. B.
click_atodertype_text_at) sowie einer Sicherheitsentscheidung. - Durchführung: Dann mach die vorgeschlagenen Aktionen, d. h. wenn die Aktion als regulär/erlaubt markiert ist, können wir sie ausführen. Wenn eine Bestätigung nötig ist, fragen wir den Benutzer und machen nur mit seiner ausdrücklichen Zustimmung weiter.
- Rückgabestatus: Nachdem wir die Aktion gemacht haben, schicken wir den Status zurück zum Modell, indem wir ein „
FunctionResponse” senden, das einen neuen Screenshot und die aktuelle URL enthält. - Wiederhole: Wir wiederholen diesen Zyklus „
see–decide–act–observe“, bis die Aufgabe erledigt ist, ein Fehler auftritt oder der Benutzer oder das Modell entscheidet, den Vorgang zu beenden.
Erstelle einen Job-Suchagenten mit Gemini 2.5 Computer Use und Streamlit
In diesem Abschnitt erstellen wir ein Streamlit-basierten Jobsuchagenten erstellen, der einen echten Browser mit Gemini 2.5 Computer Use und Playwright steuert und die Ergebnisse dann in CSV exportiert.
So läuft's ab:
- Der Nutzer gibt eine Liste mit Job-Schlagwörtern in die Streamlit-Seitenleiste ein.
- Der Agent sagt Gemini, dass es Google öffnen, jede Suchanfrage machen und einen Zeitfilter anwenden soll.
- Der Agent bleibt auf der ersten Seite der organischen Ergebnisse stehen und zeigt bei jedem Schritt Live-Screenshots zusammen mit Aktionsprotokollen an.
- Dann sammelt es Titel, Links und Ausschnitte von Seite 1 und fasst sie für alle Keywords zusammen.
- Schließlich wird eine Ergebnistabelle zusammen mit einer CSV-Datei zum Herunterladen angezeigt.
Im Hintergrund sorgt die App dafür, dass nur Domains auf einer Whitelist für die sichere Navigation zugelassen werden, unterstützt eine optionale manuelle Bestätigung für riskante Schritte und nutzt die Computernutzungsschleife für einfaches Debugging.
Bevor wir loslegen:
Ein paar Beispiele für verantwortungsbewusste Nutzung und Website-Richtlinien sind:
- Was wird automatisiert? Diese Demo macht nur die Google-Suche (auf der Whitelist) automatisch und schnappt sich die organischen Ergebnisse von Seite 1.
- Beachte die Bedingungen der Website: Automatisiere keine Websites mit restriktiven Nutzungsbedingungen und versuch niemals, CAPTCHAs zu umgehen. Wenn ein CAPTCHA erscheint, mach es einfach manuell und lass das Modell dann weiterlaufen.
- Sicherheit durch menschliches Eingreifen: Wenn Gemini was Riskantes vorschlägt (wie eine CAPTCHA-Interaktion oder Einkäufe), stoppt die App automatisch. Es geht nur weiter, wenn du die automatische Bestätigung aktivierst.
Schritt 1: Was du brauchen solltest
- Python 3.10+
- Ein Gemini-API-Schlüssel von Google AI Studio
- macOS / Windows / Linux mit installiertem Playwright Chromium
Schritt 1.1: Importe
Stell zuerst sicher, dass du die folgenden Importe installiert hast:
python -m venv .venv && source .venv/bin/activate
pip install streamlit google-genai playwright python-dotenv
playwright install chromium
Die oben genannten Befehle richten eine virtuelle Umgebung ein und installieren alle wichtigen Abhängigkeiten, die man zum Erstellen der App braucht, nämlich , die zum Erstellen der App nötig sind, nämlich Streamlit für die Benutzeroberfläche, google-genai zum Aufrufen der Gemini-API und Playwright für die Browser-Automatisierung und python-dotenv zum Laden von Umgebungsvariablen und Chromium für Playwright.
Schritt 1.2: Einrichten des Gemini-API-Schlüssels
Jetzt, wo wir die Abhängigkeiten installiert haben, richten wir als Nächstes den Gemini-API-Schlüssel aus AI Studio ein.
- Logge dich bei AI Studio ein und such nach „API-Schlüssel abrufen“.
- Klick auf API-Schlüssel erstellen
- Such dir ein bestehendes Google Cloud-Projekt aus oder mach eins mit aktivierter Abrechnung.

- Klick einfach auf Link „Ein Rechnungskonto verknüpfen“ und gib deine Kartendaten ein, um sie zu bestätigen.
- Kopiere zum Schluss den API-Schlüssel aus dem AI Studio und speicher ihn.
Erstell jetzt eine Datei namens „ .env “ in deinem Projektordner und füge dort deinen API-Schlüssel hinzu:
GOOGLE_API_KEY=YOUR_REAL_KEY
ALLOWED_HOSTS=google.com
Das Paket „ python-dotenv ” lädt „ GOOGLE_API_KEY ” zur Laufzeit, damit die App das Gemini 2.5-Computernutzungsmodell aufrufen kann.
Schritt 2: Modellkonfiguration einrichten
Jetzt machen wir uns bereit für die Laufzeit, die Importe, Konstanten, Modell-ID und Authentifizierung umfasst. Dadurch wird sichergestellt, dass die App mit Gemini kommuniziert und die Navigation sicher innerhalb einer zugelassenen Domain bleibt.
import os, io, time, csv, base64, urllib.parse
from typing import List, Dict, Tuple
import streamlit as st
from dotenv import load_dotenv
from playwright.sync_api import sync_playwright
from google import genai
from google.genai import types
from google.genai.types import Content, Part
W, H = 1440, 900
MODEL = "gemini-2.5-computer-use-preview-10-2025"
load_dotenv()
API_KEY = os.getenv("GOOGLE_API_KEY")
if not API_KEY:
st.stop()
ALLOWED_HOSTS = {h.strip().lower() for h in os.getenv("ALLOWED_HOSTS", "google.com").split(",") if h.strip()}
client = genai.Client(api_key=API_KEY)
Wir fangen damit an, die Kernbibliotheken wie Streamlit, Playwright, google-genai und kleine Hilfsprogramme (dotenv, os, io, csv, time, urllib) für die Konfiguration und E/A zu importieren.
Ein fester Viewport (W, H = 1440 × 900) sorgt dafür, dass Screenshots und Koordinatenabbildung für die Computernutzung einheitlich bleiben, und das Vorschaumodell ist auf „ gemini-2.5-computer-use-preview-10-2025 “ eingestellt.
Der API-Schlüssel wird mit load_dotenv() und os.getenv("GOOGLE_API_KEY") geladen. Die Navigation wird aus Sicherheitsgründen über eine Zulassungsliste (Standard: google.com) eingerichtet.
Schließlich wird mit „ genai.Client(api_key=API_KEY) “ das Gemini SDK gestartet, damit die App die Agentenschleife ausführen und die Ergebnisse in Streamlit anzeigen kann.
Schritt 3: Hilfsfunktionen erstellen
Bevor wir uns mit dem Kern der App beschäftigen, müssen wir ein paar Hilfsfunktionen einrichten. Sie helfen bei Sicherheitschecks, koordinieren die Konvertierung, führen Aktionen für Gemini-Funktionsaufrufe und SERP-Scraping durch und exportieren die Ergebnisse als CSV-Datei.
Schritt 3.1: Sicherheit und Umsetzung von Maßnahmen
Wir fangen damit an, einen Backend-Helfer hinzuzufügen, der die sichere Browsersteuerung ermöglicht. Dazu gehören eine Funktion zur Überprüfung der Domänen-Zulassungsliste, um die Navigation einzuschränken, eine Koordinatenkonverterfunktion, um das 0–999-Raster des Modells auf den festen Ansichtsbereich abzubilden, und eine Aktionsverteilungsfunktion, die Computer-Use-Funktionsaufrufe interpretiert und über Playwright ausführt.
def host_allowed(url: str) -> bool:
try:
netloc = urllib.parse.urlparse(url).netloc.lower()
return any(netloc.endswith(allowed) for allowed in ALLOWED_HOSTS)
except Exception:
return False
def denorm(v: int, size: int) -> int:
return int(v/1000*size)
def exec_calls(candidate, page, viewport, *, approve_all=False) -> List[Tuple[str, Dict]]:
W, H = viewport
results = []
for part in candidate.content.parts:
fc = getattr(part, "function_call", None)
if not fc:
continue
name, args = fc.name, (fc.args or {})
sd = args.get("safety_decision")
if sd and sd.get("decision") == "require_confirmation":
reason = sd.get("explanation", "Model flagged a risky action.")
log_box.warning(f"[SAFETY requires confirmation] {reason}")
if not approve_all:
st.stop()
results.append((name, {"safety_acknowledgement": "true"}))
if name == "navigate":
target = args.get("url", "")
if target and not host_allowed(target):
log_box.error(f"[BLOCKED] Non-allowlisted host: {target}")
results.append((name, {"error": "blocked_by_allowlist"}))
continue
try:
if name == "open_web_browser":
pass
elif name == "navigate":
page.goto(args["url"], timeout=30000)
elif name == "search":
page.goto("https://www.google.com", timeout=30000)
elif name == "click_at":
page.mouse.click(denorm(args["x"], W), denorm(args["y"], H))
elif name == "hover_at":
page.mouse.move(denorm(args["x"], W), denorm(args["y"], H))
elif name == "type_text_at":
x, y = denorm(args["x"], W), denorm(args["y"], H)
page.mouse.click(x, y)
if args.get("clear_before_typing", True):
page.keyboard.press("Meta+A"); page.keyboard.press("Backspace")
page.keyboard.type(args["text"])
if args.get("press_enter", True):
page.keyboard.press("Enter")
elif name == "scroll_document":
page.mouse.wheel(0, 800 if args["direction"] == "down" else -800)
elif name == "key_combination":
page.keyboard.press(args["keys"])
page.wait_for_load_state("networkidle", timeout=10000)
results.append((name, {}))
time.sleep(0.6)
except Exception as e:
results.append((name, {"error": str(e)}))
return results
Schauen wir mal, wie jede Funktion in die Pipeline passt:
host_allowed()Funktion: Diese Funktion analysiert die Ziel-URL, schreibt den Hostnamen klein und checkt, ob er mit einer Domain ausALLOWED_HOSTSendet, die in der Datei.envaufgeführt ist.denorm()Funktion: Dieses Tool wandelt die Zeigerkoordinaten in tatsächliche Pixeleinheiten für den aktuellen Ansichtsbereich um. Das ist echt wichtig für präzise Klicks, Hover-Bewegungen und die Eingabe, vor allem wenn du die Bildschirmgröße änderst oder ohne Monitor arbeitest.exec_calls()Funktion: Schließlich checkt die Funktion „execute calls“ ( )die Antwort des Modells auf Funktionsaufrufe und schickt jeden einzelnen an Playwright. Es blockiert Aufrufe an nicht zugelassene Hosts, macht die unterstützten Aktionen, wartet, bis das Netzwerk ruhig ist, um die Seite zu stabilisieren, und speichert das Ergebnis jeder Aktion (auch Fehler). Diese Ergebnisse werden dann im nächsten Schritt genutzt, um die Rückkopplung „FunctionResponses“ zum Modell zu erstellen.
Zusammen sorgen diese Funktionen dafür, dass Grenzen eingehalten werden, die Zeigermathematik genau bleibt und detaillierte Ergebnisse protokolliert werden.
Schritt 3.2: Feedback nach der Aktion und Datenerfassung
Nachdem der Agent was gemacht hat, braucht er Feedback, um zu entscheiden, was als Nächstes passiert, und um nützliche Ergebnisse zu liefern. Dieser Schritt bietet drei Hilfsfunktionen wie folgt:
def fr_from(page, results):
shot = page.screenshot(type="png")
url = page.url
frs = []
for name, result in results:
frs.append(types.FunctionResponse(
name=name,
response={"url": url, **result},
parts=[types.FunctionResponsePart(
inline_data=types.FunctionResponseBlob(mime_type="image/png", data=shot)
)]
))
return frs, shot
def scrape_google_serp(page, max_items=10):
items = []
anchors = page.locator('div#search a:has(h3)')
count = min(anchors.count(), max_items)
for i in range(count):
a = anchors.nth(i)
title = a.locator('h3').inner_text()
link = a.get_attribute('href')
snippet = ""
snips = page.locator('div#search .VwiC3b')
if snips.count() > i:
snippet = snips.nth(i).inner_text()
items.append({"title": title, "link": link, "snippet": snippet})
return items
def to_csv_download(rows: List[Dict], name="results.csv"):
if not rows:
return None
out = io.StringIO()
writer = csv.DictWriter(out, fieldnames=["keyword", "title", "link", "snippet"])
writer.writeheader(); writer.writerows(rows)
b = out.getvalue().encode("utf-8")
href = f"data:text/csv;base64,{base64.b64encode(b).decode()}"
st.download_button("Download CSV", data=b, file_name=name, mime="text/csv")
Schauen wir mal, wie die einzelnen Post-Action-Hilfsfunktionen funktionieren:
fr_from()Funktion: Das macht einen neuen PNG-Screenshot und speichert die aktuelle URL, dann erstellt es für jede ausgeführte Aktion einen „FunctionResponse“. Das gibt einen visuellen Kontext für den nächsten Inferenzschritt und sorgt dafür, dass man nachverfolgen kann, was passiert ist.scrape_google_serp()Funktion: Als Nächstes holen wir Titel, Links und Snippets von der ersten Ergebnisseite von Google raus, indem wir robuste Selektoren wiea:has(h3)für Titel/Links und.VwiC3bfür Snippets nutzen. Der Scraper sammelt die Ergebnisse untermax_itemsund liefert saubere, strukturierte Zeilen, die du direkt analysieren oder exportieren kannst.to_csv_download()Funktion: Diese Funktion macht eine CSV-Datei im Speicher mit einheitlichen Überschriften und zeigt sie über die Streamlit-Funktion „download_button()“ an.
Jetzt haben wir alle Hilfsfunktionen, die wir brauchen, und können als Nächstes eine Streamlit-App drum herum bauen.
Schritt 4: Erstellen der Streamlit-Anwendung
Dieser Schritt verbindet die Streamlit-Benutzeroberfläche mit der Schleife des Computer-Use-Agenten. Es zeigt die Benutzeroberfläche an, regelt, wie der Nutzer mit dem Agenten interagiert, und sorgt dafür, dass das Ergebnis den Bedürfnissen des Nutzers entspricht.
st.set_page_config(page_title="Jobs Search with Gemini Computer Use", layout="wide")
st.title("Jobs Search with Gemini 2.5 Computer Use")
with st.sidebar:
st.markdown("**Search inputs**")
default_kw = "data scientist remote\nml engineer \ngenai research india"
kw_text = st.text_area("Job keywords", value=default_kw, height=120)
query_mode = st.selectbox("Query style", ["<kw> jobs", 'site:linkedin.com/jobs "<kw>"'], index=0)
use_past_week = st.checkbox('Ask agent to set "Past week" filter', value=True)
turns = st.slider("Max agent turns per keyword", 3, 12, 6)
auto_confirm = st.checkbox("Auto-approve risky actions", value=False, help="If model requests confirmation (e.g., CAPTCHA), auto-approve instead of pausing.")
run_btn = st.button("Run search")
log_col, shot_col = st.columns([0.45, 0.55])
log_box = log_col.container(height=520)
shot_box = shot_col.container(height=520)
table_box = st.container()
if run_btn:
keywords = [k.strip() for k in kw_text.splitlines() if k.strip()]
all_rows = []
log_box.write("Initializing browser...")
pw = sync_playwright().start()
browser = pw.chromium.launch(headless=False)
ctx = browser.new_context(viewport={"width": W, "height": H})
page = ctx.new_page()
try:
for kw in keywords:
log_box.subheader(f"▶ {kw}")
page.goto("https://www.google.com", timeout=30000)
initial_shot = page.screenshot(type="png")
base_query = f'{kw} jobs' if query_mode == "<kw> jobs" else f'site:linkedin.com/jobs "{kw}"'
goal = (
f'Search Google for "{base_query}". '
f'{"Open Tools and set time filter to Past week. " if use_past_week else ""}'
'Stop when first-page results are fully visible; do NOT open the Jobs panel.'
)
contents = [Content(role="user", parts=[Part(text=goal), Part.from_bytes(data=initial_shot, mime_type="image/png")])]
cfg = types.GenerateContentConfig(
tools=[types.Tool(computer_use=types.ComputerUse(
environment=types.Environment.ENVIRONMENT_BROWSER
))]
)
for turn in range(turns):
log_box.caption(f"Turn {turn+1}: thinking…")
resp = client.models.generate_content(model=MODEL, contents=contents, config=cfg)
cand = resp.candidates[0]
contents.append(cand.content)
narr = " ".join([p.text for p in cand.content.parts if getattr(p, "text", None)])
if narr:
log_box.info(narr[:400])
fcs = [p.function_call for p in cand.content.parts if getattr(p, "function_call", None)]
if not fcs:
log_box.success("Agent stopped proposing actions.")
break
for fc in fcs:
log_box.write(f"→ {fc.name} {fc.args or {}}")
results = exec_calls(cand, page, (W, H), approve_all=auto_confirm)
frs, shot = fr_from(page, results)
contents.append(Content(role="user", parts=[Part(function_response=fr) for fr in frs]))
shot_box.image(shot, caption=page.url, width='stretch')
rows = scrape_google_serp(page)
for r in rows:
r["keyword"] = kw
all_rows.extend(rows)
log_box.success(f"{kw}: collected {len(rows)} results")
if all_rows:
table_box.dataframe(all_rows, width='stretch')
to_csv_download(all_rows, name="jobs_google_results.csv")
else:
st.warning("No rows collected. Try fewer keywords or fewer turns.")
finally:
browser.close()
pw.stop()
Schauen wir uns diese Pipeline mal genauer an:
- Die App fängt damit an, die Seite mit einem breiten Layout zu gestalten und einen klaren Titel zu setzen, damit die Nutzer sofort wissen, was das Tool macht.
- In der Seitenleiste geben die Nutzer Job-Schlüsselwörter ein, wählen den Abfragestil, einen Zeitfilter, die maximale Anzahl von Antworten pro Schlüsselwort und aktivieren oder deaktivieren die automatische Bestätigung für riskante Aktionen. Dann startet der Knopf„ -Suche ausführen“ den Ablauf.
- Der Hauptbereich ist in zwei Spalten aufgeteilt: links ist ein „Log“-Container, der Text-Updates zeigt, und rechts ein „Screenshot“-Container, der Live-Bilder von jedem Zug anzeigt. Darunter gibt's einen separaten Container für die Tabelle mit den Endergebnissen.
- Wenn du auf „Suchen“ klickst Suche starten, schreibt die Schnittstelle Fortschrittsmeldungen in das Protokollfenster, aktualisiert das Screenshot-Fenster, sobald neue Bilder verfügbar sind, und rendert nach der Verarbeitung aller Schlüsselwörter die aggregierten Ergebnisse als DataFrame.
Um diese App zu starten, gib einfach den folgenden Bash-Befehl in deinem Terminal ein:
python -m streamlit run app.py
Tipp: Starte Streamlit immer über deine venv, um Probleme mit „ModuleNotFoundError” zu vermeiden.
Fazit
Dieses Tutorial zeigt, wie man mit Gemini 2.5 Computer Use einen visuellen Job-Suchagenten baut, um einen Browser zu steuern, Filter anzuwenden und Ergebnisse über eine Streamlit-Benutzeroberfläche zu exportieren.
Von hier aus kannst du den Workflow erweitern, indem du eine Paginierung hinzufügst, um mehr Seiten zu durchsuchen, die Scraper-Funktionen um Veröffentlichungsdaten, Standorte und Firmennamen erweiterst oder eine Sandbox-Jobbörse auf die Whitelist setzt.
Wenn du mehr über Computeranwendungen und agentenbasierte KI erfahren möchtest, schau dir unbedingt den Kurs „Einführung in KI-Agenten“ an. Kurs „Einführung in KI-Agenten“.

Ich bin ein Google Developers Expert in ML (Gen AI), ein Kaggle 3x Expert und ein Women Techmakers Ambassador mit mehr als 3 Jahren Erfahrung im Tech-Bereich. Ich habe 2020 ein Startup im Bereich Gesundheitstechnologie mitbegründet und mache einen Master in Informatik an der Georgia Tech, der sich auf maschinelles Lernen spezialisiert.