본문 바로가기
공부/졸업과제

PyQt로 HTML 테이블 파싱을 해보자

by 맑은청이 2022. 2. 16.
728x90
반응형

오늘 할 일 

- 폴더 내에 있는 파일을 임의로 열고 HTML 내에 테이블을 PyQt5로 출력 

- 테이블의 셀 중 하나를 선택하여 답으로 지정

- PyQt 내에 선택한 셀이 답이 되는 질문을 작성하는 박스 생성

- 확인 버튼을 누르면 지정된 엑셀로 내용 전송 

 

진행순서 

1. PyQt5로 창 만들기 

2. 폴더 내 임의의 파일 열기 

3. html 내 테이블 파싱

4. UI 내에 테이블 띄우기 

5. 테이블의 내용을 답으로 선택

6. 작성한 내용 엑셀로 보내기 

7. 코드 리팩토링 및 주석 작성


1. PyQt5로 창 만들기 

제작 해야 하는 거

- 랜덤 버튼, 질문 창 확인 버튼

import sys
from PyQt5.QtWidgets import *

class MyApp(QWidget):

    def __init__(self):
        super().__init__()
        self.initUI()

    def initUI(self):
        self.setWindowTitle('DataSet Tool')
        self.center()
        self.setDisplay()
        self.show()

    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())

    def setDisplay(self):
        grid = QGridLayout()
        self.setLayout(grid)

        grid.addWidget(QLabel('Question:'), 1, 0)

        #위젯 만들기
        randomBtn = QPushButton('Show Table',self)
        quesConent = QLineEdit()
        confirmBtn = QPushButton('Confirm',self)

        #위젯 크기 설정
        randomBtn.setFixedSize(100, 30)
        confirmBtn.setFixedSize(100,30)
        quesConent.setFixedSize(500,25)


        #그리드에 위젯 추가
        grid.addWidget(randomBtn, 0, 0)
        grid.addWidget(confirmBtn, 0, 1)
        grid.addWidget(quesConent, 1, 1)



if __name__ == '__main__':
   app = QApplication(sys.argv)
   ex = MyApp()
   sys.exit(app.exec_())

 

2. 폴더 내 임의의 파일 열기 

버튼에 시그널 함수 연결 

버튼을 클릭할 때 함수가 동작하도록 한다. 

randomBtn.clicked.connect(self.randomBtnFunc)
confirmBtn.clicked.connect(self.confirmBtnFunc)

def randomBtnFunc(self):
    print("random!!!")

def confirmBtnFunc(self):
    print("confirm!!!")

잘 되는 군

일단 해당 폴더의 파일 개수를 구해서 범위로 잡고 범위 내 임의의 수를 random을 통해 구했다. 

파일 경로 내용을 read() 를 통해 다 읽어온다.

   def randomBtnFunc(self):
        filePath = 'C:/Users/ch_pa/PycharmProjects/HtmlTableParsingTool/html/'
        fileList = os.listdir(filePath) #파일 경로
        num = random.randrange(0, len(fileList) - 1)
        filePath = filePath + fileList[num]
        f = open(filePath,'r', encoding='UTF8')
        htmlDocs = f.read()
        print(htmlDocs)

 

3. html 내 테이블 파싱 & 4. UI 내에 테이블 띄우기 

 

어떻게 html 내 table을 창에 출력할 수 있을까 고민하고 html 자체 위젯을 pyqt 창 내에 출력할 수 있는 방식을 알아봤으나 찾지 못했다. (QtWebEngineWidgets 같은 경우에는 인터넷 페이지를 들고 오는 거라 html 파일을 들고 오는 내 경우엔 맞지 않았다.)

그래서 html 코드를 뽑아오고 table 코드를 html_parser로 뽑아와서 for문을 사용해 tableWidget안에 내용을 만들어준 후 grid에 추가했다. 

