Skip to main content
اعتبارسنجی انبوه آدرس با Geocoding API: پردازش ۱۰٬۰۰۰ آدرس در یک مرحله
Tutorials

اعتبارسنجی انبوه آدرس با Geocoding API: پردازش ۱۰٬۰۰۰ آدرس در یک مرحله

اعتبارسنجی انبوه آدرس در Python با MapAtlas Geocoding API: rate limiting، منطق retry، امتیازدهی اطمینان و قالب‌های آدرس اروپایی برای بیش از ۱۰٬۰۰۰ رکورد.

Brent van der Heiden11 min read
#bulk geocoding#geocoding api#address validation#python geocoding#batch geocoding#address cleaning

هر سازمانی که برای سال‌های زیادی داده‌های آدرس جمع‌آوری کرده‌است از یک مسئله یکسان رنج می‌برد: جدول آدرس بزرگ و آشفته‌ی با کیفیت نامعلوم. آدرس‌هایی که از طریق فرم‌های وب توسط کاربرانی که توجه نمی‌کردند وارد شدند. رکوردهایی که از CRM قدیمی مهاجرت شدند که داده‌های ورودی را تأیید نکردند. بارگذاری دسته‌ای از برگه Excel شریک در قالب ناسازگار. مکان‌های کسب‌وکار که از زمان جمع‌آوری داده تغییر کرده‌اند.

اجرای لجستیک روی داده‌های آدرس بد باعث تحویل‌های ناموفق می‌شود. اجرای بازاریابی روی آن پست‌های مستقیم را هدر می‌دهد. اجرای ارزیابی‌های ملک روی آن تحلیل منطقه دریافتی نادرست را تولید می‌کند. قبل از اینکه هرکدام از این عملیات اتفاق بیفتد، آدرس‌ها باید تمیز و تأیید شوند.

Geocoding سپس موثرترین راه این کار در مقیاس است. رشته آدرس خام را به Geocoding API بفرستید. داده‌های ساختاری، مختصات جغرافیایی، و نمره اطمینان دریافت کنید. آدرس‌هایی که geocode می‌شوند تا اطمینان بالا تقریباً مطمئناً معتبر هستند. آدرس‌هایی که اطمینان پایین یا هیچ تطابقی بازگردانند نیاز به بررسی انسانی دارند.

این آموزش یک اسکریپت Python کامل می‌سازد که CSV آدرس را می‌خواند، هر یک را در مقابل MapAtlas Geocoding API geocode می‌کند، نتایج را با نمرات اطمینان می‌نویسد، و رکوردهای اطمینان پایین را برای بررسی دستی علم می‌کند. محدودیت نرخ، خرابی‌های شبکه موقت، و غریب‌های قالب‌بندی آدرس EU را که آموزش‌های geocoding متمرکز بر US از دست می‌دهند، مدیریت می‌کند.

چرا کیفیت داده آدرس بدتر می‌شود

کیفیت آدرس برای دلایل قابل‌پیش‌بینی تنزل می‌یابد:

خرابی‌های ورود دستی. کاربران فرم وب سریع تایپ می‌کنند، تصحیح خودکار نام خیابان‌ها را خراب می‌کند، و تأیید که هر رشته غیر‌خالی را می‌پذیرد زباله را تأیید کند. مطالعه داده‌های پرداخت B2C نشان داد که ۷ تا ۱۲ درصد از آدرس‌های وارد شده دستی دارای خرابی‌های مهمی برای باعث ناموفقی تحویل هستند.

جابه‌جایی کسب‌وکار. پایگاه داده B2B جمع‌آوری‌شده دو سال پیش تقریباً ۱۰ تا ۱۵ درصد آدرس را خواهد داشت که دیگر با محل کسب‌وکار فعلی منطبق نیستند زیرا انتقال، ادغام، و بسته‌شدگی.

نامطابقت‌های قالب. داده‌های جمع‌آوری‌شده از منابع متعدد از قرارداد‌های متفاوت استفاده می‌کند: نام‌های کشور کامل در مقابل کدهای ISO، «St.» در مقابل «Street»، شماره خانه-قبل-خیابان در مقابل بعد، «flat» در مقابل «apt» در مقابل «wohnung». Geocoder تمام اینها را به خروجی ساختاری عادی می‌کند.

