---
title: "Crawl4AI Nedir? Kurulum ve Proxy Ayarları"
description: "Crawl4AI, web sayfalarını LLM'e hazır Markdown'a çeviren açık kaynak Python crawler'ıdır. pip ve Docker kurulumunu, ProxyConfig ile proxy ayarını anlatıyoruz."
url: https://proxynet.io/tr/blog/crawl4ai-proxy
date: 2026-09-24
author: "Acar Diveroli"
category: "Yapay Zekâ, Entegrasyon"
lang: tr
---

# Crawl4AI Nedir? Kurulum ve Proxy Ayarları

Bir ekip, şirket içi asistanının soruları ürün dokümantasyonuna bakarak yanıtlamasını istiyor. Sayfaları Requests ile indirip HTML'i modele veriyorlar, ama metnin yarısını menüler, çerez bantları ve script'ler kaplıyor; içeriğini JavaScript ile yükleyen sayfalar ise neredeyse boş geliyor. Crawl4AI aynı sayfaları gerçek bir tarayıcıda açıyor ve gövdeyi temiz Markdown olarak döndürüyor. İkinci gün sorunlar değişiyor: 300 sayfalık bir taramanın ortasında site `429` yanıtı vermeye başlıyor, derleme sunucusundaki Docker kurulumu ise yalnızca "connection reset" döndürüyor.

Bu yazıda Crawl4AI'ın bir sayfayı nasıl Markdown'a çevirdiğini, pip ve Docker kurulumunu, proxy ayarını, rotating ve sticky kullanımı, taramayı ölçülü tutan robots.txt ve hız ayarlarını anlatıyoruz. Tüm Python örneklerini Crawl4AI 0.9.4 ve Python 3.13 ile, kullanıcı adı ve parola isteyen yerel bir test proxy'si üzerinden çalıştırdık. Test makinesinde Docker yoktu; bu yüzden Docker komutları resmî kılavuzu izliyor.

> **Not: Kısa cevap**
>
> Crawl4AI açık kaynak bir Python kütüphanesidir (Apache 2.0): web sayfalarını Playwright aracılığıyla gerçek bir tarayıcıda açar ve dil modeline hazır Markdown'a çevirir. `pip install -U crawl4ai` ve `crawl4ai-setup` ile kurulur ya da 11235 portunda Docker sunucusu olarak çalıştırılır; bu sunucu 0.9.0'dan beri bir `CRAWL4AI_API_TOKEN` ister. Proxy, `CrawlerRunConfig` ya da `BrowserConfig` içine `proxy_config` olarak verilen bir `ProxyConfig(server, username, password)` nesnesidir. Rotating bir ağ geçidi için tek bir `ProxyConfig` yeter; `RoundRobinProxyStrategy` sabit bir IP listesi içindir. Docker API'si istekteki proxy'yi reddeder, bu yüzden proxy gerektiren işlerde SDK'yı kullanın.

## Crawl4AI nedir ve ne için kullanılır?

Crawl4AI, web sayfalarını indirip içeriklerini bir dil modelinin okuyabileceği biçimde döndüren açık kaynak bir Python kütüphanesidir. Chromium'u Playwright üzerinden yönetir, bu yüzden JavaScript ile oluşturulan sayfalar okunmadan önce render edilir ([statik ve dinamik sayfalar](/tr/blog/static-vs-dynamic-pages)). Tek bir tarama size şunları döndürür: sayfanın Markdown hâli, menü ve footer'ı atılmış daha kısa bir "fit" Markdown, bağlantılar, medya listesi ve istenirse bir ekran görüntüsü ya da PDF. Bir çıkarma stratejisi (extraction strategy) eklendiğinde bir JSON şemasını da doldurabilir.

