지난 시간에 이어서 계속

 

# 기본 탬플릿
import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import *
from PyQt5.QtGui import *

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

    def initUI(self):
    	# 여기에 함수 입력
    
    
    
        # 필수 설정
        self.setWindowTitle('제목 입력')
        self.setGeometry(300, 300, 300, 300)
        self.show()

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

기본적인 템플릿을 만들어보았다.

이 템플릿에서, initUI 함수에 내용을 추가하는 형태로 작성하자

 

 

1. 툴바와 메뉴바

이번에는 툴바를 만들어보자

 

이 툴바에는 저장 기능과 종료 기능만을 넣을 것이고

메뉴바에도 종료 기능 만을 넣을 것이다.

 

또한 아래 이미지를 사용할 것이다.

 

 

def initUI(self):
    #종료
    actExit = QAction(QIcon('./Day09/exit.png'), 'Exit', self)
    actExit.setShortcut('Ctrl+Q')                   
    actExit.setStatusTip('앱 종료')
    actExit.triggered.connect(qApp.quit)

    #저장
    actSave = QAction(QIcon('./Day09/save.png'), 'Save', self)
    actSave.setShortcut('Ctrl+S')
    actSave.setStatusTip('저장')

    #메뉴바
    menubar = self.menuBar()
    menubar.setNativeMenuBar(False)#네이티브 메뉴바를 쓰지 않겠다는 말
    filemenu = menubar.addMenu('&File')
    filemenu.addAction(actExit)

    #툴바
    toolbar = self.addToolBar('MainToolBar') # 툴바 타이틀은 없어도 됨
    toolbar.addAction(actSave)
    toolbar.addAction(actExit)
    
    # GUI 화면 설정
    self.setWindowTitle('Bar Window') # 창의 타이틀 이름을 바꿈
    self.move(50, 50) # 처음 창의 위치를 선정, 0,0은 왼쪽 위, 기본값은 정 중앙
    self.resize(400, 200) # 창의 크기를 바꿈
    self.show()#핵심! 창을 출력

actExit는 종료 기능을, actSave는 저장 기능을 말한다.

단, 저장은 실제로 그 기능을 지원하지 않는다. (종료기능은 실제로 동작한다.)

각 변수에는 아이콘과 단축키를 지정해 주었다.

 

실행하면 보시다시피 툴바와 아이콘이 추가된 것을 볼 수 있다.

이 툴바는 좌측의 점선부분을 끌어서 위치를 바꾸어 줄 수 있다.

 

메뉴바에도 Exit가 추가된 것을 알 수 있다.

 

2. 콤보박스

이번엔 콤보박스를 만들어보자

콤보박스란, 선택하면 옵션리스트가 나오는 것을 말한다.

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

    def initUI(self):
        lblOption = QLabel('선택값 : ', self)
        lblOption.move(20, 20)
        
        cbOption = QComboBox(self)
        cbOption.addItem('Option 1')
        cbOption.addItem('Option 2')
        cbOption.addItem('Option 3')
        cbOption.addItem('Option 4')
        cbOption.addItem('Option 5')
        cbOption.move(20, 40)
        cbOption.activated[str].connect(self.onActivated)        

        # 이 밑은 필수 설정
        self.setWindowTitle('콤보박스')
        self.setGeometry(300, 300, 300, 300)
        self.show()

    def onActivated(self, text):
        self.lblOption.setText('선택값 :' + text)
        self.lblOption.adjustSize() # 글자수 만큼 라벨 넓이를 조정

여기서 중앙의 Option은 그냥 리스트다.

onActivated 함수는 콤보박스를 선택했을 때의 함수이다.

 

출력하면 이와같이 나오는 것을 알 수 있다.

 

 

 

3. 라인에디트

이번엔 글자를 넣는 공간을 만들어보자

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

    def initUI(self):
        self.lblResult = QLabel(self)
        self.lblResult.move(20, 20)
        
        txtInput = QLineEdit(self)
        txtInput.setEchoMode(2) #글자를 넣으면 페스워드 형태로 보인다
        txtInput.move(20, 40)
        txtInput.textChanged[str].connect(self.onChanged)
        
        #필수 설정
        self.setWindowTitle('라인에디트')
        self.setGeometry(300, 300, 300, 300)
        self.show()

    def onChanged(self, text):
        self.lblResult.setText('입력값 :' + text)
        self.lblResult.adjustSize() # 라벨 사이즈 자동

이 코드엔 글 입력기와 라벨 하나가 붙어있다.

setEchoMode 메소드를 사용하면 비밀번호처럼 입력된다.

onChanged 함수가 라벨을 나타내는 함수다.

 

이 코드를 실행하면 보시다시피 빈 칸 하나만 생기는 것을 알 수 있다.

라벨은 보이지도 않지만

글자를 입력하는 순간 라벨이 보인다.

 

 

4. 다이얼 로그

버튼을 누르면 창이 뜨고, 그 창에 입력한 값을 받아오도록 하자

 

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

    def initUI(self):
        self.btnDlg = QPushButton('Dialog', self)
        self.btnDlg.move(20, 20)
        self.btnDlg.clicked.connect(self.onClicked)

        self.txtInput = QLineEdit(self)
        self.txtInput.move(20, 50)

        #필수 설정
        self.setWindowTitle('다이얼로그')
        self.setGeometry(300, 300, 300, 300)
        self.show()

    def onClicked(self):
        text, ok = QInputDialog.getText(self, '인풋다이얼로그', '이름을 적으세요')

        if ok:
            self.txtInput.setText(text)

이번것도 크게 어려운 것은 없다.

 

다만 onClicked 함수에선 총 두가지를 받아오는데

text는 입력한 내용을, ok는 승인 여부가 담긴다.

즉, 다이얼로그 버튼을 누르면 창이 뜨는데, 여기다 글을 입력하면 입력내용은 text에 담기고

확인 버튼을 눌렸다면 ok에 true가 담긴다.

 

다이얼로그에 문자열을 입력하고 ok를 누르면 이렇게 내용이 들어간다.

 

5. 이미지 넣기

 

아래 이미지를 넣어보자

 

넣어볼 고양이 이미지

파일이름은 cat.png로 하고 실행파일과 같은 경로에 넣어주자

 

