공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
67 lines
2.9 KiB
Python
67 lines
2.9 KiB
Python
# -*- coding: utf-8 -*-
|
|
# 충북 범용 공공누리(O) 본문영역 재검출: python _cb_kogl.py <엑셀> <host>
|
|
# 본문 컨테이너(#contents 등) 안의 img_opentype0N 만 인정(푸터 전역마크 제외). 게시판은 상세글 확인.
|
|
import openpyxl, sys, io, re, ssl, time, urllib.request
|
|
from bs4 import BeautifulSoup
|
|
from concurrent.futures import ThreadPoolExecutor
|
|
sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='utf-8')
|
|
F=sys.argv[1]; HOST=sys.argv[2]
|
|
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
|
|
def get(u):
|
|
for _ in range(2):
|
|
try:
|
|
req=urllib.request.Request(u,headers={'User-Agent':'Mozilla/5.0'})
|
|
return urllib.request.urlopen(req,timeout=20,context=ctx).read().decode('utf-8','ignore')
|
|
except Exception: time.sleep(0.5)
|
|
return ''
|
|
CONTENT_SEL='#contents,.contents,#content,.content,.sub_content,.sub_contents,.contents_in,.bbs_view,.board_view,.view_con,.cont_area'
|
|
def opentypes_in_content(html):
|
|
s=BeautifulSoup(html,'html.parser')
|
|
cont=s.select_one(CONTENT_SEL)
|
|
area=cont if cont else s
|
|
# 본문영역 내 opentype 이미지
|
|
found=set()
|
|
for img in area.find_all('img'):
|
|
src=(img.get('src') or '')+' '+(img.get('alt') or '')
|
|
m=re.search(r'opentype0?(\d)',src)
|
|
if m: found.add(int(m.group(1)))
|
|
return sorted(found)
|
|
wb=openpyxl.load_workbook(F); ws=wb.active
|
|
def detail_url(html):
|
|
s=BeautifulSoup(html,'html.parser')
|
|
for a in s.find_all('a'):
|
|
href=a.get('href') or ''
|
|
if re.search(r'BbsNttView|addBbsNttView|nttNo=|nttId=|BoardArticle|selectBoardArticle',href):
|
|
if href.startswith('http'): return href
|
|
if href.startswith('/'): return 'https://'+HOST+href
|
|
return None
|
|
def work(r):
|
|
L=ws.cell(r,12).value; url=ws.cell(r,11).value
|
|
if L not in ('페이지','게시판') or not str(url or '').startswith('http'): return (r,None)
|
|
h=get(url)
|
|
if not h: return (r,None)
|
|
if L=='게시판':
|
|
# 상세글 최대 3개 확인
|
|
types=set(opentypes_in_content(h))
|
|
du=detail_url(h); n=0
|
|
while du and n<3:
|
|
dh=get(du); types|=set(opentypes_in_content(dh))
|
|
nx=detail_url(dh); du=nx if nx and nx!=du else None; n+=1
|
|
if types: break
|
|
return (r,sorted(types))
|
|
else:
|
|
return (r,opentypes_in_content(h))
|
|
rows=[r for r in range(3,ws.max_row+1) if ws.cell(r,12).value in('페이지','게시판')]
|
|
print('O검출 대상',len(rows),flush=True)
|
|
results={}
|
|
with ThreadPoolExecutor(max_workers=8) as ex:
|
|
for r,t in ex.map(work,rows): results[r]=t
|
|
chg=0
|
|
for r,t in results.items():
|
|
old=ws.cell(r,15).value
|
|
new=','.join('%d유형'%x for x in t) if t else '미부착'
|
|
if new!=old: ws.cell(r,15).value=new; chg+=1
|
|
wb.save(F)
|
|
att=sum(1 for r in results if results[r])
|
|
print('O 본문부착 %d행 | 변경 %d'%(att,chg))
|