Toda organizacao que coleta dados de endereco ha varios anos tem o mesmo problema: uma grande tabela de enderecos desorganizada com qualidade desconhecida. Enderecos inseridos por formularios web por usuarios desatentos. Registros migrados de um CRM legado que nao validava entrada. Importacoes em massa de uma planilha Excel de um parceiro em formato incompativel. Locais comerciais que se mudaram desde que os dados foram coletados.
Executar logistica com dados de endereco ruim causa entregas falhadas. Executar marketing desperdiça gastos com mala direta. Executar avaliacoes imobiliarias produz analises de area de atuacao incorretas. Antes que qualquer uma dessas operacoes aconteca, os enderecos precisam ser limpos e validados.
Geocodificacao e a maneira mais eficaz de fazer isso em escala. Envie uma string de endereco bruto para a API de geocodificacao, receba componentes estruturados, coordenadas geograficas e uma pontuacao de confianca. Enderecos que geocodificam com limpeza para uma confianca alta sao quase certamente validos. Enderecos que retornam uma confianca baixa ou nenhuma correspondencia precisam de revisao humana.
Este tutorial cria um script Python completo que le um CSV de enderecos, geocodifica cada um contra a API de geocodificacao MapAtlas, escreve os resultados com pontuacoes de confianca e marca registros de baixa confianca para revisao manual. Ele lida com limitacao de taxa, erros transientes de rede e peculiaridades de formato de endereco da UE que tutoriais de geocodificacao centrados nos EUA ignoram.
Por que os dados de endereco ficam ruins
A qualidade do endereco se degrada por razoes previsiveis:
Erros de entrada manual. Usuarios de formularios web digitam rapidamente, o autocorretor maneja nomes de ruas e validacao que aceita qualquer string nao vazia permite lixo. Um estudo de dados de checkout B2C descobriu que 7-12% dos enderecos inseridos manualmente contem erros significativos o suficiente para causar falha na entrega.
Relogacoes comerciais. Um banco de dados B2B coletado dois anos atras tera aproximadamente 10-15% dos enderecos nao correspondendo mais a localizacao comercial atual devido a movimentos, fusoes e encerramentos.
Inconsistencias de formato. Dados coletados de multiplas fontes usam convencoes diferentes: nomes completos de paises versus codigos ISO, "St." versus "Street", numero da casa antes da rua versus depois, "flat" versus "apt" versus "wohnung". Um geocodificador normaliza todos estes em saida estruturada.
Migracoes de sistemas legados. Campos de endereco que foram divididos entre multiplas colunas de banco de dados geralmente sao concatenados durante migracao, perdendo estrutura. Campos de endereco em texto livre de sistemas mais antigos podem incluir notas, referencias ou formatacao que nao faz parte do endereco real.
A abordagem de geocodificacao lida com todos estes porque se baseia em correspondencia geografica, nao correspondencia de string. Um endereco que esta formatado incorretamente ainda pode geocodificar corretamente se os dados de localizacao subjacentes correspondem.
Entendendo pontuacoes de confianca
A API de geocodificacao MapAtlas retorna uma propriedade confidence em cada recurso, variando de 0,0 a 1,0. Representa o quao proximamente o resultado retornado corresponde a consulta de entrada, levando em conta diferencas de formato, abreviacoes e ambiguidade.
| Confianca | Interpretacao | Acao recomendada |
|---|---|---|
| 0,90 - 1,00 | Correspondencia exata ou quase exata | Aceitar automaticamente |
| 0,85 - 0,89 | Correspondencia forte, pequenas diferencas de formato | Aceitar com registro |
| 0,60 - 0,84 | Correspondencia parcial, rua encontrada mas numero da casa incerto | Marcar para revisao manual |
| 0,40 - 0,59 | Ambiguo, localidade correspondeu mas nao endereco especifico | Rejeitar ou escalar |
| 0,00 - 0,39 | Nenhuma correspondencia significativa | Rejeitar, provavel invalido |
Estes limites sao pontos de partida. Para uma operacao logistica onde entregas falhadas sao caras, aperte o limiar de aceitacao automatica para 0,92+. Para uma lista de correspondencia de marketing onde o custo de uma revisao manual supera o custo de alguns enderecos ruins, solte para 0,80.
A API tambem retorna uma propriedade match_type indicando qual nivel da hierarquia de endereco correspondeu: point (construcao exata), interpolated (posicao estimada entre numeros de casa conhecidos), street (rua encontrada mas nao numero da casa) ou locality (apenas a cidade correspondeu).
O script de validacao Python
Instale as dependencias:
pip install requests pandas tqdm
O script le um CSV com uma coluna address (ou colunas separadas street, city, country), geocodifica cada linha e escreve um novo CSV com resultados de validacao anexados.
#!/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()
Execute-o:
python bulk_geocode.py
Para um CSV de 10.000 enderecos em 5 requisicoes por segundo, o script e concluido em aproximadamente 35 minutos. A barra de progresso (via tqdm) mostra rendimento em tempo real e tempo estimado de conclusao.
Tratamento da saida
O CSV de saida adiciona seis colunas aos seus dados de entrada:
address, ..., geocoded_label, geocoded_lat, geocoded_lng, confidence, match_type, status
Filtre por status para gerar tres arquivos de saida:
# Apos executar main(), divida em niveis de aceitacao:
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"Limpo: {len(df[df['status']=='accept']):,} enderecos prontos para usar")
print(f"Revisao: {len(df[df['status']=='review']):,} enderecos para verificacao manual")
print(f"Rejeitar: {len(df[df['status']=='reject']):,} enderecos para descartar ou corrigir")
O arquivo addresses_review.csv e aquele que precisa de atencao humana. Padroes tipicos de revisao:
- Tipo de correspondencia
street(confianca 0,65-0,80): A rua foi encontrada mas nao o numero da casa. Provavelmente um predio novo, um endereco rural com cobertura escassa ou um erro de digitacao no numero da casa. Verifique a fonte original. - Tipo de correspondencia
locality(confianca 0,50-0,65): Apenas a cidade correspondeu. O nome da rua provavelmente esta escrito incorretamente ou nao existe nos dados. Procure o endereco em um diretorio postal. - Baixa confianca em enderecos claramente validos: Verifique se ha uma incompatibilidade de pais ou idioma. Um endereco holandesa consultado sem restricao de pais pode geocodificar contra uma cidade alemaa ou belga com nome semelhante.
Peculiaridades de endereco da UE para conhecer
Guias de geocodificacao em massa escritos para o mercado dos EUA ignoram as diferencas de formato que confundem conjuntos de dados da UE. Aqui estao os problemas mais comuns:
Alemanha, ordenacao de numero da casa. Enderecos alemaes usam formato {street} {number}: Berliner Straße 42. Muitos CRMs armazenam enderecos em formato {number} {street} porque foram construidos para convencao UK/US. Se seus enderecos alemaaes estao geocodificando com baixa confianca, tente inverter o numero e o nome da rua na string de consulta antes do envio.
Franca, arrondissements. Enderecos de Paris incluem um arrondissement (1-20) como parte do codigo postal: 75001 ate 75020. Consultas que omitem o arrondissement e usam apenas Paris sao geocodificadas para o centroide da cidade, nao o distrito especifico, isso aparece como uma correspondencia locality com baixa confianca em vez de uma correspondencia address.
Paises Baixos, formato de codigo postal. Os codigos postais holandeses seguem um padrao rigido DDDD LL (4 digitos, espaco, 2 letras maiusculas). Codigos armazenados sem o espaco (1012LG em vez de 1012 LG) ou com letras minusculas geocodificarao corretamente, mas se estiver validando o formato do codigo postal separadamente, normalize para maiusculas com um espaco.
Belgica, ambiguidade de idioma. Alguns municipios belgas tem nomes diferentes em frances e holandesa (Liège/Luik, Gent/Gand). A API lida com ambos, mas nomes inconsistentes em seu conjunto de dados (alguns registros usando frances, alguns holandesa) podem produzir niveis diferentes de confianca. Normalize para um idioma por regiao antes da geocodificacao.
Espanha e Italia, variacoes de prefixo de rua. "Calle", "Carrer", "Via", "Viale" sao todos prefixos de tipo de rua validos e podem aparecer abreviados ("C/") nos registros. O geocodificador lida com abreviacoes comuns mas abreviacoes incomuns de sistemas legados podem precisar de uma etapa de normalizacao.
Aumentando rendimento com requisicoes concorrentes
O script sequencial e seguro e simples mas lento. Para conjuntos de dados maiores, substitua o loop sequencial por um executor concorrente:
from concurrent.futures import ThreadPoolExecutor, as_completed
def main_concurrent(max_workers=10):
df = pd.read_csv(INPUT_CSV, dtype=str).fillna('')
# ... (mesma configuracao de antes) ...
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()
# ... (mesma escrita de saida de antes) ...
Com 10 workers concorrentes em 5 RPS por worker, o rendimento atinge aproximadamente 50 requisicoes por segundo, 10.000 enderecos em menos de 4 minutos. Verifique o limite de requisicoes concorrentes de seu plano MapAtlas antes de aumentar max_workers.
Para organizacoes executando validacao de endereco como operacao recorrente, limpando importacoes CRM novas semanalmente ou validando enderecos de entrega nightly, veja a pagina de solucoes de logistica e entrega para como as integracoes MapAtlas se encaixam nos fluxos de trabalho operacionais.
Se estiver criando autocomplete de endereco para impedir que dados ruins entrem em seu sistema em primeiro lugar (capturando erros na fonte em vez de limpa-los em lote), Address Autocomplete API: How One Field Lifts Checkout Conversion by 35% cobre a implementacao frontend.
Trabalhando com a saida de coordenadas
A saida validada inclui geocoded_lat e geocoded_lng para cada endereco aceito. Essas coordenadas abrem capacidades de analise que nao eram possiveis com strings de endereco bruto:
- Calculos de distancia. Calcule distancias em linha reta entre um armazem e cada endereco de entrega para estimar niveis de custo de envio.
- Analise de area de atuacao. Plote localizacoes de clientes validadas em um mapa para ver onde a demanda se concentra geograficamente.
- Atribuicao de zona de entrega. Atribua cada endereco a uma zona de entrega testando se suas coordenadas caem dentro de um poligono de zona.
- Deteccao de duplicacao. Dois registros com as mesmas coordenadas (dentro de alguns metros) sao provavelmente duplicatas, mesmo que as strings de endereco difiram na formatacao.
Para consistencia NAP (Nome/Endereco/Telefone) e seu impacto na visibilidade de busca de IA, particularmente relevante para bancos de dados de endereco de negocios locais, NAP Consistency for AI Search: Why Mismatched Addresses Kill Your ChatGPT Visibility explica por que enderecos validados por geocodificacao sao a fundacao correta para marcacao de dados estruturados.
Resumo
Geocodificacao em massa com a API de geocodificacao MapAtlas lhe da:
- Enderecos validados com pontuacoes de confianca para que voce saiba quais registros confiar automaticamente e quais precisam de revisao.
- Componentes estruturados (rua, numero da casa, codigo postal, cidade, pais) normalizados de qualquer formato de entrada.
- Coordenadas geograficas para cada endereco aceito, permitindo analise espacial e roteamento.
- Suporte de formato de endereco da UE integrado na API, nao algo que voce precise lidar no preprocessamento.
O script Python e executado em 5 enderecos por segundo sequencialmente, ate 50 por segundo com workers concorrentes. Para 10.000 enderecos, orcamento de 4-40 minutos dependendo da concorrencia. A saida e um CSV limpo com tres niveis: aceitar, revisao, rejeitar.
Inscreva-se para uma chave API MapAtlas gratuita para comecjar. A API de geocodificacao suporta consultas em massa em todos os planos, veja preco para taxas por requisicao e limites de nivel gratuito mensal.
Perguntas frequentes
Quanto tempo leva para geocodificar 10.000 enderecos com a API MapAtlas?
Com uma taxa conservadora de 5 requisicoes por segundo com um pequeno buffer de pausa, 10.000 enderecos levam aproximadamente 35-40 minutos. Com requisicoes concorrentes (10 workers) e limites de taxa apropriados, isso cai para menos de 5 minutos. O script Python neste tutorial inclui opcoes sequenciais e concorrentes.
Qual limiar de pontuacao de confianca devo usar para validacao de endereco?
Um sistema pratico de tres niveis funciona bem: aceitar enderecos com confianca acima de 0,85, marcar para revisao manual entre 0,60 e 0,85 e rejeitar abaixo de 0,60. Os limites exatos dependem de quao critica e a precisao do endereco para seu caso de uso, operacoes logisticas devem usar limites mais rigorosos do que campanhas de marketing.
A API de geocodificacao MapAtlas lida com formatos de endereco especificos da UE?
Sim. A API analisa corretamente o alemao (numero da casa apos a rua), frances (numero da casa antes da rua), holandeses (codigos postais 4+2) e outras convencoes de endereco do pais europeu. Consultas no idioma local retornam resultados melhores do que consultas transliteradas ou em formato ingles.

