# -*- coding: utf-8 -*- """공공기관 접근실패(S='접근 실패') 행 재시도. 복구되면 L/M/N/O/P/Q 채우고 S 해제. 영상은 navfix 기준(채널링크 제외) 적용. 사용: python _공공기관_retry.py [기관명 ...] """ import sys, os, re, json, time, importlib.util, warnings import openpyxl warnings.filterwarnings('ignore') spec = importlib.util.spec_from_file_location('p234', r'D:\01.프로젝트\DB수집\_스크립트\_공공기관_phase234.py') P = importlib.util.module_from_spec(spec); spec.loader.exec_module(P) specn = importlib.util.spec_from_file_location('nv', r'D:\01.프로젝트\DB수집\_스크립트\_공공기관_navfix.py') NV = importlib.util.module_from_spec(specn); specn.loader.exec_module(NV) OUTDIR = r'D:\01.프로젝트\DB수집\공공기관' LOG = open(r'D:\01.프로젝트\DB수집\_스크립트\_retry_result.txt', 'w', encoding='utf-8') def say(s): LOG.write(s + '\n'); LOG.flush() def fetch_retry(sess, url, tries=3): for i in range(tries): try: r = sess.get(url, timeout=25, verify=False, allow_redirects=True) meta = re.search(rb'charset=["\']?\s*([\w-]+)', r.content[:4096], re.I) r.encoding = meta.group(1).decode(errors='ignore') if meta else r.apparent_encoding if r.status_code == 200: from bs4 import BeautifulSoup return BeautifulSoup(r.text, 'html.parser'), r.url, r.status_code last = r.status_code except Exception as e: last = type(e).__name__ time.sleep(1.2) return None, None, last def run(name, sess): xlsx = os.path.join(OUTDIR, f'{name}.xlsx') wb = openpyxl.load_workbook(xlsx) ws = wb.active targets = [] for r in range(3, ws.max_row + 1): if ws.cell(r, 2).value is None: break if '접근 실패' in (ws.cell(r, 19).value or ''): targets.append((r, ws.cell(r, 11).value)) if not targets: return 0, 0 rec = dead = 0 for r, url in targets: if not url or not isinstance(url, str) or not url.startswith('http'): continue soup, final, code = fetch_retry(sess, url) if not soup: dead += 1 continue body = P.get_body(soup, P.BODY_SEL) form, count = P.detect_form(body) ws.cell(r, 12).value = form ws.cell(r, 13).value = (count if form == '게시판' else 1) has_img, _, _, has_txt = P.detect_media(body) vid = NV.real_video(body); aud = NV.real_audio(body) types, q = P.detect_kogl(body) if form == '게시판': for du in P.extract_detail_urls(body, final or url, limit=4): ds, _ = P.fetch(sess, du) if not ds: continue db = P.get_body(ds, P.BODY_SEL) di, _, _, _ = P.detect_media(db) has_img |= di; vid = vid or NV.real_video(db); aud = aud or NV.real_audio(db) dt, dq = P.detect_kogl(db) types |= dt parts = ['어문'] if has_txt else [] if has_img: parts.append('이미지') if vid: parts.append('영상') if aud: parts.append('오디오') ws.cell(r, 14).value = ','.join(parts) if parts else '없음' if types and types != {1, 2, 3, 4}: ws.cell(r, 15).value = ','.join(f'{n}유형' for n in sorted(types)) ws.cell(r, 16).value = '게시판' if form == '게시판' else '게시물' ws.cell(r, 17).value = 'Y' if q == 'Y' else 'N' else: ws.cell(r, 15).value = '미부착' ws.cell(r, 19).value = None # 접근실패 해제 rec += 1 wb.save(xlsx) say(f'[{name}] 재시도 {len(targets)}행 → 복구 {rec}·여전히실패 {dead}') return rec, dead def main(): probe = {r['name']: r for r in json.load(open(r'D:\01.프로젝트\DB수집\_스크립트\_공공기관_probe.json', encoding='utf-8'))} order = sorted(probe.values(), key=lambda x: -int(x['num'])) only = sys.argv[1:] if only: order = [p for p in order if p['name'] in only or str(p['num']) in only] sess = P.make_session() tr = td = 0 for p in order: try: res = run(p['name'], sess) if res: tr += res[0]; td += res[1] except Exception as e: say(f"[{p['name']}] 실패: {e}") say(f'=== 재시도 합계 복구 {tr}행·여전히실패 {td}행 ===') LOG.close() if __name__ == '__main__': main()