# -*- coding: utf-8 -*- """komipo 전 게시판 M 재조사 — 'Total : N' 추출(throttle+retry). 첫글 제목도 수집해 중복뷰 검증.""" import re, time, json, warnings import openpyxl from playwright.sync_api import sync_playwright warnings.filterwarnings('ignore') FN='한국중부발전.xlsx' UA='Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 Chrome/120 Safari/537.36' TOTAL=re.compile(r'(?:Total|총건수|총|전체)\s*[:\s]*([\d,]+)',re.I) wb=openpyxl.load_workbook(FN); ws=wb.active boards=[(r,str(ws.cell(r,11).value)) for r in range(3,ws.max_row+1) if ws.cell(r,12).value=='게시판' and ws.cell(r,11).value] print('게시판',len(boards),flush=True) res=[] with sync_playwright() as p: b=p.chromium.launch(); pg=b.new_page(user_agent=UA,viewport={'width':1280,'height':1800}) for idx,(r,u) in enumerate(boards): M=None; first='' for att in range(4): try: pg.goto(u,timeout=25000,wait_until='domcontentloaded'); pg.wait_for_timeout(1200) d=pg.evaluate(r"""()=>{ const txt=document.body.innerText||''; let firstRow=document.querySelector('table tbody tr td a, .board_list li a, tbody tr a'); return {txt, first:(firstRow?.textContent||'').trim().slice(0,30), rows:document.querySelectorAll('table tbody tr, .board_list li').length}; }""") m=TOTAL.search(d['txt']) M=int(m.group(1).replace(',','')) if m else d['rows'] first=d['first'] break except Exception: time.sleep(2.0+att) if M is None: M=ws.cell(r,13).value res.append({'r':r,'M':M,'first':first,'url':u}) ws.cell(r,13).value=M # 빈게시판이면 N=없음 동기화 if M==0 and ws.cell(r,14).value not in ('없음',): pass if (idx+1)%10==0: wb.save(FN); print(f' {idx+1}/{len(boards)}',flush=True) time.sleep(0.8) b.close() wb.save(FN) json.dump(res,open('_boardM.json','w',encoding='utf-8'),ensure_ascii=False,indent=1) from collections import Counter mc=Counter(x['M'] for x in res) print('M분포(상위):',mc.most_common(10),flush=True) # 동일 M 다수 = 중복뷰 의심 dup=[m for m,c in mc.items() if c>=3 and m] print('의심 중복M:',dup,flush=True) print('완료',flush=True)