오늘은 이전 코드에 이어서 무엇을 할거냐
목표!!
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
- index.html
실행하면 아래처럼 사이트가 생겼어요
이제 여기에 드래그앤드랍 칸을 만들어볼게요.
chat GPT 힘을 빌렸어요
- app.py
- 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, jsonifyimport osimport pdfplumberimport 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}), 200except Exception as e:return jsonify({"message": f"Error: {e}"}), 500else: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로 변환되어 저장됐어요.
다 만들고 보니 이 코드가 없는 사람이 사이트에 들어와서 저장을 할 때
제대로 저장이 될까 였습니다..
그래서 새로운 목표
파일을 생성하여 엑셀을 저장하는 것이 아닌
사이트에서 직접 저장할 수 있도록 변경하기
(사이트 왼쪽 위에 직접 저장할 수 있도록 다운로드 되게...뭔지 알죠..?)
다음 글에 이어서 작성해볼게요.