파이썬 예제 (이메일 수신, IMAP)


일반적으로 우리는 E-Mail을 확인 할 때,  이메일 사이트에 접속해 로그인 후 메일을 보내고, 수신된 메일을 확인, 삭제하는 등의 방법으로 이메일을 관리합니다.

좀 더 편리하게 MS 아웃룩 등으로 메일 서버의 주소를 등록하고 관리하기도 하죠.

오늘은 파이썬을 이용해 메일서버에 접속 후 메일 수신 처리를 진행해 보겠습니다.


[파이썬으로 E-Mail 수신]

앞서 SMTP를 이용해 이메일을 파이썬으로 보내는 방법에 대한 포스팅에 이어 이번에는 메일 수신에 대해 살펴보겠습니다.

이메일 수신과 관련해 2가지 기술적인 방법이 존재합니다.

바로 오늘 소개할 IMAP(Internet Message Access Protocol) 과 POP(Post Office Protocol) 입니다.

IMAP

  • 이메일 수신 시 해당 장치에 다운로드하는 방식이 아닌 메일서버에서 읽음.
  • 따라서 모든 장치에서 메일 확인 가능. (컴퓨터, 휴대폰, 테블릿 등)
  • 이메일 확인 시 첨부파일 자동 다운로드 X. (빠름) 

POP

  • 메일서버에 접속해 새 이메일을 다운로드.
  • 송, 수신 이메일은 접속한 장치, 로컬에 저장.

요즘은 IMAP을 더 많이 쓰는 추세인 것 같습니다.

그럼 파이썬으로 구글 메일서버의 IMAP을 이용한 메일 수신 방법을 살펴보겠습니다.

사전 준비


먼저 아래 그림과 같이 GMail 메일서버에 접속해 IMAP 기능 사용을 활성화 합니다. 다른 메일 서버(네이버, 다음 등) 도 마찬가지로 IMAP을 활성화 시켜야 합니다.





더불어 구글의 2단계 인증 및 인증 패스워드도 필요합니다.

자세한 내용은 앞선 게시물인 메일 보내기 참조 바랍니다.

소스코드

일단 파이썬 코드를 살펴보겠습니다.
import imaplib
import email


# 문자열의 인코딩 정보추출 후, 문자열, 인코딩 얻기
def findEncodingInfo(txt):    
    info = email.header.decode_header(txt)
    s, encoding = info[0]
    return s, encoding


# 메일서버 로그인
imap = imaplib.IMAP4_SSL('imap.gmail.com')
id = 'xxx@gmail.com'
pw = '구글 16자리 2단계 비밀번호'
imap.login(id, pw)

# 받은 편지함
imap.select('inbox')

# 받은 편지함 모든 메일 검색
resp, data = imap.uid('search', None, 'All')

# 여러 메일 읽기 (반복)
all_email = data[0].split()

for mail in all_email:

    #fetch 명령을 통해서 메일 가져오기 (RFC822 Protocol)
    result, data = imap.uid('fetch', mail, '(RFC822)')

    #사람이 읽기 힘든 Raw 메세지 (byte)
    raw_email = data[0][1]

    #메시지 처리(email 모듈 활용)    
    email_message = email.message_from_bytes(raw_email)

    #이메일 정보 keys
    #print(email_message.keys())
    print('FROM:', email_message['From'])
    print('SENDER:', email_message['Sender'])
    print('TO:', email_message['To'])
    print('DATE:', email_message['Date'])

    b, encode = findEncodingInfo(email_message['Subject'])
    print('SUBJECT:', str(b, encode))

    #이메일 본문 내용 확인
    print('[CONTENT]')
    print('='*80)
    if email_message.is_multipart():
        for part in email_message.get_payload():        
            bytes = part.get_payload(decode = True)    
            encode = part.get_content_charset()        
            print(str(bytes, encode))
    print('='*80)

imap.close()
imap.logout()

1~2번 라인에서 파이썬 imap, email 모듈을 불러옵니다. 파이썬 내장 모듈이므로 추가 설치는 불필요합니다.

6~9번 라인 findEncodingInfo() 함수는 전달인자로 넘어온 문자열의 인코딩 방식이 무엇인지 찾아내 바이트값인코딩 정보를 리턴해 줍니다.

13번 라인에서 imap 객체를 생성합니다.

이제 IMAP 메일 서버에 접속하고 (SSL 보안 인증), 아이디, 비번을 통해 로그인 합니다.

19번 라인에서 받은 편지함을 선택하고 22번 라인 uid('search'...)  함수를 통해 모든 메일을 검색합니다.

