"""4차: 부안/완주/군산/순창 메뉴 소스 확정.""" import re, warnings from urllib.parse import urljoin import requests from bs4 import BeautifulSoup warnings.filterwarnings('ignore') UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36' def sess(): s = requests.Session(); s.headers.update({'User-Agent': UA}); return s def fetch(s, url, t=20): r = s.get(url, timeout=t, verify=False, allow_redirects=True) meta = re.search(rb']*charset=["\']?\s*([\w-]+)', r.content[:4096], re.I) r.encoding = meta.group(1).decode('ascii','ignore') if meta else r.apparent_encoding return r S = sess() def dump_menu_divs(soup, label): print(f'\n--- {label}: class에 menu/gnb/sitemap/allmenu 포함 div·nav·ul (직계 a 텍스트) ---') for el in soup.find_all(['div','nav','ul']): cls = ' '.join(el.get('class', [])) idv = el.get('id','') if re.search(r'menu|gnb|sitemap|site_map|allmenu|lnb|depth', cls + ' ' + idv, re.I): n_a = len(el.find_all('a')) n_li = len(el.find_all('li')) if n_a >= 15: # 메가메뉴 후보 print(f' <{el.name} id="{idv}" class="{cls}"> a={n_a} li={n_li}') # 부안: 사이트맵 menuCd 추정 — index의 모든 menuCd 중 *07*(정보공개류) 상위 노드 시도 print('=== 부안군 ===') r = fetch(S, 'https://www.buan.go.kr/index.buan?contentsSid=1') soup = BeautifulSoup(r.text, 'html.parser') dump_menu_divs(soup, '부안 index') # 누리집지도/사이트맵 후보 menuCd 시도 for cd in ['DOM_000000107003000000','DOM_000000108003000000','DOM_000000107002000000', 'DOM_000000106002000000','DOM_000000109002000000']: try: rr = fetch(S, f'https://www.buan.go.kr/index.buan?menuCd={cd}', t=10) ss = BeautifulSoup(rr.text, 'html.parser') has = bool(ss.select_one('div.sitemap')) ttl = (ss.title.get_text().strip()[:30] if ss.title else '') print(f' {cd}: div.sitemap={has} title={ttl!r}') except Exception as e: print(f' {cd}: ERR {type(e).__name__}') # 완주: index 메가메뉴 + contentUid 사이트맵 후보 print('\n=== 완주군 ===') r = fetch(S, 'https://www.wanju.go.kr/index.9is') soup = BeautifulSoup(r.text, 'html.parser') dump_menu_divs(soup, '완주 index') for a in soup.find_all('a'): t = (a.get_text() or '').strip() if re.search(r'누리집|사이트맵|전체메뉴|지도', t): print(' 완주 a:', repr(t[:25]), '| href=', (a.get('href') or '')[:100], '| onclick=', (a.get('onclick') or '')[:90]) # 군산: allmenubox 전체 구조(탭 포함) print('\n=== 군산시 ===') r = fetch(S, 'https://www.gunsan.go.kr/main') soup = BeautifulSoup(r.text, 'html.parser') dump_menu_divs(soup, '군산 index') box = soup.select_one('div.allmenubox') or soup.select_one('div.allmw') if box: # 탭/카테고리 구조 — 직계 자식 요약 print(' allmenubox 하위 ul/div (a수):') for el in box.find_all(['ul','div'], recursive=True): cls = ' '.join(el.get('class', [])) n_a = len(el.find_all('a', recursive=False)) if n_a >= 5: print(f' <{el.name} class="{cls}"> 직계a={n_a} 첫a={el.find("a").get_text().strip()[:15]!r}') # 순창: HTML 내 sitemap/menu json/ajax 흔적 print('\n=== 순창군 ===') r = fetch(S, 'https://www.sunchang.go.kr/') html = r.text soup = BeautifulSoup(html, 'html.parser') dump_menu_divs(soup, '순창 index') print(' html 내 sitemap/menu ajax url 흔적:') for m in set(re.findall(r'["\']([^"\']*(?:sitemap|menu)[^"\']*\.(?:do|json|html|jsp))["\']', html, re.I)): print(' ', m[:100]) # 순창 gnb_list가 ajax라면, 흔한 e-Gov 전체메뉴 경로 시도 for guess in ['/site/main/menu/sitemap','/kr/sitemap','/sitemap.do','/main/sitemap']: try: rr = fetch(S, urljoin('https://www.sunchang.go.kr', guess), t=10) print(f' {guess} -> {rr.status_code}') except Exception as e: print(f' {guess} -> ERR')