• 목록
  • 아래로
  • 위로

안녕하세요?


개인적인 목적으로 사용하려고 파이썬과 PyQt를 이용하여 디시인사이드의 이미지를 다운받는 스크립트를 작성했는데요.


80% 정도 완성한 단계인데요 PyQt의 GUI가 반응이 없는 현상이 계속되어서 질문을 드립니다 ㅠㅠ



우선 .py 파일입니다.


import requests, time, re, sys, os, dc_get_ui
from bs4 import BeautifulSoup
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5 import uic
 

def parse_page(gall_name, page): # 특정 페이지의 글 번호를 파싱합니다
    headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36'}
    page_url = 'https://gall.dcinside.com/mgallery/board/lists?id=' + gall_name + '&page=' + str(page)
    req = requests.get(page_url, headers=headers)
    html = req.text
    soup = BeautifulSoup(html, 'html.parser')
    list_nums = soup.find_all('td',{'class':'gall_num'})
    article_no = []
    for list_num in list_nums:
        if list_num.text.isdigit() == True:
            article_no.append(list_num.text)
    return article_no


def download(gall_name, article_no): # 특정 글의 이미지 파일을 다운로드합니다
    article_url = 'https://gall.dcinside.com/mgallery/board/view/?id=' + gall_name + '&no=' + article_no
    headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36'}
    req = requests.get(article_url, headers=headers)
    html = req.text
    soup = BeautifulSoup(html, 'html.parser')
    title = soup.select_one('h3 span.title_subject').text
    title = re.sub('[^가-힣a-zA-Z0-9\s]', '', title)
    write_time = soup.select_one('span.gall_date')['title'].split(' ')[0]
    ul_li_as = soup.select('ul.appending_file li a')
    count = 0
    if len(ul_li_as) != 0:
        for ul_li_a in ul_li_as:
            base_url = ul_li_a['href'].replace('download', 'viewimage')
            file_name = ul_li_a.text
            image_req = requests.get(base_url, headers=headers)
            if image_req.status_code == 200:
                with open('./bona/' + write_time + ' ' + title + ' - ' + file_name, 'wb') as f:
                    f.write(image_req.content)
            del image_req
            count += 1
    message = title + '(' + str(count) + ')'
    print(message)
    return message


class MainDialog(QDialog, dc_get_ui.Ui_Dialog): # GUI 관련 부분입니다
    def __init__(self):
        QDialog.__init__(self, None)
        self.setupUi(self)
        self.init_UI()
   
    def init_UI(self):
        self.pushButton_1.clicked.connect(self.start)
        self.pushButton_2.clicked.connect(self.select_dir)

    def select_dir(self):
        directory = str(QFileDialog.getExistingDirectory(self, "Select Directory"))
        #os.startfile(directory)

    def start(self):
        gallery = self.lineEdit_1.text()
        if self.lineEdit_2.text() == '0':
            start = 1
        else:
            start = int(self.lineEdit_2.text())
        if self.lineEdit_3.text() < self.lineEdit_2.text():
            end = int(self.lineEdit_2.text()) + 1
        else:
            end = int(self.lineEdit_3.text()) + 1
        all_article_no = []
        for i in range(start, end):
            all_article_no = all_article_no + parse_page(gallery, i)
        for temp in all_article_no:
            result = download(gallery, temp)
            time.sleep(1)
            self.textBrowser.append(result) # 이 부분이 문제가 되고 있습니다
       

if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainDialog()
    window.show()
    app.exec_()
   



PyQt5로 만들고 .py 파일로 변환한 파일입니다.


from PyQt5 import QtCore, QtGui, QtWidgets

