• 목록
  • 아래로
  • 위로

안녕하세요?


개인적인 목적으로 사용하려고 파이썬과 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%) 1876350/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 396
공지 [중요] 호스팅 만료와 관련하여 일부 수칙이 변경됩니다. 4 마스터 23.01.14.02:23 4075
공지 [필독] 질문하는 방법 17 마스터 18.02.23.03:09 4465
922 계속 사용중이던 네트워크 공유 폴더가 갑자기 액세스 불가라고 뜹니다. 6 image 장윤서 18.06.07.16:55 34331
921 팀뷰어 대체할 원격 제어 프로그램 추천 부탁드립니다 16 이니스프리 17.11.30.15:38 32325
920 엑셀 그래프의 축 간격 조절 문제입니다. 2 image 국내산라이츄 17.08.10.11:06 12483
919 아이폰/아이패드 충전기 발열 관련 질문 드립니다 9 image 이니스프리 19.05.02.22:52 11105
918 에러 523 해결법 4 입체그림 20.02.21.16:48 6690
PyQt 실행시 프리징 현상 관련하여 질문 드립니다! ㅠㅠ 3 이니스프리 19.07.06.03:37 6685
916 프린터가 지 맘대로 프린트를 하네요. 3 곰도리푸 18.04.04.18:20 6018
915 윈도우용 메일 클라이언트가 필요합니다. 9 네모 18.01.11.20:15 5867
914 크롬에서 특정 사이트 접속 문제 관련하여 질문 드립니다 9 이니스프리 17.03.27.18:03 5309
913 오라클 클라우드 프리티어 가입이 안 되네요 ㅠㅠ 20 이니스프리 20.06.26.21:31 4827
912 파이썬 에디터로 어떤 것이 좋나요? 14 NoYeah 20.01.08.21:08 4525
911 라떼판다와 라즈베리 파이 중 어느 쪽을 사는 게 나을까요? 9 제르엘 18.10.14.19:25 3827
910 나무 위키의 수익구조는 무엇일까요? 2 NoYeah 18.03.18.15:56 3808
909 파티션 복구 프로그램 TestDisk 잘 아시는 분 계시나요? 29 이니스프리 17.11.10.14:53 3745
908 IE11에서 이미지가 깨지는 현상을 해결할 수 있을까요? 2 이니스프리 19.08.02.00:01 3702
907 파일질라로 연결하니까 보안되지 않은 서버입니다. TLS를 통한 FTP를 지원하지 않습니다. 이렇게떠요 해결방안좀 1 마카오 16.09.11.00:57 3654
906 자바스크립트 FormData와 관련된 메서드의 IE 호환성과 관련하여 질문 드립니다 ^^ 6 image 이니스프리 19.08.05.14:22 3426
905 선택약정 안 되는 중고폰의 경우에는 어떤 단점이 있는 것인가요?? 6 이니스프리 20.01.21.14:30 3100
904 집에서 시놀로지 NAS로 워드프레스나 미디어위키 돌리면 느릴까요? 10 이니스프리 17.01.16.20:56 2979
903 [Requests] multipart/form-data의 전송에 대해 질문 드립니다 ^^ 4 이니스프리 19.12.18.22:00 2904