[파이썬] 네이버 날씨와 뉴스 자동화 - 이메일로 보내기
프로젝트/파이썬

[파이썬] 네이버 날씨와 뉴스 자동화 - 이메일로 보내기

라즈베리파이를 구매한 이유 중 하나가 계속해서 켜놓을 컴퓨터가 있었으면 좋겠다는 마음이 컸습니다.

물론 의미 없이 켜놓진 않을 테니 뭔가를 해야 했는데, 전 서버 구축과 자동화를 전부터 해보고 싶어서 오늘은 그 일환으로 자동화 프로그램을 만들었습니다.


전 밖에 나갈 때 날씨 확인하는 것을 자주 까먹어 다시 집에 들어와 옷을 껴입는다던지 우산을 챙긴다던지 하는 일이 종종 생깁니다.

근데 아침마다 이메일은 확인을 꼭 합니다.(미디엄에서 오는 뉴스레터가 꽤 흥미로운 내용이 많습니다.)

그래서 만약 이메일로 날씨와 뉴스가 전송된다면 어떨까?라는 생각을 했죠.

그렇게 해서 만들어진 것이 바로 MoT(Mail of Today) 입니다.

깃허브에 들어가서 코드를 보거나 다운로드하세요!

사용 언어: 파이썬

사용 라이브러리: email, smtplib, bs4, requests, lxml

email은 이메일 메시지를 관리하는 내장 라이브러리입니다. 이를 통해 메시지를 구성합니다.

smtplib은 이메일을 보내는 내장 라이브러리입니다. 

bs4와 requests는 크롤링을 위한 모듈이며 lxml은 html 파서입니다. 그냥 웹페이지 긁어와서 정보를 찾는데 것들로 생각하시면 됩니다.

*만약 이 글을 보며 따라 하실 계획이시면 사용하시는 이메일 제공자의 smtp 사용을 활성화하셔야 합니다.

네이버 - smtp 설정

구글 - 보안이 낮은 앱의 액세스 허용앱 비밀번호 설정 (장치는 기타로 해서 아무 이름이나 하시면 됩니다.)

**제가 설명하는 이메일 전송과 관련된 내용은 자세하지 않으며 틀릴 수도 있습니다. 이렇게 쓰는구나 정도로 읽어주시기 바랍니다.


mot.py - 이메일을 보내는 파일

import smtplib
from email.mime.text import MIMEText
from email.mime.multipart import MIMEMultipart
from datetime import date
from info import *
from mail import MAIL_FORMAT, STYLES
from scrap_news import get_news_table
from scrap_weather import get_weather_table

msg = MIMEMultipart('html')
msg['Subject'] = f'{date.today().strftime("%m/%d/%Y")}의 메일'
msg['From'] = EMAIL_ADDRESS
msg['To'] = EMAIL_ADDRESS

weather_table = get_weather_table()
news_table = get_news_table()

mail_html = MAIL_FORMAT.format(
    STYLE=STYLES, WEATHER=weather_table, NEWS=news_table)
msg.attach(MIMEText(mail_html, 'html'))

with smtplib.SMTP_SSL("smtp.gmail.com", 465) as smtp:
    smtp.ehlo()
    smtp.login(EMAIL_ADDRESS, EMAIL_PASSWORD)
    smtp.sendmail(EMAIL_ADDRESS, EMAIL_ADDRESS, msg.as_string())

MIME은 전자 우편을 위한 인터넷 표준 포맷으로 메시지를 구성하는 데 쓰입니다. 

먼저, 제목을 날짜를 가지고 월/일/년의 포맷으로 설정합니다.

저는 제 이메일로 보내면 되기 때문에 송신자와 수신자가 같습니다. (EMAIL_ADDRESS와 EMAIL_PASSWORD는 info.py 안의 값입니다.)

weather_table과 news_table은 조금 이따가 보고 밑으로 가면 mail_html은 제가 미리 만들어둔 이메일 포맷에 날씨와 뉴스 정보를 추가합니다.

