프로젝트

일반

사용자정보

Actions

새기능 #5857

완료됨

새기능 #5801: 신유형개방 권리확인 사이트 수집

[신유형개방 권리확인] 한국동서발전 보도자료 수집 (3,076건)

이호영이(가) 25일 전에 추가함.

상태:
완료
우선순위:
보통
담당자:
시작시간:
2026/08/06
완료기한:
진척도:

100%

추정시간:
난이도:

설명

개요

한국동서발전(EWP) 홈페이지 보도자료 게시판 전건 크롤링 후, 기존 권리확인(양식) 파일을 템플릿으로 복사해 수집 결과를 자동 기재한 작업.

산출물

파일 내용
■ 권리확인(양식)_한국동서발전_보도자료_20260806.xlsm 권리확인 양식(매크로 포함) 수집본
■ 권리확인(양식)_한국동서발전_보도자료_20260806.xlsx 일반 엑셀 버전 (매크로 제거, 데이터 동일)

기재 열: 순번(A) / 사이트명(B)=한국동서발전 / 게시판 경로(D)=사이버홍보실>동서뉴스>보도자료 / 게시판명(E)=보도자료 / 게시물제목(F) / URL주소(G, 활성 하이퍼링크) / 게시글 등록일(H) / 첨부파일여부(K) / 의견(V)='입력'


게시판 분석

  • 자체 ASP 계열 게시판. 목록이 서버 렌더링이라 파싱 용이. 상세보기는 view(idx) JS 함수가 POST 폼(myform)을 제출하는 구조지만, 같은 파라미터를 GET 쿼리로 넣어도 동작하는 것을 확인 → 재현 가능한 상세 URL 확보.
항목
목록 URL GET /kor/subpage/content.html?pc={게시판코드}&page={n}&pageNum=100
게시판코드(pc) W7ADTHM4F3LZV1D51SG246L8B073MG0
페이지당 건수 기본 10 → pageNum=100으로 올려 31회 요청으로 전건 수집
상세 URL GET /kor/subpage/content.html?pc={pc}&state=view&idx={게시물idx}
게시물 키 onclick="view(45237)" 의 idx
전체 제목 목록 <a title="…"> 속성 (말줄임 무관, 상세 조회 불필요)
첨부 여부 행 내 new_down('idx','1') Download 버튼 유무
등록일 목록 마지막 <td> (YYYY.MM.DD)

수집 중 이슈

크롤링 도중 목록번호(no) 2개가 비어 보였는데, 마지막 페이지들을 재수집해 idx 기준으로 대조한 결과 실제 누락 없음 — 수집 중 게시물 수 재계산으로 번호만 밀린 것. (게시물 식별은 no가 아닌 idx로 관리)


엑셀 생성 방식

  1. 기존 권리확인(양식) 파일(경남경찰청본)을 복사해 템플릿으로 사용 — 매크로·수식·데이터검증(드롭다운)·서식 보존
  2. Excel COM(AutomationSecurity=3, 매크로 미실행)으로 기존 데이터 행(3~358) ClearContents + G열 하이퍼링크 삭제
  3. 수집 데이터 3,076행을 2차원 배열 일괄 입력(A~H) 후 G열만 Hyperlinks.Add로 링크 활성화
  4. SaveAs FileFormat=51로 xlsx 버전 추가 생성

확장자/무결성 검증 및 발견된 문제

  • Excel COM으로 재오픈(복구 프롬프트 여부) + openpyxl로 전행(3,076행) 셀 단위 대조 실시
  • 문제 발견: 제목이 작은따옴표로 시작하는 20건(예: '04년도 Target Budgeting 토론회 개최)에서 Excel이 선행 '텍스트 지정 접두어로 해석해 삭제 → 해당 셀만 "'" + 제목(따옴표 한 번 더)으로 재기록해 원문 복원
  • 보정 후 xlsm/xlsx 모두 전행 불일치 0건, 무작위 3건 상세 URL 실접속 제목 대조 통과

코드

크롤러 crawl_ewp.py

import requests, re, json, time, html

H = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) Chrome/126.0'}
BASE = 'https://www.ewp.co.kr/kor/subpage/content.html'
PC = 'W7ADTHM4F3LZV1D51SG246L8B073MG0'
ROW = re.compile(
    r'<td>(\d+)</td>\s*'
    r'<td class="t_left"><a href="#" onclick="view\((\d+)\); return false;" title="([^"]*)"[^>]*>.*?</a></td>(.*?)'
    r'<td>([\d.]+)</td>', re.S)