def initUI(self):
    # 이미지 사이즈 강제 변경 .scaledToWidth(w)
    pixmap = QPixmap('./Day10/cat.png').scaledToWidth(800)

    lblImage = QLabel(self)
    lblImage.setPixmap(pixmap)
    lblSize = QLabel(str(pixmap.width()) + 'x' + str(pixmap.height()))
    lblSize.setAlignment(Qt.AlignmentFlag.AlignCenter) # Qt.AlignCenter 가능

    vbox = QVBoxLayout(self)
    vbox.addWidget(lblImage)
    vbox.addWidget(lblSize)

    self.setLayout(vbox)

    # 필수설정
    self.setWindowIcon(QIcon('./Day09/iot.png'))
    self.setWindowTitle('이미지 위젯')
    self.show()

pixmap은 고양이사진 파일의 경로고

lblImage는 이미지를 배치하기 위한 변수이며

그 아래 lblSize는 이미지의 크기를 나타내는 변수로  없어도 상관없다.

출력하면 고양이가 잘 나오는 것을 알 수 있다.

 

6. 파일 불러오기

텍스트 파일을 불러와보자

이건 코드가 꽤나 길다.

 

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

    def initUI(self):
        self.textEdit = QTextEdit(self)
        self.setCentralWidget(self.textEdit)
        self.statusBar()
        
        openFile = QAction(QIcon('open.png'), '&Open', self)#아이콘이 없어서 안뜸
        openFile.setShortcut('Ctrl+O')
        openFile.setStatusTip('파일 열기')
        openFile.triggered.connect(self.onClicked)

        menubar = self.menuBar()
        menubar.setNativeMenuBar(False)
        fileMenu = menubar.addMenu('&File')
        fileMenu.addAction(openFile)

        #필수 설정
        self.setWindowTitle('라인에디트')
        self.setGeometry(300, 300, 300, 300)
        self.show()

    def onClicked(self):
        fname = QFileDialog.getOpenFileName(self, '파일열기', './')
        if fname[0]:
            file = open(fname[0], 'rt', encoding='utf-8')
            with file:
                data = file.read()
                self.textEdit.setText(data)        
            file.close()
        QMessageBox.about(self, '성공', '로드했습니다.')

    def closeEvent(self, event) -> None:
        reply = QMessageBox.question(self, '종료', '저장하지 않은 정보가 날아갑니다.<br> 종료합니까?',
                                     QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
        if reply == QMessageBox.Yes:
            event.accept() # 프로그램 종료
        else:
            event.ignore() # 프로그램 계속

initUI 함수에서, 첫부분은 메모장 프로그램처럼 텍스트를 입력할 수 있는 공간을 가져온 것이다.

 

openFile 변수에서, 아이콘을 불러오도록 하고 있지만 우리가 따로 아이콘을 다운받지 않았기 때문에 아이콘은 출력되지 않는다.

또한 파일 열기 기능에 단축키 Ctrl+O를 부여하고 파일을 열 때, 상태창에 '파일 열기'가 뜨도록 한다.

 

기본함수 외에도 onClicked함수와 closeEvent함수가 있는데

이는 Open 버튼을 눌렀을 때의 이벤트와

또, 창을 끌 때의 이벤트를 말한다.

출력해보면 텍스트를 쓸 수 있는 창이 나오는데

 

File을 클릭하면 이렇게 Open이 나오고 여기에 마우스를 올리면 상태창에 '파일 열기' 메시지가 나타난다.

 

 

파일 열기를 누르면 이와같이 파일을 여는 창이 뜨고

 

파일을 로드하면, 이렇게 파일이 가져와지면서 성공했다는 메시지가 뜬다.

 

 

파일을 끄려고 할 경우, 이렇게 경고메시지가 뜨는 것을 볼 수 있다.

 

 

PyQt5 패키지를 사용하여 GUI 창을 만들 수 있다.

 

1. 시작하기

우선 VS-Code에서 PyQt5 패키지를 설치하자

pip install PyQt5

 

2. 기본

import sys
from PyQt5.QtWidgets import QApplication, QWidget

먼저 sys를 임포트하고 PyQt5의 QWidget와 QApplication을 가져오자

 

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

    def initUI(self):
        self.setWindowTitle('Simple Window')
        self.resize(400, 200)
        self.show()

MyApp 클래스를 만들고 여기에 QWidget을 넣어준다.

__init__ 함수를 만들어주고

UI의 기본적인 설정을 위해 initUI 함수를 만들어준다.

 

initUI에서

setWindowTitle은 창의 제목을 말한다.

resize는 창의 크기를 말한다.

move는 여기서 사용하지 않았지만, 처음 창이 뜰 위치를 뜻 한다. 만약 설정을 해주지 않으면 정 중앙에 창이 뜬다.

setGeometry는 move나 resize를 합친 개념으로 창의 상하길이와 위치를 설정한다.

show는 가장 중요한 것으로 창을 나타내라는 뜻이다.

 

만약 show가 없으면 어떠한 창도 나타나지 않는다.

 

클래스가 정의되었으니, 이 클래스를 사용해주자

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

실행하는 코드 파일이 __main__ 일 경우, app을 실행한다.

 

여기까지 코드를 완성하고 파일을 실행하면 아주 간단한 창이 뜬다.

Simple Window라는 이름의 400, 200 크기를 가진 창이 나타난다.

 

3. 아이콘 적용

창에 아이콘을 적용해보자

우선 QIcon 패키지를 가져와야 한다.

import sys
from PyQt5.QtWidgets import QApplication, QWidget
from PyQt5.QtGui import QIcon

 

적용할 아이콘이다.

이 아이콘을 코드와 같은 경로에 저장하고 MyApp 클래스의 initUI 함수를 다음과 같이 바꾸어 주자

def initUI(self):    
    self.setWindowTitle('Simple Window')
    
    self.setWindowIcon(QIcon('./Day09/iot.png'))

    self.resize(400, 200)
    self.show()

여기서 중요한 것은 setWindowIcon 메서드다.

메서드의 파라미터로 QIcon과 함께 파일의 경로를 넣어주면 된다.

 

실행한 모습

창에 아이콘이 추가된 것을 볼 수 있다.

 

4. 닫기 버튼

버튼을 추가해보자

이번에도 패키지를 가져오고 initUI 함수를 바꾸면 된다.

import sys
from PyQt5.QtWidgets import QApplication, QPushButton, QWidget
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QCoreApplication

가져올 패키지들

def initUI(self):
    btn = QPushButton('Quit', self)
    btn.move(320,170)
    btn.resize(btn.sizeHint())
    btn.setToolTip('<b>경고</b>누르는 순간 즉시 종료')
    btn.clicked.connect(QCoreApplication.instance().quit)

    self.setWindowIcon(QIcon('./Day09/iot.png'))
    self.setWindowTitle('Quit Button')
    self.setGeometry(300, 300, 400, 200)
    self.show()

여기서 핵심은 btn 변수다.

btn.move는 버튼의 위치를 뜻하고

btn.setToolTip은 이 버튼에 마우스를 올렸을 때 뜰 메시지를 말한다.

참고로 이때 사용하는 코드는 HTML 코드다.

clicked.connect메소드는 버튼이 눌렸을 때, 무엇을 실행하는지로 여기서는 종료를 나타낸다.

 

버튼이 눌렸을때에 대한 반응 종류로는 아래가 있다.

clicked() 버튼을 클릭할 때 발생
pressed() 버튼이 눌렸을 때 발생
released() 버튼을 눌렀다 뗄 때 발생
toggled() 버튼의 상태가 바뀔 때 발생

 

 

실행화면, 팝업 메시지도 정상적으로 나타난다.

 

4-1. 버튼 메소드

눌리는 버튼과 비활성화된 버튼을 만들어보자

마찬가지로, initUI만 편집한다.

    def initUI(self):

        btn1 = QPushButton('&Button1',self)#alt입력시, B에 밑줄 생긴다.
        btn1.setCheckable(True)
        btn1.toggle()

        btn2 = QPushButton(self)
        btn2.setText('Button&2')

        btn3 = QPushButton('Button3', self)
        btn3.setEnabled(False)

        vbox = QVBoxLayout()
        vbox.addWidget(btn1)
        vbox.addWidget(btn2)
        vbox.addWidget(btn3)
        
        self.setLayout(vbox)

        # 이 밑은 필수 설정
        self.setWindowTitle('박스 배치')
        self.setGeometry(300, 300, 300, 300)
        self.show()

총 3종류의 버튼을 만들었다.

vbox는 버튼이 들어갈 공간이고

btn1은 토글식 버튼

btn2는 일반 버튼

btn3은 비활성화 된 버튼이다.

 

실행한 모습

 

4-2. 체크박스 버튼

이번에는 체크박스 버튼을 만들어보자

우선 Qt를 임포트 해주자

import sys
from PyQt5.QtWidgets import *
from PyQt5.QtCore import Qt

 

def initUI(self):
    cbShowTitle = QCheckBox('Show Title',self)
    cbShowTitle.move(20, 20)
    cbShowTitle.toggle()
    cbShowTitle.stateChanged.connect(self.changeTitle)

    # 이 밑은 필수 설정
    self.setWindowTitle('체크박스 체크')
    self.setGeometry(300, 300, 300, 300)
    self.show()

def changeTitle(self, state):
    if state == Qt.CheckState.Checked: # Qt.Checked도 사용가능
        self.setWindowTitle('체크박스 체크')
    else:
        self.setWindowTitle('체크박스 체크해제')

여기서 changeTitle 함수가 있는데

이는 체크박스 체크시 윈도우 타이틀이 바뀌도록 조정한 함수다.

4-3. 라디오 버튼

이번엔 라디오 버튼을 만들어보자

남자와 여자를 선택하는 버튼을 만들어 보았다.

당연하게도, 두 버튼을 둘 다 클릭할 순 없다.

보시다시피 크게 대단한 것은 없다.

 

 

5. 상태창과 시간 표시

이번에는 창의 아랫쪽에 나타나는 상태표시창을 나타내보자

그런데 상태표시창만 있으면 구분이 잘 안 될 수도 있으니 시간도 같이 표시해 보겠다.

import sys
from PyQt5.QtWidgets import QApplication, QMainWindow, qApp, QDesktopWidget
from PyQt5.QtGui import QIcon
from PyQt5.QtCore import QDate, QTime

이번에 사용할 패키지 목록이다.

가장 아랫줄은 시간 표시 패키지이다.

 

이번에도 MyApp 클래스 내의 initUI 함수를 수정해준다.

def initUI(self):
    # 상태바, 시간과 날짜
    now = QDate.currentDate()
    time = QTime.currentTime()
    self.statusBar().showMessage(now.toString('yyyy-MM-dd') + ' '+ time.toString('hh:mm:ss'))
    self.setWindowIcon(QIcon('./Day09/iot.png'))

    # 기본 코드
    self.setWindowTitle('Bar Window')
    self.move(50, 50)
    self.resize(400, 200)
    self.show()

 

now는 오늘 날짜를, time은 지금 시간을 나타내는 변수다.

그러나 이걸 그대로 쓰면 날짜나 시간이 미국식으로 표시되기 때문에, 우리에게 익숙한 형태로 표시하기 위해서

toString을 사용하여 변환을 해주어야 한다.

 

 

코드를 실행하면 창의 하단에 상태줄과 시간이 생긴 것을 알 수 있다.

 

2편에서 계속

아름다운 스프

 

HTML을 파싱하는데 사용하는 라이브러리다.

 

 

이번에 사용할 예시용 URL은 다음과 같다.

https://kin.naver.com/search/list.naver?query=%EC%A0%9C%EC%9E%84%EC%8A%A4+%EC%9B%B9+%EB%A7%9D%EC%9B%90%EA%B2%BD 

 

이 사이트에서, 우리는 질문 제목을 가져올 생각이다.

사이트 모습

이 사이트에는 여러 질문이 올라와 있는데 우리는 제목을 가져올 것이다.

제임스웹 우주망원경 질문이요

최고고도 부탁드립니다. 제임스웹 망원경이

등등 이런 택스트를 가져올 것이다.

 

 

 

우선 라이브러리를 설치하고 가져오자

 

!pip install beautifulsoup4

주피터 노트북에서는 붉은 오류 표시가 뜨겠지만 막상 실행하면 잘만 설치된다.

 

from bs4 import BeautifulSoup
import requests

url = 'https://kin.naver.com/search/list.naver?query=%EC%A0%9C%EC%9E%84%EC%8A%A4+%EC%9B%B9+%EB%A7%9D%EC%9B%90%EA%B2%BD'

BeautifulSoup 외에도 requests도 같이 가져온다.

사용할 url은 변수에 할당을 해 놓는다.

 

requests를 통해 url을 가져온다.

이때 HTML상태 코드가 200이 나와야 한다.

 

HTML 상태코드란?

웹사이트에 접속했을 때, 반환하는 코드로 3자리 숫자로 이루어져있다.

100번대는 요청을 받았고 프로세스를 진행하고 있다는 의미를

200번대는 요청을 성공적으로 받았고 성공했다는 의미를

300번대는 요청 완료를 위해 추가 작업이 필요하다는 의미를

400번대는 요청을 처리할 수 없다는 의미를

500번대는 서버에 문제가 있다는 의미를 지니고 있다.

 

가장 대표적인 상태코드로는 404코드가 있다.

404 에러는 사용자가 가장 많이 접하는 에러코드중 하나다.

 

아무튼 우리는 정상적으로 요청에 응답했을 때를 가정해야 함으로 상태코드가 200인 경우에 대한 코드를 작성한다.

response = requests.get(url)
if response.status_code == 200:
    html = response.text
    soup = BeautifulSoup(html, 'html.parser')

response에는 url 내용이 담기고 이를 정상적으로 가져왔다면 status_code에 200이 담길 것이다.

그렇게 될 경우, html 변수에는 reponse에 담긴 내용중 택스트를, soup 변수에는 그 택스트를 보기 좋게 파싱한다.

 

여기서 파싱이 뭐고 왜 필요할까?

파싱을 안한 html 변수의 값은 다음 사진과 같다.

무슨 고대 암호문 비석을 보는것 같다.

그렇다면 파싱을 한 결과물이 담긴 soup는 어떨까?

 

제법 잘 정리된 html 코드를 볼 수 있다.

이 코드를 그대로 복사해서 html 파일로 저장하고 실행하면 실제 지식인 사이트와 아주 유사한 사이트가 나오는걸 볼 수 있다.

그러나 url을 보면 알겠지만 실제 사이트가 아니다.

 

아무튼 이제 지식인 사이트에서 질문의 제목을 가져와야 한다.

다음 코드를 작성하자

title = soup.select_one('#s_content > div.section > ul > li:nth-child(1) > dl > dt > a ')

이 코드가 뭘까 싶을 것이다.

 

우선, 크롤링할 사이트에 들어가서 f12를 누르면 개발자 도구가 나타나는데

 

사진에서 표시한 아이콘을 누르면 사이트의 각 부분이 어느 코드에 해당하는지를 알 수 있다.

 

 

 

그리고 제목부분을 보면 저 제목이 코드의 어느부분에 해당하는지를 나타낸다.

title = soup.select_one('#s_content > div.section > ul > li:nth-child(1) > dl > dt > a ')

즉, 이 코드는 지식인 사이트에서, 코드를 가져올 때 그 코드의 위치를 추적하여 가져오는 기능을 한다.

괄호안은 그 코드의 위치를 말한다.

 

자 그럼 저 title를 출력하면 무엇이 나타날까??

<a class="_nclicks:kin.txt _searchListTitleAnchor"
href="https://kin.naver.com/qna/detail.naver?
d1id=11&amp;dirId=1129&amp;docId=423747412&amp;qb=7KCc7J6E7IqkIOybuSDrp53sm5Dqsr0=
&amp;enc=utf8§ion=kin&amp;rank=1&amp;search_sort=0&amp;spq=0" target="_blank">
<b>제임스웹</b> 우주<b>망원경</b> 질문이요</a>

다름아닌 사진에서 빨간색으로 표시한 부분중 가장 아랫부분이 나타나는 것을 알 수 있다.

자, 우리는 텍스트를 가져오는 것이 목표다.

title 변수에서 텍스트만 가져오자

 

'제임스웹 우주망원경 질문이요'라는 제목만 가져오는데 성공하였다.

 

여기서 15번째 줄에는 else문이 있는데

이는 여러가지 이유로 사이트를 가져오지 못했을 때를 위한 간단한 에러처리다.

 

그런데 제목을 하나만 가져왔다.

 

모든 제목을 가져와야 하니 for문과 다른 변수를 사용해보자

 

위 사진에서, ul은 html 코드에서 ul.basic1에 해당하는 부분만을 가져온 것이다.

그리고 titles에서 select_one 대신 select를 사용했는데, 이로써 여러개를 가져왔다.

그 값을 반복문에 넣어서 get_text() 메소드를 통해서 텍스트만 가져오면

모든 제목을 순차적으로 출력하는 코드가 완성된다.

 

 

 

 

 

 

 

 

국가에서 제공하는 날씨 정보를 가져와보자

 

여기서 사용할 사이트는 기상청 홈페이지로 url은 아래와 같다.

부산의 기온과 습도를 가져올 것이다.

 

 

우선 라이브러리 부터 가져오자

from urllib.request import urlopen, Request

이제 도시별 날짜 검색 함수를 만들어보자

우선 위의 url로부터 정보를 가져와야 한다.

 

def get_weather(city):
    url = 'https://www.weather.go.kr/w/obs-climate/land/city-obs.do'
    page = urlopen(url=url)

city라는 파라미터는 내가 기온과 습도를 찾을 도시를 말한다.

이제 페이지 내용을 utf-8로 인코딩을 해주어야 한다.

 

text = page.read().decode('utf-8')

여기서 text를 print 하면 엄청나게 긴 HTML 코드가 나올 것이다.

이 긴 코드중에서 내가 찾고자 하는 부산의 기온과 습도에 대한 내용이 있다.

이 긴 코드를 가공하여 내가 원하는 자료만을 가져오도록 가공해주자

text = text[text.find(f'>{city}</a>'):]

가져온 긴 코드 중에서, 내가 찾고자 하는 도시와 일치하는 데이터만을 가져온다.

 

함수를 여기까지만 만들고 파라미터를 부산으로 입력하고 실행해보면 부산에 관한 많은 데이터가 나온다.

 

아래 전체코드를 참조

from urllib.request import urlopen, Request

def get_weather(city):
    url = 'https://www.weather.go.kr/w/obs-climate/land/city-obs.do'
    page = urlopen(url=url)

    text = page.read().decode('utf-8')
    text = text[text.find(f'>{city}</a>'):]
    print(text)

get_weather('부산')

출력결과(일부)

여기서 이제 기온과 습도만 가져와보자

기온은 위 출력결과에서 11.6에 속하는, 즉 7번째 자료이고 습도는 10번째 자료다.

단, 기상청 데이터는 계속 바뀌고 있기 때문에 코딩 중에 다른 값으로 바뀔 수도 있다.

 

기온값을 가져오기 위해서, 함수 안에 다음 코드를 넣어주자

for i in range(7):
    text = text[text.find('<td>')+1:]

start = 3
end = text.find('</td>')
current_temp = text[start:end]
print(f'{city}의 현재 기온은 {current_temp}˚C 입니다.')

text값은 <td>가 7번째인 값을 찾아서 가져온다.

그리고 그 값에서, 3번째 글자(0번째 포함)에서부터 </td> 전까지의 글자를 가져와서

current_temp변수에 할당한다.

그런 다음 출력하면 기온값을 가져올 수 있다.

 

 

습도도 비슷하다.

아래 코드를 위 코드 바로 다음에 넣어두면 된다.

for i in range(3):
    text = text[text.find('<td>')+1:]

start = 3
end = text.find('</td>')
current_humid = text[start:end]
print(f'{city}의 현재 습도는 {current_humid}% 입니다.')

습도는 10번째 자리에 위치하고 있다.

그러나 여기선 for문에 range(3)이 들어갔는데, 위에서 이미 7번 했으니까

그 다음으로 3번째 데이터를 가져오는 것이다.

 

전체 코드와 그 출력결과, 기온과 습도는 실시간 정보이므로 그 값이 출력할 때마다 다를 수 있다.

 

 

 

1. folium 라이브러리 설치

주피터 노트북에서 folium을 설치해 보자

파이썬 코드로 위를 작성하면 빨간줄이 뜨는데 신경쓰지 말고 Ctrl + Enter로 실행하자

나는 약 13초가 걸리긴 했지만 어쨌든 설치가 되었다.

 

folium은 파이썬에서 지도를 표시하기 위한 라이브러리다.

자세한 사항은 아래 주소를 참조하자

https://python-visualization.github.io/folium/

일단 folium을 임포트하고 코드를 실행해주자

실행하지 않으면 사용할 수 없다.

 

새로운 블록을 만들고 다음을 작성하자

 

m = folium.Map(location=[35.117479, 129.090394], zoom_start= 15)
m

위도와 경도를 이용해 표시하고자 하는 지역을 나타낸 것이다.

zoom_start는 확대 정도로 만약 1을 입력할 경우, 세계지도가 나오는 것을 알 수 있다.

zoom_start 값의 최대는 18이다.

 

보시다시피 주피터 노트북에 위도, 경도에 맞는 지역의 좌표가 나오는 것을 알 수 있다.

 

이 folium.Map에는 여러가지 옵션을 지원하며 그 옵션은 다음과 같다.

단, 일부 옵션은 주피터 노트북에서는 사용할 수 없다.

 

    - OpenStreetMap (default)
    - Stamen Terrain 
    - Stamen Toner
    - Stamen Water Color
    - Mapbox Bright             -주피터 노트북에서는 사용불가
    - Mapbox Control room tiles
    - 그 외...

 

이러한 옵션은 다음과 같이 사용한다.

 

m = folium.Map(location=[35.117479, 129.090394],
               zoom_start= 15,
               tiles = 'Stamen Terrain')
m

Stamen~ 옵션은 지도에 무엇을, 또는 어떻게 표시할 것인가에 대한 옵션이다.

 

 

2. folium 마커표시

이번엔 지도에 마커를 표시해보자

참고로 마커 모양은 부트 스트랩에서 가져온 모양을 사용한다.

folium.Marker(location=[35.117479, 129.090394]).add_to(m)

마커의 위치를 위도와 경도로 표시하고 지도변수 m에다가 이걸 추가해 준다.

상세 코드는 아래 사진을 참조한다.

 

마커가 추가된 것을 볼 수 있다.

 

이번엔 팝업을 띄워보자

코드는 아래 처럼 바꾼다.

folium.Marker(location=[35.117479, 129.090394], popup='부경대 용당캠퍼스').add_to(m)

처음에는 팝업이 없을 것이다.

그러나 팝업을 마우스로 클릭하면, 위 사진처럼 팝업이 뜰 것이다.

 

 

이 마커는 모양을 바꿔줄 수 있다.

https://getbootstrap.com/docs/3.3/components/

위의 부트스트랩 사이트에서 원하는 모양을 가져와서 바꿔줄 수 있다.

이 아이콘을 쓰고자 한다면 이 중 leaf 만 가져와서 아래처럼 작성해 준다.

참고로 색상은 빨간색으로 지정했다.

m = folium.Map(location=[35.117479, 129.090394], zoom_start=14)
folium.Marker(location=[35.117479, 129.090394], popup='부경대 용당캠퍼스',
              icon=folium.Icon(color='red',icon='leaf')).add_to(m)
m

 

적용된 모습

 

 

이번엔 아까 만든 팝업을 바꿔보자

아까 팝업이 한 줄로 나와서 매우 불편했다.

 

이를 뜯어고치기 위해선 html 코드를 어느정도 사용해야 한다.

 

팝업의 가로세로 길이를 바꾸어주고 부경대학교 문자 크기를 키워보자

m = folium.Map(location=[35.117479, 129.090394], zoom_start=14)
iframe = folium.IFrame('<h3>부경대학교</h3><br>용당캠퍼스<br>지역산업맞춤사업단')
popup = folium.Popup(iframe, min_width=250, max_width=350)

folium.Marker(location=[35.117479, 129.090394], popup=popup,
              icon=folium.Icon(color='red',icon='leaf')).add_to(m)
m

 

html 코드를 사용하여 보다 디테일하게 작성해 준 모습이다.

 

3. folium 선택 지점에 위도와 경도 표시

 

이번엔 선택지점에 위경도를 표시해 보자

 

m = folium.Map(location=[35.117479, 129.090394],
               zoom_start= 15,
               tiles = 'openstreetmap')
m.add_child(folium.LatLngPopup())
m

지도를 누르면 해당 지점의 위도와 경도가 나오는 것을 알 수 있다.

 

 

4. folium 위도와 경도를 사용해서 경로를 표시하기

 

이번엔 지도에 선을 그어보자

 

부산에 위치한 광안대교는 경로가 위 사진과 같다.

이와 같은 선을 한번 그어보자

m = folium.Map(location=[35.117479, 129.090394],
               zoom_start= 15, tiles = 'openstreetmap')
m.add_child(folium.LatLngPopup())

gps_coord = [
    (35.1844, 129.1184),
    (35.1755, 129.1220),
    (35.1589, 129.1358),
    (35.1572, 129.1368),
    (35.1546, 129.1370),
    (35.1379, 129.1209),
    (35.1356, 129.1112),
]
folium.PolyLine(gps_coord, tooltip='광안대교 통행').add_to(m)

m

우선 선이 이어질 꼭지점에 해당하는 위도와 경도를 위와같이 리스트로 작성해주자

그런다음 해당 리스트를 PloyLine 메소드에 추가해주면 된다.

 

툴팁은 이 경로에 마우스를 올렸을 때 뜰 추가 메시지다.

아래 사진에 묘사되어 있으니 참조할 것

 

경로가 완성된 것을 볼 수 있다.

 

 

사실 주피터 노트북은 일종의 개발용 도구지 파이썬의 기능이 아니다.

하지만 여러모로 유용한 기능을 제공하므로 한 번 다루어 보자

 

1. 시작전 설정사항

주피터노트북에는 라인넘버가 없기 때문에 이를 켜주어야 한다.

Ctrl + , 로 설정에 들어가서 line number를 검색해 Notebook에서 설정해 준다.

 

2. 파일생성

 

VS-Code에서 주피터노트북 파일을 만들 때는 다른 파일들과 다르게 파일 -> 새 파일 을 통해서 만들어야 한다.

이 때, 2번째 사진의 jupyter Notebook을 눌러서 생성하고 먼저 저장부터 해주자

 

이 주피터 노트북의 장점은 마크다운 언어와 파이썬 언어를 같이 사용할 수 있다는 것에 있다.

물론 주석기능이 있지만 주석은 한계가 명확한 반면 마크다운을 활용하면 영상이나 사진도 넣을 수 있다.

 

주피터노트북을 시작한 모습

이런식으로 블록으로 생성된다.

오른쪽 아래에 Python이라고 되어 있는데, 이걸 통해서 마크다운으로 바꾸어 줄 수 있다.

이 블록에 코드를 적고 ctrl+enter를 누르면 안의 코드를 실행하고 그 결과물을 바로 아래에 보여준다.

 

왼쪽 위의 +코드를 통해서 새로운 블록을 추가해 줄 수 있다.

 

블록을 선택하고 ESC를 누른다음, M을 누르면 마크다운으로, Y를 누르면 파이썬 코드로 자유롭게 바꿔줄 수 있다.

 

3. 코드작성

 

위 블록을 마크다운 블록을 하고 마크다운에 맞게 코드를 작성한다음 Ctrl + Enter를 누르면

 

이처럼 깃허브 리드미 파일 만드는것 처럼 바뀌는 것을 볼 수 있다.

 

이번엔 파이썬 블록을 써보자

 

print('Hello World!') 를 입력하고 Ctrl + Enter를 입력하자 실행되는 모습을 알 수 있다.

 

 

 

 

4. 단축키

주피터 노트북은 많은 단축키를 지원하여 개발의 편의성을 지원하고 있다.

단축키는 다음과 같다.


셀 선택 모드에서

a : 위에 새로운 셀 추가
b : 아래에 새로운 셀 추가

c : 셀 복사하기
v : 셀 붙여넣기
x : 셀 잘라내기
dd : 셀 삭제하기
p : 셀 아래에 붙여넣기

o : 실행결과 열기/닫기
m : Markdown으로 변경
y : Code로 변경
Shift + m : 선택 셀과 아래 셀과 합치기
Ctrl + s 또는 s  : 파일 저장
Enter  : 선택 셀의 코드 입력 모드로 돌아가기

코드 입력 모드에서


Ctrl + Enter : 입력 셀 실행
Shift + Enter : 입력 셀 실행 후 아래 셀로 이동 (없으면 새로운 셀 추가)
Alt + Enter : 입력영역 실행 후 아래 새로운 영역 추가


Ctrl + a : 선택 셀의 코드 전체 선택
Ctrl + z : 선택 셀 내 실행 취소
Ctrl + y : 선택 셀 내 다시 실행
Ctrl + / : 커서 위치 라인 주석처리


Shift + Ctrl + - : 커서 위치에서 셀 둘로 나누기

 

5. 디버그

블록 왼쪽에 보면 실행버튼에 자세히가 있는데 여기에 디버그 셀이 있다.

중단점을 먼저 만들고 이걸 실행하면 디버그를 할 수 있다.

 

6. 주의사항

이건 사람마다 다를 수 있는데, 일부 환경에서는 위 사진 처럼 다른 블록에 있는 함수를 사용할 수 없다.

그러나 다른 환경에서는 다른 블록에 있는 함수를 사용할 수 있다.

 

이유는 의외로 단순한데 위 코드를 실행하지 않았기 때문이다.

 

다른 블록에 있는 함수나 변수를 사용하려면 그 블록을 먼저 실행을 해주어야 한다.

 

또한 파일을 한 번 끄고 다시 열었을 때, 모두 실행을 한 번 눌러줘야 한다.

 

7. 특이사항

파이썬에서 콘솔을 출력할 때는 print(변수명) 이런식으로 사용해야 한다.

그런데 주피터노트북에는 위 사진처럼 변수명만 적어줘도 된다. (프린트 문도 사용할 수 있다.)

 

 

 

8. json 파일 가져오기

 

test.json파일을 준비했다.

{
    "ionic":{
        "price": 20000000,
        "year" : "2022"
    },
    "genesis":{
        "price": 80000000,
        "year" : "2021"
    },
    "bmw":{
        "price":150000000,
        "year" : "2019"
    }
}

이 파일을 주피터노트북에서 파이썬 코드를 사용해서 불러와보자

 

import json

file = open('./test.json', 'r', encoding='utf-8')
jsondata = json.load(file)
print(json.dumps(jsondata))

file.close()

우선 json을 임포트 해야 한다.

그리고 파일 오픈을 통해서 가져오고 그렇게 가져온 파일을 jsondata 변수에 담고 출력한다.

참고로 print(jsondata) 라고만 적어도 된다.

그런데 파일이 다 1줄로만 나온다.

이걸 처음 작성했던 json 처럼 정렬되게 나오게 하고 싶다면 다음 옵션을 추가한다.

indent='\t'

이제 제대로 나온다.

 

단, 한글의 경우 깨질 수 있는데, 그럴 경우 print 코드를 아래로 바꾸어 주자

print(json.dumps(jsondata, indent='\t', ensure_ascii=False))

 

9. json 파일 조작하기

이제 위 json 파일의 값을 가져와 보자

 

genesis의 price의 값을 가져온다.

정상적으로 잘 출력된다.

 

 

이번엔 가격을 바꾸어보았다.

아무튼 무진장 큰 수로 바꾼다음 출력했는데 역시 정상적으로 반영되었다.

 

10. json 파일 저장

with open('./test2.json', 'w', encoding='utf-8') as file:
    # 한글 인코딩은 ensure_ascii=False 를 추가할 것
    json.dump(jdata, file, indent = '\t', ensure_ascii=False)

파일이 있는 경로에 test2.json 파일을 만드는 코드다.

마찬가지로 한글 깨짐을 방지하려면 위 코드의 주석처럼 해주자

 

11. json 파일 쓰기

아래와 같은 내용을 가진 파일을 만들어보자

파일 명은 supercar.json 이다.

{
	"audi": {
		"price": 300000000,
		"year": 2020
	},
	"porsche": {
		"price": 150000000,
		"year": 2015
	}
}

먼저 supercar라는 상위 딕셔너리를 만들고 audi, porsche라는 하위 딕셔너리를 만든다.

그리고 필요한 키와 벨류값을 넣어준다.

 

json 파일을 새로 만들 때는 dump 메소드를 사용하고 파일을 읽어올 때는 dumps 메소드를 사용한다.

한 글자 차이기 때문에 잘 기억해 놔야 한다.

코드를 실행하면 json 파일이 생긴 것을 알 수 있다.

 

 

 

 

 

 

 

 

 

1. Escape 문자

이스케이프 문자란 프로그래밍할 때 사용할 수 있도록 미리 정의해 둔 "문자 조합"으로 키워드와 동일하다.

다른곳에서 함부로 쓸 수 없으며 주로 출력물을 보기 좋게 정렬하는 용도로 사용한다.

Escape 문자 설명
\r 캐리지 리턴(줄 바꿈 문자, 현재 커서를 가장 앞으로 이동)
\n 문자열 안에서 줄을 바꿀 때
\t 문자열 사이에 탭 간격을 줄 때
\b 백 스페이스
\' 작은따옴표(')를 화면에 표현
\" 큰따옴표(")를 화면에 표현
\\ \를 화면에 표현
\f 폼 피드(줄 바꿈 문자, 현재 커서를 다음 줄로 이동)
\000 널문자 표현




