Tüm yazılar
Veri Otomasyonu 6 dk okuma

Google Sheets'i Veri Arayüzü Olarak Kullanmak: API ile Güvenli E-Tablo Otomasyonu

Google Sheets API ile e-tabloyu güvenli veri arayüzüne çevirme: servis hesabı, en az yetkiyle paylaşım, şema kontrollü okuma ve kota dostu toplu yazma örnekleri.

#python #automation #data-pipeline

Google Sheets API ile e-tablo otomasyonu, ekibin alışkın olduğu arayüzü bozmadan veriyi programın denetimine almanın en kısa yoludur. Küçük ve orta ölçekli işletmelerde sipariş listesi, fiyat tablosu ya da müşteri takibi çoğu zaman bir e-tabloda yaşar; ekip orada çalışmayı sever ve yeni bir arayüz öğrenmek istemez. Bu tabloyu veri tabanı gibi kullanmak ise kısa sürede çakışan düzenlemeler, bozulan formüller ve kimin neyi değiştirdiği bilinmeyen hücreler üretir.

Çözüm e-tabloyu ortadan kaldırmak değil, rolünü değiştirmektir: Sheets arayüz olur, gerçek kayıt bir betiğin yazdığı SQLite ya da Parquet dosyasında durur. Betik tabloyu belirli aralıklarla okur, şemayı doğrular, veriyi kalıcı depoya işler ve sonuçları yine tabloya yazar. Ekip tabloya bakmaya devam eder; otomasyon ise tek yetkili yazar olur.

Bu yazıda servis hesabıyla kimlik doğrulama, en az yetkiyle paylaşım, okuma ve yazma sınırları ve toplu güncelleme konularını sırayla ele alıyorum. İki kod örneği var: biri sayfayı okuyup şema kontrolünden geçiren, diğeri sonuçları tek istekle geri yazan betik.

Servis hesabı ile kimlik doğrulama

Servis hesabı, bir insana değil bir programa ait olan Google hesabıdır ve e-posta adresi ile JSON biçiminde bir anahtar dosyasından oluşur. Betik bu anahtarla kendi adına oturum açar; kimsenin kişisel hesabına, tarayıcı onayına ya da süresi dolan bir belirtece bağımlı kalmaz. Kurulum Google Cloud konsolunda bir proje açıp Sheets API'yi etkinleştirmek ve servis hesabı için anahtar üretmekle biter.

Anahtar dosyası bir parola gibi ele alınır. Depoya girmez, ortam değişkeni ya da sır kasası üzerinden okunur; sızdığı anda konsoldan iptal edilip yenisi üretilir. Aynı projede birden fazla otomasyon varsa her birine ayrı servis hesabı açmak, bir anahtarın iptalinin diğer işleri durdurmamasını sağlar.

Kimlik doğrulama sırasında istenen kapsam da yetkinin sınırını çizer. Yalnız okuyan bir betik için spreadsheets.readonly kapsamı yeterlidir; yazma gereken betik spreadsheets kapsamını ister. Drive kapsamı, dosya listeleme ya da oluşturma gibi gerçekten gereken bir iş yoksa hiç eklenmez.

En az yetkiyle paylaşım ve erişim düzeni

En az yetki ilkesi, servis hesabına yalnız gerçekten dokunacağı tabloların, yalnız gereken düzeyde paylaşılmasıdır. Servis hesabının e-posta adresi, tıpkı bir ekip üyesi gibi tablonun paylaşım penceresine eklenir; okuyan betik için görüntüleyici, yazan betik için düzenleyici rolü seçilir. Tüm Drive'a erişim veren alan adı düzeyinde yetkilendirme küçük ekipler için gereksiz bir risktir.

Tablonun kendi içindeki düzen de yetkinin parçasıdır. Ekibin doldurduğu sayfa ile betiğin yazdığı sayfa ayrı tutulur; betik girdi sayfasını yalnız okur, sonuç sayfasına yalnız yazar. Sonuç sayfası sayfa koruması ile yalnız servis hesabına açılırsa, ekip üyeleri elle değiştiremez ve otomasyonun ürettiği rakam tartışma konusu olmaz.

Kişisel veri taşıyan tablolarda bu düzen aynı zamanda hukuki bir gerekliliktir. Kimin hangi sütuna eriştiği paylaşım listesinden okunabilmeli, betiğin okuduğu ve yazdığı sütunlar belgelenmeli, gereksiz kişisel veri hiç tabloya taşınmamalıdır. Otomasyonun kalıcı depoya yazdığı kopya da aynı erişim kısıtlarına tabidir.