나중에 셀을 선택할 때도 도움이 될거라 여겨서 tableWidget을 선택했다. 

고생했던 건 layout에 테이블을 추가시킬 때 slot에 인자를 넘기는 법을 몰랐다는 것이다. 

grid가 넘어가지 않으니 함수가 실행되도 테이블을 추가시킬 창을 발견하지 못해서 프로그램이 종료되었다.

grid를 넘겨주는 방법은 참고 블로그를 확인하면 된다.

randomBtn.clicked.connect(lambda:self.randomBtnFunc(grid))

#slot에 인자 넘기기
randomBtn.clicked.connect(lambda:self.randomBtnFunc(grid))

#randomBtn이 눌리면 작동하는 함수
def randomBtnFunc(self,grid):
    #파일 읽기
    filePath = 'C:/Users/ch_pa/PycharmProjects/HtmlTableParsingTool/html/'
    fileList = os.listdir(filePath) #파일 경로
    num = random.randrange(0, len(fileList) - 1)
    filePath = filePath + fileList[num]
    print(filePath)
    f = open(filePath,'r', encoding='UTF8')
    htmlDocs = f.read()
    #print(htmlDocs)

    #테이블 코드 추출
    soup = BeautifulSoup(htmlDocs,'html.parser')
    temp = soup.find_all('table')
    print(len(temp))
    tableNum = random.randrange(0,len(temp)-1)

    #테이블 리스트로 출력
    p = parser.make2d(temp[tableNum])
    print(p)

    colLen = len(p[0])
    rowLen = len(p)

    #tableWidget 생성
    self.tableWidget = QTableWidget()
    self.tableWidget.setRowCount(rowLen)
    self.tableWidget.setColumnCount(colLen)

    #tableWidget 안에 테이블 내용 삽입 후 위젯 추가
    for i in range(rowLen):
        for j in range(colLen):
            self.tableWidget.setItem(i, j, QTableWidgetItem(p[i][j]))
    self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch)

    grid.addWidget(self.tableWidget,2,1)
    self.resize(1000, 1000)

5. 테이블의 내용을 답으로 선택

 

 

    def setCellValue(self,row,column):
        item = self.tableWidget.item(row, column)
        value = item.text()
        label_string = 'Row: ' + str(row + 1) + ', Column: ' + str(column + 1) + ', Value: ' + str(value)
        self.label.setText(label_string)

 