\r은 이전에 \r\n 형태로 콘솔사의 텍스트 표현 뒤 다음 줄로 내려갈때 가장 많이 쓰인 형태였으나, 현재는 유닉스, 리눅스 쪽에는 여전히 남아있지만 윈도우쪽은 \n만 사용한다.

 

위의 것들 중 빨간색으로 표시된 것들만 지금까지 많이 쓰는 Escape 문자입니다.

 

위의 것들의 사용예시는 다음과 같다.

print('1. Hello.\r\nWorld')
print('2. Hello.\nWorld')       #위와 같지만 주로 이걸 사용
print('3. Hello.\n\tWorld')
print('4. Hello.\n\t\bWorld')
print('5. Hello.\n\'World\'')   #문자열 안에 작은따옴표를 넣는 기능
print('6. Hello. "World"')
print('7. Hello.\"World\"')     #위와 같지만 밖에 사용한 따옴표가 작은 따옴표가 아닌 큰 따옴표면 사용함
print('7. Hello.\\World')        #역슬레시를 문자열에 넣기위한 것으로 파이썬은 역슬레시를 하나만 적어도 나옴

print('8. Hello\0')             #널문자 표현



#실행결과
1. Hello.
World
2. Hello.
World
3. Hello.
        World
4. Hello.
       World
