Blog'a Dön

Python ile Google Maps Veri Çekme: Ne Bozulur, Ne Çalışır

Ilyas Yıldırım
Ilyas Yıldırım
7 dk okuma

Arama kutusuna "python ile google maps veri çekme" yazın, karşınıza aşağı yukarı aynı şekle sahip yüz tane içerik çıkar. requests'i içe aktar, BeautifulSoup'u içe aktar, div'i bul, döngüye sok, CSV yaz. Kodu kopyalayın, çalıştırın, boş liste alın.

Kod tam olarak yanlış değil aslında. Google Maps'in yıllar önce ayrıldığı bir web için yazılmış. Bu yazı insanların gerçekten denediği üç yolu geziyor, her birinin nerede bittiğini anlatıyor ve siz bakmayı bıraktıktan sonra da çalışmaya devam eden sürümle bitiyor.

Birinci Yol: requests ve BeautifulSoup

Çoğu eğitimdeki yol bu ve hemen başarısız oluyor.

requests.get() sunucunun gönderdiği HTML'i indirir. Google Maps küçük bir iskelet gönderir, sonuç listesinin tamamını tarayıcıda JavaScript ile kurar. İşletmeler indirdiğiniz belgenin içinde değildir. Bir tarayıcı motoru sayfayı çalıştırana kadar var olmazlar.

Yani BeautifulSoup içinde hiç kayıt olmayan bir sayfayı ayrıştırır, döngünüz sıfır kere döner ve başlıkları doğru ama içi boş bir CSV alırsınız. Hata çıktısında bunun sebebini söyleyen hiçbir şey yoktur. İnsanlar tam da bu yüzden, hiçbir zaman eşleşmeyecek seçicileri düzeltmekle bir akşam geçirir.

Bir içerikte bunun çalıştığı gösteriliyorsa önce tarihine, sonra ekran görüntüsündeki çıktının gerçek olup olmadığına bakın.

İkinci Yol: Selenium veya Playwright

Bu yol çalışıyor. Tuzak da bu zaten.

Başsız tarayıcı sayfayı düzgün çizer, dolayısıyla kayıtlar gerçekten oradadır ve gerçekten okunabilir. Chrome'u başlatır, arama yapar, sonuç panelini yeni satır eklemeyi bırakana kadar kaydırır ve alanları çekersiniz. İlk gün sorunu çözmüş gibi hissettirir.

Sonraki birkaç ay şöyle geçer.

Seçiciler yer değiştirir. Google arayüzü sürekli değiştirir ve üretilen sınıf adları size verilmiş bir söz değildir. Bunlar değiştiğinde betiğiniz hata vermez. Sıfır satır döndürür, ya da daha kötüsü, bir sütunu sessizce boş olan satırlar döndürür.

Asıl iş kaydırmadır. Yeni kayıtlar siz paneli kaydırdıkça yüklenir. Çok yavaş kaydırırsanız büyük bir aktarım bir saat sürer. Çok hızlı kaydırırsanız satırlar yüklenemez ve bu hata olarak değil, boşluk olarak görünür. O bekleme değerini ayarlamak hayatınızın kalıcı bir parçası olur.

Liste biter. Google her arama için belli bir sayıdan sonra yeni sonuç beslemeyi keser. Bu sayı gerçek bir şehirdeki işletme sayısının çok altındadır, yani bir şehri kapsamak onu kendiniz mahallelere ve kategorilere bölüp dosyaları sonradan birleştirmek demektir.

Otomatik trafik, otomatik trafik gibi karşılanır. Tek adresten gelen tekrarlı, hızlı ve başsız istekler yavaşlatılır, doğrulamaya sokulur veya engellenir. Bunu aşmaya çalışmak kendi maliyeti olan ikinci bir projedir ve bir veri toplama betiğinin üzerine dikkatle düşünmeniz gereken bir şeye dönüştüğü noktadır.

Kimse bakımını yapmaz. Betik onu yazan kişinindir. Dördüncü ayda bozulduğunda o kişi meşguldür ve mart ayından beri kimsenin yenilemediği liste sessizce yanlıştır.