class Ui_Dialog(object):
    def setupUi(self, Dialog):
        Dialog.setObjectName("Dialog")
        Dialog.resize(800, 579)
        self.textBrowser = QtWidgets.QTextBrowser(Dialog)
        self.textBrowser.setGeometry(QtCore.QRect(20, 120, 761, 421))
        self.textBrowser.setObjectName("textBrowser")
        self.pushButton_1 = QtWidgets.QPushButton(Dialog)
        self.pushButton_1.setGeometry(QtCore.QRect(20, 70, 111, 31))
        self.pushButton_1.setObjectName("pushButton_1")
        self.comboBox = QtWidgets.QComboBox(Dialog)
        self.comboBox.setGeometry(QtCore.QRect(20, 20, 91, 31))
        self.comboBox.setObjectName("comboBox")
        self.comboBox.addItem("")
        self.comboBox.addItem("")
        self.lineEdit_1 = QtWidgets.QLineEdit(Dialog)
        self.lineEdit_1.setGeometry(QtCore.QRect(130, 20, 651, 31))
        self.lineEdit_1.setObjectName("lineEdit_1")
        self.pushButton_2 = QtWidgets.QPushButton(Dialog)
        self.pushButton_2.setGeometry(QtCore.QRect(150, 70, 111, 31))
        self.pushButton_2.setCursor(QtGui.QCursor(QtCore.Qt.ArrowCursor))
        self.pushButton_2.setAutoDefault(False)
        self.pushButton_2.setObjectName("pushButton_2")
        self.label = QtWidgets.QLabel(Dialog)
        self.label.setGeometry(QtCore.QRect(20, 551, 761, 21))
        self.label.setObjectName("label")
        self.checkBox = QtWidgets.QCheckBox(Dialog)
        self.checkBox.setGeometry(QtCore.QRect(270, 80, 131, 16))
        self.checkBox.setAutoFillBackground(False)
        self.checkBox.setChecked(True)
        self.checkBox.setObjectName("checkBox")
        self.label_2 = QtWidgets.QLabel(Dialog)
        self.label_2.setGeometry(QtCore.QRect(380, 80, 81, 16))
        self.label_2.setObjectName("label_2")
        self.label_3 = QtWidgets.QLabel(Dialog)
        self.label_3.setGeometry(QtCore.QRect(580, 80, 81, 16))
        self.label_3.setObjectName("label_3")
        self.lineEdit_2 = QtWidgets.QLineEdit(Dialog)
        self.lineEdit_2.setGeometry(QtCore.QRect(460, 70, 71, 31))
        self.lineEdit_2.setMaxLength(2)
        self.lineEdit_2.setObjectName("lineEdit_2")
        self.lineEdit_3 = QtWidgets.QLineEdit(Dialog)
        self.lineEdit_3.setGeometry(QtCore.QRect(640, 70, 71, 31))
        self.lineEdit_3.setObjectName("lineEdit_3")

        self.retranslateUi(Dialog)
        QtCore.QMetaObject.connectSlotsByName(Dialog)

    def retranslateUi(self, Dialog):
        _translate = QtCore.QCoreApplication.translate
        Dialog.setWindowTitle(_translate("Dialog", "Dialog"))
        self.pushButton_1.setToolTip(_translate("Dialog", "Start download."))
        self.pushButton_1.setText(_translate("Dialog", "Download"))
        self.comboBox.setToolTip(_translate("Dialog", "Choose between major & minor gallery."))
        self.comboBox.setItemText(0, _translate("Dialog", "Gallery"))
        self.comboBox.setItemText(1, _translate("Dialog", "Minor gallery"))
        self.lineEdit_1.setToolTip(_translate("Dialog", "Input gallery name."))
        self.pushButton_2.setToolTip(_translate("Dialog", "Select folder to download files."))
        self.pushButton_2.setText(_translate("Dialog", "Select folder"))
        self.label.setText(_translate("Dialog", "Total files downloaded : "))
        self.checkBox.setToolTip(_translate("Dialog", "Open folder after finishing downloading."))
        self.checkBox.setText(_translate("Dialog", "Open folder"))
        self.label_2.setText(_translate("Dialog", "From page :"))
        self.label_3.setText(_translate("Dialog", "To page:"))
        self.lineEdit_2.setToolTip(_translate("Dialog", "Input the starting page number which you want to download."))
        self.lineEdit_2.setInputMask(_translate("Dialog", "99"))
        self.lineEdit_2.setText(_translate("Dialog", "1"))
        self.lineEdit_3.setToolTip(_translate("Dialog", "Input the final page number which you want to download."))
        self.lineEdit_3.setInputMask(_translate("Dialog", "99"))
        self.lineEdit_3.setText(_translate("Dialog", "1"))


if __name__ == "__main__":
    import sys
    app = QtWidgets.QApplication(sys.argv)
    Dialog = QtWidgets.QDialog()
    ui = Ui_Dialog()
    ui.setupUi(Dialog)
    Dialog.show()
    sys.exit(app.exec_())




스크립트가 장황한데 문제가 되는 부분을 간단히 말씀을 드리면요.


일단 게시판 파싱과 이미지 다운로드 부분은 잘 작동을 하더군요.


그런데 GUI 관련하여 문제가 있는데 도저히 해결이 안 되네요 ㅠㅠ


파일을 다운로드 받은 후에 그 제목을 GUI에 표시하려고 self.textBrowser.append(result)을 삽입했는데요.

(실행파일 아래에서부터 8번째 줄에 있습니다)


GUI가 프리징 되면서 전혀 작동하지 않더군요.


멀티프로세싱으로 해결해보려고 해도 제 실력으로는 잘 안 되구요 ㅜㅜ


제가 구글링을 해본바로는 stackoverflow의 다음글이 관련된 내용인 것 같은데 도저히 이해가 안 되네요 ㅠㅠ