5. Hello.
'World'
6. Hello. "World"
7. Hello."World"
7. Hello.\World
8. Hello

 

 

 

2. 문자열 포맷팅(옛방식)

지금은 거의 쓰지 않지만 시험에는 나올 수 있으므로 옛날 방식 문자열 포맷팅에 대해서 알아보자

옛날에는 %를 사용해서 문자열을 포맷했다.

다음을 보자

me = '저'
name = 'Tarel'
age = 20
print('%s는 %s입니다. %d대 입니다.'%(me, name, age))

# 출력결과
저는 Tarel입니다. 20대 입니다.

 

여기서 s는 문자열을, d는 숫자를 의미한다.

여기서 s냐 d냐를 틀리면 바로 에러가 뜨기 때문에 상당히 불편하다.

 

이러한 구방식 포멧의 종류로는 다음이 있다.

코드 설명
%d 정수 Integer
%f 부동소수 floating
%o 8진수
%x 16진수
%c 문자 1개 character
%s 문자열 String
%% Literal % 문자

 

 

 

 

 

 

 

모든 언어가 그렇듯 파이썬에도 에러를 처리해줄 필요가 있다.

 

1. 에러

에러는 주로 오타로 인한 문법적 실수 에러와 실행중 발생하는 로직적 실수로 나뉜다.

