공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
122 lines
5.1 KiB
Python
122 lines
5.1 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""완주 40~끝 lnb_dep3 자식탭 G전개(개별공시지가식). 정적HTML 메뉴트리(부모 contentUid→dep3자식) 매칭.
|
|
부모행 lc+1=첫탭·나머지 자식삽입·lc병합. 스타일(글꼴/정렬/테두리)은 부모행에서 복사. JS렌더라 L/M/N 기본.
|
|
사용: python -X utf8 _완주_dep3.py [--write]
|
|
"""
|
|
import sys, os, re, copy, shutil
|
|
from urllib.parse import urljoin
|
|
from concurrent.futures import ThreadPoolExecutor, as_completed
|
|
import requests, urllib3
|
|
from bs4 import BeautifulSoup
|
|
import openpyxl
|
|
from openpyxl.worksheet.hyperlink import Hyperlink
|
|
urllib3.disable_warnings()
|
|
|
|
XLSX=r'D:\01.프로젝트\DB수집\작업파일\광역_사이트맵\전북특별자치도\8.완주군\전북특별자치도_완주군.xlsx'
|
|
BASE='https://www.wanju.go.kr'
|
|
UA={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/124.0 Safari/537.36'}
|
|
START=40
|
|
BOARD_KW=re.compile(r'공지사항|고시공고|입찰공고|입법예고|자료실|갤러리|보도자료|새소식|알림마당|질의응답|묻고답하기')
|
|
def LMN(label):
|
|
if BOARD_KW.search(label or ''): return '게시판',0,'어문'
|
|
return '페이지',1,'어문'
|
|
|
|
def cuid(u):
|
|
mm=re.search(r'contentUid=([0-9a-f]+)',u or ''); return mm.group(1) if mm else None
|
|
def leafcol(ws,r):
|
|
for c in range(10,3,-1):
|
|
if ws.cell(r,c).value not in (None,''): return c
|
|
return None
|
|
|
|
def tree_of(html):
|
|
soup=BeautifulSoup(html,'html.parser')
|
|
tree={}
|
|
for dep3 in soup.find_all('ul',class_='lnb_dep3'):
|
|
pli=dep3.find_parent('li'); pa=pli.find('a',href=True) if pli else None
|
|
if not pa: continue
|
|
pcu=cuid(pa['href'])
|
|
kids=[(li.find('a').get_text(strip=True), urljoin(BASE,li.find('a')['href']))
|
|
for li in dep3.find_all('li',recursive=False) if li.find('a',href=True)]
|
|
if pcu and kids: tree[pcu]=kids
|
|
return tree
|
|
|
|
def children_for(url, my_cuid):
|
|
try:
|
|
r=requests.get(url,headers=UA,verify=False,timeout=15)
|
|
except Exception:
|
|
return None
|
|
tree=tree_of(r.text)
|
|
for k,kids in tree.items():
|
|
if k and (k in (my_cuid or '') or (my_cuid or '') in k):
|
|
return kids
|
|
return None
|
|
|
|
def copy_style(ws, src_r, dst_r):
|
|
for c in range(2,19):
|
|
s=ws.cell(src_r,c); d=ws.cell(dst_r,c)
|
|
d.font=copy.copy(s.font); d.alignment=copy.copy(s.alignment)
|
|
d.border=copy.copy(s.border); d.fill=copy.copy(s.fill)
|
|
d.number_format=s.number_format
|
|
|
|
def remap_insert(ws, ins, n):
|
|
ranges=[(x.min_col,x.min_row,x.max_col,x.max_row) for x in list(ws.merged_cells.ranges)]
|
|
for x in list(ws.merged_cells.ranges): ws.unmerge_cells(str(x))
|
|
ws.insert_rows(ins,n)
|
|
for mc,mr,xc,xr in ranges:
|
|
if mr>=ins: mr+=n; xr+=n
|
|
elif xr>=ins: xr+=n
|
|
ws.merge_cells(start_row=mr,start_column=mc,end_row=xr,end_column=xc)
|
|
|
|
def main():
|
|
write='--write' in sys.argv
|
|
wb=openpyxl.load_workbook(XLSX); ws=wb.active
|
|
C=ws.cell(3,3).value
|
|
cands=[]
|
|
for r in range(START, ws.max_row+1):
|
|
K=ws.cell(r,11).value
|
|
if not(isinstance(K,str) and 'contentUid=' in K): continue
|
|
lc=leafcol(ws,r)
|
|
if not lc or lc>=10 or ws.cell(r,lc+1).value not in (None,''): continue
|
|
cands.append((r,lc,ws.cell(r,lc).value,K))
|
|
# fetch children concurrently
|
|
plan=[]
|
|
with ThreadPoolExecutor(max_workers=8) as ex:
|
|
futs={ex.submit(children_for, K, cuid(K)):(r,lc,lab) for r,lc,lab,K in cands}
|
|
for f in as_completed(futs):
|
|
r,lc,lab=futs[f]
|
|
ch=f.result()
|
|
if ch and len(ch)>=2: plan.append((r,lc,lab,ch))
|
|
plan.sort(key=lambda t:t[0])
|
|
print('전개대상 %d개'%len(plan))
|
|
for r,lc,lab,ch in plan: print(' r%d lc%d %s → %d: %s'%(r,lc,lab,len(ch),[t for t,_ in ch]))
|
|
if not write: return
|
|
shutil.copy(XLSX, XLSX.replace('.xlsx','_backup_dep3전.xlsx'))
|
|
tot=0
|
|
for r,lc,lab,ch in sorted(plan,key=lambda t:-t[0]): # bottom-up
|
|
l0,m0,n0=LMN(ch[0][0])
|
|
ws.cell(r,lc+1).value=ch[0][0]
|
|
ws.cell(r,11).value=ch[0][1]; ws.cell(r,12).value=l0; ws.cell(r,13).value=m0; ws.cell(r,14).value=n0
|
|
rest=ch[1:]; n=len(rest)
|
|
if n:
|
|
remap_insert(ws,r+1,n)
|
|
for i,(clab,curl) in enumerate(rest):
|
|
rr=r+1+i
|
|
copy_style(ws, r, rr)
|
|
ws.cell(rr,3).value=C
|
|
cl,cm,cn=LMN(clab)
|
|
ws.cell(rr,lc+1).value=clab
|
|
ws.cell(rr,11).value=curl
|
|
ws.cell(rr,12).value=cl; ws.cell(rr,13).value=cm; ws.cell(rr,14).value=cn
|
|
ws.row_dimensions[rr].height=ws.row_dimensions[r].height or 15
|
|
ws.merge_cells(start_row=r,start_column=lc,end_row=r+n,end_column=lc)
|
|
tot+=n
|
|
print(' r%d %s +%d'%(r,lab,n))
|
|
for r in range(3,ws.max_row+1):
|
|
v=ws.cell(r,11).value; cell=ws.cell(r,11)
|
|
if isinstance(v,str) and v.startswith('http'): cell.hyperlink=Hyperlink(ref=cell.coordinate,target=v)
|
|
else: cell.hyperlink=None
|
|
wb.save(XLSX)
|
|
print('적용: +%d행 · 스타일복사 · 하이퍼링크재구성 · 백업'%tot)
|
|
|
|
if __name__=='__main__': main()
|