이메일 보내기

|

참고
애스크장고 AskDjango

메세지를 보내는 다양한 방법

  • 이메일
    • 대량 발송 시에는 대량 발송 서비스 이용
    • 일반 네이버/지메일 계정을 통해 대량 발송 시, 스팸우려로 발송 거부 가능성 있음
    • 쇼핑몰에서 자주 사용됨
    • SMTP 프로토콜이나 특정 서비스의 API를 활용
  • 문자
    • 가장 쉬우나, 건당 발송 비용 발생
    • 선거/마트 홍보 문자 등에 사용
    • 사람들이 요즘 잘 안봄
    • 서비스 업체의 open api를 활용
  • Android/iOS 앱 푸쉬
    • 메세지 발송에는 추가 비용이 없음
    • 단, 푸쉬를 너무 많이 보내면, 고객들이 푸쉬를 끄거나, 앱을 지울수도 있음
  • 메세지 서비스 활용: 카톡, 라인, 슬랙, 텔래그램 등
    • 서비스 업체의 OpenAPI를 통해 발송 가능

이번 장에서는 네이버 메일 SMTP를 통해 이메일을 보내볼 것이다.

사전 준비

  • 네이버 메일 접속하여 POP3/SMTP 사용 활성화 (아마 기본적으로 되어있을 것이다.)
  • SMTP 접속 정보 확인
    • 서버명: smtp.naver.com
    • SMTP 포트: 465, 보안 연결(SSL)
    • 개인 아이디/ 비밀번호
import smtplib
from email.message import EmailMessage
import getpass

print('='* 30, '메일 보내기')
password = getpass.getpass('Password:')

message = EmailMessage()
message['Subject'] = '파이썬 메일 테스트'
message['From'] = '아이디@naver.com'
message['To'] = '아이디@gmail.com'
message.set_content('''이메일 내용
파이썬 이메일 테스트.

이 부분에는 이메일의 내용을 쓰실 수 있으나, HTML은 불가하다.
HTML을 쓰면 태그가 그대로 노출된다.

''')

with smtplib.SMTP_SSL('smtp.naver.com', 465) as server:
    server.ehlo()
    server.login('아이디', password)
    server.send_message(message)

print('이메일 발송')

HTML로 보내는 방법은 다음과 같다.

import smtplib
from email.message import EmailMessage
import getpass

print('='* 30, '메일 보내기')
password = getpass.getpass('Password:')

message = EmailMessage()
message['Subject'] = '파이썬 메일 테스트'
message['From'] = 'somefood@naver.com'
message['To'] = 'somefood@mikrotik.co.kr'
message.set_content('''이메일 내용
''')
message.add_alternative('''
<h1> HTML로 보내기</h1>
<ul>
    <li>파이썬</li>
    <li>자동화</li>
    <li>메일</li>
</ul>
''', subtype='html')

with smtplib.SMTP_SSL('smtp.naver.com', 465) as server:
    server.ehlo()
    server.login('somefood', password)
    server.send_message(message)

print('이메일 발송')

연습문제

  • 네이버 웹툰 목록 중 업데이트 된 웹툰 크롤링 해서 이메일로 타이틀 명 및 썸네일 사진 보내보기
# 신작 네이버 웹툰 목록을 크롤링, 요약메일 발송 해보기
# 필요한 패키지: requests, beautifulsoup4

import os
import requests
from bs4 import BeautifulSoup

import smtplib
from email.message import EmailMessage
from email.mime.application import MIMEApplication

list_url = 'http://comic.naver.com/webtoon/weekday.nhn'
html = requests.get(list_url).text
soup = BeautifulSoup(html, 'html.parser')
comic_list = []
for a_tag in soup.select('a[href*=list\.nhn]'):
    if not a_tag.select('.ico_updt'):
        continue
    img_tag = a_tag.find('img')
    title = img_tag.attrs['title'] # img 태그 내에서 title 속성 값
    img_src = img_tag['src'] # 썸네일 경로명
    img_name = os.path.basename(img_src) #썸네일 파일명
    img_data = requests.get(img_src, headers={'Referer': list_url}).content
    comic_list.append({
        'title': title,
        'img': {
            'src': img_src,
            'name': img_name,
            'data': img_data,
        },
    })

message = EmailMessage()
message['Subject'] = '웹툰 업데이트'
message['From'] = 'somefood@naver.com'
message['To'] = 'somefood@mikrotik.co.kr'
message.set_content('''이메일 내용
''')
message.add_alternative('''
<h1> 만화 목록</h1>
<ul>

''', subtype='html')

for comic in comic_list:
    with open(comic['img']['name'], 'wb') as f:
        f.write(comic['img']['data'])

    message.add_alternative('''
        <li>{}<img src="cid:{}"></li>
    '''.format(comic['title'],comic['img']['name']), subtype='html')

    with open(comic['img']['name'], 'rb') as f:
        filename = comic['img']['name']
        cid = filename
        img_data = f.read()
        part = MIMEApplication(img_data, name=filename)
        part.add_header('Content-ID', '<' + cid + '>')
        message.attach(part)

message.add_alternative('''
</ul>
''',subtype='html')

with smtplib.SMTP_SSL('smtp.naver.com', 465) as server:
    server.ehlo()
    server.login('somefood', 'elwkdldjhd123#')
    server.send_message(message)

슬랙에 음악 상위 3곡 보내기

|

슬랙봇으로 busg뮤직 상위 3곡을 보내 보았다.

