Initial commit: Excel Address to Postal Code Converter

This commit is contained in:
2026-05-26 22:38:52 +09:00
commit b91d8b8fac
8 changed files with 542 additions and 0 deletions

4
.env.example Normal file
View File

@@ -0,0 +1,4 @@
# 네이버 API 인증 정보
# https://console.ncloud.com/ 에서 발급받을 수 있습니다
NAVER_CLIENT_ID=your_client_id
NAVER_CLIENT_SECRET=your_client_secret

42
.gitignore vendored Normal file
View File

@@ -0,0 +1,42 @@
# 환경 변수 파일
.env
.env.local
# Python
__pycache__/
*.py[cod]
*$py.class
*.so
.Python
build/
develop-eggs/
dist/
downloads/
eggs/
.eggs/
lib/
lib64/
parts/
sdist/
var/
wheels/
*.egg-info/
.installed.cfg
*.egg
# IDE
.vscode/
.idea/
*.swp
*.swo
*~
.DS_Store
# 테스트 결과
.pytest_cache/
.coverage
htmlcov/
# 임시 엑셀 파일 (처리 중)
~$*.xlsx
*.tmp

170
README.md Normal file
View File

@@ -0,0 +1,170 @@
# 엑셀 주소 → 우편번호 자동 변환 앱
엑셀 파일에서 "주소" 컬럼의 주소를 읽어 네이버 지도 API를 통해 우편번호를 조회하고, "우편번호" 컬럼에 자동으로 매칭하여 저장하는 Python 애플리케이션입니다.
## 주요 기능
- 📋 엑셀 파일의 "주소" 컬럼 자동 인식
- 🌐 네이버 지도 API를 사용한 정확한 우편번호 조회
- 📝 "우편번호" 컬럼 자동 생성 및 데이터 추가
- ✅ 처리 결과 통계 표시
- 🔄 배치 처리 지원
## 설치 방법
### 1. 필수 요구사항
- Python 3.7 이상
- pip (Python 패키지 관리자)
### 2. 라이브러리 설치
```bash
pip install -r requirements.txt
```
### 3. 네이버 API 등록
1. [네이버 클라우드 콘솔](https://console.ncloud.com/)에 접속
2. 회원가입 또는 로그인
3. "Services" → "AI · Application Service" → "Naver API" 선택
4. "Application" 메뉴에서 새 애플리케이션 등록
5. Maps 서비스에서 "Geocoding API" 권한 추가
6. API 인증 정보 확인:
- Client ID
- Client Secret
### 4. 환경 변수 설정
`.env.example` 파일을 참고하여 `.env` 파일을 생성합니다:
```bash
# .env 파일
NAVER_CLIENT_ID=your_client_id_here
NAVER_CLIENT_SECRET=your_client_secret_here
```
## 사용 방법
### 기본 사용법
```bash
python main.py 파일경로.xlsx
```
또는 대화형 모드:
```bash
python main.py
# 그러면 파일 경로를 입력하도록 요청합니다
```
### 예시
```bash
python main.py customers.xlsx
```
## 엑셀 파일 형식
### 필수 요구사항
- 첫 번째 행은 헤더 행
- **"주소"** 라는 이름의 컬럼 필수 존재
### 입력 예시
| 이름 | 주소 |
|------|------|
| 홍길동 | 서울시 강남구 테헤란로 123 |
| 김철수 | 부산시 중구 중앙대로 456 |
### 출력 예시
| 이름 | 주소 | 우편번호 |
|------|------|---------|
| 홍길동 | 서울시 강남구 테헤란로 123 | 06234 |
| 김철수 | 부산시 중구 중앙대로 456 | 48058 |
## 주소 입력 형식
네이버 API는 다양한 주소 형식을 지원합니다:
### 도로명 주소 (권장)
```
서울시 강남구 테헤란로 123
경기도 성남시 분당구 정자동 100-1
```
### 지번 주소
```
서울시 강남구 역삼동 123
부산시 중구 중앙동 456-7
```
## 트러블슈팅
### "네이버 API 인증 정보가 없습니다" 오류
**.env 파일이 제대로 설정되었는지 확인하세요:**
```bash
# .env 파일이 존재하는지 확인
ls -la .env
# 파일 내용 확인 (값이 비어있지 않은지 확인)
cat .env
```
### "주소 검색 결과 없음" 경고
- 주소 형식이 정확한지 확인하세요
- 존재하지 않는 주소일 수 있습니다
- 도로명 주소 또는 정확한 주소로 다시 시도해보세요
### API 요청 타임아웃
네이버 API 응답이 느린 경우입니다:
- 잠시 후 다시 시도하세요
- 인터넷 연결 상태를 확인하세요
### "X-NCP-APIGW-API-KEY-ID" 관련 오류
API 인증 정보가 잘못되었을 가능성이 있습니다:
1. 네이버 클라우드 콘솔에서 클라이언트 ID/Secret 재확인
2. `.env` 파일에 정확히 입력했는지 확인
3. 파일을 저장한 후 다시 실행
## 처리 시간
주소 개수에 따라 처리 시간이 달라집니다:
- 100개 주소: ~1-2분
- 1,000개 주소: ~10-20분
- 10,000개 주소: ~100-200분
## API 사용 제한
네이버 API는 무료로 일정량의 요청을 제공합니다:
- 월 25,000건의 무료 쿼터 제공
- 초과 시 추가 요금 발생 가능
자세한 내용은 [네이버 Geocoding API 문서](https://api.ncloud-docs.com/docs/ai-geocoding-geocode)를 참고하세요.
## 파일 구조
```
excelpostchanger/
├── main.py # 메인 애플리케이션
├── address_handler.py # 우편번호 조회 로직
├── excel_handler.py # 엑셀 파일 처리 로직
├── config.py # 설정 파일
├── .env.example # 환경 변수 예시
├── .env # 환경 변수 (실제 사용)
├── requirements.txt # Python 의존성
└── README.md # 이 파일
```
## 라이선스
MIT License
## 지원
문제가 발생하거나 기능 요청이 있으면 이슈를 등록해주세요.

92
address_handler.py Normal file
View File

@@ -0,0 +1,92 @@
import requests
from config import NAVER_CLIENT_ID, NAVER_CLIENT_SECRET, NAVER_GEOCODING_URL
class AddressLookup:
"""네이버 지도 API를 사용하여 주소에서 우편번호를 조회"""
def __init__(self):
self.client_id = NAVER_CLIENT_ID
self.client_secret = NAVER_CLIENT_SECRET
self.api_url = NAVER_GEOCODING_URL
if not self.client_id or not self.client_secret:
raise ValueError(
"네이버 API 인증 정보가 없습니다. "
".env 파일에 NAVER_CLIENT_ID와 NAVER_CLIENT_SECRET을 설정하세요."
)
def get_postal_code(self, address):
"""
주소를 입력받아 우편번호를 조회합니다.
Args:
address (str): 조회할 주소
Returns:
str: 우편번호 (조회 실패 시 빈 문자열)
"""
try:
headers = {
'X-NCP-APIGW-API-KEY-ID': self.client_id,
'X-NCP-APIGW-API-KEY': self.client_secret
}
params = {
'query': address
}
response = requests.get(
self.api_url,
headers=headers,
params=params,
timeout=5
)
if response.status_code == 200:
data = response.json()
# 응답에서 주소 정보 추출
if data.get('addresses') and len(data['addresses']) > 0:
first_result = data['addresses'][0]
# 우편번호는 'addressElements'에서 '법정동' 정보와 함께 제공됨
postal_code = first_result.get('postalCode', '')
if postal_code:
return postal_code
else:
print(f"⚠ 경고: '{address}' - 우편번호 정보 없음")
return ''
else:
print(f"✗ 실패: '{address}' - 주소 검색 결과 없음")
return ''
else:
print(f"✗ API 오류: {response.status_code} - {address}")
return ''
except requests.exceptions.Timeout:
print(f"✗ 타임아웃: '{address}' - 요청 시간 초과")
return ''
except requests.exceptions.RequestException as e:
print(f"✗ 네트워크 오류: '{address}' - {str(e)}")
return ''
except Exception as e:
print(f"✗ 오류: '{address}' - {str(e)}")
return ''
def get_postal_codes_batch(self, addresses):
"""
여러 주소의 우편번호를 일괄 조회합니다.
Args:
addresses (list): 주소 리스트
Returns:
dict: {주소: 우편번호} 형태의 딕셔너리
"""
results = {}
for i, address in enumerate(addresses, 1):
print(f"진행 중... ({i}/{len(addresses)})")
results[address] = self.get_postal_code(address)
return results

16
config.py Normal file
View File

@@ -0,0 +1,16 @@
import os
from dotenv import load_dotenv
# .env 파일에서 환경 변수 로드
load_dotenv()
# 네이버 API 설정
NAVER_CLIENT_ID = os.getenv('NAVER_CLIENT_ID', 'urpaarosee')
NAVER_CLIENT_SECRET = os.getenv('NAVER_CLIENT_SECRET', 'edHfPK139KCg8YsxigQmyhiE3YAuwsn70cWfyZFi')
# API 엔드포인트
NAVER_GEOCODING_URL = 'https://naveropenapi.apigw.ntruss.com/map-geocode/v2/geocode'
# 엑셀 설정
ADDRESS_COLUMN = '주소'
POSTAL_CODE_COLUMN = '우편번호'

107
excel_handler.py Normal file
View File

@@ -0,0 +1,107 @@
from openpyxl import load_workbook
from config import ADDRESS_COLUMN, POSTAL_CODE_COLUMN
class ExcelHandler:
"""엑셀 파일에서 주소를 읽고 우편번호를 추가"""
def __init__(self, file_path):
"""
Args:
file_path (str): 엑셀 파일 경로
"""
self.file_path = file_path
self.workbook = None
self.worksheet = None
def open_file(self):
"""엑셀 파일을 엽니다"""
try:
self.workbook = load_workbook(self.file_path)
self.worksheet = self.workbook.active
print(f"✓ 파일 열기 성공: {self.file_path}")
return True
except FileNotFoundError:
print(f"✗ 파일을 찾을 수 없습니다: {self.file_path}")
return False
except Exception as e:
print(f"✗ 파일 열기 실패: {str(e)}")
return False
def get_column_index(self, column_name):
"""컬럼 이름으로부터 컬럼 인덱스를 찾습니다"""
for cell in self.worksheet[1]:
if cell.value == column_name:
return cell.column
return None
def get_addresses(self):
"""엑셀에서 주소 컬럼을 읽어서 주소 리스트를 반환합니다"""
address_col = self.get_column_index(ADDRESS_COLUMN)
if address_col is None:
print(f"'{ADDRESS_COLUMN}' 컬럼을 찾을 수 없습니다")
return None
addresses = []
for row in range(2, self.worksheet.max_row + 1):
cell_value = self.worksheet.cell(row, address_col).value
if cell_value:
addresses.append(str(cell_value).strip())
print(f"{len(addresses)}개의 주소를 읽었습니다")
return addresses
def add_postal_codes(self, postal_code_dict):
"""
우편번호 딕셔너리를 엑셀에 추가합니다.
Args:
postal_code_dict (dict): {주소: 우편번호} 형태의 딕셔너리
"""
# "우편번호" 컬럼이 없으면 추가
postal_col = self.get_column_index(POSTAL_CODE_COLUMN)
if postal_col is None:
postal_col = self.worksheet.max_column + 1
self.worksheet.cell(1, postal_col).value = POSTAL_CODE_COLUMN
print(f"'{POSTAL_CODE_COLUMN}' 컬럼 추가됨 (컬럼 {postal_col})")
address_col = self.get_column_index(ADDRESS_COLUMN)
if address_col is None:
print(f"'{ADDRESS_COLUMN}' 컬럼을 찾을 수 없습니다")
return False
# 주소와 매칭하여 우편번호 입력
updated_count = 0
for row in range(2, self.worksheet.max_row + 1):
address = self.worksheet.cell(row, address_col).value
if address:
address_str = str(address).strip()
if address_str in postal_code_dict:
postal_code = postal_code_dict[address_str]
self.worksheet.cell(row, postal_col).value = postal_code
updated_count += 1
print(f"{updated_count}개 행의 우편번호가 추가되었습니다")
return True
def save_file(self, output_path=None):
"""
엑셀 파일을 저장합니다.
Args:
output_path (str): 저장할 파일 경로 (None이면 원본 파일 덮어쓰기)
"""
try:
save_path = output_path or self.file_path
self.workbook.save(save_path)
print(f"✓ 파일 저장 완료: {save_path}")
return True
except Exception as e:
print(f"✗ 파일 저장 실패: {str(e)}")
return False
def close_file(self):
"""파일을 닫습니다"""
if self.workbook:
self.workbook.close()

108
main.py Normal file
View File

@@ -0,0 +1,108 @@
#!/usr/bin/env python3
"""
엑셀 파일의 주소에서 우편번호를 조회하여 추가하는 애플리케이션
"""
import sys
import os
from pathlib import Path
from address_handler import AddressLookup
from excel_handler import ExcelHandler
def main():
"""메인 함수"""
print("=" * 60)
print("엑셀 주소 → 우편번호 자동 변환 앱")
print("=" * 60)
print()
# 파일 경로 입력
if len(sys.argv) > 1:
file_path = sys.argv[1]
else:
file_path = input("처리할 엑셀 파일 경로를 입력하세요: ").strip()
# 파일 확인
if not os.path.exists(file_path):
print(f"✗ 파일을 찾을 수 없습니다: {file_path}")
return False
if not file_path.endswith(('.xlsx', '.xls')):
print("✗ 엑셀 파일(.xlsx 또는 .xls)만 지원합니다")
return False
print()
# 1. 엑셀 파일 열기
excel = ExcelHandler(file_path)
if not excel.open_file():
return False
print()
# 2. 주소 읽기
addresses = excel.get_addresses()
if addresses is None or len(addresses) == 0:
print("✗ 처리할 주소가 없습니다")
excel.close_file()
return False
print()
# 3. 네이버 API 초기화
try:
lookup = AddressLookup()
except ValueError as e:
print(f"{str(e)}")
excel.close_file()
return False
print()
print("주소에서 우편번호 조회 중...")
print("-" * 60)
# 4. 우편번호 조회
postal_codes = lookup.get_postal_codes_batch(addresses)
print()
print("-" * 60)
print()
# 5. 우편번호 추가
excel.add_postal_codes(postal_codes)
# 조회 결과 통계
successful = sum(1 for v in postal_codes.values() if v)
failed = len(postal_codes) - successful
print()
print("-" * 60)
print(f"조회 결과: 성공 {successful}개, 실패 {failed}")
print("-" * 60)
print()
# 6. 파일 저장
if excel.save_file():
print()
print("=" * 60)
print("✓ 처리 완료!")
print("=" * 60)
excel.close_file()
return True
else:
excel.close_file()
return False
if __name__ == '__main__':
try:
success = main()
sys.exit(0 if success else 1)
except KeyboardInterrupt:
print("\n\n중단되었습니다.")
sys.exit(1)
except Exception as e:
print(f"\n✗ 예상치 못한 오류: {str(e)}")
sys.exit(1)

3
requirements.txt Normal file
View File

@@ -0,0 +1,3 @@
openpyxl==3.1.5
requests==2.31.0
python-dotenv==1.0.0