مهاجرت سیستم‌های قدیمی. فیلدهای آدرس که به‌طور کامل تقسیم‌شده اغلب هنگام مهاجرت ساختار را تحت فشار قرار می‌دهند و پردازش می‌شوند. فیلدهای آدرس متن‌آزاد از سیستم‌های قدیمی‌تر ممکن است یادداشت، مرجع، یا قالب‌بندی را شامل شوند که بخشی از آدرس واقعی نیست.

روش geocoding تمام اینها را مدیریت می‌کند زیرا بر تطابق جغرافیایی متکی است، نه تطابق رشته. آدرس‌ای که به‌طور نادرست قالب‌بندی شده است هنوز می‌تواند geocode کند اگر داده‌های موقعیت مکانی اساسی منطبق باشند.

درک نمرات اطمینان

MapAtlas Geocoding API نماد confidence را بر هر ویژگی بازمی‌گرداند، از ۰.۰ تا ۱.۰. نشان‌دهنده میزان تطابق نتیجه بازگردانده‌شده با query ورودی است، بدون توجه به تفاوت‌های قالب، مختصرات، و ابهام.

اطمینانتفسیرکنش توصیه‌شده
۰.۹۰ تا ۱.۰۰تطابق دقیق یا تقریبیخودکار بپذیر
۰.۸۵ تا ۰.۸۹تطابق قوی، تفاوت‌های قالب جزئیبا ثبت بپذیر
۰.۶۰ تا ۰.۸۴تطابق جزئی، خیابان یافت شد اما شماره خانه نامعلومبرای بررسی دستی علم
۰.۴۰ تا ۰.۵۹ابهام‌آمیز، منطقه منطبق اما آدرس خاص نهرد یا escalate کن
۰.۰۰ تا ۰.۳۹هیچ تطابق معنی‌داررد، احتمالاً نامعتبر

این آستانه‌ها نقاط شروع هستند. برای عملیات لجستیک جایی که تحویل‌های ناموفق گران هستند، آستانه خودکار-بپذیر را به ۰.۹۲+ تنگ کنید. برای فهرست ایمیل بازاریابی جایی که هزینه بررسی دستی بیش از هزینه چند آدرس بد است، آستانه را به ۰.۸۰ شل کنید.

API همچنین ویژگی match_type را بازمی‌گرداند که نشان‌دهنده سطح سلسله‌مراتب آدرس منطبق است: point (ساختمان دقیق)، interpolated (موقعیت تخمین‌زده بین شماره‌های خانه شناخت‌شده)، street (خیابان یافت‌شده اما شماره خانه نه)، یا locality (فقط شهر منطبق).

اسکریپت اعتبارسنجی Python

وابستگی‌ها را نصب کنید:

pip install requests pandas tqdm

اسکریپت CSV را با ستون address (یا ستون‌های جداگانه street، city، country) می‌خواند، هر سطر را geocode می‌کند، و CSV جدید با نتایج اعتبارسنجی اضافه‌شده را می‌نویسد.

#!/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()

آن را اجرا کنید:

python bulk_geocode.py

برای CSV ۱۰،۰۰۰ آدرس در ۵ درخواست/ثانیه، اسکریپت تقریباً ۳۵ دقیقه تکمیل می‌شود. نوار پیشرفت (از طریق tqdm) تراکنش و زمان تکمیل تخمین‌زده شده را به‌صورت واقعی نشان می‌دهد.

مدیریت خروجی

CSV خروجی شش ستون را به داده‌های ورودی خود اضافه می‌کند:

address, ..., geocoded_label, geocoded_lat, geocoded_lng, confidence, match_type, status

براساس وضعیت برای تولید سه فایل خروجی فیلتر کنید:

# 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")

