🛠️ Google Maps'ten İşletme Verisi Çeken Ücretsiz Python Scripti
Firma adı · Telefon · Adres · Web sitesi · Puan · Fotoğraf → Excel & CSV

📌 Bu Script Ne Yapıyor?

Google Maps'te istediğiniz şehirde istediğiniz aramayı yapıp çıkan tüm işletmelerin;
  • Firma adı
  • Telefon numarası (05xxxxxxxxx formatında)
  • Adres
  • Web sitesi
  • Google puanı ve yorum sayısı
  • Çalışma saatleri
  • Öne çıkan fotoğraf URL'si
  • Google Maps linki
bilgilerini otomatik olarak Excel (.xlsx) ve CSV dosyasına kaydeder.

📋 KURULUM REHBERİ (Adım Adım)

ADIM 1 — Python'u Kurun
  1. python.org/downloads adresine gidin
  2. Büyük sarı "Download Python 3.x" butonuna tıklayın
  3. İndirilen .exe dosyasını çalıştırın
  4. ÖNEMLİ: Kurulum ekranında "Add Python to PATH" kutucuğunu işaretleyin!
ADIM 2 — Klasör Oluşturun

C: sürücüsünde googlemap klasörü oluşturun → C:googlemap
Ekteki google_maps_scraper.py dosyasını bu klasöre koyun.

ADIM 3 — CMD'yi Açın
  1. Dosya Gezgini'nde C:googlemap klasörüne girin
  2. Üstteki adres çubuğuna tıklayıp içini silin
  3. cmd yazıp Enter'a basın
ADIM 4 — Kütüphaneleri Kurun (Sadece 1 kez)

CMD'ye sırasıyla yapıştırın:

pip install playwright openpyxl
Bitmesini bekleyin, sonra:
python -m playwright install chromium
⏳ Bu komut ~150 MB indirir, 1-3 dakika sürebilir.

ADIM 5 — Ayarları Yapın

google_maps_scraper.py dosyasına sağ tıklayın → "Not Defteri ile Aç"
Dosyanın en üstündeki bu satırları düzenleyin:

ARAMA_TERIMI = "Beyaz esya tamircileri"   # ← İstediğiniz aramayı yazın
SEHIR        = "Ankara"                    # ← İstediğiniz şehri yazın
MAX_ISLETME  = 50                          # ← Kaç işletme çeksin?
Değiştirdikten sonra Ctrl+S ile kaydedin.

ADIM 6 — Çalıştırın

python google_maps_scraper.py
Otomatik bir tarayıcı açılır ve Google Maps'te arama yapılır. Siz sadece izleyin!
Bitince C:googlemap klasöründe isletmeler.xlsx ve isletmeler.csv oluşur.

❓ Sık Sorulan Sorular

Script hiçbir şey yazmadan kapandı?
CMD'de şunu deneyin:
python google_maps_scraper.py 2>&1 | more
Farklı şehir veya sektör?
AYARLAR bölümünden ARAMA_TERIMI ve SEHIR'i değiştirin, hepsi bu!

Kaç işletmeye kadar çeker?
Google Maps ~120 sonuç gösterir. MAX_ISLETME'yi 120'ye kadar artırabilirsiniz.

Ücretli bir şey var mı?
Hayır! Tamamen ücretsiz, API key gerektirmez.

"""
Google Maps Isletme Veri Toplayici v5
"""

import sys
import csv
import asyncio
import traceback
import re

if sys.platform == "win32":
    asyncio.set_event_loop_policy(asyncio.WindowsProactorEventLoopPolicy())

# ============================================================
#  AYARLAR
# ============================================================
ARAMA_TERIMI = "Beyaz Eşya Tamirhanesi"
SEHIR        = "Ankara"
MAX_ISLETME  = 10
CIKTI_DOSYA  = "isletmeler"
# ============================================================

async def gorsel_url_al(page):
    selectors = [
        'button.aoRNLd img', 'div.RZ66Rb img',
        'img.t0ejtc', 'div.ZKCDEc img', 'div.brR1M img',
    ]
    for sel in selectors:
        try:
            el = await page.query_selector(sel)
            if el:
                src = await el.get_attribute("src")
                if src and src.startswith("http") and "googleusercontent" in src:
                    src = re.sub(r'=w\d+-h\d+', '=w800-h600', src)
                    src = re.sub(r'=s\d+', '=s800', src)
                    return src
        except:
            pass
    try:
        imgs = await page.query_selector_all('img[src*="googleusercontent"]')
        for img in imgs:
            src = await img.get_attribute("src")
            if src:
                src = re.sub(r'=w\d+-h\d+', '=w800-h600', src)
                return src
    except:
        pass
    return ""


