공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
125 lines
6.7 KiB
Python
125 lines
6.7 KiB
Python
# -*- coding: utf-8 -*-
|
|
# 국악방송(27) E=None 섹션의 하위 sub_menu 탭 전개. 우리악기톺아보기·국악방송소개 2그룹.
|
|
# 기존 E=None 행(섹션랜딩)에 E라벨 채우고, 하위탭을 F 자식으로 메뉴순 삽입. L정적판별·N렌더.
|
|
import openpyxl, glob, os, re, sys, shutil, time, urllib.request, ssl, http.cookiejar
|
|
from copy import copy
|
|
from openpyxl.styles import PatternFill
|
|
from playwright.sync_api import sync_playwright
|
|
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
|
|
cj=http.cookiejar.CookieJar(); op=urllib.request.build_opener(urllib.request.HTTPSHandler(context=ctx),urllib.request.HTTPCookieProcessor(cj))
|
|
op.addheaders=[('User-Agent','Mozilla/5.0')]
|
|
BASE='http://www.igbf.kr'; BLUE=PatternFill(fill_type='solid',fgColor='FFBDD7EE'); MAXC=27
|
|
APPLY='--apply' in sys.argv
|
|
def url_of(sn): return '%s/gugak_web/?sub_num=%s'%(BASE,sn)
|
|
def g(u):
|
|
try: return op.open(u,timeout=20).read().decode('utf-8','replace')
|
|
except Exception: return ''
|
|
# 그룹 정의: (anchor sub_num, 섹션E명, [(sub_num, F라벨)...] 메뉴순)
|
|
GROUPS=[
|
|
('2307','우리악기 톺아보기',[('2307','소개'),('2309','가야금'),('2308','거문고'),('2310','북'),('2311','소금'),('2312','장구'),('2313','피리'),('2314','해금')]),
|
|
('754','국악방송소개',[('753','인사말'),('754','비전(Vision)'),('755','연혁'),('757','조직 및 임직원 소개'),('904','방송채널현황-라디오'),('2124','방송채널현황-TV'),('756','CI'),('2244','윤리경영헌장'),('2164','인권경영')]),
|
|
]
|
|
DIR=os.path.dirname(os.path.abspath(__file__))
|
|
d=[x for x in glob.glob(os.path.join(DIR,'27.*')) if os.path.isdir(x)][0]
|
|
xp=[p for p in glob.glob(os.path.join(d,'*.xlsx')) if not os.path.basename(p).startswith(('_','~')) and 'conflict' not in os.path.basename(p) and 'backup' not in p][0]
|
|
mt=os.path.getmtime(xp); wb=openpyxl.load_workbook(xp); ws=wb.active
|
|
def eff(r,c):
|
|
v=ws.cell(r,c).value
|
|
if v is not None: return v
|
|
for mr in ws.merged_cells.ranges:
|
|
if mr.min_col==c and mr.min_row<=r<=mr.max_row: return ws.cell(mr.min_row,c).value
|
|
return None
|
|
data=[r for r in range(3,ws.max_row+1) if any(ws.cell(r,c).value not in (None,'') for c in (2,4,5,6,7,11))]
|
|
def subnum(u):
|
|
m=re.search(r'[?&]sub_num=(\d+)',str(u)); return m.group(1) if m else None
|
|
def Lof(sn):
|
|
h=g(url_of(sn))
|
|
board = bool(re.search(r'(전체\s*[0-9,]+\s*건|총\s*[0-9,]+\s*건|pageIndex|paging|board.jsp|bbs_list|게시물이)',h))
|
|
bids=len(set(re.findall(r'bIdx=(\d+)',h)))
|
|
if board or bids>=3: return ('게시판', bids if bids else 0)
|
|
return ('페이지',1)
|
|
# anchor 행 찾기
|
|
anchors={}
|
|
for r in data:
|
|
sn=subnum(ws.cell(r,11).value)
|
|
for asn,enm,items in GROUPS:
|
|
if sn==asn and eff(r,4) and (eff(r,5) in (None,'') or eff(r,5)==enm):
|
|
anchors[asn]=r
|
|
print('anchor 행:',anchors)
|
|
# L 크롤 + N 렌더
|
|
LM={}
|
|
for asn,enm,items in GROUPS:
|
|
for sn,lab in items:
|
|
LM[sn]=Lof(sn); time.sleep(0.03)
|
|
Nmap={}
|
|
with sync_playwright() as pw:
|
|
br=pw.chromium.launch(); pg=br.new_page(viewport={'width':1200,'height':1600})
|
|
UIPAT=re.compile(r'(logo|footer|header|banner|btn|icon|bg[_-]|[_-]bg|sns|family|arrow|blank|no[_-]?img|sprite|marker|common)',re.I)
|
|
JS=r'''()=>{let root=document.querySelector("#container")||document.querySelector(".sub_container")||document.querySelector("#contents")||document.body;
|
|
let imgs=[...root.querySelectorAll("img")].filter(i=>i.clientWidth>=140&&i.clientHeight>=100).map(i=>(i.currentSrc||i.src||"").split("/").pop());
|
|
let aud=root.querySelectorAll("audio,[src*='.mp3']").length; return {imgs:imgs,aud:aud};}'''
|
|
for asn,enm,items in GROUPS:
|
|
for sn,lab in items:
|
|
try:
|
|
pg.goto(url_of(sn),timeout=25000,wait_until='domcontentloaded'); pg.wait_for_timeout(900); pg.mouse.wheel(0,2000); pg.wait_for_timeout(300)
|
|
info=pg.evaluate(JS)
|
|
photos=[s for s in info['imgs'] if not UIPAT.search(s)]
|
|
n='어문'
|
|
if LM[sn][0]=='게시판': n='어문'
|
|
else: n='어문,이미지' if photos else '어문'
|
|
Nmap[sn]=n
|
|
except Exception: Nmap[sn]='어문'
|
|
br.close()
|
|
print('L:',LM); print('N:',Nmap)
|
|
if not APPLY: print('DRY'); sys.exit(0)
|
|
# 빌드: data 순회, anchor 행을 만나면 그 그룹 전체(F자식들)로 치환
|
|
snap={(r,c):copy(ws.cell(r,c)._style) for r in data for c in range(1,MAXC+1)}
|
|
anchor_rows={r:(asn,enm,items) for asn,enm,items in GROUPS for r in [anchors.get(asn)] if r}
|
|
def rec_plain(r):
|
|
rec={'src':r,'D':eff(r,4),'E':eff(r,5),'F':ws.cell(r,6).value,'mark':set()}
|
|
for c in range(7,MAXC+1): rec[c]=ws.cell(r,c).value
|
|
rec['url']=ws.cell(r,11).value; hl=ws.cell(r,11).hyperlink; rec['hl']=hl.target if hl else rec['url']; return rec
|
|
out=[]
|
|
for r in data:
|
|
if r in anchor_rows:
|
|
asn,enm,items=anchor_rows[r]
|
|
for sn,lab in items:
|
|
L,M=LM[sn]
|
|
ch={'src':r,'D':eff(r,4),'E':enm,'F':lab,'mark':set(range(4,16)),'url':url_of(sn),'hl':url_of(sn)}
|
|
for c in range(7,MAXC+1): ch[c]=None
|
|
ch[12]=L; ch[13]=M; ch[14]=Nmap.get(sn,'어문'); ch[15]='미부착'
|
|
out.append(ch)
|
|
else:
|
|
out.append(rec_plain(r))
|
|
for mr in list(ws.merged_cells.ranges):
|
|
if mr.min_row>=3: ws.unmerge_cells(str(mr))
|
|
n=len(out)
|
|
for i,rc in enumerate(out):
|
|
r=3+i; src=rc['src']
|
|
for c in range(1,MAXC+1): ws.cell(r,c)._style=copy(snap[(src,c)])
|
|
ws.cell(r,2).value=i+1; ws.cell(r,3).value=ws.cell(src,3).value
|
|
ws.cell(r,4).value=rc['D']; ws.cell(r,5).value=rc['E']; ws.cell(r,6).value=rc['F']
|
|
for c in range(7,MAXC+1): ws.cell(r,c).value=rc.get(c)
|
|
kc=ws.cell(r,11); kc.value=rc['url']; kc.hyperlink=rc['hl'] if rc['url'] else None
|
|
for c in rc.get('mark',()): ws.cell(r,c).fill=BLUE
|
|
for r in range(3+n,ws.max_row+1):
|
|
for c in range(1,MAXC+1):
|
|
cell=ws.cell(r,c); cell.value=None; cell.hyperlink=None; cell.fill=PatternFill(fill_type=None)
|
|
def rem(col,sc):
|
|
r=3
|
|
while r<3+n:
|
|
v=ws.cell(r,col).value
|
|
if v is None or v=='': r+=1; continue
|
|
r2=r
|
|
while r2+1<3+n and ws.cell(r2+1,col).value==v and all(ws.cell(r2+1,s).value==ws.cell(r,s).value for s in sc): r2+=1
|
|
if r2>r:
|
|
for rr in range(r+1,r2+1): ws.cell(rr,col).value=None
|
|
ws.merge_cells(start_row=r,start_column=col,end_row=r2,end_column=col)
|
|
r=r2+1
|
|
rem(7,[4,5,6]); rem(6,[4,5]); rem(5,[4]); rem(4,[])
|
|
if os.path.getmtime(xp)!=mt: print('ABORT changed'); sys.exit(1)
|
|
shutil.copy(xp, os.path.join(d,'_backup',os.path.basename(xp).replace('.xlsx','_backup_subnav전개전.xlsx')))
|
|
wb.save(xp)
|
|
bs=[ws.cell(r,2).value for r in range(3,3+n)]
|
|
print('saved %d행 B연속%s'%(n,bs==list(range(1,n+1))))
|