공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
86 lines
4.0 KiB
Python
86 lines
4.0 KiB
Python
"""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'<meta[^>]*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')
|