본문 바로가기
카테고리 없음

PDF를 EXCEL로 저장하는 PYTHON 2

by j1suya 2025. 1. 12.

오늘은 이전 코드에 이어서 무엇을 할거냐

 

목표!!

1. 보기 좋은 UI 창 만들기

 

2. UI를 사이트로 생성 OR 배경화면에 바로가기로 아이콘 만들기

 

소소하지만... 쉽지 않을 것 같다..


첫 번째 목표

 

보기 좋은 UI 창 만들기 였는데

방금 목표를 바꿨다

 

나만의 웹 사이트 만들기로 ㅎ...

 

여러가지 사이트를 찾아보니 FLASK를 많이 사용하는 것 같다

 

나도 FLASK를 사용할 거다

 

근데 이게 뭐인지도 모른다....

 

일단 지피티의 도움을 받아 사이트를 만들어본다...


 

HTML이 뭔지부터 공부해보자

 

HTML

: Hypertext Markup Language 로 

웹 페이지의 구조를 정의하는 데 사용되는 마크업 언어

 

HTML에서 파일을 드래그 앤 드랍할 수 있는 영역을 만들어 봅시다.

 

일단

pip install flask pdfplumber openpyxl 

로 라이브러리를 설치해줍시다

 

  • pdfplumber: PDF에서 데이터를 추출하는 라이브러리.
  • openpyxl: 엑셀 파일을 다루는 라이브러리.

 

프로젝트 파일 구조를 아래처럼 만들어줍시다.

 

Project
├── app.py           # Flask 서버 및 PDF -> Excel 변환 처리
├── templates/
│   └── index.html   # HTML 파일
└── static/
    └── css/
        └── style.css  # 스타일 파일 (드래그 앤 드랍 영역에 대한 스타일)

 

  • app.py
from flask import Flask, render_template


app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True, port=5001)

 

  • index.html
from flask import Flask, render_template


app = Flask(__name__)

@app.route('/')
def home():
    return render_template('index.html')

if __name__ == '__main__':
    app.run(debug=True, port=5001)

 

실행하면 아래처럼 사이트가 생겼어요

 

이제 여기에 드래그앤드랍 칸을 만들어볼게요.

 

chat GPT 힘을 빌렸어요

  • app.py
from flask import Flask, render_template, request, jsonify
import os

app = Flask(__name__)
UPLOAD_FOLDER = 'uploads'
app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER

# 업로드 폴더가 없으면 생성
if not os.path.exists(UPLOAD_FOLDER):
    os.makedirs(UPLOAD_FOLDER)

@app.route('/')
def home():
    return render_template('index.html')

@app.route('/upload', methods=['POST'])
def upload_file():
    if 'file' not in request.files:
        return jsonify({"message": "No file part"}), 400
    file = request.files['file']
    if file.filename == '':
        return jsonify({"message": "No selected file"}), 400
    if file:
        filepath = os.path.join(app.config['UPLOAD_FOLDER'], file.filename)
        file.save(filepath)
        return jsonify({"message": "File uploaded successfully", "file_path": filepath}), 200

if __name__ == '__main__':
    app.run(debug=True, port=5001)

 

  • index.html 
  • <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Drag and Drop File Upload</title>
        <link rel="stylesheet" href="/static/styles.css" />
      </head>
      <body>
        <div class="upload-container">
          <h2>Drag and Drop to Upload</h2>
          <div id="drop-zone">
            <p>Drop files here or click to upload</p>
            <input type="file" id="file-input" hidden />
          </div>
          <p id="status-message"></p>
        </div>
        <script>
          const dropZone = document.getElementById("drop-zone");
          const fileInput = document.getElementById("file-input");
          const statusMessage = document.getElementById("status-message");

          // 드래그 오버 이벤트
          dropZone.addEventListener("dragover", (e) => {
            e.preventDefault();
            dropZone.classList.add("dragover");
          });

          // 드래그 아웃 이벤트
          dropZone.addEventListener("dragleave", () => {
            dropZone.classList.remove("dragover");
          });

          // 드롭 이벤트
          dropZone.addEventListener("drop", (e) => {
            e.preventDefault();
            dropZone.classList.remove("dragover");
            const files = e.dataTransfer.files;
            if (files.length > 0) {
              uploadFile(files[0]);
            }
          });

          // 클릭으로 파일 선택
          dropZone.addEventListener("click", () => {
            fileInput.click();
          });

          fileInput.addEventListener("change", () => {
            if (fileInput.files.length > 0) {
              uploadFile(fileInput.files[0]);
            }
          });

          // 파일 업로드
          function uploadFile(file) {
            const formData = new FormData();
            formData.append("file", file);

            fetch("/upload", {
              method: "POST",
              body: formData,
            })
              .then((response) => response.json())
              .then((data) => {
                if (data.message) {
                  statusMessage.textContent = data.message;
                }
              })
              .catch((error) => {
                statusMessage.textContent = "Upload failed!";
                console.error(error);
              });
          }
        </script>
      </body>
    </html>
  • styles.css
    body {
        font-family: Arial, sans-serif;
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
        margin: 0;
        background-color: #f4f4f9;
    }

    .upload-container {
        text-align: center;
        background: #fff;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    }

    #drop-zone {
        margin-top: 20px;
        padding: 40px;
        border: 2px dashed #ccc;
        border-radius: 8px;
        background: #f9f9f9;
        cursor: pointer;
        transition: background-color 0.3s;
    }

    #drop-zone.dragover {
        background-color: #e9e9e9;
    }

    #drop-zone p {
        margin: 0;
        font-size: 16px;
        color: #555;
    }

    #status-message {
        margin-top: 20px;
        color: green;
        font-size: 14px;
    }