فایل addresses_review.csv یکی است که نیاز به توجه انسانی دارد. الگوهای بررسی معمول:

  • نوع تطابق street (اطمینان ۰.۶۵ تا ۰.۸۰): خیابان یافت‌شد اما شماره خانه نه. احتمالاً ساختمان جدید، آدرس روستایی با پوشش کم، یا تایپ در شماره خانه. منبع اصلی را بررسی کنید.
  • نوع تطابق locality (اطمینان ۰.۵۰ تا ۰.۶۵): فقط شهر منطبق. نام خیابان احتمالاً غلط‌نویس یا در داده‌ها وجود ندارد. آدرس را در دایرکتوری پستی جستجو کنید.
  • اطمینان پایین در آدرس‌های به‌ظاهر معتبر: تفاوت کشور/زبان را بررسی کنید. آدرس هلندی پرس‌وجو‌شده بدون محدودیت کشور می‌تواند در برابر شهر هلندی یا بلژیکی مشابه geocode شود.

ظریف‌های آدرس EU برای آگاهی

راهنمای‌های bulk geocoding نوشته‌شده برای بازار آمریکا تفاوت‌های قالب را که مجموعه‌های داده EU را سفر می‌کنند نادیده می‌گیرند. در اینجا مسائل رایج‌ترین هستند:

آلمان، ترتیب شماره خانه. آدرس‌های آلمانی از قالب {street} {number} استفاده می‌کنند: Berliner Straße 42. بسیاری از CRMها آدرس‌ها را در قالب {number} {street} ذخیره می‌کنند زیرا برای قرارداد UK/US ساخته‌شده‌اند. اگر آدرس‌های آلمانی خود اطمینان پایین دارند، سعی کنید قبل از ارسال رشته query میان شماره و نام خیابان را معکوس کنید.

فرانسه، arrondissements. آدرس‌های پاریس شامل arrondissement (1 تا 20) به‌عنوان بخشی از کد پستی است: ۷۵۰۰۱ تا ۷۵۰۲۰. پرس‌وجو‌هایی که arrondissement را حذف می‌کنند و فقط Paris استفاده می‌کنند geocode شده‌اند تا centroid شهر، نه منطقه خاص، این به‌عنوان تطابق locality با اطمینان پایین نسبت به تطابق address ظاهر می‌شود.

هلند، قالب کد پستی. کدهای پستی هلندی از الگوی دقیق DDDD LL پیروی می‌کنند (۴ رقم، space، ۲ حرف بزرگ). کدهای ذخیره‌شده بدون space (1012LG جای ۱۰۱۲ LG) یا با حروف کوچک geocode می‌شوند، اما اگر به‌صورت جداگانه قالب کد پستی را تأیید می‌کنید، عادی کنید تا بزرگ با space.

بلژیک، ابهام زبان. برخی بلدیات بلژیک نام‌های فرانسوی و هلندی متفاوتی دارند (Liège/Luik، Gent/Gand). API هر دو را مدیریت می‌کند، اما نام‌گذاری ناسازگار در مجموعه داده (برخی رکوردها از French، برخی Dutch) ممکن است نمرات اطمینان متفاوتی تولید کنند. عادی کنید تا یک زبان برای هر منطقه.

اسپانیا و ایتالیا، تغییرات پیشوند خیابان. «Calle»، «Carrer»، «Via»، «Viale» همه پیشوند نوع خیابان معتبر هستند و می‌توانند مختصر («C/») در رکوردها ظاهر شوند. Geocoder مختصرات رایج را مدیریت می‌کند اما مختصرات غیرمعمول از سیستم‌های قدیمی ممکن است نیاز به مرحله عادی‌سازی داشته باشند.

افزایش ترافیک با درخواست‌های هم‌زمان

اسکریپت متوالی ایمن و ساده اما کند است. برای مجموعه‌های داده بزرگ‌تر، حلقه متوالی را با 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) ...

با ۱۰ کارگر هم‌زمان در ۵ RPS برای هر کارگر، ترافیک تقریباً ۵۰ درخواست/ثانیه، ۱۰،۰۰۰ آدرس در کمتر از ۴ دقیقه است. حد درخواست هم‌زمان طرح MapAtlas خود را قبل از افزایش max_workers بررسی کنید.