Kütüphane Python 3.10 ya da daha yeni bir sürüm ister; 0.9.4 sürümü 23 Eylül 2026'da [PyPI'da](https://pypi.org/project/Crawl4AI/) yayımlandı. Crawl4AI'ı bir Python SDK'sı olarak, REST API ve MCP endpoint'i sunan bir Docker sunucusu olarak ya da `crwl` komut satırı aracıyla kullanabilirsiniz.

Crawl4AI her şeyden önce bir crawler'dır: sayfaları ziyaret eder ve bağlantıları izler, sayfadan neyi çıkaracağınız size kalır ([web crawling ile web scraping farkı](/tr/blog/web-scraping-vs-web-crawling)). Yapay zekâ destekli scraping (veri kazıma) araçlarıyla genel olarak nasıl karşılaştırıldığını [AI Web Scraper Nedir ve Nasıl Çalışır?](/tr/blog/ai-web-scraper-how-it-works-2026) yazımızda anlattık.

## Crawl4AI bir sayfayı nasıl Markdown'a çevirir?

Bir `arun()` çağrısı şu adımlardan geçer:

1. **Tarayıcı `BrowserConfig` ayarlarıyla başlar:** headless mod (pencere açmadan çalışma), `User-Agent` ve orada tanımlandıysa tarayıcının tamamı için bir proxy.
2. **`CrawlerRunConfig` içinde `check_robots_txt=True` varsa robots.txt kontrol edilir.** İzin verilmeyen URL hiç açılmaz; sonuç `403` durum kodu ve "Access denied by robots.txt" mesajıyla döner.
3. **Sayfa Chromium'da yüklenir** ve JavaScript'i çalışır. Çalıştırmaya (run) bir proxy verildiyse istek onun üzerinden gider.
4. **HTML temizlenir:** script'ler ve stiller atılır, bağlantılar ve medya toplanır.
5. **`DefaultMarkdownGenerator`, `raw_markdown` alanını yazar.**
6. **Bir `content_filter`, `fit_markdown` alanını yazar:** `PruningContentFilterLXML` metin ağırlıklı blokları, `BM25ContentFilter` ise bir sorguyla eşleşen blokları tutar. Filtre yoksa `fit_markdown` boş kalır.
7. **Bir `CrawlResult` döner:** içinde `success`, `status_code`, `error_message`, `markdown` ve `links` bulunur.

Test sayfalarımızdan birinde ham Markdown 1.241, fit Markdown 712 karakter tuttu: menü ve footer gitmiş, makale kalmıştı. Bir çerez bildirimi ise yerinde durdu, çünkü filtre anlama değil, metin ve bağlantı yoğunluğuna göre puan verir. `CrawlerRunConfig` içine yazdığımız `excluded_selector=".cookie"` onu da kaldırdı.

## Crawl4AI pip ya da Docker ile nasıl kurulur?

pip yolu kütüphaneyi ve bir Chromium sürümünü kurar. Docker yolu ise başka programların HTTP üzerinden çağırdığı bir sunucu başlatır.

```bash
# Python SDK
pip install -U crawl4ai
crawl4ai-setup      # Crawl4AI'ın kullandığı Playwright tarayıcısını kurar
crawl4ai-doctor     # kurulumu kontrol etmek için bir deneme taraması yapar

# Docker sunucusu: 0.9.0 ve sonrası token ister
export CRAWL4AI_API_TOKEN="$(openssl rand -hex 32)"
docker run -d -p 11235:11235 --name crawl4ai --shm-size=1g \
  -e CRAWL4AI_API_TOKEN="$CRAWL4AI_API_TOKEN" \
  unclecode/crawl4ai:0.9.4

curl http://localhost:11235/health    # token olmadan yanıt verir
curl -X POST http://localhost:11235/md \
  -H "Authorization: Bearer $CRAWL4AI_API_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"url": "https://quotes.toscrape.com/", "f": "fit"}'
```

Resmî [self-hosting kılavuzu](https://docs.crawl4ai.com/core/self-hosting/) `latest` etiketini kullanıyor. Sürüm numaralı bir etiket ise imaj güncellendiğinde sunucunun davranışının siz fark etmeden değişmesini önler. `/playground` ve `/dashboard` sayfalarının en üstünde token'ı yazacağınız bir alan var.

> **Uyarı: docker run sonrasında connection reset**
>
> 0.9.0'dan önce yazılmış kılavuzlar ve kontrol ettiğimiz gün README'deki hızlı başlangıç bölümü, konteyneri token olmadan başlatıyor. 0.9.0'dan itibaren böyle bir sunucu yalnızca konteynerin kendi loopback adresini dinler. Bu yüzden `docker ps` sağlıklı bir konteyner gösterse de dışarıya açılan port "connection reset" yanıtı verir. Değer verilmeden yazılan kısa biçim `-e CRAWL4AI_API_TOKEN` da, değişken kabuğunuzda tanımlı değilse aynı sonucu doğurur.

Crawl4AI'ı çalıştırmanın üç yolu arasındaki asıl fark, proxy'nin nereye yazılabildiğidir:

| Yol | Kurulum | Proxy nereye yazılır | robots.txt ve hız | Uygun olduğu iş |
|---|---|---|---|---|
| Python SDK | `pip install`, `crawl4ai-setup` | `CrawlerRunConfig` ya da `BrowserConfig` içinde `proxy_config`; liste için `proxy_rotation_strategy` | `check_robots_txt`, `SemaphoreDispatcher`, `RateLimiter` | Kendi proxy'nizi gerektiren her iş |
| Docker sunucusu | Token ile `docker run`, port 11235 | İstekte yazılamaz (HTTP 400) | İstekte `check_robots_txt` kullanılabilir | Başka dillerden, n8n'den ya da ajanlardan gelen çağrılar |
| `crwl` CLI | pip ile birlikte gelir | Tarayıcı yapılandırma dosyası, `-B` | Crawler yapılandırma dosyası, `-C` | Tek bir sayfayı Markdown'a çevirmek |

## Python'da ilk taramanızdan nasıl Markdown alırsınız?

Aşağıdaki betik tek bir sayfayı proxy üzerinden açar, önce robots.txt'yi kontrol eder ve iki Markdown sürümünün boyutunu yazdırır. Proxy adresi bir ortam değişkeninden gelir; böylece parola kodun dışında kalır.

```python
"""Tek bir sayfayı proxy üzerinden tara ve Markdown'ını yazdır."""
import asyncio
import os
import sys

from crawl4ai import (
    AsyncWebCrawler,
    BrowserConfig,
    CacheMode,
    CrawlerRunConfig,
    DefaultMarkdownGenerator,
    ProxyConfig,
    PruningContentFilterLXML,
)

URL = sys.argv[1] if len(sys.argv) > 1 else "https://quotes.toscrape.com/"

async def main():
    # PROXY_URL=http://user:pass@pr.proxynet.io:8000, kodun dışında tutulur
    proxy = ProxyConfig.from_string(os.environ["PROXY_URL"])

    browser_config = BrowserConfig(
        headless=True,
        user_agent="NorthwindDocsBot/1.0 (+https://example.com/bot)",
    )
    run_config = CrawlerRunConfig(
        proxy_config=proxy,
        check_robots_txt=True,
        cache_mode=CacheMode.BYPASS,
        markdown_generator=DefaultMarkdownGenerator(
            content_filter=PruningContentFilterLXML(threshold=0.48)
        ),
    )

    async with AsyncWebCrawler(config=browser_config) as crawler:
        result = await crawler.arun(URL, config=run_config)

    if not result.success:
        print(f"failed: {result.status_code} {result.error_message}")
        return

    md = result.markdown
    print(f"status {result.status_code}")
    print(f"raw_markdown: {len(md.raw_markdown)} characters")
    print(f"fit_markdown: {len(md.fit_markdown)} characters")
    print(md.fit_markdown[:400])

asyncio.run(main())
```

Scraping alıştırması için hazırlanmış quotes.toscrape.com sitesinde şu çıktıyı verdi:

```text
status 200
raw_markdown: 4375 characters
fit_markdown: 3663 characters
```

`CacheMode.BYPASS` sayfayı her seferinde yeniden indirir; bu ayar olmadan tekrarlanan URL'ler yerel önbellekten gelir. Filtre olarak `PruningContentFilterLXML` kullanın: 0.9.4'te eski `PruningContentFilter` bir kullanımdan kaldırma (deprecation) uyarısı basıyor.

## Crawl4AI'da proxy nasıl tanımlanır?

Proxy, `server`, `username` ve `password` alanları olan bir `ProxyConfig` nesnesidir ve iki yerden birine yazılır:

```python
from crawl4ai import BrowserConfig, CrawlerRunConfig, ProxyConfig

proxy = ProxyConfig(server="http://pr.proxynet.io:8000", username="user", password="pass")

run_config = CrawlerRunConfig(proxy_config=proxy)    # yalnızca bu çalıştırma
browser_config = BrowserConfig(proxy_config=proxy)   # bu tarayıcının açtığı her sayfa
```

[Resmî proxy kılavuzu](https://docs.crawl4ai.com/advanced/proxy-security/) `CrawlerRunConfig`'i öneriyor; böylece her çalıştırma kendi proxy'sini taşır. Testimizde ikisi de çalıştı.

`ProxyConfig.from_string()` şu biçimleri okur: `http://user:pass@host:port`, `host:port:user:pass`, `host:port` ve `socks5://host:port`. `ProxyConfig.from_env("PROXIES")` bir ortam değişkenindeki virgülle ayrılmış listeyi okur. Eski `proxy=` parametresi hâlâ çalışıyor, ama kullanımdan kaldırma uyarısı basıyor.

**Parolalı SOCKS5 çalışmaz.** `socks5://` adresine kullanıcı adı ve parola eklediğimizde tarama, iki biçimde de "Browser does not support socks5 proxy authentication" hatasıyla başarısız oldu. Bu sınır Chromium'dan geliyor ([Playwright ile proxy kullanımı](/tr/blog/playwright-proxy)). Proxy'nin HTTP endpoint'ini kullanın ya da proxy panelinde sunucunuzun IP'sine izin verip (IP whitelist) parolasız bağlanın ([SOCKS ve HTTP proxy farkı](/tr/blog/socks-vs-http-proxy)).

Proxy'nin çalıştığını görmek için ziyaretçinin IP'sini gösteren bir sayfayı tarayın.

## Rotating mi sticky mi: RoundRobinProxyStrategy ne zaman gerekir?

Cevap, proxy adresinizin arkasında ne olduğuna bağlı.

**Rotating ağ geçidi**, `pr.proxynet.io:8000` gibi tek bir adrestir; sağlayıcı bu adresin arkasında çıkış IP'sini değiştirir. Bir [Rotating Proxy](https://proxynet.io/tr/rotating-proxy) ya da rotating bir [Residential Proxy](https://proxynet.io/tr/residential-proxy) kullanıyorsanız Crawl4AI'a tek bir `ProxyConfig` yeter. Resmî proxy sayfasındaki demo, sitenin gördüğü IP'yi `ProxyConfig.ip` ile karşılaştırıyor. Ağ geçidinde bu kontrol her zaman uyuşmazlık bildirir, çünkü çıkış IP'si hiçbir zaman ağ geçidinin adresi değildir.

**Sabit bir IP listesinde** ise `RoundRobinProxyStrategy` işe yarar: her istek listedeki bir sonraki proxy'yi alır. Böyle bir liste örneğin bir [Datacenter Proxy](https://proxynet.io/tr/datacenter-proxy) ya da [ISP Proxy](https://proxynet.io/tr/static-isp-residential-proxy) paketinden gelir. `proxy_session_id` verildiğinde aynı kimliği taşıyan istekler, `proxy_session_ttl` saniye geçene kadar aynı proxy'de kalır:

```python
"""Sabit bir proxy listesinde sırayla dön ya da bir oturum boyunca aynı proxy'de kal."""
import asyncio

from crawl4ai import (
    AsyncWebCrawler,
    BrowserConfig,
    CacheMode,
    CrawlerRunConfig,
    ProxyConfig,
    RoundRobinProxyStrategy,
)

# PROXIES="http://user:pass@203.0.113.10:8000,http://user:pass@203.0.113.11:8000"
strategy = RoundRobinProxyStrategy(ProxyConfig.from_env("PROXIES"))

rotate = CrawlerRunConfig(proxy_rotation_strategy=strategy, cache_mode=CacheMode.BYPASS)
sticky = CrawlerRunConfig(
    proxy_rotation_strategy=strategy,
    proxy_session_id="catalog-1",  # bu kimliği taşıyan her istek aynı proxy'yi alır
    proxy_session_ttl=600,         # saniye; süre dolunca oturum yeni bir proxy seçer
    cache_mode=CacheMode.BYPASS,
)

async def main(base):
    async with AsyncWebCrawler(config=BrowserConfig(headless=True)) as crawler:
        for label, config in (("rotate", rotate), ("sticky", sticky)):
            for page in range(1, 4):
                result = await crawler.arun(f"{base}/catalog?page={page}", config=config)
                print(label, page, result.status_code, config.proxy_config.server)

asyncio.run(main("https://shop.example.com"))
```

İki yerel proxy ile denediğimizde `rotate` istekleri sırayla birinden ötekine geçti, `sticky` istekleri ise tek bir proxy'de kaldı. Oturum parametreleri 0.9.4'ün kaynak kodunda var ama proxy belge sayfasında yok; sürüm yükselttikten sonra yeniden kontrol edin.

İki katmanı birbirine karıştırmayın. Crawl4AI'ın sticky oturumu sizin listenizden hep aynı girdiyi seçer. Rotating bir ağ geçidinin arkasında ise çıkış IP'si ancak sağlayıcı onu sabit tutarsa değişmez; [Sticky Proxy](https://proxynet.io/tr/sticky-proxy) oturumu bunu 1 ile 60 dakika arasında yapar. Bu modların ayrıntısı [IP rotasyonu](/tr/blog/ip-rotation-explained) yazımızda, sade HTTP istemcilerinde rotasyon ise [Python'da proxy döndürme](/tr/blog/rotating-proxy-python) rehberinde.

## Docker isteği neden proxy taşıyamaz?

0.9.0'dan beri Docker sunucusu varsayılan olarak güvenli ayarlarla gelir. Gövdesinde `proxy` ya da `proxy_config` bulunan bir istek HTTP 400 alır; `js_code`, `headers`, `cookies`, `magic` ve birkaç başka alan için de durum aynı ([0.9.0 geçiş notları](https://github.com/unclecode/crawl4ai/blob/main/deploy/docker/MIGRATION.md)). Sebep sunucu taraflı istek sahteciliğidir (SSRF): bu kısıt olmasa istek gönderen herkes, sunucunun tarayıcısını istediği bir proxy üzerinden ya da iç ağdaki adreslere yönlendirebilirdi.

Geçiş notları bu seçeneklerin sunucu tarafında yapılandırılmasını öneriyor. Ancak 0.9.4 kaynak kodunda bir çıkış koruması (egress guard), `config.yml` içindekiler dahil her `proxy_config` değerini siliyor ve Chromium'u sunucunun kendi filtreleme proxy'si üzerinden yönlendiriyor. Kaynak kod ayrıca `CRAWL4AI_UPSTREAM_PROXY` ya da `HTTPS_PROXY` değişkeninden bir üst (upstream) HTTP proxy okuyor; bu davranış belgelenmemiş ve biz test edemedik. Kendi proxy'nizi kullanmak için SDK'yı kendi servisinizde çalıştırın.

## robots.txt, hız ve eşzamanlılık nasıl ayarlanır?

`check_robots_txt` varsayılan olarak `False` değerindedir. 0.9.4'ün davranışını yerel sitelerde test ettik:

- **İzin verilmeyen bir yol** `403` durum kodu döndürür ve sayfa hiç istenmez.
- **`500` yanıtı veren bir robots.txt** "her şey serbest" sayılır; 2 saniyelik zaman aşımı ve ağ hatası da öyle. Oysa [RFC 9309](https://www.rfc-editor.org/rfc/rfc9309.html), sunucu hatalarında crawler'ın siteyi tamamen yasaklı kabul etmesi gerektiğini söyler.
- **Kurallar 7 gün önbellekte tutulur.** RFC 9309'a göre önbellekteki kopya 24 saatten uzun kullanılmamalı; `crawler.robots_parser.clear_cache()` önbelleği boşaltır.
- **robots.txt proxy üzerinden değil, doğrudan sizin makinenizden indirilir** ve istekte genel bir `aiohttp` User-Agent'ı kullanılır.
- **Kurallar `BrowserConfig.user_agent` değeriyle eşleştirilir.** Varsayılan değer bir Chrome dizgisidir; bu yüzden botunuzun adına yazılmış bir `Disallow` ancak `User-Agent` değeriniz o adı taşıyorsa geçerli olur. `/private` altındaki bir yol varsayılan dizgiyle açıldı, `NorthwindDocsBot/1.0` ile ise `403` döndü.

Hassas işlerde robots.txt'yi önce kendiniz kontrol edin ([robots.txt](/tr/blog/robots-txt), [User-Agent Nedir?](/tr/blog/what-is-user-agent)).

Hız, dispatcher'da (sayfaları sıraya koyup dağıtan bileşende) ayarlanır. `SemaphoreDispatcher(semaphore_count=3)` aynı anda en fazla üç sayfayı açık tutar ([concurrency ve parallelism](/tr/blog/concurrency-vs-parallelism)). `RateLimiter` aynı alan adına giden istekler arasında bekler, `429` ya da `503` sonrasında bekleme süresini `max_delay` sınırına kadar yaklaşık iki katına çıkarır ve başarılı yanıtlardan sonra kısaltır. Reddedilen sayfayı yeniden indirmez: sonuç `429` ile döner, yeniden denemek size kalır. Aşağıdaki betik sayfaları üçerli gruplar hâlinde tarar ve reddedilenlere iki tur daha şans verir:

```python
"""Tek bir sitenin sayfalarını ölçülü bir hızda tara ve her birini Markdown olarak kaydet."""
import asyncio
import os
import re
from pathlib import Path

from crawl4ai import (
    AsyncWebCrawler,
    BrowserConfig,
    CacheMode,
    CrawlerRunConfig,
    DefaultMarkdownGenerator,
    ProxyConfig,
    PruningContentFilterLXML,
    RateLimiter,
    SemaphoreDispatcher,
)

BASE = os.environ.get("DOCS_BASE", "https://docs.example.com")
URLS = [f"{BASE}/docs/{n}" for n in range(1, 13)] + [f"{BASE}/private/report"]
OUT = Path("pages")
BATCH = 3                 # aynı anda açık tutulan sayfa sayısı
PAUSE = 5.0               # iki grup arasındaki bekleme (saniye)
RETRY_CODES = {429, 503}  # sonra yeniden denemeye değer
ROUNDS = 3                # ilk tur artı iki yeniden deneme turu
ROUND_PAUSE = 60          # yeniden deneme turundan önceki bekleme (saniye); her turda ikiye katlanır

def file_name(url):
    return re.sub(r"[^a-z0-9]+", "-", url.lower()).strip("-") + ".md"

async def crawl_round(crawler, urls, run_config, dispatcher):
    """URL'leri küçük gruplar hâlinde tara, sonra yeniden denenecek olanları döndür."""
    retry = []
    for i in range(0, len(urls), BATCH):
        results = await crawler.arun_many(
            urls[i : i + BATCH], config=run_config, dispatcher=dispatcher
        )
        for r in results:
            if r.success and r.status_code == 200:
                (OUT / file_name(r.url)).write_text(r.markdown.fit_markdown, encoding="utf-8")
                print(f"saved  {r.url}")
            elif r.status_code in RETRY_CODES:
                retry.append(r.url)
                print(f"later  {r.status_code} {r.url}")
            else:
                print(f"skip   {r.status_code} {r.url}: {r.error_message}")
        await asyncio.sleep(PAUSE)
    return retry

async def main():
    OUT.mkdir(exist_ok=True)
    browser_config = BrowserConfig(
        headless=True,
        user_agent="NorthwindDocsBot/1.0 (+https://example.com/bot)",
    )
    run_config = CrawlerRunConfig(
        proxy_config=ProxyConfig.from_string(os.environ["PROXY_URL"]),
        check_robots_txt=True,
        cache_mode=CacheMode.BYPASS,
        page_timeout=30000,
        markdown_generator=DefaultMarkdownGenerator(
            content_filter=PruningContentFilterLXML(threshold=0.48)
        ),
    )
    # Tüm çalışma için tek dispatcher: RateLimiter 429'lardan öğrendiği yavaş tempoyu korur
    dispatcher = SemaphoreDispatcher(
        semaphore_count=BATCH,
        rate_limiter=RateLimiter(base_delay=(1.0, 3.0), max_delay=60.0, max_retries=3),
    )

    pending = list(URLS)
    async with AsyncWebCrawler(config=browser_config) as crawler:
        for round_no in range(ROUNDS):
            if round_no:
                wait = ROUND_PAUSE * 2 ** (round_no - 1)
                print(f"round {round_no + 1}: {len(pending)} pages again in {wait} s")
                await asyncio.sleep(wait)
            pending = await crawl_round(crawler, pending, run_config, dispatcher)
            if not pending:
                break

    print(f"done, {len(pending)} pages still refused")

asyncio.run(main())
```

`DOCS_BASE` değişkenini, `/docs/` altındaki her dördüncü isteğe `429` veren ve `/private` yolunu yasaklayan yerel bir siteye yönelttik; test için `ROUND_PAUSE` değerini 5 saniyeye indirdik. Kısaltılmış çıktı:

```text
saved  http://192.168.1.2:28130/docs/1
saved  http://192.168.1.2:28130/docs/2
saved  http://192.168.1.2:28130/docs/3
later  429 http://192.168.1.2:28130/docs/4
...
later  429 http://192.168.1.2:28130/docs/11
saved  http://192.168.1.2:28130/docs/12
skip   403 http://192.168.1.2:28130/private/report: Access denied by robots.txt
round 2: 3 pages again in 5 s
saved  http://192.168.1.2:28130/docs/4
saved  http://192.168.1.2:28130/docs/9
saved  http://192.168.1.2:28130/docs/11
done, 0 pages still refused
```

Crawl4AI'ın kendi log çıktısı da reddedilen her sayfa için "Blocked by anti-bot protection: HTTP 429 Too Many Requests" yazar. Gerçek bir `Retry-After` header'ına nasıl uyulacağı [Scraping'de HTTP Hata Kodları](/tr/blog/http-status-codes-web-scraping) yazısında.

## Crawl4AI ile Firecrawl arasındaki fark nedir?

İkisi de sayfaları dil modelleri için Markdown'a çevirir; fark, onları nasıl çalıştırdığınızda ortaya çıkar.

| | Crawl4AI | Firecrawl |
|---|---|---|
| Lisans | Apache 2.0 ve ek bir atıf şartı | AGPL-3.0 |
| Ana biçim | Python kütüphanesi; isteğe bağlı Docker sunucusu | Barındırılan (hosted) API; kendi sunucunuza da kurulabilir |
| Kendi sunucunuzda çalışan parçalar | Tek konteyner | API, worker'lar, Playwright, Redis, RabbitMQ, PostgreSQL |
| Maliyet | Sunucunuz, proxy ve kullandığınız LLM | API paketi ya da kendi sunucularınız |

Firecrawl'ın [self-hosting kılavuzu](https://github.com/firecrawl/firecrawl/blob/main/SELF_HOST.md), kendi sunucunuzda çalışan API'de varsayılan olarak kimlik doğrulama olmadığını belirtiyor. Crawl4AI, taramalarını ve proxy'lerini kendisi yönetmek isteyen bir Python ekibine uyar.

## Crawl4AI, MCP ve n8n ile nasıl kullanılır?

Docker sunucusu MCP'yi `/mcp/sse` ve `/mcp/ws` adreslerinde açar; araçları `md`, `html`, `screenshot`, `pdf`, `execute_js`, `crawl` ve `ask`. Kılavuzdaki Claude Code komutunda token yok, ama MCP endpoint'leri API ile aynı token kontrolünün arkasında duruyor. Bu yüzden header'ı ekleyin:

```bash
claude mcp add --transport sse c4ai-sse http://localhost:11235/mcp/sse \
  --header "Authorization: Bearer $CRAWL4AI_API_TOKEN"
```

Header ayarlayamayan WebSocket istemcileri token'ı `?token=` ile gönderebilir. Protokolün kendisini [MCP Nedir?](/tr/blog/what-is-mcp), bir ajana tam bir tarayıcı vermeyi ise [Playwright MCP](/tr/blog/playwright-mcp) yazımızda anlattık.

n8n'de bir HTTP Request düğümü, Bearer header'ı ve `{"url": "https://quotes.toscrape.com/", "f": "fit"}` gibi bir gövdeyle `POST /md` isteği gönderir; sayfa `markdown` alanında döner ([n8n ile web scraping](/tr/blog/n8n-proxy)).

## Kullanım alanları

- **RAG için dokümantasyon:** ürün belgelerini bir arama dizinine (retrieval index) Markdown olarak vermek; [LLM'e güvenli web erişimi](/tr/blog/llm-safe-web-access) yazısındaki kontrollerden geçirerek.
- **Ajanlar için temiz girdi:** ham HTML yerine fit Markdown ([agentic web scraping](/tr/blog/agentic-web-scraping-how-it-works-2026)).
- **Fiyat kontrolü:** ürün sayfalarını bir CSS şemasıyla JSON'a çevirmek ([fiyat takibi](/tr/price-monitoring)).
- **Kendi sitenizin envanteri:** içerik denetimi ve kırık bağlantı kontrolü için her sayfa ve bağlantı ([web crawler](/tr/web-crawler)).
- **Katalog verisi:** herkese açık katalog sayfalarından ürün adları, teknik özellikler ve fiyatlar ([veri kazıma](/tr/data-scraping)).

## Sık yapılan hatalar

- **Token olmadan `docker run`** ya da değersiz `-e CRAWL4AI_API_TOKEN`: sağlıklı görünen bir konteynerden "connection reset" gelir.
- **REST isteğine `proxy_config` yazmak:** sunucu `400` ile yanıt verir.
- **Parolalı `socks5://`:** Chromium kabul etmez.
- **Rotating bir ağ geçidini `RoundRobinProxyStrategy` listesine birkaç kez yazmak:** ağ geçidi IP'yi zaten değiştiriyor.
- **`content_filter` olmadan `fit_markdown` beklemek:** alan boş kalır.
- **robots.txt'nin kontrol edildiğini varsaymak:** kontrol varsayılan olarak kapalıdır, yüklenemeyen bir robots.txt da "izin var" sayılır.
- **`RateLimiter` olmadan yüksek eşzamanlılık:** küçük bir sitede aynı anda açılan on sayfa ani bir yük gibi görünür, ardından `429` yanıtları gelir.
- **`429` diyen bir siteyi IP değiştirerek zorlamak:** bunun yerine yavaşlayın ([bot tespiti nasıl yapılır](/tr/blog/how-bot-detection-works)).

Belgelerdeki stealth modu, "magic" modu ve anti-bot fallback özellikleri bu yazının kapsamı dışında; bunları önermiyoruz.

## Karar rehberi

| İhtiyaç | Öneri |
|---|---|
| Birkaç dokümantasyon sayfasını LLM için temiz metin olarak almak | pip kurulumu, `PruningContentFilterLXML` ile `arun()` |
| Her istekte farklı bir çıkış IP'si | Rotating residential ağ geçidiyle tek bir `ProxyConfig` |
| Çok adımlı bir akış boyunca aynı IP | Sağlayıcının sticky oturumu; liste kullanıyorsanız ek olarak `proxy_session_id` |
| Sabit bir IP listesi | `RoundRobinProxyStrategy` ile `ProxyConfig.from_env("PROXIES")` |
| n8n'den, başka bir dilden ya da bir ajandan tarama başlatmak | Token'lı Docker sunucusu; proxy işleri SDK'da kalır |
| Siteyi yormadan yüzlerce sayfa taramak | Küçük gruplar, `RateLimiter`, `check_robots_txt=True` |
| Altyapı yönetmeden çalışmak | Firecrawl gibi barındırılan bir API |

## Sıkça sorulan sorular

### Crawl4AI ücretsiz mi?

Evet, kütüphane Apache 2.0 lisansıyla ücretsizdir. LICENSE dosyası buna, herkese açık kullanımlarda projenin anılmasını isteyen bir şart ekler; örneğin bir README'de ya da "Hakkında" sayfasında. Maliyetiniz sunucu, proxy ve çağırdığınız dil modelinden oluşur.

### Crawl4AI hangi Python sürümünü ister?

PyPI sayfasına göre Python 3.10 ya da daha yeni bir sürüm. Biz 0.9.4 sürümünü Python 3.13 ile test ettik.

### Crawl4AI, Ollama gibi yerel bir LLM ile çalışır mı?

Markdown çıktısı için dil modeli gerekmez. LLM ile veri çıkarımı için belgeler, yerel bir Ollama modelinde API anahtarı olmadan `LLMConfig(provider="ollama/llama3.3")` kullanımını gösteriyor.

### Crawl4AI ile Scrapy arasındaki fark nedir?

Scrapy sade HTTP istekleri gönderir ve varsayılan olarak JavaScript çalıştırmaz; Crawl4AI ise her sayfayı Chromium'da render eder ve Markdown döndürür. Scrapy statik HTML'in büyük ölçekli taramalarına uyar ([Scrapy ile proxy kullanımı](/tr/blog/scrapy-proxy)), Crawl4AI ise bir dil modeline gidecek sayfalara. Tarayıcı ve framework kullanmadan, Requests ve BeautifulSoup ile kuyruğu ve derinlik sınırı olan bir crawler'ı [Python ile Web Crawler Nasıl Yazılır?](/tr/blog/python-web-crawler) yazımızda adım adım kurduk.

### Crawl4AI Node.js ya da başka bir dilden kullanılabilir mi?

Kütüphanenin kendisi Python'dır. Diğer dillerden Docker sunucusunun REST API'sini çağırın; örneğin token'ı header'a koyarak `POST /md` isteği gönderin.

### Bir site Crawl4AI'ı engellerse ne yapmalı?

Önce yavaşlayın: daha az paralel sayfa, daha uzun beklemeler ve her `429` sonrasında bir mola. robots.txt'yi ve sitenin kullanım şartlarını kontrol edin, resmî bir API ya da feed arayın. Site hâlâ reddediyorsa durun; işin hukuki tarafı [Web Scraping (Web Kazıma) Yasal mı?](/tr/blog/web-scraping-legal) yazısında.

## Özet

Crawl4AI sayfaları bir dil modelinin okuyabileceği Markdown'a çevirir. Proxy gerektiren işlerde SDK'yı kullanın: rotating ağ geçidi için tek bir `ProxyConfig`, sabit liste için `RoundRobinProxyStrategy`, SOCKS5'te ise parolasız bağlantı. Docker sunucusu token ister ve istekte proxy kabul etmez. `check_robots_txt` ayarını açın, dürüst bir `User-Agent` gönderin ve hızı bir `RateLimiter`'a bırakın. Birçok ülkede çıkış noktası ya da sabit bir adres için [proxy hizmetlerimize](/tr/proxy) göz atın.
