<?xml version="1.0"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="ko">
	<id>https://devcafe.co.kr/w/index.php?action=history&amp;feed=atom&amp;title=%ED%85%8D%EC%8A%A4%ED%8A%B8%ED%8C%8C%EC%9D%BC%EC%9D%84_%EC%9D%BD%EC%96%B4%EC%84%9C_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0_%28%ED%8A%9C%EB%8B%9D%EB%B3%B4%EA%B3%A0%EC%84%9C%EB%A5%BC_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC%29</id>
	<title>텍스트파일을 읽어서 엑셀로 정리하기 (튜닝보고서를 엑셀로 정리) - 편집 역사</title>
	<link rel="self" type="application/atom+xml" href="https://devcafe.co.kr/w/index.php?action=history&amp;feed=atom&amp;title=%ED%85%8D%EC%8A%A4%ED%8A%B8%ED%8C%8C%EC%9D%BC%EC%9D%84_%EC%9D%BD%EC%96%B4%EC%84%9C_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0_%28%ED%8A%9C%EB%8B%9D%EB%B3%B4%EA%B3%A0%EC%84%9C%EB%A5%BC_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC%29"/>
	<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%ED%85%8D%EC%8A%A4%ED%8A%B8%ED%8C%8C%EC%9D%BC%EC%9D%84_%EC%9D%BD%EC%96%B4%EC%84%9C_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0_(%ED%8A%9C%EB%8B%9D%EB%B3%B4%EA%B3%A0%EC%84%9C%EB%A5%BC_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC)&amp;action=history"/>
	<updated>2026-07-02T03:51:56Z</updated>
	<subtitle>이 문서의 편집 역사</subtitle>
	<generator>MediaWiki 1.42.1</generator>
	<entry>
		<id>https://devcafe.co.kr/w/index.php?title=%ED%85%8D%EC%8A%A4%ED%8A%B8%ED%8C%8C%EC%9D%BC%EC%9D%84_%EC%9D%BD%EC%96%B4%EC%84%9C_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0_(%ED%8A%9C%EB%8B%9D%EB%B3%B4%EA%B3%A0%EC%84%9C%EB%A5%BC_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC)&amp;diff=2331&amp;oldid=prev</id>
		<title>Devcafe: 새 문서: == 튜닝 보고서 파일들을 읽어서 엑셀로 정리하는 프로그램 == &lt;source lang=python&gt; import os import re import sys from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment  def extract_report_data(file_path):     &quot;&quot;&quot;튜닝 보고서 파일에서 데이터 추출&quot;&quot;&quot;     try:         # 인코딩 자동 감지         encodings = [&#039;utf-8-sig&#039;, &#039;utf-8&#039;, &#039;cp949&#039;, &#039;euc-kr&#039;]         content = None                  for encoding in encodin...</title>
		<link rel="alternate" type="text/html" href="https://devcafe.co.kr/w/index.php?title=%ED%85%8D%EC%8A%A4%ED%8A%B8%ED%8C%8C%EC%9D%BC%EC%9D%84_%EC%9D%BD%EC%96%B4%EC%84%9C_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC%ED%95%98%EA%B8%B0_(%ED%8A%9C%EB%8B%9D%EB%B3%B4%EA%B3%A0%EC%84%9C%EB%A5%BC_%EC%97%91%EC%85%80%EB%A1%9C_%EC%A0%95%EB%A6%AC)&amp;diff=2331&amp;oldid=prev"/>
		<updated>2025-11-10T08:47:22Z</updated>

		<summary type="html">&lt;p&gt;새 문서: == 튜닝 보고서 파일들을 읽어서 엑셀로 정리하는 프로그램 == &amp;lt;source lang=python&amp;gt; import os import re import sys from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment  def extract_report_data(file_path):     &amp;quot;&amp;quot;&amp;quot;튜닝 보고서 파일에서 데이터 추출&amp;quot;&amp;quot;&amp;quot;     try:         # 인코딩 자동 감지         encodings = [&amp;#039;utf-8-sig&amp;#039;, &amp;#039;utf-8&amp;#039;, &amp;#039;cp949&amp;#039;, &amp;#039;euc-kr&amp;#039;]         content = None                  for encoding in encodin...&lt;/p&gt;
