Birkaç yıldan fazla adres verisi toplayan her kuruluşun aynı sorunu vardır: kalitesi bilinmeyen, büyük ve düzensiz bir adres tablosu. Web formları aracılığıyla dikkat etmeyen kullanıcılar tarafından girilen adresler. Girişi doğrulamayan eski bir CRM sisteminden geçirilen kayıtlar. İş ortağının uyumsuz formattaki Excel tablosundan toplu aktarılanlar. Veriler toplandığından bu yana taşınan işletme konumları.
Hatalı adres verileriyle lojistik yapmak başarısız teslimatlar, pazarlama ile zaman kaybı, gayrimenkul değerlemesi ile yanlış ticari alan analizleri üretir. Bu işlemler başlamadan önce adresler temizlenip doğrulanması gerekir.
Bunu büyük ölçekte yapmanın en etkili yolu geocodingdir. Geocoding API'ye ham adres dizesi gönderin; yapılandırılmış bileşenler, coğrafi koordinatlar ve güven puanı alın. Yüksek güvenle temiz şekilde geocode edilen adresler neredeyse kesinlikle geçerlidir. Düşük güven veya hiç eşleşme döndürmeyen adresler insan incelemesi gerektirir.
Bu eğitim, CSV adres dosyasını okuyan, MapAtlas Geocoding API karşılığında her birini geocode eden, güven puanlarıyla sonuçları yazan ve düşük güvenli kayıtları manuel inceleme için işaretleyen tam bir Python script oluşturur. Hız sınırlaması, geçici ağ hataları ve ABD merkezli geocoding eğitimlerinin atladığı AB adres formati özelliklerini işler.
Adres Verisi Neden Bozulur
Adres kalitesi öngörülebilir nedenlerle düşer:
Manuel giriş hataları. Web formu kullanıcıları hızlı yazdıkları için, otomatik düzeltme cadde adlarını bozar ve boş olmayan her şeyi kabul eden doğrulama çöp bilginin geçmesini sağlar. B2C kasa verilerinin bir çalışması, manuel olarak girilen adreslerinin %7–12'sinin teslimatı başarısız olmaya yetecek kadar hatalı olduğunu bulmuştur.
İşletme taşınmaları. İki yıl önce toplanan B2B veritabanının, hamleler, birleşmeler ve kapanışlar nedeniyle adreslerinin yaklaşık %10–15'inin artık mevcut işletme konumuyla eşleşmemesi durumu ortaya çıkar.
Format tutarsızlıkları. Çeşitli kaynaklardan toplanan veriler farklı kuralları kullanır: tam ülke adları vs. ISO kodları, "Sok." vs. "Sokak", ev-numarası-caddenin-önünde vs. sonrasında, "daire" vs. "apt" vs. "wohnung". Bir geocoder bunların tümünü yapılandırılmış çıktıya normalleştirir.
Eski sistem göçleri. Birden çok veritabanı sütununa bölünen adres alanları, göç sırasında birleştirilir ve yapı kaybı meydana gelir. Eski sistemlerdeki serbest metin adres alanları, gerçek adresin parçası olmayan notlar, referanslar veya biçimlendirme içerebilir.
Geocoding yaklaşımı bunların hepsini işler çünkü dize eşleştirmesine değil, coğrafi eşleştirmeye dayanır. Yanlış biçimlendirilmiş bir adres, temel alınan konum verisi eşleşirse yine de doğru şekilde geocode edilebilir.
Understanding Confidence Scores
The MapAtlas Geocoding API returns a confidence property on each feature, ranging from 0.0 to 1.0. It represents how closely the returned result matches the input query, accounting for format differences, abbreviations, and ambiguity.
| Confidence | Interpretation | Recommended action |
|---|---|---|
| 0.90 – 1.00 | Exact or near-exact match | Accept automatically |
| 0.85 – 0.89 | Strong match, minor format differences | Accept with logging |
| 0.60 – 0.84 | Partial match, street found but house number uncertain | Flag for manual review |
| 0.40 – 0.59 | Ambiguous, locality matched but not specific address | Reject or escalate |
| 0.00 – 0.39 | No meaningful match | Reject, likely invalid |
These thresholds are starting points. For a logistics operation where failed deliveries are expensive, tighten the automatic-accept threshold to 0.92+. For a marketing mailing list where the cost of a manual review outweighs the cost of a few bad addresses, loosen it to 0.80.
The API also returns a match_type property indicating what level of the address hierarchy matched: point (exact building), interpolated (position estimated between known house numbers), street (street found but house number not), or locality (only the city matched).
The Python Validation Script
Install dependencies:
pip install requests pandas tqdm
The script reads a CSV with an address column (or separate street, city, country columns), geocodes each row, and writes a new CSV with validation results appended.
#!/usr/bin/env python3
"""
bulk_geocode.py, Validate a CSV of addresses using the MapAtlas Geocoding API.
Input CSV must have either:
- An 'address' column (full address string), or
- 'street', 'city', and 'country' columns (will be concatenated)
Outputs a new CSV with added columns:
geocoded_label, geocoded_lat, geocoded_lng, confidence, match_type, status
"""
import csv
import time
import logging
import requests
import pandas as pd
from tqdm import tqdm
from pathlib import Path
# ── Configuration ──────────────────────────────────────────────────────────────
API_KEY = 'YOUR_API_KEY'
API_BASE = 'https://api.mapatlas.eu/geocoding/v1/search'
INPUT_CSV = 'addresses.csv'
OUTPUT_CSV = 'addresses_validated.csv'
RATE_LIMIT_RPS = 5 # Requests per second (stay within your plan limits)
RETRY_ATTEMPTS = 3 # Retries on transient errors
RETRY_DELAY_S = 2.0 # Seconds between retries
# Confidence thresholds
ACCEPT_THRESHOLD = 0.85
REVIEW_THRESHOLD = 0.60
logging.basicConfig(level=logging.INFO, format='%(levelname)s %(message)s')
log = logging.getLogger(__name__)
# ── Geocoding function ─────────────────────────────────────────────────────────
def geocode_address(address_str: str) -> dict:
"""
Geocode a single address string. Returns a dict with result fields.
Retries on network errors and 429 rate-limit responses.
"""
params = {
'text': address_str,
'key': API_KEY,
'size': 1,
}
for attempt in range(RETRY_ATTEMPTS):
try:
resp = requests.get(API_BASE, params=params, timeout=10)
if resp.status_code == 429:
# Rate limited, wait and retry
wait = float(resp.headers.get('Retry-After', RETRY_DELAY_S * (attempt + 1)))
log.warning(f'Rate limited. Waiting {wait:.1f}s before retry {attempt + 1}.')
time.sleep(wait)
continue
resp.raise_for_status()
data = resp.json()
if not data.get('features'):
return {'geocoded_label': '', 'geocoded_lat': None, 'geocoded_lng': None,
'confidence': 0.0, 'match_type': 'no_match', 'status': 'reject'}
feature = data['features'][0]
props = feature['properties']
coords = feature['geometry']['coordinates'] # [lng, lat]
confidence = round(float(props.get('confidence', 0.0)), 4)
match_type = props.get('match_type', 'unknown')
if confidence >= ACCEPT_THRESHOLD:
status = 'accept'
elif confidence >= REVIEW_THRESHOLD:
status = 'review'
else:
status = 'reject'
return {
'geocoded_label': props.get('label', ''),
'geocoded_lat': round(coords[1], 6),
'geocoded_lng': round(coords[0], 6),
'confidence': confidence,
'match_type': match_type,
'status': status,
}
except requests.exceptions.RequestException as exc:
log.warning(f'Network error on attempt {attempt + 1}: {exc}')
if attempt < RETRY_ATTEMPTS - 1:
time.sleep(RETRY_DELAY_S * (attempt + 1))
# All retries exhausted
return {'geocoded_label': '', 'geocoded_lat': None, 'geocoded_lng': None,
'confidence': 0.0, 'match_type': 'error', 'status': 'error'}
# ── Main processing loop ───────────────────────────────────────────────────────
def main():
df = pd.read_csv(INPUT_CSV, dtype=str).fillna('')
# Build address string from available columns
if 'address' in df.columns:
df['_query'] = df['address']
elif all(c in df.columns for c in ['street', 'city', 'country']):
df['_query'] = df['street'] + ', ' + df['city'] + ', ' + df['country']
else:
raise ValueError("CSV must have 'address' or 'street'+'city'+'country' columns.")
results = []
sleep_interval = 1.0 / RATE_LIMIT_RPS
for query in tqdm(df['_query'], desc='Geocoding', unit='addr'):
result = geocode_address(query.strip())
results.append(result)
time.sleep(sleep_interval)
results_df = pd.DataFrame(results)
output_df = pd.concat([df.drop(columns=['_query']), results_df], axis=1)
output_df.to_csv(OUTPUT_CSV, index=False, quoting=csv.QUOTE_NONNUMERIC)
# Summary
total = len(output_df)
accept = (output_df['status'] == 'accept').sum()
review = (output_df['status'] == 'review').sum()
reject = (output_df['status'] == 'reject').sum()
errors = (output_df['status'] == 'error').sum()
log.info(f'\n── Results ────────────────────────────────')
log.info(f'Total processed : {total:,}')
log.info(f'Accept (≥{ACCEPT_THRESHOLD}) : {accept:,} ({accept/total:.1%})')
log.info(f'Review : {review:,} ({review/total:.1%})')
log.info(f'Reject : {reject:,} ({reject/total:.1%})')
log.info(f'Errors : {errors:,} ({errors/total:.1%})')
log.info(f'Output written to {OUTPUT_CSV}')
if __name__ == '__main__':
main()
Run it:
python bulk_geocode.py
For a 10,000-address CSV at 5 requests/second, the script completes in approximately 35 minutes. The progress bar (via tqdm) shows real-time throughput and estimated completion time.
Handling the Output
The output CSV adds six columns to your input data:
address, ..., geocoded_label, geocoded_lat, geocoded_lng, confidence, match_type, status
Filter by status to generate three output files:
# After running main(), split into acceptance tiers:
df = pd.read_csv('addresses_validated.csv')
df[df['status'] == 'accept'].to_csv('addresses_clean.csv', index=False)
df[df['status'] == 'review'].to_csv('addresses_review.csv', index=False)
df[df['status'] == 'reject'].to_csv('addresses_reject.csv', index=False)
print(f"Clean: {len(df[df['status']=='accept']):,} addresses ready for use")
print(f"Review: {len(df[df['status']=='review']):,} addresses for manual check")
print(f"Reject: {len(df[df['status']=='reject']):,} addresses to discard or fix")
The addresses_review.csv file is the one that needs human attention. Typical review patterns:
- Match type
street(confidence 0.65–0.80): The street was found but not the house number. Likely a new building, a rural address with sparse coverage, or a typo in the house number. Check the original source. - Match type
locality(confidence 0.50–0.65): Only the city matched. The street name is likely misspelled or doesn't exist in the data. Look up the address in a postal directory. - Low confidence on clearly valid-looking addresses: Check for country/language mismatch. A Dutch address queried without a country restriction may geocode against a similarly named German or Belgian town.
AB Adres Özelliklerini Bilin
ABD pazarı için yazılan toplu geocoding kılavuzları, AB veri kümelerini engelleyen format farklılıklarını atlar. İşte en yaygın sorunlar:
Almanya, ev numarası sıralaması. Alman adresler {cadde} {numara} biçimini kullanır: Berliner Straße 42. Birçok CRM, İngiltere/ABD kuralları için inşa edildikleri için adresleri {numara} {cadde} biçiminde depolar. Alman adresleriniz düşük güvenle geocode ediliyorsa, gönderilmeden önce sorgu dizisinde numarayı ve cadde adını tersine çevirmeyi deneyin.
Fransa, arrondissementler. Paris adresleri, posta kodunun parçası olarak bir arrondissement (1.–20.) içerir: 75001 ile 75020 arasında. Arrondissement'i atlayan ve sadece Paris kullanan sorgular, belirli bölgeye değil şehir merkezine geocode edilir, bu düşük güvenli adres eşleşmesi yerine bölge eşleşmesi olarak görünür.
Hollanda, posta kodu biçimi. Hollanda posta kodları katı bir DDDD LL düzenini takip eder (4 rakam, boşluk, 2 büyük harf). Boşluk olmadan (1012LG yerine 1012 LG) veya küçük harfle depolanan kodlar doğru şekilde geocode edilecek, ancak posta kodu biçimini ayrı ayrı doğruluyorsanız, boşlukla büyük harfe normalleştirin.
Belçika, dil belirsizliği. Bazı Belçika belediyelerinin Fransız ve Hollanda adları farklıdır (Liège/Luik, Gent/Gand). API her ikisini işler, ancak veri setinizde tutarsız adlandırma (bazı kayıtlar Fransız, bazı Hollandaca) farklı güven seviyeleri üretebilir. Geocoding'den önce bölge başına bir dile normalleştirin.
İspanya ve İtalya, cadde ön eki varyasyonları. "Calle", "Carrer", "Via", "Viale" hepsi geçerli cadde tipi önekleridir ve kayıtlarda kısaltılmış ("C/") olarak görünebilir. Geocoder yaygın kısaltmaları işler ancak eski sistemlerden gelen sıradışı kısaltmalar normalleştirme adımı gerektirebilir.
Increasing Throughput With Concurrent Requests
The sequential script is safe and simple but slow. For larger datasets, replace the sequential loop with a concurrent executor:
from concurrent.futures import ThreadPoolExecutor, as_completed
def main_concurrent(max_workers=10):
df = pd.read_csv(INPUT_CSV, dtype=str).fillna('')
# ... (same setup as before) ...
results = [None] * len(df)
with ThreadPoolExecutor(max_workers=max_workers) as executor:
future_to_idx = {
executor.submit(geocode_address, row['_query'].strip()): idx
for idx, row in df.iterrows()
}
for future in tqdm(as_completed(future_to_idx), total=len(df), desc='Geocoding'):
idx = future_to_idx[future]
results[idx] = future.result()
# ... (same output writing as before) ...
With 10 concurrent workers at 5 RPS per worker, throughput reaches approximately 50 requests/second, 10,000 addresses in under 4 minutes. Check your MapAtlas plan's concurrent request limit before increasing max_workers.
Adres doğrulamasını tekrarlanan bir işlem olarak çalıştıran, yeni CRM içe aktarımlarını haftalık temizleyen veya teslimat adreslerini her gece doğrulayan kuruluşlar için, MapAtlas tümleştirmelerinin operasyonel iş akışlarına nasıl uyduğunu görmek üzere Lojistik ve Teslimat çözümleri sayfasına bakın.
Kötü verilerin ilk etapta sisteminize girmesini önlemek için adres otomatik tamamlama oluştururken (toplu işlemde temizlemek yerine kaynakta hataları yakalamanız gerekir), Adres Otomatik Tamamlama API'si: Tek Alan Kasa Dönüşümünü %35 Artırır ön uç uygulamasını kapsamaktadır.
Koordinat Çıktısıyla Çalışma
Doğrulanan çıktı, kabul edilen her adres için geocoded_lat ve geocoded_lng içerir. Bu koordinatlar, ham adres dizeleriyle mümkün olmayan analiz yeteneklerini açar:
- Mesafe hesaplamaları. Depo ile her teslimat adresi arasında düz çizgi mesafeleri hesaplayarak kargo maliyeti seviyelerini tahmin edin.
- Ticari alan analizi. Doğrulanmış müşteri konumlarını harita üzerinde çizerek talepte coğrafi yoğunlaşmanın nerede olduğunu görün.
- Teslimat bölgesi ataması. Koordinatlarının bir bölge çokgenine düşüp düşmediğini test ederek her adresi teslimat bölgesine atayın.
- Kopya algılama. Adres dizeleri biçimlendirmede farklı olsa bile, aynı koordinatlara (birkaç metre içinde) sahip iki kayıt muhtemelen kopyalardır.
NAP (Ad/Adres/Telefon) tutarlılığı ve yerel işletme adres veritabanları için AI arama görünürlüğüne etkisi açısından, NAP Tutarlılığı AI Araması için: Uyumsuz Adresler ChatGPT Görünürlüğünüzü Neden Öldürür geocoding doğrulanmış adreslerinin neden yapılandırılmış veri işaretlemesi için doğru temel olduğunu açıklar.
Özet
MapAtlas Geocoding API'siyle toplu geocoding size şunları sağlar:
- Doğrulanmış adresler, güven puanlarıyla hangi kayıtların otomatik olarak güvenildiğini ve hangilerinin incelenmesi gerektiğini bilmeniz için.
- Yapılandırılmış bileşenler (cadde, ev numarası, posta kodu, şehir, ülke) herhangi bir giriş biçiminden normalleştirilmiş.
- Coğrafi koordinatlar, kabul edilen her adres için, mekansal analiz ve yönlendirmeyi etkinleştirmek için.
- AB adres biçimi desteği, API'ye yerleştirilmiş, ön işlemede işlemeniz gerekmeyen bir şey.
Python script'i sırayla saniyede 5 adres, eşzamanlı çalışanlarla saniyede 50 adrese kadar çalışır. 10.000 adres için, eşzamanlılığa bağlı olarak 4–40 dakika ayırın. Çıktı, üç seviyeli temiz bir CSV'dir: kabul et, gözden geçir, reddet.
Başlamak için ücretsiz MapAtlas API anahtarına kaydolun. Geocoding API'si tüm planlarda toplu sorguları destekler, istek başına oranlar ve aylık ücretsiz katman sınırları için fiyatlandırma sayfasına bakın.