https://stackoverflow.com/questions/41526832/pyqt5-qthread-signal-not-working-gui-freeze


아마도 유사한 문제가 PyQt5에서 종종 발생하는 것 같아요.



결론적으로 PyQt5 자체의 문제인지, 프리징을 제가 어떻게 해결할 수 있는지 여쭤봅니다.


그럼 즐거운 주말 되세요!


스포어 회원님들께 항상 감사드립니다 ^-^



+)

버튼을 클릭해도 실행이 안 된다고 생각하실 수 있는데요 ㅠㅠ

현재까지는 마이너 갤러리에만 작동하도록 되어있어요.

(갤러리 / 마이너 갤러리 선택 드롭다운 메뉴는 아직 작동을 하지 않아요)

'war'이나 'bona'를 입력하시면 파싱은 잘 되더군요.

작성자
이니스프리 119 Lv. (0%) 1876250/115200000EXP

당분간 일신상의 사정으로 쪽지나 댓글로 답변을 드리기 어렵습니다. 죄송합니다.

 

CSVpuymXAAAVVpd.jpg

댓글 3

title: 황금 서버 (30일)humit
profile image
+1

UI를 갱신하는 코드가 반복문 안에 있어서 발생하는 문제입니다.


저렇게 웹 요청을 보내는 네트워크 작업이나 파일을 쓰는 IO 작업의 경우에는 시간이 많이 소요되기 때문에, 멀티 프로세싱으로 하는게 좋긴 하지만 프로토 타입이라면 굳이 할 필요는 없겠네요.


해결법은 간단한데 해당 반복문 안에 QApplication.processEvents() 를 넣어줘서 pending 상태인 이벤트들을 처리해주면 됩니다. 즉 아래와 같이 한 줄만 추가해주시면 됩니다.


import requests, time, re, sys, os, dc_get_ui
from bs4 import BeautifulSoup
from PyQt5.QtCore import *
from PyQt5.QtWidgets import *
from PyQt5.QtGui import *
from PyQt5 import uic


def parse_page(gall_name, page): # 특정 페이지의 글 번호를 파싱합니다
    headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36'}
    page_url = 'https://gall.dcinside.com/mgallery/board/lists?id=' + gall_name + '&page=' + str(page)
    req = requests.get(page_url, headers=headers)
    html = req.text
    soup = BeautifulSoup(html, 'html.parser')
    list_nums = soup.find_all('td',{'class':'gall_num'})
    article_no = []
    for list_num in list_nums:
        if list_num.text.isdigit() == True:
            article_no.append(list_num.text)
    return article_no


def download(gall_name, article_no): # 특정 글의 이미지 파일을 다운로드합니다
    print(gall_name, article_no)
    article_url = 'https://gall.dcinside.com/mgallery/board/view/?id=' + gall_name + '&no=' + article_no
    headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_9_3) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/35.0.1916.47 Safari/537.36'}
    req = requests.get(article_url, headers=headers)
    html = req.text
    soup = BeautifulSoup(html, 'html.parser')
    title = soup.select_one('h3 span.title_subject').text
    title = re.sub('[^가-힣a-zA-Z0-9\s]', '', title)
    write_time = soup.select_one('span.gall_date')['title'].split(' ')[0]
    ul_li_as = soup.select('ul.appending_file li a')
    count = 0
    if len(ul_li_as) != 0:
        for ul_li_a in ul_li_as:
            base_url = ul_li_a['href'].replace('download', 'viewimage')
            file_name = ul_li_a.text
            image_req = requests.get(base_url, headers=headers)
            if image_req.status_code == 200:
                with open('./bona/' + write_time + ' ' + title + ' - ' + file_name, 'wb') as f:
                    f.write(image_req.content)
            del image_req
            count += 1
    message = title + '(' + str(count) + ')'
    print(message)
    return message
 
 
class MainDialog(QDialog, dc_get_ui.Ui_Dialog): # GUI 관련 부분입니다
    def __init__(self):
        QDialog.__init__(self, None)
        self.setupUi(self)
        self.init_UI()
   
    def init_UI(self):
        self.pushButton_1.clicked.connect(self.start)
        self.pushButton_2.clicked.connect(self.select_dir)
 
    def select_dir(self):
        directory = str(QFileDialog.getExistingDirectory(self, "Select Directory"))
        #os.startfile(directory)
 
    def start(self):
        gallery = self.lineEdit_1.text()
        print(gallery)
        if self.lineEdit_2.text() == '0':
            start = 1
        else:
            start = int(self.lineEdit_2.text())
        if self.lineEdit_3.text() < self.lineEdit_2.text():
            end = int(self.lineEdit_2.text()) + 1
        else:
            end = int(self.lineEdit_3.text()) + 1
        all_article_no = []
        for i in range(start, end):
            all_article_no = all_article_no + parse_page(gallery, i)
        for temp in all_article_no:
            result = download(gallery, temp)
            time.sleep(1)
            self.textBrowser.append(result)
            QApplication.processEvents()
       
 