Bunların hiçbiri başsız tarayıcının genel olarak yanlış araç olduğu anlamına gelmiyor. Liste kurma ölçeğinde Google Maps'in bunun için alışılmadık derecede kötü bir hedef olduğu anlamına geliyor.

Üçüncü Yol: Places API

Google'ın kendi Places API'si kararlı, belgelenmiş ve arayüz değiştiğinde bozulmuyor. Ürününüzün içindeki bir mağaza bulucu veya adres tamamlama için doğru cevap bu ve yanına yaklaşan başka bir şey yok.

Toplu aday listesi ayrı bir iş ve fiyat modeli de bunu yansıtıyor. Çağrı başına ödüyorsunuz ve fiyat katmanını istediğiniz en pahalı alan belirliyor. Yani bir isteğe telefon veya puan eklemek bütün çağrıyı üst katmana taşıyabiliyor. Bir de gelen veriyi ne kadar saklayabileceğinizi sınırlayan şartlar var.

Alan katmanlarını ve hesabı Google Places API fiyatları yazısında, lisans meselesini de Places API mi scraping mi yazısında çalıştık. Kısası şu: bu, kullanıcıya yer göstermek için bir API, CRM doldurmak için değil.

Asıl Ayakta Kalan Yol

Ayakta kalan sürüm, toplama işini hiç sizin yapmadığınız sürüm. Zaten onu çalıştıran bir API'yi çağırıyorsunuz ve Python'unuz üç şey yapıyor: iş gönder, bekle, sonuçları oku.

Aşağıdakilerin hepsi requests kütüphanesiyle BasedOnBusiness REST API'sine karşı çalışıyor. Ayarlar bölümünden, API ve Webhook'lar altından bir anahtar oluşturun. Anahtarlar bdb_live_ ile başlar.

İşi gönderin

import os
import requests

BASE = "https://www.basedonb.com/api/v1"
KEY = os.environ["BDB_API_KEY"]
HEADERS = {"Authorization": f"Bearer {KEY}"}

response = requests.post(
    f"{BASE}/scrapes",
    headers={**HEADERS, "Idempotency-Key": "dis-hekimi-istanbul-2026-08"},
    json={
        "query": "diş hekimi",
        "country": "TR",
        "state": "TR.34",
        "city": "Istanbul",
        "target_leads": 200,
    },
    timeout=30,
)
response.raise_for_status()
job = response.json()
print(job["id"], job["status"])

Bilmeye değer iki ayrıntı var. state alanı GeoNames noktalı biçimini kullanıyor, yani TR.34, US.CA, DE.BE. Alt bölümü olmayan ülkeler yalnız country alıyor. Bir ülkenin ne beklediğinden emin değilseniz /geodata/countries, /geodata/states ve /geodata/cities uç noktaları kabul edilen değerleri döndürüyor.

Idempotency-Key başlığı göründüğünden önemli. İsteğiniz zaman aşımına uğrar ve tekrar denerseniz, bu anahtar ikinci bir iş açılmasını ve kredinizin iki kez harcanmasını engelliyor.

Bitmesini bekleyin

import time

def wait_for(job_id, poll_seconds=5, timeout_seconds=1800):
    deadline = time.time() + timeout_seconds
    while time.time() < deadline:
        job = requests.get(f"{BASE}/scrapes/{job_id}", headers=HEADERS, timeout=30).json()
        if job["status"] == "done":
            return job
        if job["status"] in ("failed", "cancelled"):
            raise RuntimeError(f"Is su durumda bitti: {job['status']}")
        print(f"{job['status']} {job.get('progress', 0):.0%} · {job['leads_found']} kayit")
        time.sleep(poll_seconds)
    raise TimeoutError("Is zamaninda bitmedi")

wait_for(job["id"])

Beş saniyelik aralık kibar ve hız sınırının rahat içinde kalıyor. Sınır anahtar başına dakikada 100 istek. Döngü izin veriyor diye 200 milisaniyede bir sormayın.

Sonuçları sayfa sayfa okuyun

Sonuçlar imleçle sayfa sayfa geliyor. Devam edip etmeyeceğinizi page nesnesi söylüyor.