async def detay_cek(page):
    veri = {}

    # Firma Adı
    for sel in ['h1.DUwDvf', 'h1.fontHeadlineLarge', 'h1']:
        try:
            ad = (await page.inner_text(sel, timeout=2000)).strip()
            if ad:
                veri["Firma Adi"] = ad
                break
        except:
            pass
    veri.setdefault("Firma Adi", "")

    # Kategori
    for sel in ['button.DkEaL', 'span.YkuOqf']:
        try:
            kat = (await page.inner_text(sel, timeout=1500)).strip()
            if kat:
                veri["Kategori"] = kat
                break
        except:
            pass
    veri.setdefault("Kategori", "")

    # Puan
    try:
        el = await page.query_selector('span.ceNzKf[aria-label]')
        lbl = await el.get_attribute("aria-label") if el else ""
        m = re.search(r"[\d,\.]+", lbl or "")
        veri["Puan"] = m.group().replace(",", ".") if m else ""
    except:
        try:
            t = await page.inner_text('div.F7nice', timeout=1500)
            m = re.search(r"[\d,\.]+", t)
            veri["Puan"] = m.group().replace(",", ".") if m else ""
        except:
            veri["Puan"] = ""

    # Yorum Sayısı
    try:
        el = await page.query_selector('span[aria-label*="yorum"], span[aria-label*="review"]')
        lbl = await el.get_attribute("aria-label") if el else ""
        m = re.search(r"\d[\d\.]*", lbl or "")
        veri["Yorum Sayisi"] = m.group() if m else ""
    except:
        veri["Yorum Sayisi"] = ""

    # ✅ ADRES — aria-label'dan al: "Adres: Başak, Karapürçek..."
    try:
        el = await page.query_selector('button[data-item-id="address"]')
        if el:
            lbl = await el.get_attribute("aria-label") or ""
            # "Adres: " prefix'ini temizle
            adres = re.sub(r'^Adres:\s*', '', lbl, flags=re.IGNORECASE).strip()
            # Sondaki boşlukları temizle
            veri["Adres"] = adres.rstrip()
        else:
            veri["Adres"] = ""
    except:
        veri["Adres"] = ""

    # ✅ TELEFON — aria-label'dan al, sadece rakam bırak, 90 ile başlıyorsa 0'a çevir
    try:
        el = await page.query_selector('button[data-item-id^="phone:tel"]')
        if el:
            lbl = await el.get_attribute("aria-label") or ""
            # Sadece rakamları al
            tel = re.sub(r'\D', '', lbl)
            # +90 veya 90 ile başlıyorsa → 0 ile başlat
            if tel.startswith("90") and len(tel) == 12:
                tel = "0" + tel[2:]
            veri["Telefon"] = tel
        else:
            veri["Telefon"] = ""
    except:
        veri["Telefon"] = ""

    # Web Sitesi
    try:
        el = await page.query_selector('a[data-item-id="authority"]')
        veri["Web Sitesi"] = (await el.get_attribute("href")).strip() if el else ""
    except:
        veri["Web Sitesi"] = ""

    # Çalışma Saatleri — aria-label'dan al: "24 saat açık·..."
    try:
        el = await page.query_selector('button[data-item-id="oh"]')
        if el:
            lbl = await el.get_attribute("aria-label") or ""
            # Sadece ilk kısım (· sonrasını at)
            veri["Calisma Saatleri"] = lbl.split("·")[0].strip()
        else:
            veri["Calisma Saatleri"] = ""
    except:
        veri["Calisma Saatleri"] = ""

    # Google Maps Linki (kısa)
    url = page.url
    m = re.search(r"(https://www\.google\.com/maps/place/[^@]+/@[\d\.,]+)", url)
    veri["Google Maps"] = m.group(1) if m else url.split("!")[0]

    # Görsel URL
    veri["Gorsel URL"] = await gorsel_url_al(page)

    return veri