6. 작성 내용 엑셀로 보내기 

   def confirmBtnFunc(self):
        text = self.quesContent.text()
        reply = QMessageBox.question(self, 'Confirm', '작성한 내용은 다음과 같습니다.\n질문 : ' + self.quesContent.text() + '\n답 : ' + self.answer.text() ,QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        print(reply)
        if reply == QMessageBox.Yes:
            wb = openpyxl.load_workbook('test.xlsx')
            ws = wb.active
            print([self.fileName.text(),self.quesContent.text(),self.answer.text()])
            ws.append([self.fileName.text(),self.quesContent.text(),self.answer.text()])
            wb.save('test.xlsx')
            self.quesContent.clear()
        else:
            print(text)
    #

 

 

7. 코드 리팩토링 및 주석 작성

함수를 분리하고 주석을 작성했다.

import os
import random
import sys

import openpyxl

from bs4 import BeautifulSoup
from html_table_parser import parser_functions as parser
from PyQt5.QtWidgets import *


class MyApp(QWidget):
    def __init__(self):
        super().__init__()
        self.initUI()

    #UI초기화 함수
    def initUI(self):
        self.setDisplay() #창 설정
        self.clickedConnFunc() #시그널 함수 위젯과 연결
        self.center() #창을 가운데에서 생성
        self.show() #창 실행

    #창을 설정하는 함수
    def setDisplay(self):
        self.setWindowTitle('DataSet Tool') #창의 제목
        self.grid = QGridLayout() #그리드 레이아웃
        self.setLayout(self.grid)
        self.makeWidget() #위젯 생성
        self.setWidgetSize() #위젯 크기 설정
        self.addWidgetInGrid() #위젯을 그리드에 추가

    # 위젯을 생성하는 함수
    def makeWidget(self):
        self.randomBtn = QPushButton('Show Table', self) #임의의 파일에서 임의의 테이블을 추출하는 버튼
        self.confirmBtn = QPushButton('Confirm', self) #질문을 생성한 후 엑셀에 추가하는 버튼
        self.quesContent = QLineEdit() #질문이 들어가는 라인 에디타
        self.tableWidget = QTableWidget() #테이블을 표시할 위젯
        self.label = QLabel('')
        self.answer = QLabel('')
        self.fileName = QLabel('')

    # 위젯 크기 설정하는 함수
    def setWidgetSize(self):
        self.randomBtn.setFixedSize(100, 30)
        self.confirmBtn.setFixedSize(100, 30)
        self.quesContent.setFixedSize(800, 25)

    # 그리드에 위젯 추가하는 함수
    def addWidgetInGrid(self):
        self.grid.addWidget(QLabel('Question:'), 1, 0)
        self.grid.addWidget(self.randomBtn, 0, 0)
        self.grid.addWidget(self.confirmBtn, 0, 1)
        self.grid.addWidget(self.quesContent, 1, 1)
        self.grid.addWidget(self.label, 3, 1)

    #시그널 함수를 연결하는 함수
    def clickedConnFunc(self):
        self.randomBtn.clicked.connect(self.randomBtnFunc)
        self.confirmBtn.clicked.connect(self.confirmBtnFunc)
        self.tableWidget.cellClicked.connect(self.setCellValue)


    #randomBtn이 눌리면 작동하는 함수
    def randomBtnFunc(self):
        self.readFile() #임의의 파일을 읽어오는 함수
        self.getTableOrder() #임의의 테이블 코드를 추출하는 함수
        self.setTableInWindow() #테이블을 창에 표시하는 함수
        self.resize(1000, 1000)

    # 임의의 파일을 읽어오는 함수
    def readFile(self):
        # 데이터가 있는 폴더 설정
        #------------------------------------------------------------------------------------
        filePath = 'C:/Users/ch_pa/PycharmProjects/HtmlTableParsingTool/html/'
        #------------------------------------------------------------------------------------
        fileList = os.listdir(filePath)  # 파일 경로
        num = random.randrange(0, len(fileList) - 1) #폴더 내 임의의 파일 선택
        self.fileName.setText(fileList[num])
        filePath = filePath + fileList[num]
        f = open(filePath, 'r', encoding='UTF8')
        self.htmlDocs = f.read()

    # 임의의 테이블 코드를 추출하는 함수
    def getTableOrder(self):
        #html parser을 활용하여 파일 내에 테이블 코드를 추출함
        soup = BeautifulSoup(self.htmlDocs, 'html.parser')
        temp = soup.find_all('table')

        self.tableNum = random.randrange(0, len(temp) - 1) #테이블의 수를 활용하여 임의의 테이블을 선택
        self.table = parser.make2d(temp[self.tableNum]) #테이블 위젯에서 테이블 값을 한 줄 씩 저장
        self.colLen = len(self.table[0]) #테이블의 열 갯수 추출
        self.rowLen = len(self.table) #테이블의 행 갯수 추출

        #테이블 위젯에서 열과 행 설정
        self.tableWidget.setRowCount(self.rowLen)
        self.tableWidget.setColumnCount(self.colLen)

    # 테이블을 창에 표시하는 함수
    def setTableInWindow(self):
        #테이블의 열과 행의 갯수에 따라 테이블 위젯의 내용 입력
        for i in range(self.rowLen):
            for j in range(self.colLen):
                self.tableWidget.setItem(i, j, QTableWidgetItem(self.table[i][j]))

        self.tableWidget.horizontalHeader().setSectionResizeMode(QHeaderView.Stretch) #테이블의 열 너비에 맞춤
        self.grid.addWidget(self.tableWidget, 2, 1)

    # 테이블에서 선택한 셀의 값을 answer 안에 설정하고 표시
    def setCellValue(self,row,column):
        item = self.tableWidget.item(row, column)
        value = item.text()
        label_string = 'Row: ' + str(row + 1) + ', Column: ' + str(column + 1) + ', Value: ' + str(value)
        self.answer.setText(str(value))
        self.label.setText(label_string)

    # 질문 내용과 답을 확인하고 엑셀로 입력하는 함수
    def confirmBtnFunc(self):
        text = self.quesContent.text() #질문 내용 추출
        #메세지활용을 통해 질문과 답 확인 표시 후 응답 변수 값 설정
        reply = QMessageBox.question(self, 'Confirm', '작성한 내용은 다음과 같습니다.\n질문 : ' + self.quesContent.text() + '\n답 : ' + self.answer.text() ,QMessageBox.Yes | QMessageBox.No, QMessageBox.No)

        #응답에 따른 조건문 실행
        if reply == QMessageBox.Yes:
            wb = openpyxl.load_workbook('test.xlsx') #test.xlsx이라는 엑셀 파일을 로드
            ws = wb.active
            ws.append([self.fileName.text(),self.quesContent.text(),self.answer.text()]) #[파일 명, 질문 내용, 답] 행추가
            wb.save('test.xlsx') #엑셀 파일 저장
            self.quesContent.clear() #질문 내용 초기화
        else:
            print(text)

    #팡이 생성될 때 가운데로 오게 하는 함수
    def center(self):
        qr = self.frameGeometry()
        cp = QDesktopWidget().availableGeometry().center()
        qr.moveCenter(cp)
        self.move(qr.topLeft())


if __name__ == '__main__':
   app = QApplication(sys.argv)
   ex = MyApp()
   sys.exit(app.exec_())

 


프로그램 실행 화면 

1. 실행 창 
2. Show Table 버튼 클릭 시 임의의 테이블 출력
3. Confirm 버튼 클릭 시 확인 창 출력 
4. 엑셀에 파일 명, 질문, 답이 삽입된 것을 확인 가능


다음에 추가할 내용 

- 확장자 html 만 데이터로 사용 -> 현재 pdf, hwp 확장자면 

- 데이터 경로 설정 창 

- 테이블 표시 시 원본 html 화면 볼 수 있게 버튼 만들기 

- 연구실에 추가적으로 어떤 내용 삽입해야 하는지 물어보기


참고 블로그

https://blog.naver.com/PostView.nhn?blogId=21ahn&logNo=221388594129 

 

[python] signal의 connect시 lambda 사용, callback함수,

http://guslabview.tistory.com/214 Callback(콜백 함수)에 대Ȣ...

blog.naver.com

https://codetorial.net/articles/qtablewidget_pyqt5_2.html

 

[PyQt5] QTableWidget 위젯으로 테이블 사용하기 2 - Codetorial

2) 스크롤 버튼 만들기 import sys import numpy as np from PyQt5.QtWidgets import * class MyApp(QWidget): def __init__(self): super().__init__() self.initUI() def initUI(self): self.tableWidget = QTableWidget() self.tableWidget.setRowCount(20) self.t

codetorial.net

https://needjarvis.tistory.com/640

 

[Python] 엑셀 데이터 읽기/쓰기 (OpenPyXL)

엑셀을 쉽게 읽는 방법으로 판다스(Pandas)를 활용하는 방법도 있지만, OpenPyXL을 사용하여 엑셀을 디테일하게 컨트롤 할 수 있다. 테스트에 사용한 엑셀 키움증권과 카카오에 있는 FAQ 내용을 몇개

needjarvis.tistory.com

 

728x90
반응형