일이 조금 있어서 네이버 카페의 크롤링 코드를 만들어야했다. 여러가지 글을 종합해서 처리할게 있었다. 가장 궁금했던건 어떻게 OTP인증이나 User-Agent가 브라우저가 아닌 요청이 Allow될지 였는데, 역시나 Selenium을 통해서 하면 User의 Action을 구사해서 할 수있었다.(Python 짱짱맨) 아래는 코드이다.

import time
from selenium import webdriver
import pandas as pd
from bs4 import BeautifulSoup as bs
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.common.by import By

if __name__ == "__main__" :

    ### 로그인 프로세스
    login_url='https://nid.naver.com/nidlogin.login'
    user_id="<USER  ID>"
    user_pw="<USER  PW>"

    browser=webdriver.Chrome(ChromeDriverManager().install())
    browser.get(login_url)
    browser.implicitly_wait(2)

    browser.execute_script("document.getElementsByName('id')[0].value=\'"+ user_id + "\'")
    browser.execute_script("document.getElementsByName('pw')[0].value=\'"+ user_pw + "\'")

    browser.find_element(by=By.XPATH,value='//*[@id="log.login"]').click()
    time.sleep(1)

    ### 크롤링 Target Url
    baseurl=''
    browser.get(baseurl)
    
    pageNum = 0

    while(True) :
        
        time.sleep(1)
        
        pageNum +=1 
        print(pageNum)

        browser.get(baseurl +'&search.page='+ str(pageNum))
        
        ### 카페 메인 게시클 화면이 iframe으로 구성되어있음. iframe id가 cafe_main
        browser.switch_to.frame('cafe_main')

        soup = bs(browser.page_source ,'html.parser')
        soup = soup.find_all(class_ = 'article-board m-tcol-c')[1]

        datas = soup.select("#main-area > div:nth-child(4) > table > tbody > tr")

        for data in datas:
            ### article 은 한줄입니다. 원하는 속성 get 후 처리
            article = data.find(class_= "article")
            
    print('Finish')

## QLabel.py

from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QHBoxLayout
import sys

class my_window(QWidget) :

    def __init__(self) :
        super().__init__()
        lb = QLabel("이것은 QLabel")
        box = QHBoxLayout()
        box.addWidget(lb)
        
        self.setLayout(box)
        

if __name__ == "__main__" :
    app = QApplication(sys.argv)
    first_window = my_window()
    first_window.show()
    app.exec_()

QLabel은 PyQt의 가장 기본적인 WIdget이다. 사용자와의 상호작용없는 단순 String을 표현하기도 하고 Pixmap이나 setMovie를 통해서 그림을 출력할 수도 있다. 자주사용하는 코드를 알아보자.


사용예제 코드1 (선언, Font와 글자색 변경)

## QLabel_1.py

from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QHBoxLayout
from PyQt5 import QtGui
import sys

class my_window(QWidget) :

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

        ### QLabel을 선언, 생성자에 str은 label에 표시될 문자열
        lb = QLabel("이것은 QLabel")

        ### QLabel의 문자열을 변경
        lb.setText("저것은 QLabel")

        ### QFont(글씨체,폰트사이즈) None일시 DEFAULT
        lb.setFont(QtGui.QFont(None,20))
        lb.setStyleSheet("Color:blue")

        ### 배치코
        box = QHBoxLayout()
        box.addWidget(lb)
        
        self.setLayout(box)
        

if __name__ == "__main__" :
    app = QApplication(sys.argv)
    first_window = my_window()
    first_window.show()
    app.exec_()

 

사용예제 코드2 (PIXMAP)(선언과 크기조정)

## QLabel.py

from PyQt5.QtWidgets import QApplication, QLabel, QWidget, QHBoxLayout
from PyQt5.QtGui import QPixmap
import sys

class my_window(QWidget) :

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

        ### QLabel을 선언, 생성자에 str은 label에 표시될 문자열
        lb = QLabel()

        ### pixmap객체 설정
        pix = QPixmap("./img.png")
        
        ### pix.scaled(width, height)로 강제조정
        ### pix.scaledToWidth(width) 혹은 .scaledToHeight(heigh)로 비율유지조정
        pix.scaled(400,500)
        
        ### label에 pixmap설정
        lb.setPixmap(pix)
        
        box = QHBoxLayout()
        box.addWidget(lb)
        
        self.setLayout(box)

    def print_world(self):
        print("무야호")

