- 3
- 이니스프리
- 조회 수 6945
안녕하세요?
개인적인 목적으로 사용하려고 파이썬과 PyQt를 이용하여 디시인사이드의 이미지를 다운받는 스크립트를 작성했는데요.
80% 정도 완성한 단계인데요 PyQt의 GUI가 반응이 없는 현상이 계속되어서 질문을 드립니다 ㅠㅠ
우선 .py 파일입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 | 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' } 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 파일로 변환한 파일입니다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 | 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'를 입력하시면 파싱은 잘 되더군요.
작성자
댓글 3




허걱 밤늦게 정말 감사합니다!
그러지 않아도 지금 막 페이스북의 파이썬 코리아에 질문글 올리려고 하던 참이었거든요 ㄷㄷ
저 혼자 구글링하면서 해결해보려고 해도 도저히 해결되지 않더군요 ㅠㅠ
humit 님께서 말씀해주신대로 반복문 안에 QApplication.processEvents()를 삽입한 후에 돌려보니
GUI가 정상적으로 작동하고 파일을 받아올 때마다 그 내역을 출력해주네요 ^^
그럼 날씨가 무더운데 humit 님께서도 바쁘시겠지만 항상 건강하시길 기원합니다!
바쁘신데 번번이 큰 도움을 주셔서 진심으로 감사드립니다 :)



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

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_()
프로그램이 단일 스레드로 돌아가고 있어서 창을 움직일 때 약간 뚝뚝 끊기는 느낌을 받으실 수 있습니다.