async def haritadan_veri_cek():
    from playwright.async_api import async_playwright

    isletmeler = []
    sorgu = f"{ARAMA_TERIMI} {SEHIR}"

    print("Tarayici baslatiliyor...")
    async with async_playwright() as p:
        browser = await p.chromium.launch(headless=False, args=["--no-sandbox"])
        context = await browser.new_context(locale="tr-TR")
        page = await context.new_page()

        url = "https://www.google.com/maps/search/" + sorgu.replace(" ", "+")
        print(f"Aciliyor: {url}")
        await page.goto(url, wait_until="domcontentloaded", timeout=30000)
        await page.wait_for_timeout(4000)

        for btn_text in ["Tumunu kabul et", "Accept all", "Kabul et"]:
            try:
                await page.click(f'button:has-text("{btn_text}")', timeout=2000)
                await page.wait_for_timeout(1000)
                break
            except:
                pass

        try:
            await page.wait_for_selector('div[role="feed"]', timeout=15000)
        except Exception as e:
            print(f"HATA: Liste paneli bulunamadi - {e}")
            await page.screenshot(path="hata_ekran.png")
            await browser.close()
            return []

        print("Sonuclar yukleniyor...")
        for i in range(MAX_ISLETME // 4 + 2):
            await page.evaluate("""
                const feed = document.querySelector('div[role="feed"]');
                if (feed) feed.scrollTop += 2000;
            """)
            await page.wait_for_timeout(1000)
            sys.stdout.write(f"\r  Scroll {i+1}...")
            sys.stdout.flush()
        print()

        kartlar = await page.query_selector_all('a.hfpxzc')
        print(f"{len(kartlar)} isletme bulundu, detaylar cekiliyor...\n")

        for i, kart in enumerate(kartlar[:MAX_ISLETME]):
            try:
                await kart.click()
                await page.wait_for_timeout(3000)

                veri = await detay_cek(page)
                isletmeler.append(veri)

                ad  = veri.get("Firma Adi", "?")[:25]
                tel = veri.get("Telefon", "") or "YOK"
                adr = (veri.get("Adres", "") or "YOK")[:30]
                img = "VAR" if veri.get("Gorsel URL") else "YOK"
                print(f"  [{i+1:02d}] {ad:<25} | Tel:{tel:<16} | Adres:{adr:<30} | Img:{img}")

            except Exception as e:
                print(f"  [{i+1:02d}] Hata: {e}")
                continue

        await browser.close()

    return isletmeler


def xlsx_kaydet(isletmeler, dosya):
    try:
        from openpyxl import Workbook
        from openpyxl.styles import Font, PatternFill, Alignment, Border, Side

        wb = Workbook()
        ws = wb.active
        ws.title = "Isletmeler"

        basliklar = [
            "Firma Adi", "Telefon", "Adres", "Web Sitesi",
            "Kategori", "Puan", "Yorum Sayisi", "Calisma Saatleri",
            "Google Maps", "Gorsel URL"
        ]

        baslik_font = Font(name="Arial", bold=True, color="FFFFFF", size=11)
        baslik_fill = PatternFill("solid", start_color="1A73E8")
        kenar = Border(
            left=Side(style="thin"), right=Side(style="thin"),
            top=Side(style="thin"), bottom=Side(style="thin")
        )

        for col, b in enumerate(basliklar, 1):
            c = ws.cell(row=1, column=col, value=b)
            c.font = baslik_font
            c.fill = baslik_fill
            c.alignment = Alignment(horizontal="center", vertical="center")
            c.border = kenar
        ws.row_dimensions[1].height = 22

        for satir, isletme in enumerate(isletmeler, 2):
            renk = "EAF4FF" if satir % 2 == 0 else "FFFFFF"
            for col, b in enumerate(basliklar, 1):
                deger = isletme.get(b, "")
                c = ws.cell(row=satir, column=col, value=deger)
                c.font = Font(name="Arial", size=10)
                c.fill = PatternFill("solid", start_color=renk)
                c.alignment = Alignment(vertical="center", wrap_text=(b == "Adres"))
                c.border = kenar
                if b == "Gorsel URL" and deger and deger.startswith("http"):
                    c.hyperlink = deger
                    c.font = Font(name="Arial", size=10, color="0563C1", underline="single")

        genislikler = [32, 18, 38, 28, 20, 7, 10, 18, 42, 18]
        for col, g in enumerate(genislikler, 1):
            ws.column_dimensions[ws.cell(row=1, column=col).column_letter].width = g

        ws.freeze_panes = "A2"
        path = f"{dosya}.xlsx"
        wb.save(path)
        print(f"XLSX kaydedildi: {path}")
    except Exception as e:
        print(f"XLSX hatasi: {e}")
        traceback.print_exc()


def csv_kaydet(isletmeler, dosya):
    try:
        basliklar = [
            "Firma Adi", "Telefon", "Adres", "Web Sitesi",
            "Kategori", "Puan", "Yorum Sayisi", "Calisma Saatleri",
            "Google Maps", "Gorsel URL"
        ]
        path = f"{dosya}.csv"
        with open(path, "w", newline="", encoding="utf-8-sig") as f:
            w = csv.DictWriter(f, fieldnames=basliklar, extrasaction="ignore")
            w.writeheader()
            w.writerows(isletmeler)
        print(f"CSV  kaydedildi: {path}")
    except Exception as e:
        print(f"CSV hatasi: {e}")


def main():
    print("=" * 60)
    print("  Google Maps Scraper v5 - Telefon/Adres Duzeltildi")
    print("=" * 60)

    try:
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
        isletmeler = loop.run_until_complete(haritadan_veri_cek())
        loop.close()
    except Exception as e:
        print(f"\nKRITIK HATA: {e}")
        traceback.print_exc()
        input("\nEnter'a basin...")
        return

    if not isletmeler:
        print("\nHic veri cekilemedi!")
        input("\nEnter'a basin...")
        return

    xlsx_kaydet(isletmeler, CIKTI_DOSYA)
    csv_kaydet(isletmeler, CIKTI_DOSYA)

    telefon_var = sum(1 for x in isletmeler if x.get("Telefon"))
    adres_var   = sum(1 for x in isletmeler if x.get("Adres"))
    gorsel_var  = sum(1 for x in isletmeler if x.get("Gorsel URL"))
    web_var     = sum(1 for x in isletmeler if x.get("Web Sitesi"))

    print(f"\n--- OZET ---")
    print(f"Toplam isletme : {len(isletmeler)}")
    print(f"Telefon        : {telefon_var}")
    print(f"Adres          : {adres_var}")
    print(f"Web Sitesi     : {web_var}")
    print(f"Gorsel URL     : {gorsel_var}")
    input("\nEnter'a basin...")


if __name__ == "__main__":
    main()