Kurs
Gemini ekibi yakın zamanda tanıttı Gemini 2.5 Computer Use: Canlı bir ekranı görebilen ve bir insan operatör gibi tıklayarak, yazarak, kaydırarak ve web’de gezerek işlem yapabilen uzmanlaşmış bir model.
Bu kılavuzda soyut kıyaslamaları atlayıp pratik bir şey inşa edeceğiz: Computer Use kullanarak gerçek bir tarayıcıyı kontrol eden, Google’da iş ilanlarını arayan, bir filtre uygulayan ve üçüncü taraf arama API’lerine gerek kalmadan sonuçları CSV’ye aktaran bir Streamlit uygulaması.
Bu eğitimde şunları öğreneceksiniz:
- Gemini 2.5 Computer Use döngüsünü yapılandırma
- Playwright (Chromium) ile, izinli liste ve riskli adımlar için isteğe bağlı insan onayı kullanarak aracıyı çalıştırma.
- Her turdan sonra canlı eylem günlükleri ve ekran görüntüleri olan bir Streamlit arayüzü oluşturma.
- Organik sonuçları kazıma ve bir CSV indirme.
Sonunda, sizin için doğru iş ilanlarını derleyen bir iş arama aracısına sahip olacaksınız.

Gemini 2.5 hakkında daha fazla bilgi almak isterseniz, özellikler, testler, erişim, kıyaslamalar ve daha fazlasını kapsayan Gemini 2.5 Pro eğitimini incelemenizi öneririm. Ayrıca Google’ın en yeni modeli olan Gemini 3 Pro hakkında da bilgi edinmelisiniz.
Gemini 2.5 Computer Use Nedir?
Gemini 2.5 Computer Use, Gemini API içinde tarayıcı-kontrol aracılar oluşturmanıza imkân veren uzmanlaşmış (önizleme) bir model ve araçtır. Siteye özgü API’ler çağırmak yerine model ekran görüntülerinden çalışır. Sayfada ne olduğunu görebilir ve ardından navigate, click_at, scroll_document gibi UI eylemleri üreterek harekete geçebilir.
İstemci kodu bu önerilen eylemleri alır, uygular ve modele bir sonraki adımı belirlemesi için güncel bir ekran görüntüsü ve URL kombinasyonunu geri gönderir.
Computer Use döngüsü nasıl çalışır