이는 파이썬 말고도 거의 모든 언어에 공통적으로 해당한다.

 

문법적 실수는 애초에 컴파일 자체가 안 된다.

실행하는 순간 어느 부분에 문제가 있는지 에러메시지가 출력된다.

이건 처리하는데 그렇게 많은 시간이 걸리지 않는다.

 

중요한 것은 '로직적 실수로 인한 에러'로 이것은 Exception이라고 하는데 이건 언제 어디서 어떻게 발생할지 아무도 예상할 수 없기 때문에 잡아내는 것이 어렵다.

처음 완성한 프로그램이 당장은 아무문제 없이 돌아갈 수는 있어도, 쓰다보면 예상치 못한 에러가 발생하기 마련이며 제 아무리 똑똑한 프로그래머도 이는 피해갈 수 없는 것이 현실이다.

 

하지만 프로그램을 잘 쓰다가 이런 에러가 발생하면 그 프로그램의 신뢰도는 추락하게 된다.

신뢰할 수 없는 프로그램은 아무도 사용하지 않는다.

때문에 에러를 100% 없앨 수는 없어도 최대한 많은 에러를 예상하고 잡아내는 것이 정말 중요하다고 할 수 있다.

 

2. try - except

0으로 나누기 에러를 잡아보자

def div(a, b):
    return a / b