Sayfayı okuyup şemayı doğrulayan betik

Okuma betiği, sayfanın tamamını tek istekte alır, ilk satırı başlık sayar ve beklenen sütunların adını, sırasını ve tipini bildirilen şemayla karşılaştırır. Şemaya uymayan tablo işlenmez; hata olarak raporlanır. Sheets hücreleri tip taşımadığından her değer metin gelir; sayıya ve tarihe çevirme işi betiğe düşer.

Okuma isteğindeki iki seçenek sonucu doğrudan etkiler. valueRenderOption varsayılan olarak FORMATTED_STRING değerini alır ve hücreyi ekranda göründüğü gibi, binlik ayracı ve para simgesiyle döndürür; UNFORMATTED_VALUE seçilirse sayı hücreleri sayı olarak gelir. Tarih hücreleri ise dateTimeRenderOption ile ya seri numarası ya da biçimlendirilmiş metin olarak alınır. Örnek betik varsayılan biçimlendirilmiş metni okur ve dönüştürmeyi kendisi yapar; böylece Türkçe yerel ayarındaki virgüllü ondalık gibi tuzaklar hata listesinde görünür hale gelir.

Aşağıdaki örnek resmi google-api-python-client ve google-auth paketlerini kullanır. Boş satırlar API yanıtında eksik ya da kısa liste olarak gelir; betik bu satırları başlık uzunluğuna tamamlar.

import os
from decimal import Decimal, InvalidOperation

from google.oauth2 import service_account
from googleapiclient.discovery import build

KAPSAM = ["https://www.googleapis.com/auth/spreadsheets.readonly"]
TABLO_ID = os.environ["SHEETS_TABLO_ID"]
ARALIK = "Siparisler!A1:E"
SEMA = {"siparis_no": str, "musteri": str, "adet": int, "tutar": Decimal, "tarih": str}


def istemci():
    kimlik = service_account.Credentials.from_service_account_file(
        os.environ["GOOGLE_SA_ANAHTAR"], scopes=KAPSAM)
    return build("sheets", "v4", credentials=kimlik, cache_discovery=False)


def satirlari_oku(svc):
    yanit = svc.spreadsheets().values().get(
        spreadsheetId=TABLO_ID, range=ARALIK).execute()
    degerler = yanit.get("values", [])
    if not degerler:
        raise ValueError("sayfa boş")
    baslik, gov = degerler[0], degerler[1:]
    if baslik != list(SEMA):
        raise ValueError("başlık şemayla uyuşmuyor: %r" % baslik)
    return [satir + [""] * (len(baslik) - len(satir)) for satir in gov]


def donustur(satirlar):
    kayitlar, hatalar = [], []
    for i, satir in enumerate(satirlar, start=2):
        kayit = {}
        try:
            for (ad, tip), deger in zip(SEMA.items(), satir):
                kayit[ad] = tip(deger.strip().replace(",", ".")) if tip is not str else deger.strip()
        except (ValueError, InvalidOperation) as e:
            hatalar.append((i, str(e)))
            continue
        kayitlar.append(kayit)
    return kayitlar, hatalar


if __name__ == "__main__":
    kayitlar, hatalar = donustur(satirlari_oku(istemci()))
    print(len(kayitlar), "kayıt okundu,", len(hatalar), "satır hatalı")
    for satir_no, neden in hatalar:
        print("satır", satir_no, ":", neden)

Hatalı satırın numarasıyla raporlanması önemlidir; ekip üyesi doğrudan o satıra gidip düzeltir. Aynı kontrol daha geniş bir veri kalitesi düzeninin parçası olacaksa, Excel kalite kontrol yazısındaki tutarlılık kuralları e-tabloya da uygulanır.

Okuma ve yazma sınırları, toplu güncelleme

Sheets API'nin kota sınırı, isteklerin tek tek değil toplu gönderilmesini zorunlu kılar. Google'ın kullanım sınırları belgesine göre okuma ve yazma istekleri proje başına dakikada 300, kullanıcı başına dakikada 60 ile sınırlıdır; sınır aşıldığında istek 429 koduyla reddedilir. Hücre başına bir istek gönderen döngü bu sınırı birkaç saniyede tüketir.

Toplu yazmanın kuralları şöyle sıralanır:

  • Bir sayfanın tamamı ya da bir aralığı tek batchUpdate çağrısıyla yazılır; satır satır update çağrılmaz.
  • Değerler RAW seçeneğiyle gönderilir; USER_ENTERED yalnız formül yazılacaksa kullanılır, aksi halde metin görünümlü sayılar tarih sanılır.
  • 429 yanıtı üstel geri çekilmeyle yeniden denenir; aynı yazma iki kez uygulandığında sonucun değişmemesi için hedef aralık her seferinde temizlenip baştan yazılır.
  • Bir çalışmada toplam istek sayısı loglanır; kota tüketimi böylece ölçülür.

