DB_JOB/작업파일/완료/광역_사이트맵/전북특별자치도/3.김제시/_imgcheck.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

90 lines
3.7 KiB
Python

# -*- coding: utf-8 -*-
"""검출된 본문이미지가 '진짜 사진'인지 크기로 검증. 아이콘·작은배너·서명 제외.
기준: 가로>=250 AND 세로>=250 (또는 한 변>=400) 그리고 용량>=12KB → 사진으로 인정.
JPEG/PNG/GIF 헤더만 읽어 치수 파악(전체 다운로드 최소화는 생략, 작은파일이라 통째 읽음).
사용: python _imgcheck.py "<json: [[행,menuCd,라벨], ...]>" (해당 보드 본문이미지 표본 검사)
"""
import urllib.request, ssl, re, html as ht, sys, json, struct, io
ctx=ssl.create_default_context(); ctx.check_hostname=False; ctx.verify_mode=ssl.CERT_NONE
BASE='https://www.gimje.go.kr'
def get_bytes(url):
req=urllib.request.Request(url, headers={'User-Agent':'Mozilla/5.0'})
return urllib.request.urlopen(req,context=ctx,timeout=25).read()
def get(url):
return get_bytes(url).decode('utf-8','replace')
def img_size(data):
"""(w,h) from JPEG/PNG/GIF bytes, else (None,None)."""
if data[:8]==b'\x89PNG\r\n\x1a\n':
w,h=struct.unpack('>II', data[16:24]); return w,h
if data[:3]==b'GIF':
w,h=struct.unpack('<HH', data[6:10]); return w,h
if data[:2]==b'\xff\xd8': # JPEG
i=2
while i<len(data)-9:
if data[i]!=0xFF: i+=1; continue
m=data[i+1]
if m in (0xC0,0xC1,0xC2,0xC3,0xC5,0xC6,0xC7,0xC9,0xCA,0xCB):
h,w=struct.unpack('>HH', data[i+5:i+9]); return w,h
seg=struct.unpack('>H', data[i+2:i+4])[0]; i+=2+seg
return None,None
def is_real_photo(url):
if url.startswith('/'): url=BASE+url
try:
data=get_bytes(url)
except Exception as ex:
return None, f'ERR {ex}'
w,h=img_size(data); kb=len(data)//1024
if w is None: return None, f'치수불명 {kb}KB'
real = (kb>=12) and ((w>=250 and h>=250) or max(w,h)>=400)
return real, f'{w}x{h} {kb}KB'
# 본문이미지 추출(스캐너와 동일 규칙)
def view_links(menucd):
d=get(f'{BASE}/index.gimje?menuCd={menucd}')
out=[]
for l in re.findall(r'href="(/board/view\.gimje\?[^"]+)"', d):
l=ht.unescape(l)
if l not in out: out.append(l)
return out
def body_content_imgs(det):
s=det.find('bbs_view')
if s<0: s=det.find('bbs_con')
if s<0: return []
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]
seg=det[s:(min(ends) if ends else s+12000)]
out=[]
for i in re.findall(r'<img[^>]+src="([^"]+)"', seg):
i=i.strip(); low=i.lower()
if '/images/common/' in low or '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:
out.append(i)
return out
if __name__=='__main__':
targets=json.loads(sys.argv[1])
for row,menucd,label in targets:
links=view_links(menucd)[:10]
photo_posts=0; checked_imgs=[]
for l in links:
try: det=get(BASE+l)
except: continue
imgs=body_content_imgs(det)
postreal=False
for im in imgs[:2]:
real,info=is_real_photo(im)
checked_imgs.append((real,info,im.split('/')[-1][:24]))
if real: postreal=True
if postreal: photo_posts+=1
verdict='어문,이미지' if photo_posts>0 else '어문'
print(f'{row} | {label[:28]} | 진짜사진게시물 {photo_posts}{verdict}')
for real,info,nm in checked_imgs[:5]:
mark='📷' if real else ('?' if real is None else '🔸아이콘')
print(f' {mark} {info} {nm}')