if __name__ == "__main__" :
    app = QApplication(sys.argv)
    first_window = my_window()
    first_window.show()
    app.exec_()

동일한 방식으로 동영상형식인 QMovie도 설정 가능하다.

 

## myqt.py

from PyQt5.QtWidgets import QApplication, QWidget
import sys

if __name__ == "__main__" :
    app = QApplication(sys.argv)
    first_window = QWidget()
    first_window.show()
    app.exec_()

우리의 첫 프로그램 화면이다. 눈으로 보이게 된 화면이 물론 멋지긴 하지만, 밋밋하다. 빈 도화지만으로 보고 예술작품이라고 할 수는 없으니 말이다. 프로그램이라 하면 자고로 버튼 / 글자 / 그림 등 콘텐츠가 있어야 되지 않겠는가, 이러한 요소를 위젯(Widget)이라고 부른다. 특히 우리는 Pyqt5를 배우고 있으니 용어를 Q-Widget이라고 하자(한국이었으면 K-Widget이었겠네)


QWidget

QWidget은  키보드 / 마우스 / 휠 등의 여러 이벤트를 수신하고 자기 자신이 그래픽 요소를 가지는 User Interface의 기본 요소이다. 우리가 프로그램에서 보는 여러 가지 화면 요소들은 모두 요소다. 심지어 우리의 첫 코드에 QMainWIndow또한 위젯을 상속받았다. 아래는 Widget들중 아주 일부인 QLabel, QPushButton, QSlider이다.

<여러가지 위젯들, QLabel, QPushButton, QSlider>

 


Slot & Signal

위젯은 User Interface요소이다. 그 말은 즉슨 사용자와 상호작용한다는 이야기이다. 사용자의 입력이나 행동 우리가 "Event"라고 부르는 것은 저번 포스팅에서 알아보았다.

2021.08.29 - [Python/파이선과 친해지기] - [Python] - Python과 예쁘게 친해지기-QApplication과 EVENTLOOP

 

[Python] - Python과 예쁘게 친해지기-QApplication과 EVENTLOOP

아직 우리는 기초단계에 있기 때문에 지난 글을 읽고 오지 않으셨다면 꼭 읽고 와주시기를 바란다. 2021.08.27 - [Python/파이선과 친해지기] - [Python] - Python과 예쁘게 친해지기-PyQt [Python] - Python과 예.

tutoreducto.tistory.com

이러한 Event는 Signal로 발송이 되고, 발송된 Siganl은 그 Signal과 형식(파라미터)이 맞는 Slot에 연결할 수 있다. 코드를 통해서 실용적으로 알아보자. 예를 들면 버튼을 누른다는 행위(사용자의 행위)로 프로그램에서 무엇인가를 출력하는 예제는 다음과 같다.

## myqt.py

from PyQt5.QtWidgets import QApplication, QWidget, QLabel, QPushButton, QSlider, QHBoxLayout
import sys

class my_window(QWidget) :

    def __init__(self) :
        super().__init__()
        box = QHBoxLayout()
        btn = QPushButton("버튼") 
        btn.clicked.connect(self.print_function)	### btn의 clicked 시그널이 발송된다.
        box.addWidget(btn)
        
        self.setLayout(box)

	### print_function은 btn clicked시그널 발송 시 동작
    def print_function(self) :
        print("무야호")
        

if __name__ == "__main__" :
    app = QApplication(sys.argv)
    first_window = my_window()
    first_window.show()
    app.exec_()

<무야호 SIgnal과 SLot의 예제>

위와 같다. 각 시그널은 일정한 데이터를 실어서 발송할 수 있다.(예를 들면 slider의 경우 현재 slider값 등) QWidget별로 사용할 수 있는 시그널 슬롯이 정말 다양하니 앞으로 Widget강의를 통하여 알아보도록 하자