사전 준비: Slack 가입, Bot 생성, Python Slacker 설치

import requests
from bs4 import BeautifulSoup
from slacker import Slacker
token = 'xoxb-12312312312312312312' # slack으로 만든 봇 ID를 입력해준다.

html = requests.get('https://music.bugs.co.kr/chart').text
soup = BeautifulSoup(html,'html.parser')

top3_list=[]

count=0 # 나는 3곡만 할꺼니가 카운트를 줬다.

for tag in soup.select('.list.trackList.byChart tbody tr'):  # bugs차트에서 top100 부분
    if count == 3:
        break # 3곡 하고 break로 종료
    else:
        titles = tag.select('th a')   # 타이틀은 th태그로 둘러쌓여있음. list화
        imgs = tag.select('td img')   # td img 태그. list화
        lyrics = tag.select('.trackInfo') # trackInfo 클래스 검색
        for title, img, lyric in zip(titles,imgs, lyrics): # zip함수를 이용하여 순서 튜플화
            lyric_src = lyric['href']                               #가사 사이트 저장
            get_lyric = requests.get(lyric['href']).text            
            lyric_soup = BeautifulSoup(get_lyric, 'html.parser')    #가사 사이트 크롤링
            lyric = lyric_soup.find('xmp').text                     #가사는 xmp태그로 둘러져 있다.
            lyric = '\n'.join(lyric.splitlines()[:6])               #splitlines를 개행별로 나눈 후 다시 개행 입혀줌
            top3_list.append({'title':title.text,                   #top3_list에 dict형식으로 append 해줌.
                              'img_src':img['src'],
                              'lyric_src':lyric_src,
                              'lyric':lyric,
                             })
    count+=1

for i in top3_list:
    attachments = []
    attachments.append({
        "fallback": i['title'], #알림 메세지에 보이는 텍스트
        "title": i['title'], # 제목 (볼드체로 보여짐)
        "title_link": i['lyric_src'], # 제목 링크
    # 본문 텍스트
        "text": i['lyric'],
    # "image_url": "", # 이미지 URL
        "thumb_url": i['img_src'], # 썸네일 URL
        "color": "#7CD197", # 첨부별 왼쪽 Bar 색상
    })
    slack.chat.post_message('#webtoon', '2019년 벅스차트 TOP 3', attachments=attachments)

결과는 다음과 같이 표시된다.

BeautifulSoup 사용하기

|

HTTP 응답

웹서버에서는 일반적으로 HTML, CSS, JavaScript, Image 형식의 응답을 한다. HTML 문서는 중첩된 태그로 구성된 계층적인 구조이다.

<!doctype html>
<html>
  <head>
    <meta charset="utf-8" />
    <title>AskDjango</title>
  </head>
  <body>
    <h1>AskDjango VOD</h1>
    <ul id="vod_list">
      <li class="vod">파이썬 차근차근 시작하기</li>
      <li class="vod">장고 기본편</li>
    </ul>
    <hr/>
  </body>
</html>

20181218_TIL

|

ASK 장고를 통해 크롤링 공부중

게시글 하나가 문제가 생겨서 포스트가 업데이트가 안되고 있었다.

아직은 문제를 몰라 해당 게시글을 삭제해둔 상태이다.

requests 써보기

|

GET 요청해보기

import requests
response = requests.get('http://news.naver.com/main/home.nhn')

헤더에 커스텀 내용을 추가하고 싶을 시에는 dic형 변수를 만들어 입력해주는게 편하다.

request_headers ={
    'User-Agent' : ('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_6) AppleWebKit/537.36 '
                  '(KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.36'),
    'Referer': 'http://news.naver.com/main/home.nhn', # 뉴스홈
}

requests의 기본 User-Agent는 ‘python-requests/버전’이런 식인데, 이것을 통해 응답을 거부할 수 있기에 fake User-Agent값을 설정해 주면 좋다. fake_useragent모듈에서 UserAgent클래스를 추가한다.

from fake_useragent import UserAgent
ua = UserAgent()
ua.ie

ua.ie는 User-Agent 값을 출력하는데, 실행시킬 때마다 다른 값이 표시된다.

인자를 보내는 방법이다. requests.get 함수에 params=값을 넣어주면 된다.

get_params ={'k1':'v1','k1':'v3','k2':'v2'}
response = requests.get('http://httpbin.org/get', params=get_params)

get_params = (('k1', 'v1'), ('k1', 'v3'), ('k2', 'v2'))
response = requests.get('http://httpbin.org/get', params=get_params)

두개의 경우를 표시했는데, 전자의 경우 key k1이 두개가 있다. 이런경우에는 마지막 k1의 value가 저장된다. 후자는 배열로 [v1,v3]가 저장된다.

{
  "args": {
    "k1": "v3", 
    "k2": "v2"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.20.0"
  }, 
  "origin": "121.190.90.190", 
  "url": "http://httpbin.org/get?k1=v3&k2=v2"
}
{
  "args": {
    "k1": [
      "v1", 
      "v3"
    ], 
    "k2": "v2"
  }, 
  "headers": {
    "Accept": "*/*", 
    "Accept-Encoding": "gzip, deflate", 
    "Connection": "close", 
    "Host": "httpbin.org", 
    "User-Agent": "python-requests/2.20.0"
  }, 
  "origin": "121.190.90.190", 
  "url": "http://httpbin.org/get?k1=v1&k1=v3&k2=v2"
}

#POST 요청해보기

response = requests.post(‘http://httpbin.org/post’)