DB_JOB/작업파일/공공기관3/_imgtiles.py
hehihoho3 b0acd1e705 백업: 셀병합 정상화(D~J)+KDHC 옵션2/행높이17, 오늘(06-21) 제출 17곳 검수·압축
- 공공기관2/3 작업본 + 오늘 제출 17곳 D~J 카테고리 셀병합 정상화
- 한국지역난방공사 옵션2(고아셀 F98 수정)+전행 높이17
- 제출_프리랜서2_2026-06-21.zip 생성(17개 xlsx, 2,468행)

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

82 lines
3.8 KiB
Python

# -*- coding: utf-8 -*-
"""각 candidate 행의 본문 콘텐츠이미지(필터통과)만 모아 행라벨과 함께 큰 타일 montage 생성."""
import sys,io,os,re,json,warnings
sys.stdout=io.TextIOWrapper(sys.stdout.buffer,encoding='utf-8')
warnings.filterwarnings('ignore')
from playwright.sync_api import sync_playwright
from PIL import Image,ImageDraw,ImageFont
import openpyxl
TEMP=r'D:\01.프로젝트\DB수집\_temp\imgtiles_kead'
os.makedirs(TEMP,exist_ok=True)
plan=json.load(open(r'D:\01.프로젝트\DB수집\_temp\nshot_한국장애인고용공단\plan_한국장애인고용공단.json',encoding='utf-8'))
cands=[p for p in plan if p['auto']=='candidate']
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/|/resources/|/images/common)',re.I)
JS="""()=>{const out=[];const scope=document.querySelector('#contents,#content,#container,.contents,.content,#sub_content,.sub_content,main')||document.body;
for(const i of scope.querySelectorAll('img')){const r=i.getBoundingClientRect();out.push({src:i.currentSrc||i.src||'',nw:i.naturalWidth,nh:i.naturalHeight,rw:Math.round(r.width),rh:Math.round(r.height),x:Math.round(r.x),y:Math.round(r.y)});}return out;}"""
def good(it):
s=it['src']
if not s or NOISE.search(s): return False
return it['nw']>=170 and it['nh']>=110 and it['rw']>=100 and it['rh']>=80
try:
fnt=ImageFont.truetype('malgun.ttf',20); fnt2=ImageFont.truetype('malgun.ttf',16)
except: fnt=ImageFont.load_default(); fnt2=fnt
results={}
with sync_playwright() as pw:
b=pw.chromium.launch()
pg=b.new_page(viewport={'width':1280,'height':2000})
for p in cands:
r=p['row']; url=p['url']
try:
pg.goto(url,wait_until='networkidle',timeout=25000)
imgs=pg.evaluate(JS)
except Exception as e:
results[r]={'err':str(e)[:30],'imgs':[]}; continue
gi=[it for it in imgs if good(it)]
# screenshot each good image region by element via clip
crops=[]
for j,it in enumerate(gi[:4]):
try:
if it['rw']<10 or it['rh']<10 or it['y']<0: continue
clip={'x':max(0,it['x']),'y':max(0,it['y']),'width':min(it['rw'],1200),'height':min(it['rh'],700)}
path=os.path.join(TEMP,f'r{r}_{j}.png')
pg.screenshot(path=path,clip=clip)
crops.append(path)
except Exception: pass
results[r]={'n':len(gi),'crops':crops,'N':p['N']}
b.close()
json.dump({str(k):v for k,v in results.items()},open(os.path.join(TEMP,'_map.json'),'w',encoding='utf-8'),ensure_ascii=False)
# build tiled montages: each row = label + up to 4 crops side by side
rows_sorted=sorted(results.items())
CW,CH=300,200; LBLW=70; GAP=6
def load_thumb(pth):
try:
im=Image.open(pth).convert('RGB'); im.thumbnail((CW,CH)); return im
except: return None
# group 10 rows per montage
import math
page=0
batch=[]
def flush(batch,page):
if not batch: return
H=sum(CH+GAP for _ in batch)+GAP
W=LBLW+(CW+GAP)*4+GAP
canvas=Image.new('RGB',(W,H),'white'); d=ImageDraw.Draw(canvas)
y=GAP
for r,info in batch:
d.text((4,y+4),f'r{r}',fill='red',font=fnt)
d.text((4,y+30),f'#{info.get("n",0)}',fill='blue',font=fnt2)
x=LBLW
for c in info.get('crops',[]):
t=load_thumb(c)
if t: canvas.paste(t,(x,y))
d.rectangle([x,y,x+CW,y+CH],outline='gray')
x+=CW+GAP
y+=CH+GAP
out=os.path.join(TEMP,f'tiles_{page}.png'); canvas.save(out); print('TILE',out)
for r,info in rows_sorted:
batch.append((r,info))
if len(batch)==10:
flush(batch,page); page+=1; batch=[]
flush(batch,page)
print('done. rows:',len(rows_sorted))