코드를 실행하니 아래처럼 사이트 중앙에 드래그앤 드랍 칸이 생겼어요!

중앙을 누르니 파일에도 들어가지네요.


이제 드래그앤 드랍 칸에 PDF를 EXCEL로 변환시켜주는 기능을 넣어볼게요.

**참고!

저는 특정 PDF를 EXCEL로 저장해야하므로 첫 장의 행대로 추출이 아닌

PDF 두번 째 장 표의 첫 행을 기준으로 변환시킬겁니다..

  • app.py
    from flask import Flask, render_template, request, jsonify
    import os
    import pdfplumber
    import pandas as pd

    app = Flask(__name__)

    # 업로드 폴더 지정
    UPLOAD_FOLDER = os.path.join(os.getcwd(), 'uploads')  # 현재 작업 디렉토리에 uploads 폴더 생성
    os.makedirs(UPLOAD_FOLDER, exist_ok=True)  # 업로드 폴더가 없으면 생성

    @app.route('/')
    def home():
        return render_template('index.html')

    @app.route('/upload', methods=['POST'])
    def upload_file():
        if 'file' not in request.files:
            return jsonify({"message": "No file part"}), 400

        file = request.files['file']
        if file.filename == '':
            return jsonify({"message": "No selected file"}), 400

        if file and file.filename.lower().endswith('.pdf'):
            # PDF 파일 저장 (uploads 폴더에 저장)
            pdf_path = os.path.join(UPLOAD_FOLDER, file.filename)
            file.save(pdf_path)

            try:
                # PDF를 엑셀로 변환
                output_path = convert_pdf_to_excel(pdf_path)
                return jsonify({
                    "message": "File converted successfully",
                    "file_path": output_path
                }), 200
            except Exception as e:
                return jsonify({"message": f"Error: {e}"}), 500
        else:
            return jsonify({"message": "Only PDF files are supported"}), 400

    def convert_pdf_to_excel(pdf_path):
        """PDF에서 표를 추출하여 엑셀로 저장"""
        with pdfplumber.open(pdf_path) as pdf:
            all_data = []  # 모든 데이터를 저장할 리스트
            header = []  # 헤더 저장
            header_order = []  # 헤더 순서 저장

            # 두 번째 페이지에서 두 번째 테이블의 첫 번째 행을 헤더로 추출
            for page_number, page in enumerate(pdf.pages, start=1):
                if page_number == 2:  # 두 번째 페이지만 처리
                    tables = page.extract_tables()
                    if len(tables) > 1:  # 두 번째 테이블이 있는 경우
                        table = tables[1]  # 두 번째 테이블 선택
                        header = table[0]  # 첫 번째 행을 헤더로 설정
                        header_order = table[0]  # 두 번째 테이블의 헤더 순서 저장
                        break  # 두 번째 페이지의 두 번째 테이블을 찾았으면 반복문 종료

            # 데이터 수집
            for page_number, page in enumerate(pdf.pages, start=1):
                tables = page.extract_tables()
                for table in tables:
                    data_rows = table[1:]  # 첫 번째 행은 헤더로 간주하고 제외
                    for row in data_rows:
                        adjusted_row = [row[header.index(col)] if col in header else '' for col in header_order]
                        all_data.append(adjusted_row)

            # 데이터프레임 생성 후 엑셀로 저장
            df = pd.DataFrame(all_data, columns=header_order)

            # PDF와 동일한 경로에 엑셀 저장
            excel_path = pdf_path.replace('.pdf', '.xlsx')
            df.to_excel(excel_path, index=False)
            print(f"엑셀 파일이 저장되었습니다: {excel_path}")
            return excel_path

    if __name__ == '__main__':
        app.run(debug=True, port=5001)
  • index.html
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Drag and Drop File Upload</title>
        <link rel="stylesheet" href="/static/styles.css" />
      </head>
      <body>
        <div class="upload-container">
          <h2>Drag and Drop to Upload</h2>
          <div id="drop-zone">
            <p>Drop files here or click to upload</p>
            <input type="file" id="file-input" hidden />
          </div>
          <p id="status-message"></p>
          <!-- 상태 메시지 -->
        </div>

        <script>
          const dropZone = document.getElementById("drop-zone");
          const fileInput = document.getElementById("file-input");
          const statusMessage = document.getElementById("status-message");

          // 드래그 오버 이벤트
          dropZone.addEventListener("dragover", (e) => {
            e.preventDefault();
            dropZone.classList.add("dragover");
          });

          // 드래그 아웃 이벤트
          dropZone.addEventListener("dragleave", () => {
            dropZone.classList.remove("dragover");
          });

          // 드롭 이벤트
          dropZone.addEventListener("drop", (e) => {
            e.preventDefault();
            dropZone.classList.remove("dragover");
            const files = e.dataTransfer.files;
            if (files.length > 0) {
              resetStatusMessage(); // 새 파일을 넣을 때마다 초기화
              uploadFile(files[0]);
            }
          });

          // 클릭으로 파일 선택
          dropZone.addEventListener("click", () => {
            fileInput.click();
          });

          fileInput.addEventListener("change", () => {
            if (fileInput.files.length > 0) {
              resetStatusMessage(); // 새 파일을 넣을 때마다 초기화
              uploadFile(fileInput.files[0]);
            }
          });

          // 상태 메시지 초기화
          function resetStatusMessage() {
            statusMessage.textContent = ""; // 이전 메시지 지우기
          }

          // 파일 업로드
          function uploadFile(file) {
            const formData = new FormData();
            formData.append("file", file);

            // 업로드 시작 시 "실행 중..." 메시지 표시
            statusMessage.textContent = "Running...";

            fetch("/upload", {
              method: "POST",
              body: formData,
            })
              .then((response) => response.json())
              .then((data) => {
                // 업로드 완료 후 메시지 업데이트
                if (data.message) {
                  statusMessage.textContent = data.message;
                }
              })
              .catch((error) => {
                // 오류 발생 시 메시지 표시
                statusMessage.textContent = "Upload failed!";
                console.error(error);
              });
          }
        </script>
      </body>
    </html>
  • styles.css
    body {
        font-family: Arial, sans-serif;
        display: flex;
        justify-content: center;
        align-items: center;
        height: 100vh;
        margin: 0;
        background-color: #f4f4f9;
    }

    .upload-container {
        text-align: center;
        background: #fff;
        padding: 20px;
        border-radius: 8px;
        box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
    }

    #drop-zone {
        margin-top: 20px;
        padding: 40px;
        border: 2px dashed #ccc;
        border-radius: 8px;
        background: #f9f9f9;
        cursor: pointer;
        transition: background-color 0.3s;
    }

    #drop-zone.dragover {
        background-color: #e9e9e9;
    }

    #drop-zone p {
        margin: 0;
        font-size: 16px;
        color: #555;
    }

    #status-message {
        margin-top: 20px;
        color: green;
        font-size: 14px;
    }

 

<결과>

코드를 실행하니 아래 사진처럼 uploads란 파일이 생성되고, 

pdf가 excel로 변환되어 저장됐어요.

 

다 만들고 보니 이 코드가 없는 사람이 사이트에 들어와서 저장을 할 때

제대로 저장이 될까 였습니다..

 

그래서 새로운 목표

 

파일을 생성하여 엑셀을 저장하는 것이 아닌

사이트에서 직접 저장할 수 있도록 변경하기

 

(사이트 왼쪽 위에 직접 저장할 수 있도록 다운로드 되게...뭔지 알죠..?)

 

다음 글에 이어서 작성해볼게요.