이번 시간을 마지막으로 PyQt5에서 알아야 할 기본적인 개념을 다 알아보았다. 깊게 들어가면 복잡 시려지지만 이 정도의 개념만으로도 충분히 나만의 GUI 프로그램을 만들 수 있다. 다음 시간부터는 WIdget들을 하나하나 알아보도록 하자

 

아직 우리는 기초단계에 있기 때문에 지난 글을 읽고 오지 않으셨다면 꼭 읽고 와주시기를 바란다.

2021.08.27 - [Python/파이선과 친해지기] - [Python] - Python과 예쁘게 친해지기-PyQt

 

[Python] - Python과 예쁘게 친해지기-PyQt

오랜만에 Python과 친해지기 강의를 쓰기 위해 키보드를 잡았다. 사실 이 정도면 Python에서 사용되는 대부분의 용어나 개념을 설명했다고 생각하는데, client 프로그램을 만들기 위한 QT Library를 다

tutoreducto.tistory.com

 

## myqt.py

from PyQt5.QtWidgets import QApplication, QWidget, QLabel
import sys

if __name__ == "__main__" :
    app = QApplication(sys.argv)
    first_window = QWidget()
    first_window.show()
    app.exec_()

우리가 지난시간 작성한 코드다. 이름대로 기 때문에 다 어떤 동작을 하는지 대충 예상할 수 있는데, 특이한 인스턴스가 하나 보인다.

 

"QApplication"

 

얘는 뭐하는 애일까?


QApplication과 EVENT

 

"QApplication은 Q-Widget 기반의 애플리케이션 기능을 포함한 QGuiApplication의 특수한 형태입니다. QApplication은 위젯의 초기화 / finalization을 처리합니다."

- qt공식 홈페이지 : https://doc.qt.io/qt-5/qapplication.html#details -

 

말이 조금 어려운데, 결국  Q-Widget이 동작하기 위한 기반 위젯이 QApplication이다. 요놈은 다른 QWidget의 생성 / 전시 / 삭제를 처리한다. 우리의 앱에서 최초의 QWidget이 나오기 전에 QApplication이 있어야 하는 이유가 바로 QApplication이 QWidget의 생성을 담당하기 때문이다.

 

한 프로그램에는 QApplication은 한개만 존재한다. 창이 2개 이상이 되더라도 QApplication은 하나만 존재한다.(음.... Python형 싱글턴 객체라고 볼 수 있겠다.) 여담으로 Qt기반의 Widget이 아닌 경우에 저기 정의에 있는 QGuiApplicatio을 사용한다. 홈페이지에 나와있는 기능은 또한 다음의 것들도 처리하는 것으로 적혀있다. 의역하니, 원문을 보고 싶은 사람은 위 qt공식 홈페이지를 방문하자(여기는 Qt가 C++형태이니 함수명이나 클래스명은 그냥 참고만 하자)

  • palette(), font()와 doubleclickInterval()등의 데스크탑 세팅을 초기화한다. 또한 제어판 설정 같은 데스크톱 세팅을 추적하고 반영한다.
  • 이벤트 처리를 담당한다. 관련된 widget에서 이벤트를 송/수신한다. 사용자는sendEvent()와 postEvent()를 위젯에 사용하여 자신만의 이벤트를 전송한다.
  • Command line 인자를 파싱하고 적절하게 초기화한다.-> 요래서 QApplication에 sys.argv가 인자로 들어가는 ㅋㅋ루삥뽕이다.
  • QStyle()으로 애플리케이션의 디자인을 설정한다. setStyle()로 이건 변경할 수 있다.
  • trainslate()기능을 이용해서 지역 언어로 설정할 수 있다.
  • desktop()이나 clipdoard()등을 이용해서 매지컬 오브젝트를 사용한다.
  • QApplication은 widget의 정보를 추적한다. 즉 widgetAt()으로 위치도 가져오고 topLevelWidget()으로 리스트도 가져오고 한다.
  • setOverrideCursur()매서드를 이용해서 애플리케이션의 마우스 움직임을 처리한다.

 

두 번째 기능에 집중해보자. QApplicationd은 이벤트 처리를 담당한다고 한다. 이벤트는 무엇이고 이걸 어떻게 처리한다는 것인가? 