Kaynak: Gemini Dokümantasyonu
Yukarıdaki aracı döngüsü uçtan uca şu şekilde çalışır:
- İstek gönderin: Computer Use aracını, bir hedef yönergesini ve kullanıcı arayüzünün ilk ekran görüntüsünü içeren bir istek göndererek başlayın.
- Yanıt alın: Model, anlatım ve bir veya daha fazla fonksiyon çağrısı (ör.
click_atveyatype_text_at) ve bir güvenlik kararıyla yanıt verir. - Yürütme: Önerilen eylemleri uygularız; yani eylem normal/izinli ise çalıştırırız, onay gerekiyorsa kullanıcıyı uyarır ve yalnızca açık onayla devam ederiz.
- Durumu döndürün: Eylemi uyguladıktan sonra, taze bir ekran görüntüsü ve mevcut URL içeren bir
FunctionResponsegöndererek durumu modele geri döndürürüz. - Tekrarla: Bu
gör–karar ver–eyleme geç–gözlemledöngüsünü görev tamamlanana, bir hata oluşana veya kullanıcı/model durdurmayı seçene kadar tekrarlarız.
Gemini 2.5 Computer Use ve Streamlit ile Bir İş Arama Aracısı Oluşturun
Bu bölümde, Gemini 2.5 Computer Use ve Playwright kullanarak gerçek bir tarayıcıyı yöneten ve ardından sonuçları CSV’ye aktaran Streamlit destekli bir iş arama aracısı oluşturacağız.
Nasıl çalıştığı şöyle:
- Kullanıcı, Streamlit kenar çubuğuna iş anahtar kelimeleri listesi girer.
- Aracı, Gemini’ye Google’ı açmasını, her sorguyu çalıştırmasını ve bir zaman filtresi uygulamasını söyler.
- Aracı, organik sonuçların ilk sayfasında durur ve her turda eylem günlüklerinin yanında canlı ekran görüntüleri gösterir.
- Daha sonra başlıkları, bağlantıları ve özetleri 1. sayfadan kazır ve tüm anahtar kelimeler arasında birleştirir.
- Son olarak, indirilebilir bir CSV dosyasıyla birlikte bir sonuç tablosu oluşturur.
Arka planda, uygulama güvenli gezinme için izinli bir etki alanı zorunlu kılar, riskli adımlar için isteğe bağlı insan onayını destekler ve kolay hata ayıklama için Computer Use döngüsünü kullanır.
Başlamadan önce:
Bazı sorumlu kullanım durumları ve site politikaları şunları içerir:
- Otomasyon kapsamı: Bu demo yalnızca Google Arama’yı (izinli liste) otomatikleştirir ve organik sonuçların 1. sayfasını kazar.
- Site koşullarına saygı gösterin: Hizmet Şartları kısıtlayıcı olan siteleri otomatikleştirmeyin ve hiçbir zaman CAPTCHA’ları atlatmaya çalışmayın. Bir CAPTCHA görünürse, onu elle tamamlayın ve ardından modelin devam etmesine izin verin.
- İnsan-denetimli güvenlik: Gemini riskli bir eylem (CAPTCHA etkileşimi, satın alma gibi) önerirse, uygulama varsayılan olarak durur. Yalnızca otomatik onayı açıkça etkinleştirirseniz devam eder.
Adım 1: Önkoşullar
- Python 3.10+
- Şuradan bir Gemini API anahtarı: Google AI Studio
- Playwright Chromium yüklü macOS / Windows / Linux
Adım 1.1: İçe aktarımlar
Önce, aşağıdaki paketlerin yüklü olduğundan emin olun:
python -m venv .venv && source .venv/bin/activate
pip install streamlit google-genai playwright python-dotenv
playwright install chromium
Yukarıdaki komutlar bir sanal ortam kurar ve uygulamayı geliştirmek için gerekli tüm çekirdek bağımlılıkları yükler; yani Streamlit arayüz için, google-genai Gemini API’yi çağırmak için, Playwright tarayıcı otomasyonu için ve python-dotenv ortam değişkenlerini yüklemek, Chromium ise Playwright için.
Adım 1.2: Gemini API anahtarını ayarlama
Artık bağımlılıkları yüklediğimize göre, sırada AI Studio’dan Gemini API Anahtarını ayarlamak var.
- AI Studio’ya giriş yapın ve “Get API Key” araması yapın
- Create API key seçeneğine tıklayın
- Mevcut bir Google Cloud projesi seçin veya faturalandırması etkin bir tane oluşturun.