x, y = input('두 수를 입력하세요 > ').split()
x = int(x)
y = int(y)

try:
    print(div(x, y))
except Exception as e:
    print(e)
    
print('최종실행 완료')

함수 div는 a를 b로 나뉘어주는 함수다.

x와 y는 각각 숫자로써 받아오고

try - except를 활용해 x를 y로 나누는 것에 문제가 없는지 확인하고 있다.

 

이 코드를 실행하고 3과 0을 입력해보자

실행결과

두 수를 입력하세요 > 3 0
division by zero
최종실행 완료

평소같았으면 최종실행 완료 메시지가 뜨지 않는다.

에러가 뜨자마자 모든 코드가 강제종료되기 때문이다.

그러나 예외처리를 해주었기 때문에 division by zero라는 메시지만 띄우고 그 다음 코드를 정상적으로 실행한다.

 

이번엔 숫자 x와 y를 정상적으로 받아오는지를 확인해보자

여기선 x와 y를 수로만 받아오게 했는데 만약 문자열이나 공백없이 가져온다면 에러가 발생할 것이다.

try:
    x, y = input('두 수를 입력하세요 > ').split()
    x = int(x)
    y = int(y)
except Exception as e:
    print(e)

print('최종실행 완료')

이 코드를 실행하고 숫자가 아닌 문자열을 입력해보자