posts, seen, page = [], set(), 1
sess = requests.Session(); sess.headers.update(H)
while True:
    r = sess.get(BASE, params={'pc': PC, 'page': str(page), 'pageNum': '100'}, timeout=40)
    rows = ROW.findall(r.text)
    new = 0
    for no, idx, title, mid, date in rows:
        if idx in seen: continue
        seen.add(idx); new += 1
        posts.append({'no': int(no), 'idx': int(idx),
                      'title': html.unescape(title).strip(),
                      'attach': 'Y' if 'new_down' in mid else '',
                      'date': date.strip().rstrip('.').replace('.', '-'),
                      'url': f'{BASE}?pc={PC}&state=view&idx={idx}'})
    if not rows or new == 0: break
    page += 1; time.sleep(0.4)

posts.sort(key=lambda p: p['no'])          # 순번 1 = 가장 오래된 글
json.dump(posts, open('ewp_posts.json', 'w', encoding='utf-8'), ensure_ascii=False)

템플릿 복사 + 기재 build_ewp_excel.py

import json, shutil
import win32com.client as win32

shutil.copy2(TPL_경남양식, OUT)             # 양식 통째로 복사 (매크로·서식 보존)
posts = json.load(open('ewp_posts.json', encoding='utf-8'))

excel = win32.DispatchEx('Excel.Application')
excel.AutomationSecurity = 3                # 매크로 실행 차단
wb = excel.Workbooks.Open(OUT, UpdateLinks=0)
ws = wb.Worksheets('권리확인')
excel.Calculation = -4135                   # 수동 계산

# 1) 기존 데이터 제거
ws.Range(ws.Cells(3, 1), ws.Cells(358, 15)).ClearContents()      # A~O
ws.Range(ws.Cells(3, 22), ws.Cells(358, 22)).ClearContents()     # V
ws.Range(ws.Cells(3, 7), ws.Cells(358, 7)).Hyperlinks.Delete()

# 2) 3,076행 일괄 입력 (배열 방식 — 셀 단위 대비 수십 배 빠름)
n = len(posts)
data = [[i, '한국동서발전', None, '사이버홍보실>동서뉴스>보도자료', '보도자료',
         p['title'], p['url'], p['date']] for i, p in enumerate(posts, 1)]
ws.Range(ws.Cells(3, 1), ws.Cells(2 + n, 8)).Value = data
ws.Range(ws.Cells(3, 11), ws.Cells(2 + n, 11)).Value = [[p['attach'] or None] for p in posts]
ws.Range(ws.Cells(3, 22), ws.Cells(2 + n, 22)).Value = [['입력'] for _ in posts]

# 3) G열 하이퍼링크 활성화
for i, p in enumerate(posts):
    ws.Hyperlinks.Add(Anchor=ws.Cells(3 + i, 7), Address=p['url'], TextToDisplay=p['url'])

wb.Save()
wb.SaveAs(OUT_XLSX, FileFormat=51)          # xlsx 버전 (51 = xlOpenXMLWorkbook)

선행 작은따옴표 보정

# Excel이 셀 값 선두의 '를 텍스트 지정자로 먹는 문제 → 한 번 더 붙여 원문 보존
targets = [(3 + i, p['title']) for i, p in enumerate(posts) if p['title'].startswith("'")]
for r, title in targets:                    # 20건
    ws.Cells(r, 6).Value = "'" + title

전행 무결성 대조 (openpyxl)

import openpyxl
wb = openpyxl.load_workbook(path)
ws = wb['권리확인']
bad = sum(1 for i, p in enumerate(posts)
          if ws.cell(3 + i, 6).value != p['title'] or ws.cell(3 + i, 7).value != p['url'])
# 보정 후 xlsm/xlsx 모두 bad == 0

표시할 데이터가 없습니다.

Actions

내보내기 Atom PDF