&lt;p&gt;&lt;b&gt;새 문서&lt;/b&gt;&lt;/p&gt;&lt;div&gt;== 튜닝 보고서 파일들을 읽어서 엑셀로 정리하는 프로그램 ==&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
import os&lt;br /&gt;
import re&lt;br /&gt;
import sys&lt;br /&gt;
from openpyxl import Workbook&lt;br /&gt;
from openpyxl.styles import Font, PatternFill, Alignment&lt;br /&gt;
&lt;br /&gt;
def extract_report_data(file_path):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;튜닝 보고서 파일에서 데이터 추출&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    try:&lt;br /&gt;
        # 인코딩 자동 감지&lt;br /&gt;
        encodings = [&amp;#039;utf-8-sig&amp;#039;, &amp;#039;utf-8&amp;#039;, &amp;#039;cp949&amp;#039;, &amp;#039;euc-kr&amp;#039;]&lt;br /&gt;
        content = None&lt;br /&gt;
        &lt;br /&gt;
        for encoding in encodings:&lt;br /&gt;
            try:&lt;br /&gt;
                with open(file_path, &amp;#039;r&amp;#039;, encoding=encoding) as f:&lt;br /&gt;
                    content = f.read()&lt;br /&gt;
                break&lt;br /&gt;
            except UnicodeDecodeError:&lt;br /&gt;
                continue&lt;br /&gt;
        &lt;br /&gt;
        if content is None:&lt;br /&gt;
            print(f&amp;quot;인코딩 오류: {file_path}&amp;quot;)&lt;br /&gt;
            return None&lt;br /&gt;
        &lt;br /&gt;
        data = {&lt;br /&gt;
            &amp;#039;DB&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;식별자&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;업무구분&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;수집유형&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;프로그램명&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;서비스&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;온라인/배치&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;수행빈도&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;Dynamic SQL여부&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;개발담당자&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;담당자연락처&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;Logical Reads(전)&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;Logical Reads(후)&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;Elapsed Time(전)&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;Elapsed Time(후)&amp;#039;: &amp;#039;&amp;#039;,&lt;br /&gt;
            &amp;#039;파일명&amp;#039;: os.path.basename(file_path)&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        # 각 항목 추출 (정규식 사용)&lt;br /&gt;
        patterns = {&lt;br /&gt;
            &amp;#039;DB&amp;#039;: r&amp;#039;1\)\s*DB\s*[:：]\s*(.+)&amp;#039;,&lt;br /&gt;
            &amp;#039;식별자&amp;#039;: r&amp;#039;2\)\s*식별자\s*[:：]\s*(.+)&amp;#039;,&lt;br /&gt;
            &amp;#039;업무구분&amp;#039;: r&amp;#039;\(?\s*3\)?\s*업무\s*구분\s*[:：]\s*(.+)&amp;#039;,&lt;br /&gt;
            &amp;#039;수집유형&amp;#039;: r&amp;#039;4\)\s*수집\s*유형\s*[:：]\s*(.+)&amp;#039;,&lt;br /&gt;
            &amp;#039;프로그램명&amp;#039;: r&amp;#039;5\)\s*프로그램명\s*/\s*화면명\s*[:：]\s*(.+)&amp;#039;,&lt;br /&gt;
            &amp;#039;서비스&amp;#039;: r&amp;#039;6\)\s*서비스\s*[:：]\s*(.+)&amp;#039;,&lt;br /&gt;
            &amp;#039;온라인/배치&amp;#039;: r&amp;#039;7\)\s*온라인\s*/\s*배치\s*[:：]\s*(.+)&amp;#039;,&lt;br /&gt;
            &amp;#039;수행빈도&amp;#039;: r&amp;#039;8\)\s*수행\s*빈도.*[:：]\s*(.+)&amp;#039;,&lt;br /&gt;
            &amp;#039;Dynamic SQL여부&amp;#039;: r&amp;#039;9\)\s*Dynamic\s*S[OQ]L\s*여부.*[:：]\s*(.+)&amp;#039;,&lt;br /&gt;
            &amp;#039;개발담당자&amp;#039;: r&amp;#039;10\)\s*개발\s*담당자\s*[:：]\s*(.+)&amp;#039;,&lt;br /&gt;
            &amp;#039;담당자연락처&amp;#039;: r&amp;#039;11\)\s*담당자\s*연락처\s*[:：]\s*(.+)&amp;#039;,&lt;br /&gt;
        }&lt;br /&gt;
        &lt;br /&gt;
        for key, pattern in patterns.items():&lt;br /&gt;
            match = re.search(pattern, content, re.IGNORECASE | re.MULTILINE)&lt;br /&gt;
            if match:&lt;br /&gt;
                data[key] = match.group(1).strip()&lt;br /&gt;
        &lt;br /&gt;
        # Logical Reads 추출&lt;br /&gt;
        logical_reads = re.search(r&amp;#039;Logical\s*Reads.*[:：]\s*([0-9,]+)\s*/\s*([0-9,]+)&amp;#039;, content, re.IGNORECASE)&lt;br /&gt;
        if logical_reads:&lt;br /&gt;
            data[&amp;#039;Logical Reads(전)&amp;#039;] = logical_reads.group(1).strip().replace(&amp;#039;,&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
            data[&amp;#039;Logical Reads(후)&amp;#039;] = logical_reads.group(2).strip().replace(&amp;#039;,&amp;#039;, &amp;#039;&amp;#039;)&lt;br /&gt;
        &lt;br /&gt;
        # Elapsed Time 추출&lt;br /&gt;
        elapsed_time = re.search(r&amp;#039;Elapsed\s*Time.*[:：]\s*([0-9.]+)\s*/\s*([0-9.]+)&amp;#039;, content, re.IGNORECASE)&lt;br /&gt;
        if elapsed_time:&lt;br /&gt;
            data[&amp;#039;Elapsed Time(전)&amp;#039;] = elapsed_time.group(1).strip()&lt;br /&gt;
            data[&amp;#039;Elapsed Time(후)&amp;#039;] = elapsed_time.group(2).strip()&lt;br /&gt;
        &lt;br /&gt;
        return data&lt;br /&gt;
        &lt;br /&gt;
    except Exception as e:&lt;br /&gt;
        print(f&amp;quot;파일 처리 오류 [{file_path}]: {str(e)}&amp;quot;)&lt;br /&gt;
        return None&lt;br /&gt;
&lt;br /&gt;
def create_excel_report(folder_path, output_file=&amp;#039;튜닝보고서_정리.xlsx&amp;#039;):&lt;br /&gt;
    &amp;quot;&amp;quot;&amp;quot;폴더의 모든 튜닝 보고서를 읽어 엑셀로 정리&amp;quot;&amp;quot;&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    # 워크북 생성&lt;br /&gt;
    wb = Workbook()&lt;br /&gt;
    ws = wb.active&lt;br /&gt;
    ws.title = &amp;quot;튜닝보고서&amp;quot;&lt;br /&gt;
    &lt;br /&gt;
    # 헤더 작성&lt;br /&gt;
    headers = [&amp;#039;No&amp;#039;, &amp;#039;DB&amp;#039;, &amp;#039;식별자&amp;#039;, &amp;#039;업무구분&amp;#039;, &amp;#039;수집유형&amp;#039;, &amp;#039;프로그램명&amp;#039;, &amp;#039;서비스&amp;#039;, &lt;br /&gt;
               &amp;#039;온라인/배치&amp;#039;, &amp;#039;수행빈도&amp;#039;, &amp;#039;Dynamic SQL여부&amp;#039;, &amp;#039;개발담당자&amp;#039;, &amp;#039;담당자연락처&amp;#039;,&lt;br /&gt;
               &amp;#039;Logical Reads(전)&amp;#039;, &amp;#039;Logical Reads(후)&amp;#039;, &amp;#039;개선율(%)&amp;#039;, &lt;br /&gt;
               &amp;#039;Elapsed Time(전)&amp;#039;, &amp;#039;Elapsed Time(후)&amp;#039;, &amp;#039;개선율(%)&amp;#039;, &amp;#039;파일명&amp;#039;]&lt;br /&gt;
    &lt;br /&gt;
    # 헤더 스타일&lt;br /&gt;
    header_fill = PatternFill(start_color=&amp;quot;366092&amp;quot;, end_color=&amp;quot;366092&amp;quot;, fill_type=&amp;quot;solid&amp;quot;)&lt;br /&gt;
    header_font = Font(bold=True, color=&amp;quot;FFFFFF&amp;quot;, size=11)&lt;br /&gt;
    &lt;br /&gt;
    for col_idx, header in enumerate(headers, 1):&lt;br /&gt;
        cell = ws.cell(row=1, column=col_idx, value=header)&lt;br /&gt;
        cell.fill = header_fill&lt;br /&gt;
        cell.font = header_font&lt;br /&gt;
        cell.alignment = Alignment(horizontal=&amp;#039;center&amp;#039;, vertical=&amp;#039;center&amp;#039;)&lt;br /&gt;
    &lt;br /&gt;
    # 파일 읽기&lt;br /&gt;
    files = [f for f in os.listdir(folder_path) if f.endswith((&amp;#039;.txt&amp;#039;, &amp;#039;.doc&amp;#039;, &amp;#039;.docx&amp;#039;, &amp;#039;.hwp&amp;#039;))]&lt;br /&gt;
    &lt;br /&gt;
    if not files:&lt;br /&gt;
        print(f&amp;quot;폴더에 파일이 없습니다: {folder_path}&amp;quot;)&lt;br /&gt;
        return&lt;br /&gt;
    &lt;br /&gt;
    print(f&amp;quot;총 {len(files)}개 파일 처리 중...&amp;quot;)&lt;br /&gt;
    &lt;br /&gt;
    row_idx = 2&lt;br /&gt;
    success_count = 0&lt;br /&gt;
    &lt;br /&gt;
    for idx, filename in enumerate(files, 1):&lt;br /&gt;
        file_path = os.path.join(folder_path, filename)&lt;br /&gt;
        print(f&amp;quot;[{idx}/{len(files)}] 처리 중: {filename}&amp;quot;)&lt;br /&gt;
        &lt;br /&gt;
        data = extract_report_data(file_path)&lt;br /&gt;
        &lt;br /&gt;
        if data:&lt;br /&gt;
            # 개선율 계산&lt;br /&gt;
            logical_improve = &amp;#039;&amp;#039;&lt;br /&gt;
            elapsed_improve = &amp;#039;&amp;#039;&lt;br /&gt;
            &lt;br /&gt;
            try:&lt;br /&gt;
                if data[&amp;#039;Logical Reads(전)&amp;#039;] and data[&amp;#039;Logical Reads(후)&amp;#039;]:&lt;br /&gt;
                    before = float(data[&amp;#039;Logical Reads(전)&amp;#039;])&lt;br /&gt;
                    after = float(data[&amp;#039;Logical Reads(후)&amp;#039;])&lt;br /&gt;
                    if before &amp;gt; 0:&lt;br /&gt;
                        logical_improve = round((before - after) / before * 100, 2)&lt;br /&gt;
            except:&lt;br /&gt;
                pass&lt;br /&gt;
            &lt;br /&gt;
            try:&lt;br /&gt;
                if data[&amp;#039;Elapsed Time(전)&amp;#039;] and data[&amp;#039;Elapsed Time(후)&amp;#039;]:&lt;br /&gt;
                    before = float(data[&amp;#039;Elapsed Time(전)&amp;#039;])&lt;br /&gt;
                    after = float(data[&amp;#039;Elapsed Time(후)&amp;#039;])&lt;br /&gt;
                    if before &amp;gt; 0:&lt;br /&gt;
                        elapsed_improve = round((before - after) / before * 100, 2)&lt;br /&gt;
            except:&lt;br /&gt;
                pass&lt;br /&gt;
            &lt;br /&gt;
            # 데이터 쓰기&lt;br /&gt;
            ws.cell(row=row_idx, column=1, value=success_count + 1)&lt;br /&gt;
            ws.cell(row=row_idx, column=2, value=data[&amp;#039;DB&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=3, value=data[&amp;#039;식별자&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=4, value=data[&amp;#039;업무구분&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=5, value=data[&amp;#039;수집유형&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=6, value=data[&amp;#039;프로그램명&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=7, value=data[&amp;#039;서비스&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=8, value=data[&amp;#039;온라인/배치&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=9, value=data[&amp;#039;수행빈도&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=10, value=data[&amp;#039;Dynamic SQL여부&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=11, value=data[&amp;#039;개발담당자&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=12, value=data[&amp;#039;담당자연락처&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=13, value=data[&amp;#039;Logical Reads(전)&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=14, value=data[&amp;#039;Logical Reads(후)&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=15, value=logical_improve)&lt;br /&gt;
            ws.cell(row=row_idx, column=16, value=data[&amp;#039;Elapsed Time(전)&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=17, value=data[&amp;#039;Elapsed Time(후)&amp;#039;])&lt;br /&gt;
            ws.cell(row=row_idx, column=18, value=elapsed_improve)&lt;br /&gt;
            ws.cell(row=row_idx, column=19, value=data[&amp;#039;파일명&amp;#039;])&lt;br /&gt;
            &lt;br /&gt;
            row_idx += 1&lt;br /&gt;
            success_count += 1&lt;br /&gt;
    &lt;br /&gt;
    # 컬럼 너비 자동 조정&lt;br /&gt;
    for col in ws.columns:&lt;br /&gt;
        max_length = 0&lt;br /&gt;
        column = col[0].column_letter&lt;br /&gt;
        for cell in col:&lt;br /&gt;
            try:&lt;br /&gt;
                if len(str(cell.value)) &amp;gt; max_length:&lt;br /&gt;
                    max_length = len(str(cell.value))&lt;br /&gt;
            except:&lt;br /&gt;
                pass&lt;br /&gt;
        adjusted_width = min(max_length + 2, 50)&lt;br /&gt;
        ws.column_dimensions[column].width = adjusted_width&lt;br /&gt;
    &lt;br /&gt;
    # 엑셀 저장&lt;br /&gt;
    output_path = os.path.join(folder_path, output_file)&lt;br /&gt;
    wb.save(output_path)&lt;br /&gt;
    &lt;br /&gt;
    print(f&amp;quot;\n완료! {success_count}개 파일 처리 완료&amp;quot;)&lt;br /&gt;
    print(f&amp;quot;저장 위치: {output_path}&amp;quot;)&lt;br /&gt;
&lt;br /&gt;
if __name__ == &amp;#039;__main__&amp;#039;:&lt;br /&gt;
    if len(sys.argv) != 2:&lt;br /&gt;
        print(&amp;quot;사용법: python parse_tuning_reports.py &amp;lt;폴더경로&amp;gt;&amp;quot;)&lt;br /&gt;
        print(&amp;quot;예시: python parse_tuning_reports.py C:\\튜닝보고서&amp;quot;)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
    &lt;br /&gt;
    folder_path = sys.argv[1]&lt;br /&gt;
    &lt;br /&gt;
    if not os.path.exists(folder_path):&lt;br /&gt;
        print(f&amp;quot;폴더를 찾을 수 없습니다: {folder_path}&amp;quot;)&lt;br /&gt;
        sys.exit(1)&lt;br /&gt;
    &lt;br /&gt;
    create_excel_report(folder_path)&lt;br /&gt;
```&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 사용 방법: ===&lt;br /&gt;
&amp;lt;source lang=python&amp;gt;&lt;br /&gt;
python parse_tuning_reports.py C:\튜닝보고서폴더&lt;br /&gt;
&amp;lt;/source&amp;gt;&lt;br /&gt;
&lt;br /&gt;
&lt;br /&gt;
=== 주요 기능: ===&lt;br /&gt;
1. ✅ 폴더 내 모든 튜닝 보고서 파일 자동 처리&lt;br /&gt;
2. ✅ UTF-8, CP949(ANSI) 자동 인식&lt;br /&gt;
3. ✅ 11개 항목 자동 추출&lt;br /&gt;
4. ✅ 성능 개선율 자동 계산&lt;br /&gt;
5. ✅ 엑셀 파일로 자동 정리 (헤더 스타일 포함)&lt;br /&gt;
&lt;br /&gt;
혹시 파일 형식이 조금 다르면 샘플 파일을 보여주시면 패턴을 수정해드릴게요!&lt;/div&gt;</summary>
		<author><name>Devcafe</name></author>
	</entry>
</feed>