실행결과

두 수를 입력하세요 > asd
not enough values to unpack (expected 2, got 1)
최종실행 완료

보시다시피 에러처리가 잘 되고 있는 것을 알 수 있다.

만약 일반적인 에러처럼 아예 꺼버리고 싶다면 exit()문을 추가해 줄 수 있다.

그런데 exit는 거의 안쓴다고 한다.

그도 그럴게 굳이 그럴 필요가 있나 싶으니...

 

 

에러에는 여러가지 종류가 있다.

def div(a, b):
    return a / b

x, y = input('두 수를 입력하세요 > ').split()
x = int(x)
y = int(y)

try:
    print(div(x, y))
except ZeroDivisionError as e:
    print('0으로 나눌 수 없음')
except Exception as e:
    print(e)

여기서 보면 except가 두 개가 있는 것을 알 수 있다.

여기서 except ZeroDivisionError 를 굳이 추가해줄 필요는 없다.

그냥 except Exception이 모든 에러를 다 잡아주기 때문이다.

 

그러나 사용자나 다른사람들의 요구 때문에 에러 종류에 따라 메시지나 처리를 다르게 해야할 수도 있기 때문에 이런걸 사용할 수 있다.

 

여기서 주의해야할 점은 Exception이 가장 아래에 나와야한다는 것이다.