if __name__ == '__main__':
    app = QApplication(sys.argv)
    window = MainDialog()
    window.show()
    app.exec_()
    



프로그램이 단일 스레드로 돌아가고 있어서 창을 움직일 때 약간 뚝뚝 끊기는 느낌을 받으실 수 있습니다.


comment menu
2019.07.10. 00:20

신고

"humit님의 댓글"

이 댓글을 신고 하시겠습니까?

이니스프리 작성자 → humit
profile image

허걱 밤늦게 정말 감사합니다!

그러지 않아도 지금 막 페이스북의 파이썬 코리아에 질문글 올리려고 하던 참이었거든요 ㄷㄷ

저 혼자 구글링하면서 해결해보려고 해도 도저히 해결되지 않더군요 ㅠㅠ

 

humit 님께서 말씀해주신대로 반복문 안에 QApplication.processEvents()를 삽입한 후에 돌려보니

GUI가 정상적으로 작동하고 파일을 받아올 때마다 그 내역을 출력해주네요 ^^

 

그럼 날씨가 무더운데 humit 님께서도 바쁘시겠지만 항상 건강하시길 기원합니다!

바쁘신데 번번이 큰 도움을 주셔서 진심으로 감사드립니다 :)

comment menu
2019.07.10. 00:29

신고

"이니스프리님의 댓글"

이 댓글을 신고 하시겠습니까?

title: 황금 서버 (30일)humit → 이니스프리
profile image

네 열심히 코딩하세요 ㅎㅎㅎ

comment menu
2019.07.10. 00:41

신고

"humit님의 댓글"

이 댓글을 신고 하시겠습니까?

권한이 없습니다.
번호 제목 글쓴이 날짜 조회 수
공지 [작업 완료] 설 명절 맞이 서버 업데이트 안내 3 마스터 24.02.11.17:21 377
공지 [중요] 호스팅 만료와 관련하여 일부 수칙이 변경됩니다. 4 마스터 23.01.14.02:23 4061
공지 [필독] 질문하는 방법 17 마스터 18.02.23.03:09 4464
922 미디어플렛폼 vs 커뮤니티 55 title: 에그joyful 19.01.13.15:55 996
921 현재로서는 CKEditor 4.14를 사용하는게 최선일까요? 37 이니스프리 20.05.06.01:47 577
920 유튜브 다운로더에 대해 여쭤봅니다. 31 Nginx 20.05.12.22:05 609
919 히어로 무비 추천 부탁드립니다! 30 이니스프리 19.10.29.13:48 307
918 베리즈 웹 쉐어 연결 시간 초과 문제 29 image 루니 17.08.07.11:38 1166
917 데스크탑이냐 랩탑이냐, 그것이 문제입니다. 29 네모 19.11.28.14:15 331
916 파티션 복구 프로그램 TestDisk 잘 아시는 분 계시나요? 29 이니스프리 17.11.10.14:53 3745
915 AWS Lightsail 관련해서 질문이 있습니다... 24 MYIG 20.05.26.00:18 575
914 호스팅 신청했는데요 도움 좀 부탁드립니다. 24 GsusWeb 20.06.20.21:34 227
913 윈도우 서버를 VPS에 구축하는 것에 대해 질문 드립니다. 23 이니스프리 20.02.17.21:06 632
912 글쓰기 포인트 차감을 우회하여 도배하는 스팸에 대해 질문드려요~! 22 이니스프리 20.05.31.00:25 424
911 도움이 절실합니다. Freenom 질문입니다.(왕초보) 22 image 슬립 17.11.14.12:55 1322
910 음 제 블로그가 증발한건가요...? 21 갱생협스 19.01.13.22:50 270
909 호스팅 FTP 계속 타임 아웃이 생깁니다..! 21 image BVC_Liper_Okbul 18.06.29.20:33 563
908 JSP의 장단점에 대해 질문 드립니다. 21 이니스프리 20.06.09.21:42 218
907 네이버 사전을 크롤링할 때 한자의 인코딩 관련 하여 질문 드립니다. (일부 한자만 깨지는 현상) 21 image 이니스프리 19.11.22.16:22 1236
906 홈... 이런게 가능할까요? 21 모니터 17.10.10.18:37 401
905 게임 용어에 대해 질문 드립니다 (탱커/딜러/힐러) 21 이니스프리 20.06.05.20:45 2112
904 로딩 로딩 도와줘요 21 모니터 17.10.12.13:32 573
903 오라클 클라우드 프리티어 가입이 안 되네요 ㅠㅠ 20 이니스프리 20.06.26.21:31 4827