# -*- 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))