برای سازمان‌هایی که اعتبارسنجی آدرس را به‌عنوان عملیات تکراری اجرا می‌کنند، بارگذاری نسخه جدید CRM به‌طور هفتگی، یا اعتبارسنجی آدرس‌های تحویل در شب، صفحه راه‌حل Logistics & Delivery را ببینید برای اینکه انضمام MapAtlas چگونه در workflow‌های عملیاتی جا‌گذار می‌شود.

اگر address autocomplete را می‌سازید تا داده‌های بدی از ابتدا وارد سیستم خود را جلوگیری کنید (خرابی‌ها را در منبع جایی که بجای تمیز‌کردن در batch بگیرید)، Address Autocomplete API: چگونه یک فیلد تبدیل checkout را ۳۵ درصد بالا می‌برد پیاده‌سازی frontend را پوشش می‌دهد.

کار با خروجی مختصات

خروجی تأیید شده geocoded_lat و geocoded_lng برای هر آدرس پذیرفته شده شامل است. این مختصات توانایی تجزیه‌وتحلیل را باز می‌کند که با رشته‌های آدرس خام ممکن نبود:

  • محاسبات فاصله. فاصله خط‌مستقیم را بین انبار و هر آدرس تحویل محاسبه کنید برای تخمین سطح هزینه تحویل.
  • تجزیه‌وتحلیل منطقه دریافتی. مکان‌های مشتری تأیید‌شده را روی نقشه رسم کنید تا ببینید کجا تقاضا به‌طور جغرافیایی متمرکز است.
  • تخصیص منطقه تحویل. هر آدرس را با آزمایش اینکه آیا مختصات آن در polygon منطقه می‌افتد یا نه به منطقه تحویل تخصیص دهید.
  • تشخیص تکراری. دو رکورد با مختصات یکسان (چند متر درون) احتمالاً تکراری هستند، حتی اگر رشته‌های آدرس در قالب‌بندی متفاوت باشند.

برای سازگاری NAP (نام/آدرس/تلفن) و تأثیر آن روی دید‌رسانی جستجوی هوش مصنوعی، به‌خصوص مرتبط برای پایگاه‌های داده آدرس کسب‌وکار محلی، NAP Consistency برای جستجوی هوش مصنوعی: چرا آدرس‌های عدم تطابق ChatGPT دید‌رسانی شما را بکشند توضیح می‌دهد که چرا آدرس‌های geocode تأیید‌شده بنیاد درست برای نشانه‌گذاری داده‌های ساختاری هستند.

خلاصه

Bulk geocoding با MapAtlas Geocoding API شما را می‌دهد:

  • آدرس‌های تأیید‌شده با نمرات اطمینان تا بدانید کدام رکوردها را خودکار و کدام نیاز به بررسی دارند.
  • اجزای ساختاری (خیابان، شماره خانه، کد پستی، شهر، کشور) از هر قالب ورودی عادی‌شده.
  • مختصات جغرافیایی برای هر آدرس پذیرفته‌شده، فعال‌سازی تجزیه‌وتحلیل مکانی و مسیریابی.
  • پشتیبانی قالب آدرس EU در API ساخته‌شده، چیزی که نیاز دارید در پیش‌پردازش مدیریت کنید.

اسکریپت Python در ۵ آدرس/ثانیه متوالی، تا ۵۰/ثانیه با کارگرهای هم‌زمان اجرا می‌شود. برای ۱۰،۰۰۰ آدرس، بسته‌ی ۴ تا ۴۰ دقیقه بسته‌ی بر همزمانی. خروجی CSV تمیز با سه سطح است: بپذیر، بررسی، رد.


برای کلید API رایگان MapAtlas ثبت نام کنید تا شروع کنید. Geocoding API bulk queries را روی تمام طرح‌ها پشتیبانی می‌کند، قیمت‌گذاری را برای نرخ درخواست و محدودیت سطح ماهانه رایگان ببینید.

این مفید بود؟ آن را به اشتراک بگذارید.

درباره نویسنده

Brent van der Heiden

نوشته

Brent van der Heiden

Co-Founder & CEO at MapAtlas

Brent built MapAtlas out of a conviction that developers deserve location APIs with fair pricing and genuine end-user privacy. He writes about geospatial infrastructure, AI search visibility, and how location data powers the products people rely on every day.

مشاهده همه مقالات
بازگشت به وبلاگ