공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
110 lines
4.8 KiB
Python
110 lines
4.8 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""김제 게시판 N 재판정: 게시물 상세 본문(bbs_view) 안의 실제 콘텐츠 이미지 유무 검사.
|
|
콘텐츠 이미지 = /upload_data/ (에디터 본문삽입). 제외 = /images/common/(로고·opentype마크)·이모티콘·아이콘.
|
|
첨부파일(bbs_file 영역)은 본문 이미지로 치지 않음(별도).
|
|
출력: 보드별 표본 게시물수 / 본문이미지 있는 게시물수 / 샘플 경로.
|
|
"""
|
|
import urllib.request, ssl, re, html as ht, sys, json
|
|
|
|
ctx = ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
|
|
BASE='https://www.gimje.go.kr'
|
|
|
|
def get(url):
|
|
req=urllib.request.Request(url, headers={'User-Agent':'Mozilla/5.0'})
|
|
return urllib.request.urlopen(req,context=ctx,timeout=25).read().decode('utf-8','replace')
|
|
|
|
def view_links(menucd):
|
|
d=get(f'{BASE}/index.gimje?menuCd={menucd}')
|
|
raw=re.findall(r'href="(/board/view\.gimje\?[^"]+)"', d)
|
|
out=[]
|
|
for l in raw:
|
|
l=ht.unescape(l)
|
|
if l not in out: out.append(l)
|
|
return out, d
|
|
|
|
def list_thumbs(listhtml):
|
|
"""목록 페이지의 콘텐츠 썸네일(갤러리/자료형 신호). 사이트공통 팝업·common 제외."""
|
|
imgs=re.findall(r'<img[^>]+src="([^"]*upload_data[^"]*)"', listhtml)
|
|
out=[]
|
|
for i in imgs:
|
|
i=i.strip()
|
|
if 'BBS_0000269' in i: continue # 사이트공통 팝업 공지
|
|
if '/images/common/' in i.lower() or 'opentype' in i.lower(): continue
|
|
out.append(i)
|
|
return out
|
|
|
|
def body_imgs(det):
|
|
s=det.find('bbs_view')
|
|
if s<0: s=det.find('bbs_con')
|
|
if s<0: return None, [] # 본문 컨테이너 못찾음
|
|
ends=[p for p in (det.find('bbs_file',s), det.find('bbs_btn',s), det.find('bbs_navi',s), det.find('bbs_paging',s)) if p>0]
|
|
e=min(ends) if ends else s+12000
|
|
seg=det[s:e]
|
|
imgs=re.findall(r'<img[^>]+src="([^"]+)"', seg)
|
|
content=[]
|
|
for i in imgs:
|
|
i=i.strip()
|
|
low=i.lower()
|
|
if '/images/common/' in low: continue
|
|
if 'opentype' in low: continue
|
|
if any(k in low for k in ('icon','emoticon','blank.','spacer','btn_','/common/','logo')): continue
|
|
if '/upload_data/' in low or '/editor' in low or 'board_data' in low or '/se2/' in low:
|
|
content.append(i)
|
|
return len(imgs), content
|
|
|
|
def scan(menucd, nmax=10):
|
|
links, listhtml = view_links(menucd)
|
|
thumbs=list_thumbs(listhtml)
|
|
links=links[:nmax]
|
|
posts=[]
|
|
for l in links:
|
|
try:
|
|
det=get(BASE+l)
|
|
except Exception as ex:
|
|
posts.append({'link':l,'err':str(ex)}); continue
|
|
total, cont = body_imgs(det)
|
|
posts.append({'link':l,'total_img':total,'content_img':cont})
|
|
nwith=sum(1 for p in posts if p.get('content_img'))
|
|
return {'checked':len(posts),'with_img':nwith,'thumbs':len(thumbs),
|
|
'samples':[p['content_img'][0] for p in posts if p.get('content_img')][:3],
|
|
'thumb_samples':thumbs[:3],'posts':posts}
|
|
|
|
def targets_from_xlsx(path, rfrom=162, only_image=True):
|
|
import openpyxl
|
|
wb=openpyxl.load_workbook(path); ws=wb.active
|
|
last=max(r for r in range(3,ws.max_row+1) if ws.cell(r,2).value not in (None,''))
|
|
out=[]
|
|
for r in range(rfrom,last+1):
|
|
L=ws.cell(r,12).value; N=str(ws.cell(r,14).value or '')
|
|
if not (L and '게시판' in str(L)): continue
|
|
if only_image and '이미지' not in N: continue
|
|
url=str(ws.cell(r,11).value or '')
|
|
m=re.search(r'menuCd=([A-Za-z0-9_]+)', url)
|
|
if not m: continue
|
|
parts=[ws.cell(r,c).value for c in range(4,11)]
|
|
label=' > '.join(str(p) for p in parts if p not in (None,''))
|
|
out.append([r, m.group(1), label, N])
|
|
return out
|
|
|
|
if __name__=='__main__':
|
|
if sys.argv[1].startswith('--'):
|
|
mode=sys.argv[1]
|
|
oi = (mode=='--suspects') # --suspects: 이미지표시만 / --allboards: 전체 게시판
|
|
targets=[[r,m,l] for r,m,l,n in targets_from_xlsx('전북특별자치도_김제시.xlsx', only_image=oi)]
|
|
else:
|
|
targets=json.loads(sys.argv[1]) # [[row,menucd,label], ...]
|
|
for row, menucd, label in targets:
|
|
try:
|
|
r=scan(menucd)
|
|
gallery = r['thumbs']>=2 # 목록 썸네일 2개 이상 = 갤러리/자료형
|
|
body = r['with_img']>0 # 본문 인라인 사진
|
|
verdict='어문,이미지' if (gallery or body) else '어문'
|
|
mode = ('갤러리' if gallery else '')+('+본문' if body else '') or '없음'
|
|
print(f"행{row} | {label[:30]} | 표본{r['checked']} 본문img{r['with_img']} 목록썸네일{r['thumbs']} [{mode}] → {verdict}")
|
|
for s in r['thumb_samples'][:1]:
|
|
print(f" 썸네일: {s[:72]}")
|
|
for s in r['samples'][:1]:
|
|
print(f" 본문img: {s[:72]}")
|
|
except Exception as ex:
|
|
print(f"행{row} | {label[:30]} | ERR {ex}")
|