이벤트

 

이벤트는 dispatch 되는 신호이다. 구현은 QEvent()라는 객체를 상속받아 구현되는데, 외부의 자극(유저의 입력 / 프로그램의 움직임 등 겁나 광범위하다.)과 그에 대하 한 결과로 이루어져 있다. 당연하게도 프로그램을 사용하면 엄청나게 많은 이벤트들이 발생한다. 그걸 정확하게 처리하는 기술이 바로 Event Loop이다. QT에는 이벤트 큐(Event Queue)라는 자료구조가 존재한다. 이벤트는 발생된 순서대로 이 Event Queue에 push 한다. QApplication은 이 이벤트 큐를 무한루프 하며 이벤트를 하나하나 처리한다. 큐에 저장된 Event는 Event Handler에 의해 수집되며 이벤트를 accept 할지 ignore 할지를 판단한다. 결정하면 Event를 받는 slot에 정의된 기능에 따라 Event는 처리되고, 다음 Event를 검사한다.


이론적인 이야기가 많았다. 물론 이걸 몰라도 GUI 프로그래밍을 할 수는 있지만 한 단계 높은 성장을 위해서는 필수적으로 알아야 하는 내용이라 적어두었으니, 꼭 관심 가지고 읽어주시기 바란다. 다음 시간에는 드디어 우리의 QWidget을 조금 발전시켜보자.

오랜만에 Python과 친해지기 강의를 쓰기 위해 키보드를 잡았다. 사실 이 정도면 Python에서 사용되는 대부분의 용어나 개념을 설명했다고 생각하는데, client 프로그램을 만들기 위한 QT Library를 다루지 않은 게 항상 마음에 걸렸다. Python

누구나 인정하듯 빠른생산속도를 압도적인 장점으로 다른 언어와 차별점을 두는 특징이 있다. 이번 Python과 예쁘게 친해지기 시간을 통해서 Python으로 GUI Programming에 입문하는 사람이 많았으면 좋겠다. 프로토타입 작성할 때 이거만 한 게 없다.

 

* 본 포스팅은 PyQt5를 지원합니다.


GUI

 

"Graphic User Interface"

 

그렇다. 사용자는 우리 프로그래머들처럼 검정화면만 보고 희열을 느끼는 변태가 아니다(헤헿...콘솔....까맣다...이쁘다...) DOS운영체제가 window로 바뀌며 그래픽 요소가 생기듯, 언제나 사람-지향적인 환경은 글자가 즐비한 환경이 아닌 Graphic, 가시적인 요소로 이루어진 환경이다. 전통적인 프로그램 언어인 C++에서도 WINDOW API로 당연히 GUI Programming을 지원하며, QT라는 고-급 라이브러리를 이용해서 복잡한 C++ WINDOW 프로그램을 이쁘게 만들 수 있다. JAVA에서는 AWT나 SWING 최근 들어서는 JAVAFX를 통해서 JAVA 나름대로의 그래픽 환경을 조성한다. 우리 Python은 실행 속도의 한계도 있고 기타 여러 가지 이유로 C++에 있는 QT LIBRARY를 Pythonic 하게 변환하는(즉 컴파일 시에는 원래 QT로 바뀌는) PyQt를 제공한다.

<PYQT 로고>


PYQT의 설치

새로운 물건을 발견하면 당연히 설레는 기분이 먼저아니겠는가 우선 바로 손으로 찍어먹어 볼 수 있게 PyQt를 설치하겠다.  

c$> pip3 install pyqt5

단순한 명령어다. pyqt5또한 pip를 통해서 다운로드 가능하니 다운로드를 하자. 다음 idle에서 import PyQt5 시  에러가 없으면 설치에 성공한 것이다.

>>> import PyQt5
>>>

HELLO PYQT!

우리의 영광스러운 첫코드는 myqt.py로 이름을 짓도록 하자. 앞으로 이 코드 기준으로 많은 예제를 실습할 예정이니, 떨리는 손을 다른 손으로 잡고 코딩에 들어가자 자 이제 PyQt5를 만나러 가보자