그리고 이렇게 만들어진 HTML을 메시지에 추가합니다.

마지막 4줄은 실제로 이메일을 보내는 과정입니다.

SSL을 통해 제가 사용하는 지메일 서버로 접속하고 ehlo로 서버가 존재하는지 확인합니다. 

로그인 후 송신자, 수신자, 메시지의 순으로 인자로 sendmail함수를 실행하면 메시지를 수신자에게 보내게 됩니다.


scrap_weather.py - 날씨 크롤링

from bs4 import BeautifulSoup
import requests
from mail import WEATHER_FORMAT


def prepare_soup():
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36'}
    r = requests.get(
        'https://search.naver.com/search.naver?where=nexearch&sm=top_hty&fbm=1&ie=utf8&query=%EB%82%A0%EC%94%A8', headers=headers)
    bs = BeautifulSoup(r.text, 'lxml')
    return bs


def get_weather_table():
    bs = prepare_soup()
    cur_temp = bs.find('p', {'class': 'info_temperature'}
                       ).text.replace('도씨', '')
    minmax_temp = bs.find('span', {'class': 'merge'}).text.strip()
    weather = bs.find('p', {'class': 'cast_txt'}).text.strip().split(',')[0]
    fine_dust = bs.find('dl', {'class': 'indicator'}).contents[3].contents[1]
    ultrafine_dust = bs.find(
        'dl', {'class': 'indicator'}).contents[7].contents[1]
    ozone = bs.find('dl', {'class': 'indicator'}).contents[11].contents[1]
    weather_table = WEATHER_FORMAT.format(
        cur_temp, minmax_temp, weather, fine_dust, ultrafine_dust, ozone)
    return weather_table

prepare_soup 함수는 네이버에서 날씨를 검색해서 나오는 html을 가져오는데 네이버는 그냥 접속하면 접속을 거부하기 때문에 헤더를 이용해 일반 사용자인 것처럼 꾸밉니다.

get_weather_table에서는 얻은 HTML에서 현재 온도, 오늘 최고/최저 온도, 날씨, (초) 미세먼지, 오존 수치를 가져와 제가 만든 날씨 포맷에 추가하고 이를 반환합니다.


scrap_news.py - 뉴스 크롤링

from bs4 import BeautifulSoup
import requests
from mail import NEWS_TABLE_FORMAT, ARTICLE_FORMAT


def prepare_soup():
    headers = {
        'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 11_2_1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.182 Safari/537.36'}
    r = requests.get('https://news.naver.com', headers=headers)
    bs = BeautifulSoup(r.text, 'lxml')
    return bs


def get_headline_table(bs):
    articles = ''
    for img_headline in bs.find_all('a', {'class': 'lnk_hdline_main_article'}) + bs.find_all('a', {'class': 'lnk_hdline_article'}):
        link = ('https://news.naver.com' if img_headline['href'].startswith(
            '/') else '') + img_headline['href']
        title = img_headline.text.strip(' \n')
        articles += ARTICLE_FORMAT.format(link, title)
    headline_table = NEWS_TABLE_FORMAT.format(
        TYPE='헤드라인', ARTICLES=articles.rstrip('\n'))
    return headline_table


def get_specific_news_table(bs):
    news_types = ('정치', '경제', '사회', '생활/문화', '세계', 'IT/과학')
    news_table = ''
    for news_type, news_list in zip(news_types, bs.find_all('div', {'class': 'com_list'})):
        articles = ''
        for article in news_list.find_all('a'):
            if title := article.text.strip(' \n'):
                link = article['href']
                articles += ARTICLE_FORMAT.format(link, title)
        news_table += NEWS_TABLE_FORMAT.format(TYPE=news_type,
                                               ARTICLES=articles.rstrip('\n'))
    return news_table


def get_news_table():
    bs = prepare_soup()
    news_table = get_headline_table(bs) + get_specific_news_table(bs)
    return news_table

날씨 크롤링과 비슷하지만 네이버 뉴스는 헤드라인과 나머지 뉴스가 구성된 방법이 약간 달라 코드를 분리하였습니다.

