import sys, io, re, ssl, json, urllib.request sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') ctx = ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE def fetch(u): return urllib.request.urlopen(urllib.request.Request(u, headers={'User-Agent':'Mozilla/5.0'}), context=ctx, timeout=25).read().decode('utf-8','replace') BASE='https://www.jangsu.go.kr/index.jangsu?menuCd=' home = fetch('https://www.jangsu.go.kr/index.jangsu') # 모든 menuCd + 라벨 (a 태그 안 텍스트/스팬) pairs={} for m in re.finditer(r'menuCd=(DOM_000000\d{12})"[^>]*>\s*(?:]*>)?\s*([^<]{1,40})', home): cd, lab = m.group(1), m.group(2).strip() code = cd[len('DOM_000000'):] # 12 digits if not lab or lab in ('로그인','새창이동'): continue if cd not in pairs: pairs[cd]=lab def parse(code): # 12 digits -> (대,중,소,세부) return code[0:3], code[3:6], code[6:9], code[9:12] # 분류 대={}; 중={}; 소=[] for cd,lab in pairs.items(): code=cd[len('DOM_000000'):] d,e,f,g = parse(code) if e=='000' and f=='000' and g=='000': 대[d]=lab elif f=='000' and g=='000': 중[d+e]=lab elif g=='000': 소.append((d,e,f,cd,lab)) print(f'대분류 {len(대)} | 중분류 {len(중)} | 소분류 {len(소)}') print('대분류:', [(k,v) for k,v in sorted(대.items())]) print('\n중분류 표본:', [(k,v) for k,v in sorted(중.items())][:15]) # 소분류 트리 (대>중>소) print('\n소분류 트리(앞 25):') q='?' for d,e,f,cd,lab in sorted(소)[:25]: print(' '+대.get(d,q)+' > '+중.get(d+e,q)+' > '+lab+' ['+cd+']') # 탭 fetch 테스트 — 차량등록(101005001000) test=fetch(BASE+'DOM_000000101005001000') taps=re.findall(r'', test, re.S) if taps: items=re.findall(r'menuCd=(DOM_000000\d{12})\"[^>]*>(?:)?([^<]+)', taps[0]) print('\n차량등록 본문탭:', [(c[-6:],t.strip()) for c,t in items]) else: print('\n차량등록 탭 없음(정적). 본문 일부:', re.sub(r'\s+',' ',re.sub(r'<[^>]+>','',test))[:200]) json.dump({'대':대,'중':중,'소':[list(x) for x in 소],'pairs':pairs}, open(r'D:\01.프로젝트\DB수집\작업파일\_스크립트\_jangsu_tree.json','w',encoding='utf-8'), ensure_ascii=False, indent=1) print('\nsaved _jangsu_tree.json')