## myqt.py

from PyQt5.QtWidgets import QApplication, QWidget, QLabel
import sys

if __name__ == "__main__" :
    app = QApplication(sys.argv)
    first_window = QWidget()
    first_window.show()
    app.exec_()

아직은 저렴해 보이는 이 도화지가 우리 여행의 출발점이다. 주석을 조금 달아 코드를 설명하자면 다음과 같다.

  • from PyQt5.QtWidgets import QApplication, QWidget, QLabel : PyQt5.QtWidgets에는 화면에 표현할 요소인 Widget이 사전에 정의되어 있다. 이번 코드에서는 QApplication과 QWidget만 사용한다. 어 나 QLabel은 안 썼는데?
  • app = QApplication(sys.argv) : 시스템 인자를 매개변수로 QApplication을 구동한다. QApplication은 이벤트 루프를 처리하는 widget으로 생각해주면 된다. 
  • first_window.show() : 모든 widget은 parent의 속성에 종속적이지만, 단독이라면 보이지 않는(invisible) 상태이다. show() 함수를 호출하여 눈에 보이게 하였다.
  • app.exec_() : QApplication을 시작한다. 이는 기존 QT의 exec와 같은 함수이나, Python에서 exec은 예약어이기에 언더스코어(_)가 붙는다. 이는 print도 동일하다. 포스팅을 따라오다 보면 print_()를 만날 수 있다.

새로운 내용을 시작하려니 필자도 벌써 설레는 기분이다. 이번 시간에 우리는 pip를 이용해서 PyQt5를 설치하였고, 우리의 처음 Application을 띄워보았다. 다음 시간에는 우리 도화지를 조금 더 이쁘게 꾸며줄 템플릿인 MainWindow을 알아보고 QAppliaction에 대해서 조금 자세하게 알아보자

Python과 다른 언어와의 큰 차이점을 두자면(요즈음 들어서는 많은 언어가 지원하기에 퇴색된 기능이지만) Type으로부터의 자유로움을 손에 꼽을 수 있다.

>>> a=1 >>> b="heyhey" >>> type(a) <class 'int'> >>> type(b) <class 'str'>

위와 같이 다른 언어에서의 변수 선언 시 type을 같이 지정해 주어야 하는 재래식 언어와는 많은 차별점을 둔다.

다만 이런 Python의 장점은 코드의 규모가 커지고 방대해질 수록 많은 문제를 야기하며, 프로그램의 동작상에서 동작하는 Runtime level의 문제라기보다는, 프로그래머 사이 작성되는 코드 혹은 과거의 나와 싸우고 있는 프로그래머들에게 Type의 힌트를 줄 필요성이 생겼다.

<최솟값을 찾는 함수 : JAVA의 경우>

// 최소값을 찾는 JAVA함수, int를 반환하고 ArrayList<int>를 받는다는 것을 함수에서 알 수 있다. public int findMinimumNumber(ArrayList<int> arr){ int result = arr[0]; for(int a : arr) if(result > a) result=a; return result; }

<최솟값을 찾는 함수 : Python의 경우>

### 내장함수 min에 의해 처리될 수 있는 value를 입력으로 받고 그의 원소값을 반환 ### 어떤형의 parameter를 받고 반환하는지 알 수 없다. def find_minimum(value) : return min(value)

위와 같은 문제들을 해결하기 위해 함수에 주석을 달거나 하는 프로그래머 개개인의 노력을 이용했다.

### param : list ret : int def find_minimum(value) : return min(value)

Python도 이 문제를 인식한 듯 Type Annotation을 제공한다. 다음과 같다.

def find_minimum(value : list) -> int : return min(value)

사용법은 간단한다.

  • 변수명 뒤 콜론(:)을 붙이고 type명을 기술 type명에는 클래스도 들어갈 수 있다.
  • 함수의 반환은 화살표(->)을 붙이고 type명을 기술 당연하게도 여기도 클래스가 들어갈 수 있다.

단! 이렇게 적는 type annotation은 주석의 확장으로 밖에 여겨지지 않으며, 인터프리터는 해석 시 type annotation을 염두하지 않는다.

+ Recent posts