위의 날씨 크롤링과 같이 제가 만든 포맷에 정보를 추가한 후 반환합니다.


mail.py - HTML 정보

STYLES = '''
<style>
    body {
        width: 70vw;
        text-align: center;
        color: rgba(96, 173, 141, 0.932);
        background-color: black;
    }
    h1 {
        color: rgb(116, 171, 253);
    }
    .container {
        width: 100%;
        margin: 10px;
    }
    .weather {
        font-size: 20px;
    }
    
    table {
        width: 100%;
        text-align: center;
        border: 2px solid;
        margin-bottom: 15px;
    }
    
    th {
        border-bottom: 1px solid;
        font-size: 16px;
        color: rgb(245, 98, 0);
        background-color: white;
        margin-bottom: 10px;
    }
    td {
        border: 1px solid rgba(32, 229, 255, 0.932);
    }
    a {
        color: white;
        text-decoration: none;
    }
</style>
'''

WEATHER_FORMAT = '''
        <table class="weather">
            <thead>
                <th>온도</th>
                <th>날씨</th>
                <th>미세먼지</th>
                <th>초미세먼지</th>
                <th>오존</th>
            </thead>
            <tbody>
                <td>{}{}</td>
                <td>{}</td>
                <td>{}</td>
                <td>{}</td>
                <td>{}</td>
            </tbody>
        </table>
'''

NEWS_TABLE_FORMAT = '''
<table>
    <thead>
        <th>{TYPE}</th>
    </thead>
    <tbody>
{ARTICLES}
    </tbody>
</table>
'''

ARTICLE_FORMAT = '''
        <tr>
            <td><a href="{}" target="_blank">{}</a></td>
        </tr>
'''

MAIL_FORMAT = '''
<!DOCTYPE html>
<html lang="ko">
<head>
    <meta charset="UTF-8">
    <title>Document</title>
</head>
{STYLE}
<body>
    <div class="container">
    <h1>오늘자 MoT</h1>
{WEATHER}
{NEWS}
    </div>
</body>
</html>'''

전체적인 구성은 테이블을 사용하였고 CSS는 썩 잘하지 못하고 부트스트랩 같은 프레임워크를 쓰기에는 따로 다운로드해서 코드를 HTML에 붙여 넣어야 해서 그냥 적당히 만들었습니다. 마음껏 수정하시기 바랍니다.

(혹시 더 나은 디자인이 있다면 pull request를 보내주시면 감사하겠습니다. 제가 만들었지만 딱히 잘 만들었다고는....)

참고로 CSS 부분이 따로 있는 이유는 중괄호가 사용되어 파이썬의 format의 사용법과 충돌하기 때문입니다.


***만약 실제로 사용하실 분들은 info.py라는 파일을 만들어서 EMAIL_ADDRESS에 본인의 이메일과 EMAIL_PASSWORD에 이메일 비밀번호(구글은 앱 비밀번호)를 입력하고 사용하는 이메일 제공자에 맞게 mot.py의 주소와 포트를 바꿔야합니다. (구글은 그대로, 네이버는 smtp.naver.com에 포트 587)


결과 사진 - 사용하는 이메일 클라이언트가 배경이 어두워서 전체적으로 네온 느낌으로 했습니다.


결과는 괜찮게 나온 것 같습니다. 뉴스 관련 테이블은 전부 링크여서 필요하면 들어가서 볼 수도 있고 디자인도 잠깐잠깐 보기에는 나쁘지 않은 것 같습니다. (그래도 더 잘하실 수 있는 분은 가능하면 pull request를....)

처음에는 테이블을 다 수작업으로 만들었는데 (반)자동 생성하니까 편하더라고요.

확실히 지금 당장 쉬운 방법보단 좀 생각을 하고 길게 보는 방향으로 생각해야겠습니다. 

라즈베리파이에서 자동으로 실행하는 방법은 따로 라즈베리파이 탭에 올리도록 하겠습니다.