# -*- coding: utf-8 -*- """억제된 페이지→게시판 후보(아직 페이지인 행)를 빈게시판 신호로 재분석. 읽기전용.""" import sys, os, re, json, importlib.util, time from concurrent.futures import ThreadPoolExecutor, as_completed import openpyxl HERE = os.path.dirname(os.path.abspath(__file__)); TEMP = os.path.join(HERE,'..','_temp') MODULES = ['_chungnam_phase234_all.py','_chungbuk_phase234_all.py','_jeonbuk_phase234_all.py'] def load(): sites,mods={},{} for f in MODULES: sp=importlib.util.spec_from_file_location(f[:-3],os.path.join(HERE,f));m=importlib.util.module_from_spec(sp);sp.loader.exec_module(m) for k,v in m.SITES.items(): sites[k]=v;mods[k]=m return sites,mods EMPTY=re.compile(r'게시물이?\s*없|등록된?\s*(?:게시물|자료|글|내용)\s*가?\s*없|자료가\s*없|검색된\s*(?:게시물|결과)\s*가?\s*없|데이터가\s*없') BOARDDOM='.board_list,.bbs_list,table.board_list,.board_view,.board_wrap,.bbs,.tbl_list,.board,.list_wrap,.gallery_list,.photo_list,table.board' WRITE=re.compile(r'글쓰기|글등록|등록하기|write|글작성') def mkfetch(M,cfg): if hasattr(M,'make_session'): s=M.make_session(weak_ssl=cfg.get('weak_ssl',False)); return lambda u:M.fetch(s,u) return lambda u:M.fetch(u) def analyze(M,url,bsel,fetch): soup=None for k in range(3): soup=fetch(url) if soup is not None: break time.sleep(0.4*(k+1)) if soup is None: return {'v':'fail'} b=M.get_body(soup,bsel) txt=b.get_text(' ',strip=True) search=len([i for i in b.find_all('input') if (i.get('type') or 'text').lower() in ('text','search')])>0 rows=[el for el in b.select('table tbody tr, .board_list li, ul.bbs_list li, .bbs_list li') if el.find('a',href=True)] empty=bool(EMPTY.search(txt)) boarddom=bool(b.select(BOARDDOM)) paging=bool(b.select('.pagination,.paging,nav.paging,.page_nav,.paginate')) write=bool(WRITE.search(txt)) if empty or (boarddom and (paging or write)): v='empty_board' else: v='page' return {'v':v,'search':search,'rows':len(rows),'empty':empty,'boarddom':boarddom,'paging':paging,'write':write, 'snip':txt[:160]} def main(): sites,mods=load(); names=[a for a in sys.argv[1:] if not a.startswith('--')] for n in names: cfg=sites[n];M=mods[n];fetch=mkfetch(M,cfg) wb=openpyxl.load_workbook(cfg['xlsx'],read_only=True);ws=wb.active d=json.load(open(os.path.join(TEMP,f'_audit_{n}.json'),encoding='utf-8')) cands=[] for x in d['L_diffs']: if x['oldL']=='페이지' and x['newL']=='게시판': if ws.cell(x['r'],12).value=='페이지': # 아직 페이지(억제됨) cands.append(x) wb.close() res=[] with ThreadPoolExecutor(max_workers=6) as ex: futs={ex.submit(analyze,M,x['url'],cfg['body_sel'],fetch):x for x in cands} for f in as_completed(futs): x=futs[f] try:r=f.result() except:r={'v':'fail'} res.append((x,r)) eb=[(x,r) for x,r in res if r['v']=='empty_board'] pg=[(x,r) for x,r in res if r['v']=='page'] nf=len([1 for _,r in res if r['v']=='fail']) print('\n==== %s: 억제후보%d → 빈게시판의심%d 페이지%d 실패%d'%(n,len(cands),len(eb),len(pg),nf)) print('--- 빈게시판 의심(empty_board) 예시 ---') for x,r in sorted(eb,key=lambda t:t[0]['r'])[:8]: print(f" r{x['r']} {(x['label'] or '')[:20]} | empty={r['empty']} boarddom={r['boarddom']} paging={r['paging']} | {x['url'][:62]}") print(f" · {r['snip'][:110]}") print('--- 순수 페이지 예시 ---') for x,r in sorted(pg,key=lambda t:t[0]['r'])[:5]: print(f" r{x['r']} {(x['label'] or '')[:20]} | boarddom={r['boarddom']} | {x['url'][:62]}") print(f" · {r['snip'][:110]}") json.dump([{**x,**r} for x,r in res],open(os.path.join(TEMP,f'_emptyboard_{n}.json'),'w',encoding='utf-8'),ensure_ascii=False,indent=1) if __name__=='__main__': main()