공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
78 lines
3.2 KiB
Python
78 lines
3.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""서산 본문탭(div.tab_menu>ul.tab_button) 탐지 스캔(읽기전용).
|
|
각 L=페이지 행 URL을 열어 탭그룹 탐지. 조건: 탭>=2, 모든 href 실제링크,
|
|
현재행 URL이 탭집합에 포함. 탭그룹을 시그니처로 dedup해 보고.
|
|
type: contents.do=페이지, selectBbsNttList.do=게시판.
|
|
"""
|
|
import openpyxl, urllib.request, ssl, re, html as ht, sys
|
|
from urllib.parse import urlparse, parse_qs
|
|
|
|
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
|
|
F='충청남도_서산시.xlsx'
|
|
BASE='https://www.seosan.go.kr'
|
|
|
|
def get(url):
|
|
req=urllib.request.Request(url, headers={'User-Agent':'Mozilla/5.0'})
|
|
return urllib.request.urlopen(req,context=ctx,timeout=20).read().decode('utf-8','replace')
|
|
|
|
def norm(u):
|
|
"""path+정렬된 query 로 정규화(도메인 제거)."""
|
|
p=urlparse(u)
|
|
q=parse_qs(p.query)
|
|
qs='&'.join(f'{k}={q[k][0]}' for k in sorted(q))
|
|
return f'{p.path}?{qs}'
|
|
|
|
def find_tabs(html):
|
|
m=re.search(r'<div class="tab_menu">(.*?)</div>', html, re.S)
|
|
if not m: return []
|
|
block=m.group(1)
|
|
if 'tab_button' not in block: return []
|
|
tabs=re.findall(r'<li([^>]*)>\s*<a href="([^"]+)"[^>]*>([^<]+)</a>', block)
|
|
out=[]
|
|
for cls,href,txt in tabs:
|
|
href=ht.unescape(href).strip()
|
|
if href.startswith('#') or href.lower().startswith('javascript') or not href:
|
|
continue
|
|
out.append({'on':'on' in cls,'href':href,'label':txt.strip(),
|
|
'type':'게시판' if 'selectBbsNttList' in href else '페이지'})
|
|
return out
|
|
|
|
def main():
|
|
wb=openpyxl.load_workbook(F); ws=wb.active
|
|
last=max(r for r in range(3,ws.max_row+1) if ws.cell(r,2).value not in (None,''))
|
|
# excel URL 집합
|
|
excel_urls={}
|
|
for r in range(3,last+1):
|
|
u=ws.cell(r,11).value
|
|
if u and 'seosan.go.kr' in str(u):
|
|
excel_urls.setdefault(norm(str(u)),[]).append(r)
|
|
groups={}
|
|
scanned=0
|
|
for r in range(3,last+1):
|
|
L=ws.cell(r,12).value; u=ws.cell(r,11).value
|
|
if not (u and 'seosan.go.kr/www' in str(u)): continue
|
|
if str(L) not in ('페이지','게시판'): continue
|
|
try: d=get(str(u))
|
|
except: continue
|
|
scanned+=1
|
|
tabs=find_tabs(d)
|
|
if len(tabs)<2: continue
|
|
nset={norm(BASE+t['href']) for t in tabs}
|
|
if norm(str(u)) not in nset: continue # 자기 탭그룹 아님
|
|
sig='|'.join(sorted(nset))
|
|
if sig in groups: continue
|
|
# 엑셀에 없는 탭 개수
|
|
missing=[t for t in tabs if norm(BASE+t['href']) not in excel_urls]
|
|
groups[sig]={'anchor_row':r,'tabs':tabs,'missing':len(missing)}
|
|
print(f'스캔 {scanned}행 / 탭그룹 {len(groups)}개')
|
|
for sig,g in groups.items():
|
|
ar=g['anchor_row']
|
|
lab=ws.cell(ar,6).value or ws.cell(ar,5).value
|
|
print(f"\n[앵커 행{ar} '{lab}'] 탭{len(g['tabs'])} 신규{g['missing']}")
|
|
for t in g['tabs']:
|
|
inx=excel_urls.get(norm(BASE+t['href']))
|
|
print(f" {'[ON]' if t['on'] else ' '} {t['type']:3} {t['label'][:24]:24} {t['href'][:46]} {'엑셀행'+str(inx) if inx else '★신규'}")
|
|
|
|
if __name__=='__main__':
|
|
main()
|