DB_JOB/_스크립트/_공공기관_nboard.py
hehihoho3 df16c98366 백업: DB수집 전체 스냅샷 (공공기관2 정리 전)
공공기관2 작업 중. _temp 몽타주(재생성가능)는 제외.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-18 18:15:40 +09:00

126 lines
5.2 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# -*- coding: utf-8 -*-
"""공공기관 N 이미지 — 게시판 전용 재판정(상세글 렌더 + 크기필터).
매뉴얼: 게시판 N 이미지 = 상세글 표본의 본문 실사진(첨부PDF/뷰어 제외).
대상: plan_{기관}.json 에서 L==게시판 & 원본N('이미지' 포함) 행.
각 게시판: 목록에서 상세URL 추출→상위 4개 렌더→본문 콘텐츠이미지(≥170×110, 노이즈제외) 있으면 이미지.
결과를 엑셀 N에 반영(게시판만). 사용: python _공공기관_nboard.py [기관명 ...]
"""
import sys, os, re, json, time, warnings, importlib.util
import requests, openpyxl
from urllib.parse import urljoin
from bs4 import BeautifulSoup
warnings.filterwarnings('ignore')
OUTDIR = r'D:\01.프로젝트\DB수집\공공기관'
TEMP = r'D:\01.프로젝트\DB수집\_temp'
UA = 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/120.0.0.0 Safari/537.36'
spec = importlib.util.spec_from_file_location('p234', r'D:\01.프로젝트\DB수집\_스크립트\_공공기관_phase234.py')
P = importlib.util.module_from_spec(spec); spec.loader.exec_module(P)
NOISE = re.compile(r'(ico[_\-/]|/icon|logo|btn|bul[_\-]|bg[_\-]|banner|sns|blank|spacer|loading|arrow|/dot|line[_\.]|top_|foot|header|common|_icon|symbol|copyright|qr_|no_img|noimage|share|facebook|insta|twitter|naver|kakao|/skin/|/template/|synap|pdf|viewer|/editor/icon)', re.I)
JS_IMGS = """() => { const o=[]; for(const i of document.querySelectorAll('img')){const r=i.getBoundingClientRect(); o.push({src:i.currentSrc||i.src||'',nw:i.naturalWidth,nh:i.naturalHeight,rw:Math.round(r.width),rh:Math.round(r.height)});} return o; }"""
def has_content_img(items):
for it in items:
s = it.get('src', '')
if not s or NOISE.search(s):
continue
if it.get('nw', 0) >= 170 and it.get('nh', 0) >= 110 and it.get('rw', 0) >= 100 and it.get('rh', 0) >= 80:
return True
return False
def add_image(N):
parts = [p for p in (N or '').split(',') if p]
if '없음' in parts:
parts = [p for p in parts if p != '없음']
if '어문' not in parts:
parts = ['어문'] + parts
if '이미지' not in parts:
# 어문 다음에 이미지
out = []
for p in parts:
out.append(p)
if p == '어문':
out.append('이미지')
parts = out
return ','.join(parts)
def rm_image(N):
parts = [p for p in (N or '').split(',') if p and p != '이미지']
return ','.join(parts) if parts else '없음'
def run(name, pg):
pf = os.path.join(TEMP, f'nshot_{name}', f'plan_{name}.json')
if not os.path.exists(pf):
print(f'[{name}] plan없음 스킵'); return
plan = json.load(open(pf, encoding='utf-8'))
boards = [it for it in plan if it.get('L') == '게시판' and '이미지' in (it.get('N') or '')]
if not boards:
print(f'[{name}] 재판정 게시판 0'); return
xlsx = os.path.join(OUTDIR, f'{name}.xlsx')
wb = openpyxl.load_workbook(xlsx)
ws = wb.active
sess = P.make_session()
img_cnt = txt_cnt = 0
montshots = []
for it in boards:
r, url = it['row'], it['url']
try:
soup, _ = P.fetch(sess, url)
if not soup:
continue
body = P.get_body(soup, P.BODY_SEL)
details = P.extract_detail_urls(body, url, limit=4)
found = False
for du in details:
try:
pg.goto(du, timeout=20000, wait_until='domcontentloaded'); pg.wait_for_timeout(1500)
if has_content_img(pg.evaluate(JS_IMGS)):
found = True
sp = os.path.join(TEMP, f'nshot_{name}', f'bd_r{r}.png')
try:
pg.screenshot(path=sp); montshots.append((r, sp))
except Exception:
pass
break
except Exception:
continue
cur = ws.cell(r, 14).value or ''
if found:
ws.cell(r, 14).value = add_image(cur); img_cnt += 1
else:
ws.cell(r, 14).value = rm_image(cur); txt_cnt += 1
except Exception:
continue
wb.save(xlsx)
print(f'[{name}] 게시판재판정 {len(boards)}개 → 이미지 {img_cnt}·어문 {txt_cnt}')
return montshots
def main():
from playwright.sync_api import sync_playwright
probe = {r['name']: r for r in json.load(open(r'D:\01.프로젝트\DB수집\_스크립트\_공공기관_probe.json', encoding='utf-8'))}
order = sorted(probe.values(), key=lambda x: -int(x['num']))
only = sys.argv[1:]
if only:
order = [p for p in order if p['name'] in only or str(p['num']) in only]
with sync_playwright() as pw:
b = pw.chromium.launch()
pg = b.new_page(user_agent=UA, viewport={'width': 1280, 'height': 1600})
for p in order:
try:
run(p['name'], pg)
except Exception as e:
print(f"[{p['name']}] 실패: {e}")
b.close()
print('=== 게시판 N 재판정 완료 ===')
if __name__ == '__main__':
main()