Use Cases

CBS ve Haritalama Veri Çıkarımı için CAPTCHA Kullanımı

Gayrimenkul analizi ya da şehir planlaması için parsel sınırlarını, imar durumlarını ve taşkın bölgesi kayıtlarını topluyorsanız, çoğu resmi CBS portalının sorgu formunu bir görüntü/OCR CAPTCHA'sının arkasına koyduğunu görürsünüz. Kısa cevap şu: bu CAPTCHA'yı bir API çağrısıyla çözer, dönen metni forma yerleştirir ve sorguyu yeniden gönderirsiniz — böylece elle giriş yapmadan binlerce parseli tek bir iş akışında işleyebilirsiniz. Bu rehber, il ve ilçe CBS sistemlerinden yetkili veri toplarken CAPTCHA katmanını CaptchaAI ile nasıl otomatikleştireceğinizi Python ve JavaScript örnekleriyle anlatır.

CBS portallarında karşılaşacağınız CAPTCHA türleri

Coğrafi portallar tek tip değildir: tapu-kadastro sistemleri eski görüntü CAPTCHA'ları kullanırken, ulusal veri portalları reCAPTCHA v2'ye geçmiş olabilir. İş akışınızı kurmadan önce hangi türle uğraştığınızı bilmek gerekir.

Portal türü CAPTCHA türü Tetikleyen işlem
İlçe CBS / tapu-kadastro sistemleri Görüntü metni CAPTCHA Parsel arama sorguları
İl coğrafi veri portalları Özel CAPTCHA Veri indirme istekleri
USGS ve ulusal veri portalları reCAPTCHA v2 Toplu veri erişimi
Belediye imar haritaları Görüntü CAPTCHA Tekrarlı mülk sorguları
Çevresel veri tabanları Matematik CAPTCHA Rapor oluşturma
Taşkın bölgesi sorgusu Görüntü metni CAPTCHA Adres sorguları

Python ile parsel ve adres sorgusu çıkarımı

Aşağıdaki çıkarıcı, sorguyu "gerektiğinde çöz" mantığıyla ele alır: önce sayfayı çeker, yalnızca bir görüntü CAPTCHA'sı algılarsa çözüm adımını çalıştırır, çözülen metni gizli form alanlarıyla birlikte geri gönderir. Aynı session yeniden kullanıldığı için oturum çerezi korunur; bulk_extract ise sorgular arasına gecikme koyarak portalı yormadan çok sayıda parseli sırayla işler.

import requests
import base64
import time
import re