def fetch_all(job_id, page_size=500):
    rows, cursor = [], None
    while True:
        params = {"limit": page_size}
        if cursor:
            params["cursor"] = cursor
        page = requests.get(
            f"{BASE}/scrapes/{job_id}/results",
            headers=HEADERS,
            params=params,
            timeout=60,
        ).json()
        rows.extend(page["results"])
        if not page["page"]["has_more"]:
            return rows
        cursor = page["page"]["next_cursor"]

limit en fazla 500 kabul ediyor. Her satırda place_id, title, category, address, phone, website, rating, reviews_count, latitude, longitude, price_level ve business_status var. Ayrıca o kayıt için site zenginleştirmesinin bitip bitmediğini söyleyen enrich_status ve email_enrich_status alanları geliyor.

Dosyayı yazın

import pandas as pd

frame = pd.DataFrame(fetch_all(job["id"]))
with_site = frame[frame["website"].notna()]
print(f"{len(frame)} kayit, {len(with_site)} tanesinde site var")
frame.to_csv("dis-hekimi-istanbul.csv", index=False)

Programın tamamı bu. Seçici yok, kaydırma zamanlaması yok, tarayıcı yok ve Google bir paneli yeniden tasarladığında değişen hiçbir şey yok.

Sormayı bırakın, webhook kullanın

Kendiniz çalıştırdığınız bir betikte döngüyle sormak sorun değil. Bir hattın içinde ise işin size haber vermesini istersiniz.

Bir webhook uç noktası tanımlayın, iş bittiğinde scrape.done olayı gelsin. Olay, oluşturma sırasında bir kez gösterilen imzalama sırrına karşı doğrulayacağınız bir X-Webhook-Signature başlığı taşıyor. Başarısız gönderimler 1 dakika, 5 dakika, 30 dakika ve 2 saat sonra tekrarlanıyor. X-Webhook-Event-Id değerini saklayın ve daha önce işlediğiniz olayları atlayın, çünkü bir tekrar denemesi ilk gönderimi işledikten sonra gelebilir.

Hukuki Tarafa Kısa Bir Not

Python seçmek kuralların hiçbirini değiştirmiyor. Açık işletme bilgisi çoğu ülkede genel olarak toplanabilir, kişisel veri için hukuki dayanak gerekir ve Google'ın şartları araç ne olursa olsun kendi mülklerinden otomatik toplamayı sınırlar. İletişim kurmak ise ayrı mevzuata tabidir ve liste nereden gelirse gelsin geçerlidir.

Kendi scraper'ınızı yazmakla bir sağlayıcıyı çağırmak arasındaki pratik fark, bu sorumluluğu kimin taşıdığıdır. Ayrımları Google Maps scraping yasal mı yazısında ayrıntılı anlattık.

Hangisini Kurmalı

Öğreniyorsanız Selenium sürümünü yazın. Gerçekten iyi bir alıştırma ve sonrasında satın aldığınız her aracı daha iyi anlarsınız.

Çıktıya bağlı bir şey varsa yazmayın. Bakım maliyeti gerçek, hep uygunsuz zamanlarda geliyor ve ekibinize verdiğiniz tahminde görünmüyor.

Kaba bir test: liste bir şeyi düzenli aralıklarla besliyorsa veya sizden başkası okuyorsa, API istiyorsunuz demektir. Sadece kendi araştırmanız için tek seferlikse her şey işe yarar, hiç kod yazmadan dışa aktarma dahil.

Gerçek Veride Deneyin

Yeni hesaplara kart istemeden tek seferlik 50 dışa aktarma kredisi veriliyor, 1 kredi 1 işletme kaydı demek. Bu, yukarıdaki betikleri baştan sona çalıştırmaya yeter: küçük bir iş gönderin, durumunu sorun, sonuçları sayfalayın ve CSV'yi açın.

Web sitesi olan işletmelere sitede yayınlanmış e-postalar, sosyal profiller ve teknoloji bilgileri ek kredi almadan ekleniyor ve kaynak sayfa da kaydediliyor. Böylece kayıtlar hattınızda sırada ne varsa ona hazır geliyor. Bütün uç noktalar, hata kodları ve webhook içerikleri API belgelerinde.