Okuma tarafında da aynı kural geçerlidir. Birden fazla sayfa ya da aralık tek batchGet çağrısıyla alınır; sayfayı satır satır ya da sütun sütun sorgulayan döngü kotayı okuma tarafında tüketir. Betiğin çalışma sıklığı da kota bütçesinin parçasıdır: on beş dakikada bir çalışan bir okuyucu çoğu iş için yeterlidir. Anlık tetik gerekiyorsa tablonun kendisine bağlanan bir Apps Script onEdit tetikleyicisi otomasyonun webhook adresini çağırır ve betik yalnız o zaman okur.

Aşağıdaki betik sonuç sayfasını önce temizler, ardından başlık ve satırları tek istekle yazar. Kalıcı depo olarak SQLite kullanılıyorsa sorgu sonucu doğrudan bu fonksiyona verilir; şemayı ve boru hattını kurmak için SQLite ile veri boru hatları yazısı başlangıç noktasıdır.

import os
import time

from google.oauth2 import service_account
from googleapiclient.discovery import build
from googleapiclient.errors import HttpError

KAPSAM = ["https://www.googleapis.com/auth/spreadsheets"]
TABLO_ID = os.environ["SHEETS_TABLO_ID"]
HEDEF = "Ozet!A1"


def istemci():
    kimlik = service_account.Credentials.from_service_account_file(
        os.environ["GOOGLE_SA_ANAHTAR"], scopes=KAPSAM)
    return build("sheets", "v4", credentials=kimlik, cache_discovery=False)


def yeniden_dene(cagri, deneme=5):
    for i in range(deneme):
        try:
            return cagri.execute()
        except HttpError as e:
            if e.resp.status not in (429, 500, 503) or i == deneme - 1:
                raise
            time.sleep(2 ** i)


def toplu_yaz(svc, baslik, satirlar):
    degerler = svc.spreadsheets().values()
    yeniden_dene(degerler.clear(spreadsheetId=TABLO_ID, range="Ozet!A:Z", body={}))
    govde = {
        "valueInputOption": "RAW",
        "data": [{"range": HEDEF, "values": [baslik] + [list(map(str, s)) for s in satirlar]}],
    }
    yanit = yeniden_dene(degerler.batchUpdate(spreadsheetId=TABLO_ID, body=govde))
    return yanit.get("totalUpdatedCells", 0)


if __name__ == "__main__":
    ozet = [("2026-09", "Ankara", 42, "18350.00"), ("2026-09", "İzmir", 17, "7120.50")]
    hucre = toplu_yaz(istemci(), ["ay", "sehir", "siparis", "tutar"], ozet)
    print(hucre, "hücre 2 istekle güncellendi")

Zamanlayıcıdan çalışan bu türden bir betik için kilit ve yeniden çalıştırma güvenliği de gerekir; temizle-yaz sırası sayesinde yarım kalan bir çalışma bir sonraki turda üzerine yazılır. Çalışma sonunda yazılan hücre sayısı ve gönderilen istek sayısı loglanır. Sonuç sayfasına bir de son güncelleme zamanı hücresi eklenirse ekip, tablonun ne zaman yenilendiğini betik loglarına bakmadan görür ve eski veriye göre karar vermez.

Sonuç: tabloyu arayüz, betiği tek yazar yapmak

Google Sheets bir ekip için arayüz olarak değerlidir, veri tabanı olarak değil. Servis hesabı betiğe kendi kimliğini verir, en az yetkiyle paylaşım hangi tabloya kimin dokunduğunu görünür kılar, şema kontrolü bozuk satırı kapıda durdurur, toplu yazma da kotayı korur. Bu dört parça bir araya geldiğinde e-tablo, arkasında güvenilir bir kayıt bulunan bir ekran haline gelir.

İlk adım olarak ekibin en çok kullandığı tek bir tabloyu seçin, girdi ve sonuç sayfalarını ayırın ve okuma betiğini şema kontrolüyle çalıştırın. Hatalı satır listesi ilk günden tablonun gerçek durumunu gösterir; yazma tarafını ancak bu liste kısaldıktan sonra açın.

Elinizdeki e-tabloları bu düzene taşımak ve otomasyonu kendi işinize uyarlamak için iletişim bölümünden yazabilirsiniz.