class GISDataExtractor:
    def __init__(self, api_key):
        self.api_key = api_key
        self.session = requests.Session()
        self.session.headers.update({
            "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36"
        })

    def lookup_parcel(self, portal_url, parcel_id):
        """Look up parcel data by ID, solving CAPTCHAs as needed."""
        response = self.session.get(
            f"{portal_url}/parcel", params={"id": parcel_id}
        )

        if self._has_image_captcha(response.text):
            captcha_url = self._extract_captcha_url(response.text, portal_url)
            captcha_text = self._solve_captcha(captcha_url)

            # Re-submit with solved CAPTCHA
            response = self.session.post(f"{portal_url}/parcel", data={
                "id": parcel_id,
                "captcha": captcha_text,
                **self._extract_hidden_fields(response.text)
            })

        return self._parse_parcel_data(response.text)

    def search_by_address(self, portal_url, address):
        """Search GIS records by street address."""
        response = self.session.get(
            f"{portal_url}/search", params={"address": address}
        )

        if self._has_image_captcha(response.text):
            captcha_url = self._extract_captcha_url(response.text, portal_url)
            captcha_text = self._solve_captcha(captcha_url)

            response = self.session.post(f"{portal_url}/search", data={
                "address": address,
                "captcha": captcha_text,
                **self._extract_hidden_fields(response.text)
            })

        return self._parse_search_results(response.text)

    def bulk_extract(self, portal_url, parcel_ids, delay=3):
        """Extract data for multiple parcels with rate limiting."""
        results = {}

        for parcel_id in parcel_ids:
            try:
                results[parcel_id] = self.lookup_parcel(portal_url, parcel_id)
            except Exception as e:
                results[parcel_id] = {"error": str(e)}
            time.sleep(delay)

        return results

    def _has_image_captcha(self, html):
        return bool(re.search(
            r'captcha|verification.?image|security.?code',
            html, re.IGNORECASE
        ))

    def _extract_captcha_url(self, html, base_url):
        from bs4 import BeautifulSoup
        from urllib.parse import urljoin
        soup = BeautifulSoup(html, "html.parser")

        img = (
            soup.find("img", attrs={"src": lambda s: s and "captcha" in s.lower()}) or
            soup.find("img", {"id": re.compile(r"captcha", re.I)}) or
            soup.find("img", {"class": re.compile(r"captcha", re.I)})
        )

        if img and img.get("src"):
            return urljoin(base_url, img["src"])
        raise ValueError("CAPTCHA image not found")

    def _solve_captcha(self, captcha_url):
        """Download and solve image CAPTCHA."""
        img_response = self.session.get(captcha_url)
        img_base64 = base64.b64encode(img_response.content).decode("utf-8")

        resp = requests.post("https://ocr.captchaai.com/in.php", data={
            "key": self.api_key,
            "method": "base64",
            "body": img_base64,
            "json": 1
        })
        task_id = resp.json()["request"]

        for _ in range(30):
            time.sleep(3)
            result = requests.get("https://ocr.captchaai.com/res.php", params={
                "key": self.api_key,
                "action": "get",
                "id": task_id,
                "json": 1
            })
            data = result.json()
            if data["status"] == 1:
                return data["request"]

        raise TimeoutError("CAPTCHA solve timed out")

    def _extract_hidden_fields(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")
        fields = {}
        for inp in soup.select("input[type='hidden']"):
            name = inp.get("name")
            if name:
                fields[name] = inp.get("value", "")
        return fields

    def _parse_parcel_data(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        def text_or_none(node):
            return node.text.strip() if node and node.text else None

        return {
            "parcel_id": text_or_none(soup.select_one(".parcel-id, #parcelId")),
            "owner": text_or_none(soup.select_one(".owner, .owner-name")),
            "address": text_or_none(soup.select_one(".address, .situs")),
            "zoning": text_or_none(soup.select_one(".zoning, .zone-code")),
            "acreage": text_or_none(soup.select_one(".acreage, .area")),
            "assessed_value": text_or_none(soup.select_one(".assessed, .value")),
            "land_use": text_or_none(soup.select_one(".land-use, .use-code"))
        }

    def _parse_search_results(self, html):
        from bs4 import BeautifulSoup
        soup = BeautifulSoup(html, "html.parser")

        def text_or_none(node):
            return node.text.strip() if node and node.text else None

        results = []
        for row in soup.select(".result-row, tr.parcel"):
            results.append({
                "parcel_id": text_or_none(row.select_one(".parcel-id")),
                "address": text_or_none(row.select_one(".address")),
                "owner": text_or_none(row.select_one(".owner"))
            })
        return results


# Usage
extractor = GISDataExtractor("YOUR_API_KEY")

# Single parcel lookup
parcel = extractor.lookup_parcel(
    "https://gis.county.example.gov",
    "12-34-567-890"
)
print(f"Owner: {parcel['owner']}, Zoning: {parcel['zoning']}")

# Bulk extraction
parcels = extractor.bulk_extract(
    "https://gis.county.example.gov",
    ["12-34-567-890", "12-34-567-891", "12-34-567-892"]
)

JavaScript ile koordinat ve bölge tabanlı çıkarım

Sorgunuz parsel numarası yerine enlem/boylam üzerinden yürüyorsa, aşağıdaki sınıf her koordinat için portalı yoklar ve CAPTCHA çıktığında çözüp veriyi ayrıştırır. extractRegion ise verilen sınırların içindeki alanı ızgara adımıyla tarar; her istek arasına gecikme koyarak portalı makul bir hızda sorgular.

class GISExtractor {
  constructor(apiKey) {
    this.apiKey = apiKey;
  }

  async extractByCoordinates(portalUrl, lat, lng) {
    const url = `${portalUrl}/identify?lat=${lat}&lng=${lng}`;
    const response = await fetch(url);
    const html = await response.text();

    if (this.hasCaptcha(html)) {
      return this.solveAndExtract(portalUrl, html, { lat, lng });
    }

    return this.parseGISData(html);
  }

  async extractRegion(portalUrl, bounds, gridSize = 0.01) {
    const results = [];
    const { north, south, east, west } = bounds;

    for (let lat = south; lat <= north; lat += gridSize) {
      for (let lng = west; lng <= east; lng += gridSize) {
        try {
          const data = await this.extractByCoordinates(portalUrl, lat, lng);
          if (data.parcelId) results.push(data);
        } catch (error) {
          console.error(`Failed at ${lat},${lng}: ${error.message}`);
        }
        // Rate limit
        await new Promise(r => setTimeout(r, 2000));
      }
    }

    return results;
  }

  hasCaptcha(html) {
    return /captcha|verification.?image|security.?code/i.test(html);
  }

  async solveAndExtract(portalUrl, html, params) {
    const imgMatch = html.match(/src="([^"]*captcha[^"]*)"/i);
    if (!imgMatch) throw new Error('CAPTCHA image not found');

    const imgUrl = new URL(imgMatch[1], portalUrl).href;
    const imgResp = await fetch(imgUrl);
    const buffer = await imgResp.arrayBuffer();
    const base64 = Buffer.from(buffer).toString('base64');

    const submitResp = await fetch('https://ocr.captchaai.com/in.php', {
      method: 'POST',
      body: new URLSearchParams({
        key: this.apiKey,
        method: 'base64',
        body: base64,
        json: '1'
      })
    });
    const { request: taskId } = await submitResp.json();

    for (let i = 0; i < 30; i++) {
      await new Promise(r => setTimeout(r, 3000));
      const result = await fetch(
        `https://ocr.captchaai.com/res.php?key=${this.apiKey}&action=get&id=${taskId}&json=1`
      );
      const data = await result.json();
      if (data.status === 1) {
        const response = await fetch(portalUrl, {
          method: 'POST',
          body: new URLSearchParams({
            ...params,
            captcha: data.request
          })
        });
        return this.parseGISData(await response.text());
      }
    }
    throw new Error('CAPTCHA solve timed out');
  }

  parseGISData(html) {
    return {
      parcelId: html.match(/parcel.?id[^>]*>([^<]+)/i)?.[1]?.trim(),
      zoning: html.match(/zon(?:e|ing)[^>]*>([^<]+)/i)?.[1]?.trim(),
      acreage: html.match(/acreage|area[^>]*>([^<]+)/i)?.[1]?.trim(),
      landUse: html.match(/land.?use[^>]*>([^<]+)/i)?.[1]?.trim()
    };
  }
}

// Usage
const gis = new GISExtractor('YOUR_API_KEY');

// Single coordinate lookup
const data = await gis.extractByCoordinates(
  'https://gis.county.example.gov',
  34.0522, -118.2437
);

// Extract entire region
const region = await gis.extractRegion('https://gis.county.example.gov', {
  north: 34.10, south: 34.00, east: -118.20, west: -118.30
});

Görüntü CAPTCHA'sı için API parametreleri

Görüntü CAPTCHA'sının doğruluğu, çözücüye verdiğiniz ipuçlarına bağlıdır. Kod uzunluğunu ya da yalnızca sayısal olduğunu biliyorsanız bu parametreleri geçmek çözüm oranını belirgin biçimde artırır.

Parametre Değer Kullanım
method base64 Standart görüntü CAPTCHA'sı
numeric 1 Yalnızca sayısal CAPTCHA'lar
min_len 4 Karakter sayısı biliniyorsa alt sınır
max_len 6 Karakter sayısı biliniyorsa üst sınır
language 0 Latin karakterler
textinstructions Özel metin Matematik CAPTCHA'ları veya biçimli kodlar

Toplu çalıştırma öncesi kontrol listesi

  • Büyük bir toplu çalıştırmayı başlatmadan önce harita görünümünü, bölge filtresini ve sayfalama kontrollerini tek bir örnek sorguyla doğrulayın.
  • Hem normalize edilmiş koordinat/parsel yükünü hem de portalın ham yanıtını saklayın; böylece bir ayrıştırma hatası çıktığında sorunu geriye dönük ayıklayabilirsiniz.
  • CAPTCHA yoğunluğu belirgin biçimde arttığında yeniden denemeleri üst üste bindirmek yerine toplu işlemi kısa süreliğine duraklatın ve istek hızınızı düşürün.

Sık karşılaşılan sorunlar

Aşağıdaki değerler gözlemlenen davranışlara dayanır; portalların uygulaması ilçeden ilçeye değiştiği için sonuçlar ortama göre farklılık gösterebilir.

Sorun Neden Çözüm
CAPTCHA görüntüsü bozuk yükleniyor Oturum çerezi gerekiyor Önce arama sayfasını yükleyin
Çözülen metin reddediliyor Büyük/küçük harf duyarlılığı case_sensitive=1 parametresini ekleyin
Portal farklı bir CAPTCHA döndürüyor Oturuma özel CAPTCHA Aynı oturumda indirip çözün
CAPTCHA sonrası parsel verisi gelmiyor Gizli form alanları eksik Göndermeden önce tüm gizli input'ları çıkarın

KVKK ve yetkili veri toplama

CBS portallarından toplanan parsel ve mülk kayıtları çoğu zaman malik adı gibi kişisel veriler içerir. Türkiye'de bu veriler KVKK (Kişisel Verilerin Korunması Kanunu) kapsamına girer; bu nedenle çıkarım iş akışınızı yetkili bir amaç ve net bir saklama politikasıyla sınırlayın. CaptchaAI yalnızca portalın önündeki CAPTCHA doğrulamasını çözer — hangi veriyi, hangi hukuki dayanakla topladığınız sizin sorumluluğunuzdadır. Kamuya açık imar ve harita katmanlarıyla çalışmak, kişisel veri içeren kayıtları toplu indirmeye kıyasla çok daha düşük risk taşır.

Maliyet: thread tabanlı planlar

CaptchaAI thread tabanlı fiyatlandırır: her plan, aynı anda çalışan CAPTCHA (thread) sayısına göre ücretlendirilir ve thread başına çözüm sayısı sınırsızdır. Tek bir operatörün ara ara yaptığı parsel sorguları için BASIC ($15/ay, 5 thread) çoğu zaman yeterlidir; gece boyunca binlerce koordinatı tarayan bölgesel bir çıkarım işi içinse STANDARD ($30/ay, 15 thread) daha rahat bir eşzamanlılık verir. Fiyatlar USD üzerinden sabit olduğundan, TL kurundaki dalgalanmadan bağımsız olarak aylık maliyetinizi önceden planlayabilirsiniz.

Sık sorulan sorular

CBS portalından toplu parsel verisi çekmek KVKK açısından uygun mu?

Malik adı gibi kişisel veriler KVKK kapsamındadır. Yetkili bir amacınız, hukuki dayanağınız ve saklama sınırınız varsa uygundur; aksi halde yalnızca kamuya açık imar ve harita katmanlarıyla çalışın. CaptchaAI sadece CAPTCHA'yı çözer, veriyi toplama sorumluluğu size aittir.

CaptchaAI CBS portallarındaki hangi CAPTCHA türlerini çözer?

Bu portallarda en sık görülen görüntü/OCR metin CAPTCHA'larını, matematik ve yalnızca sayısal CAPTCHA'ları ve reCAPTCHA v2'yi çözer. hCaptcha ve FunCaptcha desteklenmez; bir portal bunları kullanıyorsa iş akışınızı buna göre planlayın.

Matematik veya yalnızca sayısal CAPTCHA'lar için hangi parametreleri kullanmalıyım?

Sayısal kodlarda numeric=1, uzunluğu biliyorsanız min_len ve max_len gönderin. "5 sayıyı toplayın" gibi işlemlerde talimatı textinstructions parametresine yazın; çözücü metni buna göre yorumlar.

Bu iş akışı için hangi CaptchaAI planı yeterli?

Tek operatörün düşük hacimli parsel sorguları için BASIC ($15/ay, 5 thread) yeterlidir. Bölge tarama gibi eşzamanlı çok sayıda istek gönderen işler için STANDARD ($30/ay, 15 thread) veya üzerini tercih edin.

Sonraki adımlar

CBS portalı CAPTCHA'larını iş akışınızın sessiz bir adımı haline getirin: CaptchaAI API anahtarınızı alın, yukarıdaki çıkarıcıyı bağlayın ve parsel sorgularınızı otomatik çözüme bırakın.


İlgili rehberler

Bu makale için yorumlar devre dışı bırakılmıştır.