resp에는 검색 성공 여부 ('OK') 가 data에는 받은 편지함 메일 ID들의 byte 값이 리스트로 한꺼번에 리턴 됩니다. (각 메일별 공백으로 구분)

Ex) data = [b'11 12 13......']

25번 라인은 리스트의 split()  함수를 이용해 각 메일 ID를 공백으로 잘라내어 저장합니다. 모든 메일의 ID를 리스트로 저장해 반복하며 보낸사람, 제목, 내용 등을 출력하기 위해서 입니다.

Ex) all_mail = [b'11', b'12', b'13....]

메일 ID는 받은 편지함에 있는 메일의 구분 번호라고 생각하면 됩니다.

이제 all_mail 리스트를 순회하며 수신된 메일들의 보낸사람, 받는사람, 제목, 내용 등을 반복해 출력하도록 합니다.

30번 라인 imap.uid('fetch', mail, '(RFC822)') 함수를 통해 해당 메일을 RFC822 (전자메일 표준)에 맞도록 가져옵니다.

result에는 fetch의 여부('OK')가 data[0][1]에는 byte 타입으로 메일의 raw 데이터가 포함되었습니다.

37번 라인 email 모듈의 message_from_byte()함수를 이용해 byte값을 Message 타입으로 변환합니다.

41번 라인부터 email_message 를 이용해 해당 메일의 정보를 출력합니다. 40번 라인의 주석을 제거하면 email_message에서 사용가능한 keys('From', 'To', 'Subject' 등) 정보를 출력해 줍니다.

50번 라인부터는 메일 본문의 출력인데 '보낸사람', '받는사람', '제목' 등과 코드의 형태가 다릅니다.

메일의 본문은 여러 파트로 나누어 저장되는 형태이므로 get_paylaod()  함수를 이용해 반복하여 모든 본문의 내용을 출력합니다.

요즘 메일내용은 웹에서 보기 편하게 HTML로 작성되는 경우가 많으므로, 아래처럼 HTML 코드 형태로 보이게 됩니다.


이메일의 표준 규격에 관한 좀 더 자세한 정보는 MIME 위키백과를 참조하시기 바랍니다.

감사합니다.

댓글

  1. 적용해 보고 있는데 subject에서 글씨가 깨지는게 어떻게 해결 할 수 있을까요??

    답글삭제
    답글
    1. 먼저 이런 에러가 나왔는데요 혹시 어떻게 할 수 있을까요??

      print('SUBJECT:', str(b, encode))
      TypeError: str() argument 2 must be str, not None

      삭제
    2. 먼저 에러의 원인은 수신된 이메일은 다양한 문자집합 형태를 가집니다.
      보내는 사람의 지역, 환경, 기기에 따라 달라질 수 있기 때문입니다. (cp949, utf-8, utf-16 등)

      따라서 이메일 수신 시 어떤 문자집합으로 작성된 문자열인지 파악한 후 이를 표시해야 문자가 제대로 표시되는데, 이 인코딩 정보를 찾는 역할을 findEncodingInfo() 함수에서 담당합니다.

      위 에러는 findEncodingInfo() 함수에서 리턴된 인코딩 정보가 'None'인 메일이 있다는 것이므로 try 구문을 통해 오류처리를 진행해 주면 됩니다.

      예를 들면 아래와 같습니다.

      try:
      print('SUBJECT:', str(b, encode))
      except:
      오류시 여기로
      else:
      정상시 여기로

      마찬가지로 문자가 깨지는 이유도 인코딩, 디코딩 정보가 맞지 않기 때문입니다.

      삭제
  2. Unknown님처럼 subject 출력시 에러가 납니다.
    구글링으로 해결했습니다.

    from email import policy # 임포트하고

    email_message = email.message_from_bytes(raw_email, policy=policy.default) # policy 추가

    print('SUBJECT:', str(b)) # suject 출력시 encode는 삭제.

    답글삭제
    답글
    1. 수신메일의 문자집합이 각자 다 달라서 함수를 추가해 인코딩 정보를 찾아서 출력하는 방식으로 만들었는데 이런 방법이 있었군요.

      좋은 정보 감사합니다.

      삭제
  3. TypeError: str() argument 'encoding' must be str, not None
    본문 리딩시 에러인데 해결하신 분 없으세요

    답글삭제

댓글 쓰기

이 블로그의 인기 게시물

Qt Designer 설치하기

C++ 예제 (소켓 서버, 이미지, 파일전송)