# -*- coding: utf-8 -*- import openpyxl, time, json, re, ssl, urllib.request, sys from urllib.parse import urlparse from bs4 import BeautifulSoup ctx=ssl.create_default_context();ctx.check_hostname=False;ctx.verify_mode=ssl.CERT_NONE HDR={'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/124 Safari/537.36'} def fetch(u): return urllib.request.urlopen(urllib.request.Request(u,headers=HDR),timeout=25,context=ctx).read() UI=re.compile(r'banner|/common/|icon|btn|logo|bullet|sprite|blank|no_img|arrow|/dot|/bg|popup|/sns|/mark|webwatch',re.I) DIAG=re.compile(r'조직도|체계도|순서도|흐름도|구성도|개념도|체계$|절차도|로드맵|추진체계|다이어그램|한눈|chart|graph|플랫폼\s*구축|구성$') ws=openpyxl.load_workbook('한국중부발전.xlsx').active rows=[] for r in range(3,ws.max_row+1): if all(ws.cell(r,c).value in (None,'') for c in range(2,16)): continue rows.append((r,ws.cell(r,2).value,ws.cell(r,11).value,ws.cell(r,12).value,ws.cell(r,13).value,ws.cell(r,14).value)) out=[] for (r,b,K,L,M,N) in rows: if not K or 'komipo.co.kr' not in str(K): # 사이트/외부 continue try: soup=BeautifulSoup(fetch(str(K)),'html.parser') except Exception as e: out.append((r,b,'FETCHERR',L,M,N,str(e)[:30])); time.sleep(0.4); continue isboard='/board/' in str(K) and ('boardMain' in str(K) or 'boardList' in str(K)) isfr='/fr/' in str(K) rL=rM=rN=None if isboard or isfr: em=soup.select_one('div.total em.count') or soup.select_one('.total .count') rM=int(re.sub(r'[^0-9]','',em.get_text())) if em else 0 rL='게시판' cont=soup.select_one('#content') or soup thumbs=[im for im in cont.find_all('img') if 'getImage' in im.get('src','') and not UI.search(im.get('src',''))] rN='없음' if rM==0 else ('어문,이미지' if thumbs else '어문') else: # in-page tabs? ntab=0 for ul in soup.find_all('ul'): cls=' '.join(ul.get('class') or []) if 'tab' in cls and 'tab_link' not in cls: tt=[li for li in ul.find_all('li',recursive=False) if li.find('a') and (li.find('a').get('data-tab') or (li.find('a').get('href','') or '').startswith('#'))] if len(tt)>=2: ntab=max(ntab,len(tt)) rL='페이지'; rM=ntab if ntab>=2 else 1 cont=soup.select_one('#content') or soup.select_one('div.contents') real=[] if cont: for im in cont.find_all('img'): src=im.get('src','');alt=im.get('alt','') if UI.search(src): continue if 'getImage' in src and ('코로나' in alt or '정규직' in alt): continue real.append((src,alt)) diag_only=real and all(DIAG.search(a or '') for s,a in real) rN='어문,이미지' if (real and not diag_only) else '어문' # compare (normalize trailing spaces) sN=(N or '').strip() flags=[] if rL!=(L or '').strip(): flags.append('L:%s!=%s'%(L,rL)) if rM!=M: flags.append('M:%s!=%s'%(M,rM)) if rN!=sN: flags.append('N:%s!=%s'%(sN,rN)) if flags: out.append((r,b,'DIFF',L,M,sN,rL,rM,rN,' '.join(flags),str(K)[:46],[a or s.split('/')[-1] for s,a in (real if not (isboard or isfr) else [])][:4])) time.sleep(0.55) json.dump(out,open('recheck.json','w',encoding='utf-8'),ensure_ascii=False,indent=1,default=str) print('대조 mismatch:',len([x for x in out if x[2]=='DIFF']),'/ fetch err:',len([x for x in out if x[2]=='FETCHERR'])) for x in out: if x[2]=='DIFF': print(f'r{x[0]} b{x[1]} [{x[9]}] {x[10]} {x[11]}') else: print(f'r{x[0]} b{x[1]} FETCHERR {x[6]}')