import io, re, time, requests, gc import pandas as pd import streamlit as st from concurrent.futures import ThreadPoolExecutor, as_completed from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment, Border, Side from openpyxl.utils import get_column_letter st.set_page_config(page_title="사업장 주소·전화번호 보완", page_icon="📋", layout="centered") st.markdown("""""", unsafe_allow_html=True) FSC_KEY = "2446da601452d231f1ff77a4c5ac9598896028fb96e8be586bf7f4be58b6231c" KAKAO_KEY = "e8eef7246a2cd0f3d2d55af8a806e3ef" NAV_HDR = {"User-Agent":"Mozilla/5.0 (iPhone; CPU iPhone OS 16_0 like Mac OS X) AppleWebKit/605.1.15","Referer":"https://map.naver.com/","Accept-Language":"ko-KR,ko;q=0.9"} WORKERS = 20 def kakao_keyword(name, addr): try: r = requests.get("https://dapi.kakao.com/v2/local/search/keyword.json", headers={"Authorization":f"KakaoAK {KAKAO_KEY}"}, params={"query":f"{name} {addr[:20]}","size":5}, timeout=5) docs = r.json().get("documents",[]) gu = re.search(r'[\w]+구|[\w]+군', addr) gu = gu.group() if gu else "" if gu: local = [d for d in docs if gu in d.get("address_name","") or gu in d.get("road_address_name","")] if local: return next((d for d in local if name[:4] in d.get("place_name","")), local[0]) return next((d for d in docs if name[:4] in d.get("place_name","")), docs[0] if docs else None) except: return None def fsc_info(name, biz_no): try: r = requests.get( "https://apis.data.go.kr/1160100/service/GetCorpBasicInfoService_V2/getCorpOutline_V2", params={"serviceKey":FSC_KEY,"pageNo":1,"numOfRows":5,"resultType":"json","corpNm":name}, timeout=5) items = r.json().get("response",{}).get("body",{}).get("items",{}).get("item",[]) if not items: return None if isinstance(items, dict): items = [items] bc = re.sub(r'\D','',str(biz_no)) m = next((i for i in items if re.sub(r'\D','',str(i.get("bzno",""))) == bc), items[0]) return {"phone":m.get("enpTlno",""), "addr":m.get("enpBsadr","") or m.get("enpDtadr","")} except: return None def naver_phone(name, addr): clean = re.sub(r'주식회사|유한회사|\(주\)|\(유\)|[(){}\[\]]','',name).strip() gu = re.search(r'[\w]+구', addr); gu = gu.group() if gu else "" try: r = requests.get("https://m.map.naver.com/search2/search.nhn", headers=NAV_HDR, params={"query":f"{clean} {gu}","type":"all"}, timeout=8) if r.status_code == 429: time.sleep(30); return naver_phone(name, addr) for pat in [r'tel:(0\d{1,2}-?\d{3,4}-?\d{4})',r'(? 0 else "" m = re.search(r'[\w]+구|[\w]+군', str(sample)) gu = m.group() if m else "" tasks = [] for idx in df.index: row = df.loc[idx] tasks.append((idx, str(row.get(name_col,"")) if name_col else "", str(row.get(addr_col,"")) if addr_col else "", str(row.get(biz_col,"")) if biz_col else "", str(row.get(type_col,"")) in ["법인","1"] if type_col else False, gu)) with ThreadPoolExecutor(max_workers=WORKERS) as executor: futures = {executor.submit(process_one, t): t[0] for t in tasks} for future in as_completed(futures): try: idx, res = future.result() df.loc[idx,"전화번호"] = str(res["phone"]) df.loc[idx,"위도"] = res["lat"] df.loc[idx,"경도"] = res["lon"] if addr_col and res["addr"]: df.loc[idx,addr_col] = res["addr"] if res["phone"]: phone_ok += 1 if res["lat"]: coord_ok += 1 except: pass completed += 1 prog.progress(completed/total, text=f"📞{phone_ok} 📍{coord_ok} | {completed}/{total}") prog.empty() col_map = {} if name_col: col_map[name_col]="사업장명" if type_col: col_map[type_col]="법인여부" if addr_col: col_map[addr_col]="주소" col_map["전화번호"]="전화번호" if emp_col: col_map[emp_col]="가입자수" if pay_col: col_map[pay_col]="당월고지금액" if ind_col: col_map[ind_col]="업종" col_map["위도"]="위도"; col_map["경도"]="경도" out = df[[c for c in col_map if c in df.columns]].rename(columns=col_map) # 전화번호/주소 없으면 삭제 + 해당 구/군 아닌 주소도 삭제 mask = (out["전화번호"].notna() & (out["전화번호"]!="") & out["주소"].notna() & (out["주소"]!="")) if gu: mask = mask & out["주소"].str.contains(gu, na=False) out = out[mask].reset_index(drop=True) del df; gc.collect() st.success(f"✅ 완료! 📞{phone_ok:,}개 | 📍{coord_ok:,}개 | 최종 {len(out):,}개") st.balloons() st.download_button("⬇️ 보완 완료 엑셀 다운로드", make_excel(out), fname+"_보완완료.xlsx", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", use_container_width=True)