Exception이 사실상 모든 에러를 다 잡는 상위개념이기 때문에 Exception이 위에 있으면 그 아래 하위 에러처리문은

전부 무시당하기 때문이다.

 

3. finally

try - except 문의 연장선으로 finally는 try문 내에서 에러가 발생하든 발생하지 않든 무조건 실행하도록 하는 부분이다.

아래 코드를 보자

def div(a, b):
    return a / b

x, y = input('두 수를 입력하세요 > ').split()
x = int(x)
y = int(y)

try:
    print(div(x, y))
except Exception as e:
    print(e)
finally:
    print("계산이 완료되었습니다.")

except 다음에 finally가 있는 것을 볼 수 있다.

이 코드를 정상적으로 써보고, 또 에러를 일으켜서 해보자

# 에러를 발생시키지 않는 경우
두 수를 입력하세요 > 3 2
1.5
계산이 완료되었습니다.



# 에러를 발생시킨 경우
두 수를 입력하세요 > 3 0
division by zero
계산이 완료되었습니다.

계산이 완료되었다는 문구가 무조건 나오는 것을 알 수 있다.

 

 

finally는 무조건 실행된다.

그런데 except에 코드를 종료시키는 exit를 추가하면 어떨까?

위 코드를 살짝 변형해보자

def div(a, b):
    return a / b

x, y = input('두 수를 입력하세요 > ').split()
x = int(x)
y = int(y)

try:
    print(div(x, y))
except Exception as e:
    print(e)
    exit()
finally:
    print("계산이 완료되었습니다.")

except에 exit()가 추가되었다.

고로 에러를 일으키면 exit문에 의해 강제로 종료될 것이다.

그렇다면 finally문은 어떻게 될까?

# 실행결과
두 수를 입력하세요 > 3 0
division by zero
계산이 완료되었습니다.

exit가 실행되기 전에 finally가 실행되는 것을 알 수 있다.

 

이러한 특징은 자바스크립트를 비롯한 타 언어에도 비슷한 형태로 존재한다.

자바스크립트의 경우는 try - catch - finally 의 형태로 사용하는데 자바스크립트도 catch에서 강제종료하기 전에

finally를 실행하는 경우가 있다.

의외로 모르는 사람이 많고 언어마다 미묘한 차이가 있으니 잘 찾아보자

 

 

 

+ Recent posts