오늘은 파이썬 주식 분석 자동 프로그램 을 만들어 보려고 합니다. 파이썬은 활용도가 무궁무진한 것 같아요.
자동으로 분석하여 나에게 자동으로 메일을 보낼 수 있도록 한번 같이 만들어봅니다.
파이썬 주식 분석 자동 프로그램 만들기
1. 학습 목표
요즘은 재테크가 선택이 아닌 필수 입니다. 금리가 낮을수록 주식으로 재테크를 하고자 하는 분들이 많이 늘어납니다. 그래서 그런지 퇴근후에 재테크 공부하시는 분들도 많은것 같습니다.
그런데, 주식의 경우는 장이 열리고, 끝나는 시간이 오전 9시~15시30분인데요. 회사에서 집중적으로 업무를 하는 시간이기도 하고, 학생들은 한창 수업이 있는 시간입니다. 또 어떤 경우는 회사는 주식 관련 사이트를 접근 차단을 해 두어, 주식 정보를 보기가 참 어렵죠.
그래서 이런 불편점을 해소가 위해서 주식정보를 받아 분석 보고서를 만들고, 이메일로 전송해주면 어떨까? 하는 생각이들었습니다. 이번 프로젝트는 주식 일별 시세 정보를 보고서로 만들어서 이메일로 전송해 주는 주식 분석 보고서 자동화 프로젝트를 준비했습니다.
[구현 순서]
주식 분석 보고서 자동화 프로젝트를 위한 구현 순서는 아래와 같습니다.
주식 종목 코드를 사용하여, 일별 시세를 크롤링합니다. 크롤링한 자료로 간단한 차트 그리고, 파일로 저장합니다. 이 차트를 사용하여 분석보고서를 만듭니다. 마지막으로 분석 보고자료를 이메일로 전송합니다.
[그림 1] 주식 분석 보고서 자동화 프로젝트 구현 순서
[최종 결과]
전송된 이메일입니다. 주식 분석 보고서 (stock_report.pptx)가 잘 첨부되어 있습니다.
[그림 2] 주식 분석 보고서 메일 내용 상세
[언어 & 환경(IDE)]
Python 3.7 & Jupter notebook
[프로젝트 리소스 및 소스 코드]
– res/stock_report : 동작 확인을 위한 리스소 파일이 위치한 폴더
– stock_report.ipynb : 구현된 소스 코드
※ 프로젝트에 필요한 리소스 및 소스코드의 경로 구성은 자유롭게 설정하면 됩니다. 그러나, 아래 설명되어 있는 코드 구현이 제시된 구성으로 되어 있으니, 동일하게 경로와 파일명으로 구성해 둔다면 별다른 코드 수정 없이 실행해 볼 수 있습니다.
2. 사전 준비
주식 분석 보고서 자동화 프로젝트를 위해서 세가지 사전 준비가 필요합니다.
1) 보고서 자동화 프로젝트
‘보고서 자동화 프로젝트 ‘를 수행해 보셨다면, 쉽게 이해할 수 있습니다. 해당 내용을 아시면 3번으로 바로 이동하세요.
<보고서 자동화 프로젝트>
파이썬으로 파워포인트를 편집해보겠습니다.
[그림 1] 4개 슬라이드
[프로젝트 리소스 및 소스 코드]
– res/powerpoint_handling : 동작 확인을 위한 리스소 파일이 위치한 폴더
– powerpoint_handling.ipynb : 구현된 소스 코드
2. 사전 준비
> 링크 (python-pptx 라이브러리 문서) python-pptx.readthedocs.io/en/latest/
> 링크 (python-pptx 라이브러리 quick-start 문서) : python-pptx.readthedocs.io/en/latest/user/quickstart.html
$ pip install python-pptx
3. 사전 지식 쌓기
파워포인트에서는 다양한 레이아웃을 제공하고 있습니다. 기본 테마 (office테마)의 경우, 11개의 레이아웃을 지원하고 있습니다.
[그림 2] office테마에서 지원하는 레이아웃
테마가 다른 경우 지원되는 레이아웃이 달라지며, 이번 장에서는 office 테마 범위 내에서 내용을 다루도록 하겠습니다.
지식을 쌓을 내용은 다음과 같습니다.
3-1) 모든 레이아웃 적용해 보기
3-2) 레이아웃 별 placeholder 속성 확인하기
3-1) 모든 레이아웃 적용해 보기
office테마에서 지원하는 11개의 레이아웃을 각 슬라이드에 적용해 보겠습니다.
코드
from pptx import Presentation # 라이브러리
from pptx.util import Inches # 사진, 표등을 그리기 위해
prs = Presentation() # 파워포인트 객체 선언
for i in range(0, 11):
title_slide_layout = prs.slide_layouts[i] # 슬라이드 종류 선택
slide = prs.slides.add_slide(title_slide_layout) # 슬라이드 추가
prs.save('add all slides.pptx')
실행 결과
총 11개의 슬라이드가 추가되었고, 5번째 슬라이드를 클릭해보니 “비교” 레이아웃이 적용되어 있습니다.
[그림 x-3] 저장 결과
코드 설명
1~2라인 : 필요한 라이브러리를 import 합니다.
4라인 : 파워포인트 객체를 선언합니다.
6라인 : 총 11개의 레이아웃을 추가하기 위해 반복문을 사용합니다.
7라인 : slide_layouts [i]는 슬라이드 레이아웃을 의미합니다. 위의 [그림 2]에서 보이는 순서대로 0은 제목 슬라이드를 의미하고, 1은 제목 및 내용 슬라이드를 2는 구역 머리글 슬라이드를 의미합니다. 테마를 변경하는 경우 목적에 맞게 변경할 필요해야 합니다.
8라인 : 슬라이드를 추가합니다.
10라인 : 구성된 내용을 저장합니다.
3-2) 레이아웃 별 placeholder 속성 확인하기
placeholder는 콘텐츠를 추가하는 것을 훨씬 더 쉽게 만들어 줍니다. 레이아웃에서 관리되고 있는 palceholder를 확인하고, 그에 맞게 정보를 추가하여 장표를 꾸미면 됩니다.
이를 위해 각 레이아웃별 placeholder를 확인해봅시다.
코드
from pptx import Presentation # 라이브러리
from pptx.util import Inches # 사진, 표등을 그리기 위해
prs = Presentation()
for i in range(0, 11):
print("--------[%d] ------ "%(i))
slide = prs.slides.add_slide(prs.slide_layouts[i])
for shape in slide.placeholders:
print('%d %s' % (shape.placeholder_format.idx, shape.name))
실행 결과
[그림 x-3]의 테마는 —-[4]—-에 해당하는데, 총 5개의 palceholder가 있네요.
큰제목에 내용을 쓰려면 slide.placeholder [0]을 사용하면 됩니다. 또는 다른 속성 값으로도 접근 가능한데, 이 부분은 문서를 통해 확인하세요.
[그림 4] 레이아웃별 속성 확인
코드 설명
6 라인 : 총 11개의 레이아웃을 추가하기 위해 반복문을 사용합니다.
7 라인 : 슬라이드를 추가합니다.
8 라인 : 슬라이드를 구성하고 있는 placeholder 목록을 확인하기 위해 반복문을 사용합니다.
9 라인 : placeholder의 index(순서)와 이름을 출력합니다.
4. 구현
여러 레이아웃 중에 작업 시 가장 많이 사용하는 4가지 레이아웃을 좀 더 상세히 다뤄보겠습니다.
[구현 순서]
슬라이드를 하나씩 추가하고, 저장하면서 그 결과를 확인해보겠습니다.
Step1 | 객체 선언 |
Step2 | 제목 슬라이드 |
Step3 | 제목 + 내용 슬라이드 |
Step4 | 사진 슬라이드 |
Step5 | 표 슬라이드 |
Step1) 객체 선언
라이브러리를 import 하고, 객체를 선언합니다.
from pptx import Presentation # 라이브러리
from pptx.util import Inches # 사진, 표등을 그리기 위해
prs = Presentation() # 파워포인트 객체 선언
Step2) 제목 슬라이드
‘제목 슬라이드’를 추가해보겠습니다.
코드
title_slide_layout = prs.slide_layouts[0] # 0 : 제목슬라이드에 해당
slide = prs.slides.add_slide(title_slide_layout) # 슬라이드 추가
# 제목 - 제목에 값넣기
title = slide.placeholders[0] # 제목
title.text = "Hello, World!" # 제목에 값 넣기
# 부제목
subtitle = slide.placeholders[1] # 제목상자는 placeholders[0], 부제목상자는 [1]
subtitle.text = "python-pptx was here!"
# 저장
prs.save('test.pptx')
실행 결과
저장된 test.pptx를 열어보세요. 와우!! 감탄사가 절로 나오죠?ㅎㅎ
[그림 5] 제목 슬라이드
코드 설명
1~2라인 : prs.slide_layouts[0]에서 0은 제목 슬라이드를 의미하며, 해당 레이아웃을 추가합니다.
5~6라인 : 제목 슬라이드의 레이아웃(0)의 경우는 [그림 4]에서 알 수 있듯 2가지 placeholder가 있습니다. 첫 번째는 제목을 의미하며, 두 번째는 부제목을 의미합니다. slide.placeholders[0]에서 0은 제목을 의미하며, 제목의 값을 “Hello, World!”로 설정합니다.
9~10라인 : 두번째 placeholder는 부제목을 의미하며, 부제목에 “python-pptx was here!” 값을 설정합니다.
13라인 : 파일을 저장합니다.
Step3) 제목 + 내용 슬라이드
‘제목 내용 슬라이드’를 추가해보겠습니다.
코드
bullet_slide_layout = prs.slide_layouts[1] # 1 : 제목 + 내용 슬라이드
slide = prs.slides.add_slide(bullet_slide_layout) # 기존에 있던 슬라이드에 추가
# 제목
title_shape = slide.placeholders[0]
title_shape.text = 'Adding a Bullet Slide'
# 내용
body_shape = slide.placeholders[1]
tf = body_shape.text_frame
tf.text = 'Find the bullet slide layout'
# 단락 추가
p = tf.add_paragraph()
p.text = 'Use _TextFrame.text for first bullet'
p.level = 1 # 1 : 들여쓰기 레벨
# 단락 추가
p = tf.add_paragraph()
p.text = 'Use _TextFrame.add_paragraph() for subsequent bullets'
p.level = 2 # 2 : 들여쓰기 레벨
prs.save('test.pptx')
실행 결과
[그림 6] 제목 + 내용 슬라이드
코드 설명
1~2라인 : prs.slide_layouts[1]에서 1은 제목+내용 슬라이드를 의미하며, 해당 레이아웃을 추가합니다.
5~6라인 : 제목+내용 슬라이드의 레이아웃(1)의 경우는 [그림 4]에서 알 수 있듯 2가지 placeholder가 있습니다. 첫 번째는 제목을 의미하며, 두 번째는 내용을 의미합니다. slide.placeholders[0]에서 0은 제목을 의미하며, 제목의 값을 “Adding a Bullet Slide”로 설정합니다.
9~11라인 : 두번째 placeholder는 내용을 의미하며, “Find the bullet slide layout” 값을 설정합니다.
13~16라인 : add_paragraph 함수를 호출하여 단락을 추가합니다. p.level로 들여 쓰기 레벨을 설정할 수 있습니다.
19~21라인 : add_paragraph 함수를 호출하여 단락을 또 추가합니다. 그리고, 들여쓰기 레벨을 2로 설정합니다.
Step4) 사진 슬라이드
‘빈 화면’슬라이드를 추가해서 원하는 사진을 삽입해보겠습니다.
코드
img_path = 'res/powerpoint_handling/slide_test.jpg'
blank_slide_layout = prs.slide_layouts[6] # 6 : 제목/내용이 없는 '빈' 슬라이드
slide = prs.slides.add_slide(blank_slide_layout)
left = top = Inches(1)
width = height = Inches(1)
# width, hegith가 없을 경우 원본 사이즈로
pic = slide.shapes.add_picture(img_path, left, top, width=width,height=height)
left = Inches(3)
width = Inches(5.5)
height = Inches(4)
pic = slide.shapes.add_picture(img_path, left, top, width=width,height=height)
prs.save('test.pptx')
실행 결과
[그림 7] 사진 슬라이드
코드 설명
1 라인 : 넣고 싶은 이미지 파일을 설정합니다.
3~4라인 : prs.slide_layouts[6]에서 6은 빈 슬라이드를 의미합니다.
6~9라인 : add_picture() 함수를 호출하여 이미지를 추가합니다. 이미지의 경우 위치와 크기 설정이 필요합니다. top, left는 시작점을 width와 height는 이미지의 크기를 의미합니다.
11~14라인 : add_picture() 함수를 호출하여 이미지를 추가하는데, 기존보다 크게 추가해 보았습니다.
Step5) 표 슬라이드
‘제목만’ 슬라이드를 추가하여, 표를 삽입해 보겠습니다.
코드
title_only_slide_layout = prs.slide_layouts[5]
slide = prs.slides.add_slide(title_only_slide_layout)
shapes = slide.shapes
title_shape = slide.placeholders[0]
title_shape.text = 'Adding a Table'
rows = cols = 2
left = top = Inches(2.0)
width = Inches(6.0)
height = Inches(0.8)
table = shapes.add_table(rows, cols, left, top, width, height).table
# set column widths
table.columns[0].width = Inches(2.0)
table.columns[1].width = Inches(4.0)
# write column headings
table.cell(0, 0).text = 'Foo'
table.cell(0, 1).text = 'Bar'
# write body cells
table.cell(1, 0).text = 'Baz'
table.cell(1, 1).text = 'Qux'
prs.save('test.pptx')
실행 결과
[그림 8] 표 슬라이드
코드 설명
1~2라인 : prs.slide_layouts[5]에서 5는 ‘제목만’ 슬라이드를 의미합니다.
5~6라인 : 제목을 의미하는 placeholder에 ‘Adding a Table’을 설정합니다.
8~13라인 : add_table() 함수를 호출하여 표를 추가합니다. 표는 몇 행 몇렬로 만들 것인지와 그릴 위치와 크기 설정합니다. rows와 cols를 2로 설정하여 2행 2열의 표를 만들었습니다.
16~17라인 : 칼럼 크기를 설정합니다.
20~25라인 : 표의 각 cell에 값을 설정합니다. table.cell(행, 열)입니다.
5. 요약정리
이번 장에서는 파이썬으로 파워포인트를 제어해보았습니다. 슬라이드에서 지원하는 레이아웃을 추가하고, 각 속성들을 설정하는 방법을 배웠습니다.
이번장 내용을 모두 학습하셨다면, 주식 분석 보고서 자동화 프로젝트 를 진행해보세요. 재미난 프로젝트가 될 것입니다.
여러분이 사용하고 있는 테마와 보고서에 한번 적용해시고요.
반복적인 업무를 자동화함으로써 칼퇴하십시오!
2) 이메일 전송 자동화 프로젝트
‘이메일 전송 자동화 프로젝트’를 수행해 보세요.
이번 프로젝트는 사전에 배운 지식을 두가지나 활용합니다. 융합프로젝트답죠? 필요한 사전 준비가 필요하므로, 아직 보지 않으셨다면, 한번 수행해보세요.
이메일 전송 자동화 프로젝트
메일은 어떻게 하면 업무 효율을 높일 수 있을까요?
필자의 경우는 서명이나 서식을 작성해 두어 업무 효율을 높였습니다. 이 정도의 메일 서식을 넣어두어도 반복적인 업무를 줄일 수 있었습니다.
[그림 1] 업무 효율을 높이는 메일 서명 예시
그런데, 만약에 파이썬을 통해 메일을 자동으로 보낼 수 있다면 어떨까요? 상상만 해도 너무 즐겁습니다.
이번 장에서는 파이썬으로 네이버 이메일 보내는 법을 학습해보겠습니다. 한국인들이 많이 사용하는 네이버 메일을 기준으로 설명하였으며, 동작 방법은 비슷하므로 다른 메일에도 활용할 수 있습니다.
2가지 종류의 이메일을 보내보겠습니다.
텍스트만 구성된 메일과 파일이 첨부된 메일입니다.
[그림 2] 텍스트 메일 전송 결과 / [그림 3] 텍스트 및 파일이 첨부된 메일 전송 결과
첨부파일도 보낼 수 있으니, 만약 주식 또는 트렌드 분석처럼 주기적으로 받고 싶은 메일이 있다면 같이 연동해서 활용할 수 있습니다.
[언어 & 환경(IDE)]
Python 3.7 & Jupter notebook
[프로젝트 리소스 및 소스 코드]
– email_sending.ipynb : 네이버 메일 보내기 구현 코드
– res/email_sending: 네이버 메일 보내기 리스소 파일
2. 사전 준비
메일 자동화 프로젝트를 위해 두 가지 사전 준비가 필요합니다
첫째, 라이브러리 설치
둘째, 수신 메일 IMAP 설정
입니다.
다음과 같은 순서로 진행해보겠습니다.
2-1) 라이브러리
2-2) 수신 메일 IMAP 설정
2-1) 라이브러리
메일 자동화를 위해 두가지 라이브러리를 사용합니다. smtplib과 email 라이브러리 입니다.
smtplib 라이브러리는 이메일 전송을 위해 필요합니다. 파이썬 설치시 기본으로 설치된 라이브러리이므로, 별도의 설치는 필요없습니다.
> 링크 (smtplib 라이브러리 문서) : docs.python.org/ko/3/library/smtplib.html
email 라이브러리 또한 기본 라이브러리로 별도의 설치는 필요 없습니다. 이메일 메시지를 관리하며, 한글 또는 파일을 첨부하여 메시지를 보낼 때 편리합니다.
> 링크 (email 라이브러리 문서) : docs.python.org/ko/3/library/email.html
2-2) 발신(send) 메일 SMTP 설정
네이버 이메일 계정을 준비합니다. 수신, 발신용 2개의 이메일 주소를 권장하지만, 1개의 이메일이 있어도 상관없습니다. 계정이 2개인 경우는 수신/송신 에 대한 이해를 좀 더 쉽게 할 수 있는 장점이 있습니다.
발신(send)용 메일 계정에 SMTP를 ‘사용’으로 설정합니다.
순서 : [환경설정] 버튼 클릭 > [POP3/IMAP 설정] 버튼 클릭 > [IMAP/SMTP설정] 탭 클릭 > [사용함] 선택 > [확인] 버튼 클릭 |
[그림 4] SMTP 설정
POP3와 IMAP은 메일을 받을 때(수신, receive)의 프로토콜이며, SMTP(Simple Mail Transfer Protocol)는 메일을 보낼 때(발신, send)의 프로토콜입니다.
설정 후 코딩에 사용할 내용은 6번, 7번에 해당되는 내용입니다.
– SMTP 서버명 : smtp.naver.com
– SMTP 포트 : 587, 보안 연결(TLS) 필요
3. 사전 지식 쌓기
지식을 쌓을 내용은 다음과 같습니다.
3-1) 이메일 전송 흐름
3-2) 메일 내용 작성 및 파일 첨부 하기
3-1) 이메일 전송 흐름
[그림 5] 이메일 전송 흐름
SMTP는 Simple Mail Transfer Protocol의 약자
POP3는 Post Office Protocol의 약자
IMAP은 Internet Message Access Protocol약자 입니다.
모두 Protocol이라는 단어로 끝납니다. Protocol이란, 통신에 있어 상호간에 약속한 ‘규약’입니다.
규약에 맞춰 서버들이 통신을 하고, 목적에 맞게 동작한다고 생각하면 됩니다.
즉, 편지를 쓰면 우체국에 모았다가 받는 사람의 우체국을 통해 개개인에게 전달 되듯, 인터넷 상에서도 우체국 역할을 해주는 ‘서버’가 있습니다. SMTP/POP3/IMAP서버가 바로 그 역할을 합니다.
이메일을 보내면 SMTP 서버(우체국 역할)에 저장하고, 받을 사람의 POP3서버나 IMAP서버(우체국 역할)로 보내지게 됩니다. 이 흐름과 규약에 맞춰 메일을 주고 받을 수 있게 됩니다.
그럼 이제 이메일을 보내는 작업을 프로그램화 한다고 생각해봅시다. 메일을 보내기 위해 여러분이 하는 행동을 잘 생각해보세요.
[그림 6] 이메일 전송 순서
첫번째, 발신자의 계정으로 로그인을 합니다. id와 password를 이용하죠.
두번째, 메일을 작성합니다.
세번째, ‘보내기’ 버튼을 눌러 메일 보내기를 ‘요청’ 합니다.
그럼 뒷단에서는 SMTP 프로토콜과 POP3/IMAP 프로토콜을 이용하여 각 서버들이 통신을 거처 수신자의 계정으로 메일이 도착하게 됩니다.
이를 그대로 코드로 옮겨 보겠습니다.
코드
import smtplib
smtp_info = dict({"smtp_server" : "smtp.naver.com", # SMTP 서버 주소
"smtp_user_id" : "<송신자(sender) 메일 계정>@naver.com",
"smtp_user_pw" : "<송신자(sender) 메일 패스워드>" ,
"smtp_port" : 587}) # SMTP 서버 포트
def send_email(smtp_info, msg):
with smtplib.SMTP(smtp_info["smtp_server"], smtp_info["smtp_port"]) as server:
# TLS 보안 연결
server.starttls()
# 로그인
server.login(smtp_info["smtp_user_id"], smtp_info["smtp_user_pw"])
# 로그인 된 서버에 이메일 전송
response = server.sendmail(msg['from'], msg['to'], msg.as_string()) # 메시지를 보낼때는 .as_string() 메소드를 사용해서 문자열로 바꿔줍니다.
# 이메일을 성공적으로 보내면 결과는 {}
if not response:
print('이메일을 성공적으로 보냈습니다.')
else:
print(response)
코드 설명
1라인 : 이메일 전송을 위한 smtplib 라이브러리를 import합니다.
3~6라인 : 전송(send)을 위한 정보입니다. smtp_server는 보내질 서버로 [그림 4]의 6번에 해당합니다. smtp_user_id는 보낼 사람의 메일 id이며, smtp_user_pw는 password입니다. 이 두 정보는 이후 로그인을 할 때 사용합니다. smtp_port는 [그림 4]의 7번에 해당합니다.
8~22라인 : 메일 전송을 위한 함수입니다. 발신자의 정보(smtp_info)와 수신자정보와 보낼 메시지를 담은 정보(msg)를 입력으로 받습니다. SMTP 서버를 연결하고, TLS 보안을 사용하는 포트이므로 ([그림 4] 참고) 이므로 TLS 보안을 연결합니다. 13라인은 발신자 계정으로 로그인을 하고, 16라인은 메시지를 전송하게 됩니다.
※ “Step2) 텍스트로만 구성된 이메일 보내기”에서 이메일을 보내보세요.
실제 메일을 보내는 순서와 프로그램이 유사하죠?
3-2) 메일 내용 작성 및 파일 첨부 하기
메일 내용을 작성 할 때, 간단하게 text로만 구성을 할 수도 있지만, 파일을 첨부할 수도 있습니다.
email 라이브러리를 사용하여, 쉽게 작업을 할 수 있습니다.
> 링크 (email 라이브러리 문서) : docs.python.org/ko/3/library/email.html
> 링크 (email 라이브러리 quick-start) : docs.python.org/ko/3.4/library/email-examples.html
email 라이브러리를 통해 다양한 종류의 메일을 보낼 수 있습니다. html 로 구성된 메일, 파일 첨부등을 할 수 있습니다.
이 장에서는 text로 구성된 메일 및 파일 첨부 하는 방법에 대해서만 학습하도록 하겠습니다.
text로 구성된 메일을 보낼 경우는 MIMEText 함수를 사용하면 됩니다.
image를 보낼 경우는 MIMEImage 함수, audio를 보내고 싶다면 MIMEAudio 함수를, 그외의 MIMEBase 함수를 호출합니다. 즉, 보내고자 하는 media의 종류에 따라 호출하는 함수가 다릅니다.
또한 다양한 종류의 메시지를 한번에 보내고 싶을 경우 MIMEMultipart 함수를 사용해서 각 객체를 추가하면 됩니다.
코드
# 이메일 메시지에 다양한 형식을 중첩하여 담기 위한 객체
from email.mime.multipart import MIMEMultipart
# 이메일 메시지를 이진 데이터로 바꿔주는 인코더
from email import encoders
# 텍스트 형식
from email.mime.text import MIMEText
# 이미지 형식
from email.mime.image import MIMEImage
# 오디오 형식
from email.mime.audio import MIMEAudio
# 위의 모든 객체들을 생성할 수 있는 기본 객체
# MIMEBase(_maintype, _subtype)
# MIMEBase(<메인 타입>, <서브 타입>)
from email.mime.base import MIMEBase
msg_dict = {
'text' : {'maintype' : 'text', 'subtype' :'plain', 'filename' : 'test.txt'}, # 텍스트 첨부파일
'image' : {'maintype' : 'image', 'subtype' :'jpg', 'filename' : 'test.jpg' }, # 이미지 첨부파일
'audio' : {'maintype' : 'audio', 'subtype' :'mp3', 'filename' : 'test.mp3' }, # 오디오 첨부파일
'video' : {'maintype' : 'video', 'subtype' :'mp4', 'filename' : 'test.mp4' }, # 비디오 첨부파일
'application' : {'maintype' : 'application', 'subtype' : 'octect-stream', 'filename' : 'test.pdf'} # 그외 첨부파일
}
def make_multimsg(msg_dict):
multi = MIMEMultipart(_subtype='mixed')
for key, value in msg_dict.items():
# 각 타입에 적절한 MIMExxx()함수를 호출하여 msg 객체를 생성한다.
if key == 'text':
with open(value['filename'], encoding='utf-8') as fp:
msg = MIMEText(fp.read(), _subtype=value['subtype'])
elif key == 'image':
with open(value['filename'], 'rb') as fp:
msg = MIMEImage(fp.read(), _subtype=value['subtype'])
elif key == 'audio':
with open(value['filename'], 'rb') as fp:
msg = MIMEAudio(fp.read(), _subtype=value['subtype'])
else:
with open(value['filename'], 'rb') as fp:
msg = MIMEBase(value['maintype'], _subtype=value['subtype'])
msg.set_payload(fp.read())
encoders.encode_base64(msg)
# 파일 이름을 첨부파일 제목으로 추가
msg.add_header('Content-Disposition', 'attachment', filename=value['filename'])
# 첨부파일 추가
multi.attach(msg)
return multi
코드 설명
1~17라인 : 필요한 라이브러리를 import합니다.
19~25라인 : 추가할 메시지에 대한 정보를 담습니다. key에는 media type을 의미하고, value에는 maintype, subtype, filename 정보를 담습니다. 첨부 파일을 없애고 싶다면, 빼고 싶은 라인에 주석표시(#)를 하고, 추가하고 싶다면 라인을 추가하여 쉽고, 편하게 첨부 파일을 관리하도록 구현하였습니다.
27~52라인 : 다양한 종류의 메시지를 추가해주는 함수입니다. 메시지의 정보를 담은 msg_dict를 입력으로 받습니다. msg_dict는 key와 value로 이루어져 있습니다. key에 해당하는 media type별로 적절한 함수를 호출해줍니다. msg_dict가 여러개인 경우, 반복문으로 반복 수행하고 42라인처럼 attach함수를 통해 객체를 추가합니다.
4. 구현
이번장에서는 두가지 종류의 이메일을 보내보겠습니다.
텍스트로만 구성된 이메일과 다양한 종류의 첨부파일이 있는 이메일입니다.
[코드 파일]
– email_sending.ipynb
[구현 순서]
점진적으로 구현을 완성해 갈 수 있도록 Step1 > Step2, Step3 순서로 진행되며, 목적에 맞게 Step2와 Step3을 사용하세요.
Step1 | 이메일 전송 함수 만들기 |
Step2 | 텍스트로만 구성된 이메일 보내기 |
Step3 | 텍스트와 다양한 종류의 파일이 첨부된 이메일 보내기 |
Step1) 이메일 전송 함수 만들기
코드
import smtplib
# 이메일 메시지에 다양한 형식을 중첩하여 담기 위한 객체
from email.mime.multipart import MIMEMultipart
# 이메일 메시지를 이진 데이터로 바꿔주는 인코더
from email import encoders
# 텍스트 형식
from email.mime.text import MIMEText
# 이미지 형식
from email.mime.image import MIMEImage
# 오디오 형식
from email.mime.audio import MIMEAudio
# 위의 모든 객체들을 생성할 수 있는 기본 객체
# MIMEBase(_maintype, _subtype)
# MIMEBase(<메인 타입>, <서브 타입>)
from email.mime.base import MIMEBase
def send_email(smtp_info, msg):
with smtplib.SMTP(smtp_info["smtp_server"], smtp_info["smtp_port"]) as server:
# TLS 보안 연결
server.starttls()
# 로그인
server.login(smtp_info["smtp_user_id"], smtp_info["smtp_user_pw"])
# 로그인 된 서버에 이메일 전송
response = server.sendmail(msg['from'], msg['to'], msg.as_string()) # 메시지를 보낼때는 .as_string() 메소드를 사용해서 문자열로 바꿔줍니다.
# 이메일을 성공적으로 보내면 결과는 {}
if not response:
print('이메일을 성공적으로 보냈습니다.')
else:
print(response)
def make_multimsg(msg_dict):
multi = MIMEMultipart(_subtype='mixed')
for key, value in msg_dict.items():
# 각 타입에 적절한 MIMExxx()함수를 호출하여 msg 객체를 생성한다.
if key == 'text':
with open(value['filename'], encoding='utf-8') as fp:
msg = MIMEText(fp.read(), _subtype=value['subtype'])
elif key == 'image':
with open(value['filename'], 'rb') as fp:
msg = MIMEImage(fp.read(), _subtype=value['subtype'])
elif key == 'audio':
with open(value['filename'], 'rb') as fp:
msg = MIMEAudio(fp.read(), _subtype=value['subtype'])
else:
with open(value['filename'], 'rb') as fp:
msg = MIMEBase(value['maintype'], _subtype=value['subtype'])
msg.set_payload(fp.read())
encoders.encode_base64(msg)
# 파일 이름을 첨부파일 제목으로 추가
msg.add_header('Content-Disposition', 'attachment', filename=value['filename'])
# 첨부파일 추가
multi.attach(msg)
return multi
코드 설명
‘3. 사전 학습 쌓기’ 에서 구현한 함수입니다. 자세한 설명은 해당 챕터에서 확인하세요.
Step2) 텍스트로만 구성된 이메일 보내기
제목과 내용으로만 구성된 이메일을 보내보겠습니다.
코드
from email.mime.text import MIMEText
smtp_info = dict({"smtp_server" : "smtp.naver.com", # SMTP 서버 주소
"smtp_user_id" : "<송신자(sender) 메일 계정>@naver.com",
"smtp_user_pw" : "<송신자(sender) 메일 패스워드>",
"smtp_port" : 587}) # SMTP 서버 포트
# 메일 내용 작성
title = "기본 이메일 입니다."
content = "메일 내용입니다."
sender = "<송신자(sender) 메일 계정>@naver.com"
receiver = "<수신자(receiver) 메일 주소>@naver.com"
# 메일 객체 생성 : 메시지 내용에는 한글이 들어가기 때문에 한글을 지원하는 문자 체계인 UTF-8을 명시해줍니다.
msg = MIMEText(_text = content, _charset = "utf-8") # 메일 내용
msg['Subject'] = title # 메일 제목
msg['From'] = sender # 송신자
msg['To'] = receiver # 수신자
send_email(smtp_info, msg )
실행 결과
코드 설명
1~6 라인 : ‘3. 사전 지식 쌓기’를 참고하세요.
9~12라인 : 이메일 내용을 작성에 필요한 정보입니다. 제목, 내용, 송신자, 수신자가 필요합니다.
15라인 : text로만 구성된 이메일을 보냄으로 MIMEText 함수로 객체를 생성합니다.
17~19라인 : 생성된 객체에 제목, 송신자, 수신자 정보를 추가해줍니다.
22라인 : 이메일 전송을 요청합니다.
Step3) 텍스트와 다양한 종류의 파일이 첨부된 이메일 보내기
제목, 내용뿐만 아니라 첨부파일이 있는 이메일을 보내보겠습니다.
코드
smtp_info = dict({"smtp_server" : "smtp.naver.com", # SMTP 서버 주소
"smtp_user_id" : "<송신자(sender) 메일 계정>@naver.com"
"smtp_user_pw" : "<송신자(sender) 메일 패스워드>"
"smtp_port" : 587}) # SMTP 서버 포트
msg_dict = {
'text' : {'maintype' : 'text', 'subtype' :'plain', 'filename' : 'test.txt'}, # 텍스트 첨부파일
'image' : {'maintype' : 'image', 'subtype' :'jpg', 'filename' : 'test.jpg' }, # 이미지 첨부파일
'audio' : {'maintype' : 'audio', 'subtype' :'mp3', 'filename' : 'test.mp3' }, # 오디오 첨부파일
'video' : {'maintype' : 'video', 'subtype' :'mp4', 'filename' : 'test.mp4' }, # 비디오 첨부파일
'application' : {'maintype' : 'application', 'subtype' : 'octect-stream', 'filename' : 'test.pdf'} # 그외 첨부파일
}
#####################
# 메일 내용 작성
#####################
title = "첨부파일이 있는 이메일입니다."
content = "메일 내용입니다."
sender = "<송신자(sender) 메일 계정>@naver.com"
receiver = "<수신자(receiver) 메일 주소>@naver.com"
# 메일 내용
msg = MIMEText(_text = content, _charset = "utf-8")
# 첨부파일 추가
multi = make_multimsg(attach_dict)
multi['subject'] = title
multi['from'] = sender
multi['to'] = receiver
multi.attach(msg)
# 첨부파일이 추가된 이메일 전송
send_email(smtp_info, multi )
실행 결과
코드 설명
1~12 라인 : ‘3. 사전 지식 쌓기’를 참고하세요.
16~19라인 : 메일 내용을 작성에 필요한 정보입니다. 제목, 내용, 송신자, 수신자가 필요합니다.
22라인 : text로만 구성된 객체를 만들기 위해 MIMEText 함수를 호출합니다.
26라인 : 다양한 첨부파일에 대해서 객체를 만듭니다.
27~30라인 : 생성된 객체에 제목, 송신자, 수신자 정보를 추가해주고, text객체와 첨부파일 객체를 attach함수를 통해 붙여줍니다.
33라인 : 이메일 전송을 요청합니다.
5. 요약정리
이번 장에서는 이메일 자동 전송 프로젝트를 진행해 보았습니다.이메일 전송 흐름에 대해 이해 했습니다. 이메일 전송방법을 프로그램으로 작성할 때, 이제까지 이메일을 보낸 흐름과 동일하다는 점을 알게 되었습니다. 로그인 -> 메일 작성 -> 보내기 요청 순서로 말이죠. smtplib과 email 두가지 라이브러리를 사용하여, 텍스트로만 작성된 이메일, 다양한 첨부파일이 추가된 이메일을 보내보았습니다.
이메일 자동 전송 프로젝트의 학습을 마치셨다면, 주식분석 자동화 프로젝트를 하실 수 있습니다. 주식 뿐만이 아니라, 크롤링을 통해 분야별 트렌드를 모아 주기적으로 이메일로 전송해 보는건 어떨까요?
ㅁ trouble shooting
import socket
socket.gethostname()
출력된 결과에 한글이 있다면, 컴퓨터 이름을 모두 영어로 변경할 것
> 변경 방법 : 내 PC > 오른쪽마우스 > 설정 > 컴퓨터 이름, 도메인 및 작업 그룹 설정의 ‘설정 변경’ > 변경 > 영어 이름 변경
3) 라이브러리 설치하기
다음은 설치가 필요한 라이브러리 목록입니다.
$ pip install matplotlib
$ pip install pandas
matplotlib 라이브러리는 그래프를 그리는데 도움을 주는 라이브러리 입니다.
[그림 3] matplotlib에서 지원하는 그래프 예시
> 접속 URL : matplotlib.org/
해당 url로 접속해 보면, 다양한 종류의 그래프를 볼 수 있으며 마음에 드는 그래프를 클릭하면 소스코드들도 제공하고 있는 유용한 사이트라고 생각이 들겁니다.
pandas 라이브러리는 데이터분석용에 특화된 라이브러리 입니다. 이번 장에서는 주식데이터를 크롤링하고, 주식 데이터를 예쁘게 정제할 때 사용합니다.
3. 사전 지식 쌓기
주식 분석 보고서 자동화 프로젝트 진행에 앞서 지식을 쌓을 내용은 다음과 같습니다.
3-1) 종목 코드 가져오기
특정 회사의 일별시세를 가져오고 싶다면, 제일 먼저 알아야 하는 것이 ‘종목 코드’입니다.
다음 그림은 ‘네이버 금융’에서 ‘삼성전자’를 검색한 결과 인데요,
삼성전자 글자 옆에 ‘005930’이라는 6자리 숫자값과, url에서 보면 code= 옆에 6자리 숫자가 동일합니다.
[그림 4] 종목 코드
주식 종목코드는 상장사의 식별코드인데요, 마치 사람에게 있어서는 주민번호랑 동일합니다. 그러므로, 회사별 종목 코드를 알아야 프로그램으로 일별 시세를 가지고 오기 편하겠죠.
이러한 종목코드는 공개되어 있고, 한국거래소에서 다운로드 받을 수 있습니다.
> 한국 거래소 종목 코드 다운로드 : kind.krx.co.kr/corpgeneral/corpList.do?method=download
해당 URL을 접속하면 ‘상장법인목록.xls’ 파일이 다운로드 되는데요. 이 파일을 열어보면 다음과 같습니다.
원하는 종목코드 값 뿐만이 아니라, 업종, 대표자명, 지역까지 정보가 들어가 있네요. 여기서 ‘회사명’과 ‘종목코드’를 사용할 것입니다.
[그림 5] ‘상장법인목록.xls’ 파일
코드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | import pandas as pd def get_stock_code(): # 종목코드 다운로드 stock_code = pd.read_html(‘http://kind.krx.co.kr/corpgeneral/corpList.do?method=download’, header=0)[0] # 필요없는 column들은 제외 stock_code = stock_code[[‘회사명’, ‘종목코드’]] # 한글 컬럼명을 영어로 변경 stock_code = stock_code.rename(columns={‘회사명’: ‘company’, ‘종목코드’: ‘code’}) # 종목코드가 6자리이기 때문에 6자리를 맞춰주기 위해 설정해줌 stock_code.code = stock_code.code.map(‘{:06d}’.format) return stock_code | cs |
실행 결과
get_stock_code()를 호출하면, 회사명(company)와 종목코드(code)만 있는 데이터 프로그램이 출력됩니다.
[그림 6] get_stock_code() 호출 결과
코드 설명
1라인 : 필요한 라이브러리를 import 합니다.
3~22라인 : 종목코드를 가지고와서 필요한 정보만 추출하는 함수를 정의합니다. 5라인에서 종목 코드를 다운로드 받습니다. url은 한국거래소에서 상장법인목록을 엑셀로 다운로드하는 링크입니다. 다운로드를 받고, pandas에서 관리하는 data frame 자료구조로 변경시킵니다. 8라인은 사용할 컬럼만 추출합니다. [그림 5] ‘상장법인목록.xls’ 파일에서 보이는 많은 컬럼 중에 “회사명”과 “종목코드” 만 사용할 것입니다. 11라인은 컬럼명을 한글에서 영어로 변경합니다. 코딩을 편하게 하기 위해서 변경한 것이고, 특별한 이유는 없습니다. 14라인에서는 종목 코드는 6자리를 의미하고, 부족한 숫자는 앞에 0으로 채워져 있습니다. 이 형태에 맞춰서 변경을 시켰습니다.
3-2) 일별 시세 가져오기
특정 회사의 일별 시세 정보를 가져와 보겠습니다. 네이버 금융에서 크롤링합니다.
[그림 4] 종목 코드에서 네이버 금융의 URL 구조를 보면, code=종목코드 6자리로 설정해주어야 합니다.
일별시세 URL은 finance.naver.com/item/sise_day.nhn?code={종목코드} 라는 것을 알 수 있습니다.
그리고, 한 페이지당 2주치의 정보만 보여주고 있는데요, 더 많은 정보를 보려면 아래 숫자를 클릭하면 됩니다. 클릭하면서 변화하는 URL을 관찰해보세요. page={페이지숫자}로
변경됨을 알 수 있습니다.
[그림 7] 네이버 금융 URL 구조
이제 느낌이 오시죠? code={종목코드}&page={페이지숫자} 로 URL을 구성해서 크롤링하면 됩니다. 종목코드는 3-1) 에서 가지고 정보를 사용하면 되겠죠?
(정리)
– 종목 검색 : https://finance.naver.com/item/sise_day.nhn?code={종목코드}
– 일별 시세 구조 : https://finance.naver.com/item/sise_day.nhn?code={종목코드}&page={페이지번호}
코드
1 2 3 4 5 6 7 8 9 10 11 12 13 | import pandas as pd def get_stock(code): df = pd.DataFrame() for page in range(1,21): # 일별 시세 url url = ‘{url}&page={page}’.format(url=url, page=page) print(url) current_df = pd.read_html(url, header=0)[0] df = df.append(current_df, ignore_index=True) return df | cs |
실행 결과
get_stock() 호출해보세요.
code = '005930' # 삼성전자 종목코드
df = get_stock(code)
df.head()
[그림 8] 일별 시세 함수 호출 결과
코드 설명
1라인 : 필요한 라이브러리를 import합니다.
3~12라인 : 일별 시세를 가져오는 함수를 정의합니다. 종목코드를 입력받소, 일별시세 정보를 전달하는 함수입니다. 5라인에서 반복문을 도는데요, 원하는 페이지 수만큼 반복하여 일별시세를 크롤링하려고 합니다. 여기서는 총 20페이지까지만 크롤링 합니다. 20페이지면 총 40주간의 데이터이네요. 더 필요하신 분들은 숫자를 늘려보세요. 7~8라인은 네이버금융의 URL구조에 맞춰 필요한 정보를 추가하였습니다. 10라인에서는 url에 해당하는 일별 시세를 가지고 옵니다. 20번 반복하는 동안 데이터가 계속 누적되어야 하니, df.append()를 호출합니다. df라는 이름을 가진 변수에 누적하는 역할을 합니다.
종목코드를 모두 외워서 사용할 수는 없겠지요. 우리에게 더 친숙한 것은 회사명입니다. 그래서 ‘4. 구현’ 에서는 회사명을 주면, 회사명에 해당하는 종목 코드를 추출하여 일별시세를 요청하도록 코딩해보겠습니다.
4. 구현
주식 분석 보고서 자동화 프로젝트를 위한 구현 순서는 아래와 같습니다.
주식 종목 코드를 사용하여, 일별 시세를 크롤링합니다. 크롤링한 자료로 간단한 차트 그리고, 파일로 저장합니다. 이 차트를 사용하여 분석보고서를 만듭니다. 마지막으로 분석 보고자료를 이메일로 전송합니다.
주식 분석 보고서 자동화 프로젝트 구현 순서
[구현 순서]
점진적으로 구현을 완성해 나가봅시다.
Step1 | 종목 코드 및 일별 시세 가져오기 |
Step2 | 보고 자료 준비하기 |
Step3 | 보고서 작성하기 |
Step4 | 보고서를 이메일로 전송하기 |
Step1) 종목 코드 및 일별 시세 가져오기
우리에게 더 친숙한 것은 종목코드 보다는 회사명입니다.
회사명으로 종목코드를 구하고, 일별 시세를 가져오는 순서로 진행합니다.
코드
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 | import pandas as pd ################################# ## 함수 정의 ################################# def get_stock_code(): # 종목코드 다운로드 stock_code = pd.read_html(‘http://kind.krx.co.kr/corpgeneral/corpList.do?method=download’, header=0)[0] # 필요없는 column들은 제외 stock_code = stock_code[[‘회사명’, ‘종목코드’]] # 한글 컬럼명을 영어로 변경 stock_code = stock_code.rename(columns={‘회사명’: ‘company’, ‘종목코드’: ‘code’}) # 종목코드가 6자리이기 때문에 6자리를 맞춰주기 위해 설정해줌 stock_code.code = stock_code.code.map(‘{:06d}’.format) return stock_code def get_stock(code): df = pd.DataFrame() for page in range(1,21): # 일별 시세 url url = ‘{url}&page={page}’.format(url=url, page=page) print(url) current_df = pd.read_html(url, header=0)[0] df = df.append(current_df, ignore_index=True) return df def clean_data(df): # df.dropna()를 이용해 결측값 있는 행 제거 df = df.dropna() # 한글로 된 컬럼명을 영어로 바꿔줌 df = df.rename(columns= {‘날짜’: ‘date’, ‘종가’: ‘close’, ‘전일비’: ‘diff’, ‘시가’: ‘open’, ‘고가’: ‘high’, ‘저가’: ‘low’, ‘거래량’: ‘volume’}) # 데이터의 타입을 int형으로 바꿔줌 df[[‘close’, ‘diff’, ‘open’, ‘high’, ‘low’, ‘volume’]] = df[[‘close’, ‘diff’, ‘open’, ‘high’, ‘low’, ‘volume’]].astype(int) # 컬럼명 ‘date’의 타입을 date로 바꿔줌 df[‘date’] = pd.to_datetime(df[‘date’]) # 일자(date)를 기준으로 오름차순 정렬 df = df.sort_values(by=[‘date’], ascending=True) return df ################################# ## 함수 호출 ################################# # 종목 코드 가져오기 company=‘삼성전자’ stock_code = get_stock_code() # 일별 시세 가져오기 code = stock_code[stock_code.company==company].code.values[0].strip() ## strip() : 공백제거 df = get_stock(code) # 일별 시세 클린징 df = clean_data(df) | cs |
실행 결과
삼성전자의 일별 시세 입니다.
현재부터 총 40주의 데이터가 누적되어 있습니다.
데이터 기간은 2020년 1월 10일 ~ 2020년 10월 30일이네요.
[그림 9] 40주간의 일별 시세 정보
코드 설명
1라인 : 필요한 라이브러리를 import 합니다.
6~19라인 : 3. 사전 지식 쌓기의 1) 종목 코드 가져오기에서 설명한 함수입니다. 해당 장을 참고하세요.
21~31라인 : 3. 사전 지식 쌓기의 2) 일별 시세 가져오기에서 설명한 함수입니다. 해당 장을 참고하세요.
33~48라인 : 일별 시세 함수를 호출한 결과를 [그림 8] 일별 시세 함수 호출 결과 에서 확인해 볼 수 있는데요. 약간 이상하다는 느낌이 드나요? 네, NaN이라는 글자가 보입니다. 이는 관측되지 않은 값을 의미하는데요, 중간중간에 관측되지 않은 결과들이 들어가 있습니다. 관측되지 않았다면, 제거하는게 좋겠지요. 35라인이 관측되지 않은 값 (=결측치)를 제거하는 코드 입니다. 38라인에서는 한글로된 컬럼명을 영어로 바꾸고, 40라인에서는 숫자값(integer)로 표현되어야 할 컬럼에 대해서는 숫자로 데이터 자료형을 변경합니다. 좀 더 설명을 하자면, 시세는 가격이므로 종가/시가/고가 등은 가격으로 표기되길 원하므로 숫자로 변경했다고 생각하면 됩니다. 43라인에서는 날짜 정보를 date자료형을 사용하고자 합니다. 단순히 문자열이 아닌, 연산을 할 수 있는 date 자료형입니다. 즉, ‘2020-10-31’에 +1을 하면, 에러가 날 것입니다. 왜냐하면 문자에다가 1을 더하는 꼴이니깐요. 즉 ‘a’에 +1을 하면 결과가 무엇인가요? 여러분도 모르시겠죠? 그런데, date라면 이야기가 다름니다. 2020년 10월 31일에서 +1을 하면 어떻게 되나요? 2020년 11월 1일이 되겠죠. 이렇듯 해당 데이터가 날짜로 인식되도록 date자료형으로 변경하였습니다. 46라인에서는 날짜를 기준으로 오름차순 정렬을 하였습니다. 그래프를 그릴때는 과거데이터를 왼쪽에 최신데이터를 오른쪽에 그려야 하므로, 이렇게 정렬을 해줍니다.
54라인 : 삼성전자에 대한 일별 시세를 가져 올 예정입니다.
55라인 : 전체 종목 코드를 가져옵니다.
58라인 : 전체 종목 코드 중 company에 해당하는 종목 코드를 추출합니다.
59라인 : 일별 시세 정보를 가져옵니다.
62라인 : 일별 시세에서 결측데이터를 삭제하는 등의 전처리를 마무리한 최종 결과를 가져옵니다.
Step2) 보고 자료 준비하기
일별 시세 정보를 이용하여 차트와 테이블을 그려보고, 각각을 파일로 저장해보겠습니다.
코드
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 | import matplotlib.pyplot as plt from pandas.plotting import table import os # %matplotlib inline 은 jupyter notebook 사용자 용 – jupyter notebook 내에 그래프가 그려지게 한다. %matplotlib inline ################################# ## 차트 그리기 ################################# plt.figure(figsize=(10,4)) plt.plot(df[‘date’], df[‘close’]) plt.xlabel(‘date’) plt.ylabel(‘close’) ################################# ## 차트 저장 및 출력하기 ################################# chart_fname = os.path.join(“res/stock_report” ,‘{company}_chart.png’.format(company=company)) plt.savefig(chart_fname) plt.show() ################################# ## 일별 시세 그리기 ################################# plt.figure(figsize=(15,4)) ax = plt.subplot(111, frame_on=False) # no visible frame ax.xaxis.set_visible(False) # hide the x axis ax.yaxis.set_visible(False) # hide the y axis df = df.sort_values(by=[‘date’], ascending=False) table(ax, df.head(10), loc=‘center’, cellLoc = ‘center’, rowLoc = ‘center’) # where df is your data frame ################################# ## 일별 시세 저장하기 ################################# table_fname = os.path.join(“res/stock_report” ,‘{company}_table.png’.format(company=company)) plt.savefig(table_fname) | cs |
실행 결과
2개의 파일이 저장되었습니다.
1) res/stock_report/삼성전자_chart.png
2) res/stock_report/삼성전자_table.png
각 이미지를 클릭해서 보죠. 일별 시세로 그래프와 테이블 이미지가 잘 생성됨을 알 수 있습니다.
수행하는 일자에 따라 그래프의 모양과 테이블의 값은 당연히 다르겠죠?
[그림 10] 삼성전자_chart.png
[그림 11] 삼성전자_table.png
코드 설명
1~3라인 : 필요한 라이브러리를 import 합니다.
6라인 : jupyter notebook이나 colab을 사용하시는 분들께만 필요한 옵션입니다. pycharm과 같은 IDE를 사용하신다면, 주석처리 해주시세요. 해당 코드의 의미는 jupyter notebook 내에 그래프를 그리라는 의미입니다. 마치 결과를 출력해주듯 말이죠. 주석 처리를 하면 별도의 창이 생성되어 그래프가 출력됩니다.
19~13라인 : matplotlib 라이브러리로 주식 차트를 그립니다. 10라인에서 차트의 크기를 설정합니다. 11라인은 x축은 date를 y측은 close 값을 사용해서 차트를 그리겠다는 의미입니다. 12~13라인에서는 x축은 date로 y축에는 close로 이름을 붙여 주었습니다.
18~20라인 : 보고서에 첨부할 차트를 이미지 파일로 저장합니다.
25~30라인 : 일별 시세를 표 형태를 가진 그래프로 표현합니다. 25라인은 표의 크기를 설정하였습니다. 시세의 경우는 과거부터 보는 것보다는 최신데이터를 먼저 보는 것이 좋으므로, 29라인에서 date기준으로 내림차순으로 정렬하였습니다.
34~35라인 : 일별 시세를 이미지 파일로 저장합니다.
저장된 두 이미지 파일로 보고서를 만들어 봅시다.
Step3) 보고서 작성하기
보고서는 파워포인트로 만들어보겠습니다.
제목 장표 1장, 차트 및 테이블로 구성된 장표 1장으로 총 2장을 만들어 보겠습니다.
파워포인트로 보고서를 작성하는 방법은‘보고서 자동화 프로젝트’ 에서 다뤄보았습니다. 코드에서 궁금한 내용들이 있다면, 해당 장을 참고하세요.
코드
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 | import datetime from pptx import Presentation # 라이브러리 from pptx.util import Inches # 사진, 표등을 그리기 위해 import os ################################# ## 파워포인트 객체 선언 ################################# today = datetime.datetime.today().strftime(‘%Y%m%d’) prs = Presentation() # 파워포인트 객체 선언 ################################# ## 제목 장표 추가 ################################# title_slide_layout = prs.slide_layouts[0] # 0 : 제목슬라이드에 해당 slide = prs.slides.add_slide(title_slide_layout) # 제목 슬라이드를 파워포인트 객체에 추가 # 제목 – 제목에 값넣기 title = slide.shapes.title # 제목 title.text = “주식 보고서” # 제목에 값 넣기 # 부제목 subtitle = slide.placeholders[1] # 제목상자는 placeholders[0], 부제목상자는 [1] subtitle.text = “보고서 작성일 : {date}”.format(date=today) ################################# ## 차트 및 테이블 장표 추가 ################################# title_only_slide_layout = prs.slide_layouts[5] slide = prs.slides.add_slide(title_only_slide_layout) shapes = slide.shapes shapes.title.text = ‘{company}, {close}원에 거래 마감’.format(company=company, close=df.iloc[0][‘close’]) print(shapes.title.text) # 차트 추가 left = Inches(0.5) height = Inches(2.5) width = Inches(9) top = Inches(2) # width, hegith가 없을 경우 원본 사이즈로 pic = slide.shapes.add_picture(chart_fname, left, top, width=width,height=height) # 테이블 추가 left = Inches(–1) height = Inches(3) width = Inches(12) top = Inches(4) pic = slide.shapes.add_picture(table_fname, left, top, width=width,height=height) cursor_sp = slide.shapes[0]._element cursor_sp.addprevious(pic._element) # 해당 요소를 뒤로 보내기 합니다. ################################# ## 보고서 저장 ################################# ppt_fname = os.path.join(“res/stock_report” ,‘stock_report.pptx’) prs.save(ppt_fname) | cs |
실행 결과
저장된 stock_report.pptx를 열어보면 다음과 같이 2개의 장표로 구성되어 있습니다.
[그림 12] stock_report.pptx 구성 장표
주식 보고서에 추가하고 싶은 정보가 있다면, 자유롭게 추가해서 구성해 보세요. 나만의 주식 보고서를 만들면 더욱 재미난 프로젝트가 될 것입니다.
코드 설명
1~4라인 : 필요한 라이브러리를 import 합니다.
9~10라인 : 파워포인트 객체를 선언합니다.
15~24라인 : 제목 장표를 추가합니다. 제목은 ‘주식 보고서’이며, 부제목으로 ‘보고서 작성일’을 기재하였습니다.
29~52라인 : 장표를 하나 더 추가하여 차트와 테이블 정보를 위치시킵니다.
57~58라인 : 보고서를 저장합니다.
Step4) 보고서를 이메일로 전송하기
작성된 보고서를 이메일로 전송해보겠습니다. 이메일 보내기도 ‘이메일 전송 자동화 프로젝트에서 다뤘는데요. 코드를 보다가 궁금해지면 해당 장을 참고하시면 됩니다.
코드
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 88 89 90 91 92 93 94 95 96 97 | import smtplib # 이메일 메시지에 다양한 형식을 중첩하여 담기 위한 객체 from email.mime.multipart import MIMEMultipart # 이메일 메시지를 이진 데이터로 바꿔주는 인코더 from email import encoders # 텍스트 형식 from email.mime.text import MIMEText # 이미지 형식 from email.mime.image import MIMEImage # 오디오 형식 from email.mime.audio import MIMEAudio # 위의 모든 객체들을 생성할 수 있는 기본 객체 # MIMEBase(_maintype, _subtype) # MIMEBase(<메인 타입>, <서브 타입>) from email.mime.base import MIMEBase ################################# ## 함수 정의 ################################# def send_email(smtp_info, msg): with smtplib.SMTP(smtp_info[“smtp_server”], smtp_info[“smtp_port”]) as server: # TLS 보안 연결 server.starttls() # 로그인 server.login(smtp_info[“smtp_user_id”], smtp_info[“smtp_user_pw”]) # 로그인 된 서버에 이메일 전송 response = server.sendmail(msg[‘from’], msg[‘to’], msg.as_string()) # 메시지를 보낼때는 .as_string() 메소드를 사용해서 문자열로 바꿔줍니다. # 이메일을 성공적으로 보내면 결과는 {} if not response: print(‘이메일을 성공적으로 보냈습니다.’) else: print(response) def make_multimsg(msg_dict): multi = MIMEMultipart(_subtype=‘mixed’) for key, value in msg_dict.items(): # 각 타입에 적절한 MIMExxx()함수를 호출하여 msg 객체를 생성한다. if key == ‘text’: with open(value[‘filename’], encoding=‘utf-8’) as fp: msg = MIMEText(fp.read(), _subtype=value[‘subtype’]) elif key == ‘image’: with open(value[‘filename’], ‘rb’) as fp: msg = MIMEImage(fp.read(), _subtype=value[‘subtype’]) elif key == ‘audio’: with open(value[‘filename’], ‘rb’) as fp: msg = MIMEAudio(fp.read(), _subtype=value[‘subtype’]) else: with open(value[‘filename’], ‘rb’) as fp: msg = MIMEBase(value[‘maintype’], _subtype=value[‘subtype’]) msg.set_payload(fp.read()) encoders.encode_base64(msg) # 경로가 있는 경우, 파일의 이름만 추출 ex) res/stock_report/stock_report.pptx -> stock_report.pptx _, fname = os.path.split(value[‘filename’]) # 파일 이름을 첨부파일 제목으로 추가 msg.add_header(‘Content-Disposition’, ‘attachment’, filename = fname) # 첨부파일 추가 multi.attach(msg) return multi ################################# ## 함수 호출 ################################# smtp_info = dict({“smtp_server” : “smtp.naver.com”, # SMTP 서버 주소 “smtp_user_id” : “<송신자(sender) 메일 계정>@naver.com” , “smtp_user_pw” : “<송신자(sender) 메일 패스워드>” , “smtp_port” : 587}) # SMTP 서버 포트 msg_dict = { ‘application’ : {‘maintype’ : ‘application’, ‘subtype’ : ‘octect-stream’, ‘filename’ : ‘res/email_sending/test.pdf’} # 그외 첨부파일 } # 메일 내용 작성 title = ‘({date}). 주식 보고서 분석 자료 입니다’.format(date=today) content = ‘주식 보고서 분석 자료 입니다’ sender = “<송신자(sender) 메일 계정>@naver.com” receiver = “<수신자(receiver) 메일 주소>@naver.com” msg = MIMEText(_text = content, _charset = “utf-8”) # 첨부파일 추가 msg_dict[‘application’][‘filename’] = ppt_fname multi = make_multimsg(msg_dict) multi[‘Subject’] = title multi[‘From’] = sender multi[‘To’] = receiver multi.attach(msg) # 이메일 전송 send_email(smtp_info, multi ) | cs |
실행 결과
이메일이 도착했습니다.
[그림 13] 이메일 도착
메일 제목도, 첨부파일도 설정한대로 잘 전송이 되었습니다.
[그림 14] 주식 분석 보고서 메일 내용 상세
코드 설명
1~67라인 : todo ‘5장 이메일 전송 자동화 프로젝트’에서 학습한 내용입니다.
72~75라인 : 이메일 내용을 작성에 필요한 정보입니다. 제목, 내용, 송신자, 수신자가 필요합니다.
77~78라인 : 첨부파일에 대해서 객체를 만듭니다. 2개이상의 첨부파일을 넣을 경우, 추가를 하면 됩니다. filename이 임시로 test.pdf로 설정되어 있지만, 89라인에서 보고서 파일명으로 변경됩니다.
81~86라인 : 생성된 객체에 제목, 송신자, 수신자 정보를 추가하여 Text형의 메일 형태를 만듭니다.
89~94라인 : text객체와 첨부파일 객체를 attach함수를 통해 추가해줍니다.
97라인 : 이메일 전송을 요청합니다.
5. 요약정리
이번 장에서는 주식 분석 보고서 자동화 프로젝트를 완성해보았습니다.
주식 종목 코드를 사용하여, 일별 시세를 크롤링했습니다. 일별 시세 값을 저장할 수 있다는것만으로도 다양한 활용을 할 수 있게 됩니다. 또한 크롤링한 데이터를 사용해서 간단한 차트를 그리고, 이 차트를 사용하여 분석보고서를 만들어보았습니다. 그리고 마지막으로 분석 보고자료를 이메일로 전송합니다.
한단계 한단계가 버릴 정보가 없습니다. 주식에 대해서 잘 아시는 분들이라면, 일별 시세 값을 이용해서 다양한 차트를 그리실 수 있을 것이고, 필요한 정보들을 추가 구성한 파워포인트를 만들 수 있습니다. 즉, 나만의 주식 보고서를 만들 수 있는 것이지요.
어떠세요? 재테크를 도와줄 멋진 프로젝트인가요?
이번 프로젝트에서는 ‘삼성전자’만 수행하도록 만들었습니다. 혹시 두곳 이상의 회사 정보를 받고 싶다면, 반복문만 추가하면 됩니다. 코드의 어느 부분을 반복하고, 어느 부분은 반복하지 말아야 하는지도 같이 고민해보세요. 여러분의 실력이 향상될 것입니다.