- Yalnızca link a billing account seçeneğine tıklayın ve doğrulama için kart bilgilerinizi ekleyin.
- Son olarak API Anahtarını AI Studio’dan kopyalayın ve kaydedin.
Şimdi, proje klasörünüzde bir .env dosyası oluşturun ve API Anahtarınızı buraya ekleyin:
GOOGLE_API_KEY=YOUR_REAL_KEY
ALLOWED_HOSTS=google.com
python-dotenv paketi, uygulamanın Gemini 2.5 Computer Use modelini çağırabilmesi için çalışma zamanında GOOGLE_API_KEY’i yükleyecektir.
Adım 2: Model yapılandırmasını ayarlama
Şimdi, çalışma zamanı için içe aktarımlar, sabitler, model kimliği ve kimlik doğrulama dâhil hazırlık yapalım. Bu, uygulamanın Gemini ile konuşmasını ve gezinmenin güvenli bir izinli alan içinde kalmasını sağlar.
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)
Streamlit, Playwright, google-genai ve yapılandırma/İçDış (dotenv, os, io, csv, time, urllib) için küçük yardımcılar gibi çekirdek kütüphaneleri içe aktararak başlıyoruz.
Sabit bir görünüm alanı (W, H = 1440 × 900), Computer Use için ekran görüntülerini ve koordinat eşlemesini tutarlı kılar ve önizleme modeli gemini-2.5-computer-use-preview-10-2025 olarak ayarlanır.
API anahtarı load_dotenv() ve os.getenv("GOOGLE_API_KEY") ile yüklenir. Gezinme, güvenlik için izinli bir liste (varsayılan google.com) ile ayarlanır.
Son olarak, genai.Client(api_key=API_KEY) Gemini SDK’yı başlatır; böylece uygulama aracı döngüsünü çalıştırabilir ve sonuçları Streamlit’te sunabilir.
Adım 3: Yardımcı fonksiyonlar oluşturma
Uygulamanın özüne geçmeden önce birkaç yardımcı fonksiyon kurmamız gerekiyor. Bunlar güvenlik kontrollerine, koordinat dönüşümüne, Gemini’nin fonksiyon çağrıları için eylemleri yürütmeye, SERP kazımaya ve sonuçları CSV olarak dışa aktarmaya yardımcı olur.
Adım 3.1: Güvenlik ve Eylem Yürütme
Güvenli tarayıcı kontrolünü sağlayan bir arka uç yardımcısı ekleyerek başlıyoruz. Buna gezinmeyi sınırlamak için etki alanı izin listesi kontrolü, modelin 0–999 ızgarasını sabit görünüm alanına eşleyen bir koordinat dönüştürücü ve Computer Use fonksiyon çağrılarını yorumlayıp Playwright aracılığıyla yürüten bir eylem dağıtıcı fonksiyon dâhildir.
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
Her fonksiyonun iş hattına nasıl uyduğunu anlayalım:
host_allowed()fonksiyonu: Hedef URL’yi ayrıştırır, ana makine adını küçük harfe normalleştirir ve.envdosyasında listelenenALLOWED_HOSTS’teki herhangi bir alan adıyla bitip bitmediğini denetler.denorm()fonksiyonu: İşaretçi koordinatlarını mevcut görünüm alanı için gerçek piksel birimlerine dönüştürür. Özellikle ekran boyutlarını değiştirdiğinizde veya başsız çalıştırdığınızda, tıklamalar, üzerine gelmeler ve yazma işlemleri için kritiktir.exec_calls()fonksiyonu: Son olarak yürütme çağrıları fonksiyonu, modelin yanıtını fonksiyon çağrıları için tarar ve her birini Playwright’a iletir. İzinli listede olmayan ana makinelere yapılan navigate çağrılarını engeller, desteklenen eylemleri yürütür, sayfanın dengelenmesi için ağ boşta durumunu bekler ve eylem başına sonucu (hatalar dâhil) kaydeder. Bu sonuçlar bir sonraki adımda modele geri gönderilecekFunctionResponseleri oluşturmak için kullanılır.
Birlikte, bu fonksiyonlar sınırları uygular, işaretçi hesaplamalarını doğru tutar ve ayrıntılı sonuçlar kaydeder.
Adım 3.2: Eylem sonrası geri bildirim ve veri yakalama
Eylemler yürütüldükten sonra, aracının bir sonraki adıma karar verebilmesi ve faydalı çıktılar döndürebilmesi için geri bildirime ihtiyacı vardır. Bu adım, aşağıdaki üç yardımcı fonksiyonu sağlar:
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")
Her eylem sonrası yardımcı fonksiyonun nasıl çalıştığına bakalım:
fr_from()fonksiyonu: Güncel bir PNG ekran görüntüsü ve mevcut URL’yi yakalar, ardından her yürütülen eylem için birFunctionResponseoluşturur. Bu, bir sonraki çıkarım turu için görsel bağlamı gömer ve olan bitenin denetlenebilir bir izini korur.scrape_google_serp()fonksiyonu: Ardından Google’ın ilk sonuç sayfasından başlıkları, bağlantıları ve özetleri, başlık/bağlantılar içina:has(h3)ve özetler için.VwiC3bgibi dayanıklı seçiciler kullanarak çıkarırız. Kazıyıcı, sonuçlarımax_itemsile sınırlar ve analiz ya da dışa aktarma için hazır, temiz ve yapılandırılmış satırlar döndürür.to_csv_download()fonksiyonu: Bu fonksiyon, tutarlı başlıklara sahip bir CSV’yi bellek içinde üretir ve Streamlit’indownload_button()fonksiyonu aracılığıyla kullanılabilir hale getirir.
Artık tüm yardımcı fonksiyonlar hazır; sırada bunların etrafında bir Streamlit uygulaması kurmak var.
Adım 4: Streamlit uygulamasını oluşturma
Bu adım, Streamlit arayüzünü Computer Use aracı döngüsüne bağlar. Arayüzü oluşturur, kullanıcının aracıyla nasıl etkileşeceğini yönetir ve çıktının ihtiyaçlarıyla eşleşmesini sağlar.
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()
Bu iş hattını ayrıntılı olarak anlayalım:
- Uygulama, sayfayı geniş yerleşimle yapılandırarak ve aracın ne yaptığını kullanıcıların hemen anlamasını sağlayacak net bir başlık ayarlayarak başlar.
- Kenar çubuğunda kullanıcılar iş anahtar kelimelerini girer, sorgu stilini ve bir zaman filtresini seçer, aracı için anahtar kelime başına azami tur sayısını belirler ve riskli eylemler için otomatik onayı etkinleştirip devre dışı bırakır. Ardından Run search düğmesi iş akışını tetikler.
- Ana alan iki sütuna bölünmüştür: solda metinsel güncellemeleri akıtan bir “günlük” konteyneri ve sağda her turdan canlı görüntüleri gösteren bir “ekran görüntüsü” konteyneri. Bunların altında nihai sonuç tablosu için ayrı bir konteyner bulunur.
- Kullanıcı Run search düğmesine tıkladığında, arayüz ilerleme iletilerini günlük paneline yazar, yeni görüntüler geldikçe ekran görüntüsü panelini günceller ve tüm anahtar kelimeler işlendiğinde birleştirilmiş sonuçları bir veri çerçevesi olarak görüntüler.
Bu uygulamayı çalıştırmak için terminalinizde şu bash komutunu çalıştırmanız yeterlidir:
python -m streamlit run app.py
İpucu: “ModuleNotFoundError” hatalarından kaçınmak için Streamlit’i her zaman sanal ortamınızdan başlatın.
Sonuç
Bu eğitim, bir tarayıcıyı kontrol etmek, filtreler uygulamak ve sonuçları bir Streamlit arayüzü kullanarak dışa aktarmak için Gemini 2.5 Computer Use’u kullanarak görsel bir iş arama aracısının nasıl oluşturulacağını gösteriyor.
Buradan sonra, daha fazla sayfada gezinmek için sayfalama ekleyerek iş akışını genişletebilir, yayımlanma tarihleri, konumlar ve şirket adlarıyla kazıyıcı yeteneklerini artırabilir veya bir kum havuzu iş panosunu izinli listeye alabilirsiniz.
Bilgisayar kullanımı ve aracısal yapay zekâ hakkında daha fazla bilgi edinmek istiyorsanız AI Aracılara Giriş kursuna mutlaka göz atın.
ML (Üretken Yapay Zekâ) alanında Google Developers Uzmanıyım, Kaggle 3x Expert unvanına sahibim ve 3+ yıllık teknoloji deneyimiyle Women Techmakers Elçisiyim. 2020'de bir sağlık teknolojileri girişiminin kurucu ortağı oldum ve Georgia Tech'te makine öğrenmesi alanında uzmanlaşarak bilgisayar bilimleri yüksek lisansı yapıyorum.

