공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
141 lines
5.2 KiB
Python
141 lines
5.2 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""부여군 N(저작물 유형) 재판정 — 장식/공통UI 이미지 false '이미지' 제거.
|
|
- phase234 detect_media 는 KOGL 마크만 제외 → file_icon.gif·see_btn.gif·btn_page_*.gif 등
|
|
게시판 스킨/공통버튼 아이콘을 '이미지'로 오탐.
|
|
- [[feedback_N_image_rule]] 적용: 장식/공통UI·페이징버튼·첨부아이콘 제외, 실제 사진/업로드·지도/PDF임베드만 인정.
|
|
- 대상: 현재 N 에 '이미지' 포함된 같은도메인 행만(false 양성 제거 전용 → 이미지 추가 안 함).
|
|
- 게시판은 상세글 상위5 추적. body=#txt|#contents|main.
|
|
사용: python -X utf8 _buyeo_rejudge_N.py [--write]
|
|
"""
|
|
import sys, warnings, re
|
|
from urllib.parse import urljoin
|
|
import requests, openpyxl
|
|
from bs4 import BeautifulSoup
|
|
|
|
warnings.filterwarnings('ignore')
|
|
S = requests.Session(); S.headers.update({'User-Agent': 'Mozilla/5.0'})
|
|
XLSX = r'D:\01.프로젝트\DB수집\작업파일\광역_사이트맵\충청남도\7.부여군\충청남도_부여군.xlsx'
|
|
DOMAIN = 'buyeo.go.kr'
|
|
|
|
KOGL = re.compile(r'(?:new_)?img_open(?:type|code)\d', re.I)
|
|
# 장식/공통UI: 기존 논산 DECO + 부여 게시판 스킨/공통버튼/첨부아이콘 패턴 보강
|
|
DECO = re.compile(
|
|
r'/site/common/img/|move\.png|no[-_]?img|blank\.|spacer\.|'
|
|
r'/img/(?:icon|ico|bul|bullet|arrow|btn|bg|tit|h\d)|'
|
|
r'ico_|btn_|bul_|bg_|_bg\.|icon_|'
|
|
r'file_icon|_icon\.|icon\.gif|_btn\.|see_btn|' # 첨부아이콘·바로보기버튼
|
|
r'/skin/|/images/(?:kr/)?common/|/common/img/|' # 게시판 스킨·공통 UI 디렉터리
|
|
r'btn_page|page_(?:next|prev|first|last)', re.I) # 페이징 버튼
|
|
MAPPDF = re.compile(r'pdf|viewer\.html|map|kakao|daum|naver.*map|google.*map|/map', re.I)
|
|
VIDEO = re.compile(r'youtube\.com|youtu\.be|/embed/|\.mp4|\.webm|vimeo', re.I)
|
|
DETAIL = re.compile(r'mode=V|view\.do|/view|seq=|idx=|mng_no=|bbsSeq|nttId|no=', re.I)
|
|
|
|
|
|
def get(url):
|
|
r = S.get(url, verify=False, timeout=20)
|
|
return BeautifulSoup(r.content, 'html.parser')
|
|
|
|
|
|
def body(soup):
|
|
return soup.select_one('#txt') or soup.select_one('#contents') or soup.select_one('main') or soup
|
|
|
|
|
|
def real_imgs(node):
|
|
n = 0
|
|
for im in node.find_all('img'):
|
|
src = im.get('src') or ''
|
|
if not src or KOGL.search(src) or DECO.search(src):
|
|
continue
|
|
n += 1
|
|
return n
|
|
|
|
|
|
def media_flags(node):
|
|
img = real_imgs(node) > 0
|
|
vid = False
|
|
for ifr in node.find_all('iframe'):
|
|
s = ifr.get('src') or ''
|
|
if VIDEO.search(s):
|
|
vid = True
|
|
elif MAPPDF.search(s):
|
|
img = True
|
|
for a in node.find_all('a'):
|
|
if VIDEO.search(a.get('href') or ''):
|
|
vid = True
|
|
if node.find('video'):
|
|
vid = True
|
|
return img, vid
|
|
|
|
|
|
def detail_links(soup, base):
|
|
out = []
|
|
for a in body(soup).find_all('a'):
|
|
h = a.get('href') or ''
|
|
if h.startswith('#'):
|
|
continue
|
|
if DETAIL.search(h):
|
|
out.append(urljoin(base, h))
|
|
seen = set(); res = []
|
|
for u in out:
|
|
if u not in seen:
|
|
seen.add(u); res.append(u)
|
|
return res[:5]
|
|
|
|
|
|
def judge(k, L, M, N):
|
|
if not isinstance(k, str) or DOMAIN not in k:
|
|
return N, 'skip(외부)'
|
|
try:
|
|
soup = get(k)
|
|
except Exception as e:
|
|
return N, f'fetch실패:{str(e)[:25]}'
|
|
bd = body(soup)
|
|
txtlen = len(bd.get_text(strip=True))
|
|
if L == '게시판' and (M in (0, '0', None)):
|
|
return '없음', 'board0'
|
|
has_img, has_vid = media_flags(bd)
|
|
detail = False
|
|
if L == '게시판' and not has_img:
|
|
for du in detail_links(soup, k):
|
|
try:
|
|
di, dv = media_flags(body(get(du)))
|
|
if di: has_img = True
|
|
if dv: has_vid = True
|
|
if di or dv: detail = True
|
|
if has_img and has_vid: break
|
|
except Exception:
|
|
pass
|
|
parts = []
|
|
if txtlen >= 1: parts.append('어문')
|
|
if has_img: parts.append('이미지')
|
|
if has_vid: parts.append('영상')
|
|
return (','.join(parts) if parts else '없음'), ('detail' if detail else '')
|
|
|
|
|
|
def main():
|
|
write = '--write' in sys.argv
|
|
wb = openpyxl.load_workbook(XLSX); ws = wb.active
|
|
changes = []
|
|
for r in range(3, ws.max_row + 1):
|
|
N = ws.cell(r, 14).value
|
|
if not (isinstance(N, str) and '이미지' in N):
|
|
continue
|
|
k = ws.cell(r, 11).value; L = ws.cell(r, 12).value; M = ws.cell(r, 13).value
|
|
new, tag = judge(k, L, M, N)
|
|
if new != N:
|
|
changes.append((r, N, new, tag, ws.cell(r, 7).value or ws.cell(r, 6).value, k))
|
|
print(f'현재 N에 이미지 포함 행 재판정 → 변경 {len(changes)}건')
|
|
for r, old, new, tag, label, k in changes:
|
|
print(f' 행{r} [{old} → {new}] {tag} | {label} | {str(k)[-45:]}')
|
|
if write and changes:
|
|
for r, old, new, tag, label, k in changes:
|
|
ws.cell(r, 14).value = new
|
|
wb.save(XLSX)
|
|
print(f'\n저장: {XLSX} ({len(changes)}건 수정)')
|
|
elif not write:
|
|
print('\n(계획만. --write 로 반영)')
|
|
|
|
|
|
if __name__ == '__main__':
|
|
main()
|