Selenium & PyAutoGUI를 활용한 네이버 메일 자동화
목적: 네이버 로그인부터 메일 작성, 내용 입력 및 전송까지의 과정을 자동화. 특히 단순 자동화를 넘어 pyperclip을 이용해 클립보드에 텍스트를 복사한 뒤 Ctrl+V를 수행함으로써, 한글 입력 시 발생할 수 있는 브라우저 매크로 오류를 우회하는 전략을 사용
# 주요 기능 및 라이브러리 역할
from selenium import webdriver
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.chrome.options import Options
from webdriver_manager.chrome import ChromeDriverManager
from selenium.webdriver.support.ui import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC
from selenium.webdriver.common.by import By
import time
import pyautogui
import pyperclip
# Selenium (webdriver): 웹 브라우저를 제어하여 요소 탐색 및 클릭 수행.
# PyAutoGUI: 브라우저 내 입력을 제어하며, 사용자로부터 팝업창을 통해 정보를 입력받음.
# Pyperclip: 한글 등 다국어 텍스트 입력 시 발생할 수 있는 전송 오류를 방지하기 위해 클립보드 복사/붙여넣기 방식을 활용.
# WebDriver Manager: 크롬 드라이버를 수동으로 다운로드할 필요 없이 자동으로 설치 및 업데이트 관리.
## PyAutoGUI, Pyperclip을 쓰는 기술적 이유
- 한글 입력 이슈 해결
- 자음/모음 분리: 안녕하세요가 ㅇㅏㄴㄴㅕㅇㅎㅏㅅㅔㅇㅛ처럼 분리되어 입력되는 현상.
- 글자 누락: 서버 응답 속도가 느릴 경우 중간 글자가 씹히는 현상.
- 인코딩 오류: 브라우저와 Selenium 드라이버 간의 통신 과정에서 한글이 깨지는 현상.
- 입력 속도 최적화
- send_keys()는 글자를 한 글자씩 브라우저로 전송합니다. 본문 내용이 아주 긴 경우, 모든 글자를 전송하는 데 시간이 상당히 걸립니다.
- send_keys(): "안녕하세요... (긴 글)" → 전송까지 초 단위 소요.
- Ctrl + V: 클립보드의 데이터를 즉시 브라우저로 전달 → 즉시 입력
- send_keys()는 글자를 한 글자씩 브라우저로 전송합니다. 본문 내용이 아주 긴 경우, 모든 글자를 전송하는 데 시간이 상당히 걸립니다.
- 브라우저의 보안 정책 우회
- 일부 웹사이트는 자동화 툴(Selenium)이 입력하는 send_keys()를 감지하여 봇(Bot)으로 차단하는 경우가 있습니다.
- pyautogui를 사용하여 Ctrl+V를 수행하는 것은 실제 사용자가 키보드를 누르는 행위와 매우 유사하게 동작합니다. 따라서 자동화 감지 로직을 비교적 안전하게 우회할 수 있는 '사람 같은' 입력 방식이 됩니다.
- 일부 웹사이트는 자동화 툴(Selenium)이 입력하는 send_keys()를 감지하여 봇(Bot)으로 차단하는 경우가 있습니다.
# 사용자 정보 입력 (PyAutoGUI 활용)
uid = pyautogui.prompt("네이버 ID: ", default="l1234")
pwd = pyautogui.password("비밀번호: ")
toname = pyautogui.prompt("받는사람 이름: ", default="홍길동")
tomail = pyautogui.prompt("받는사람 메일: ", default="12345@gmail.com")
response = pyautogui.confirm(
'어떤 내용을 하시겠습니까?',
buttons=['가입안내', '합격안내', '계약서']
)
if response=="가입안내":
to_subject = "[테스트] 가입안내입니다."
to_content = f"{toname}님 가입을 환영합니다.\n\n최고의 서비스로 최선을 다하겠습니다.\n\n감사합니다."
elif response=="합격안내":
to_subject = "[테스트] 합격결과입니다."
to_content = f'''{toname}님 이번 면접에 최종 합격 하였습니다.\n
저희 가족이 되심을 축하합니다.
입사일은 2026년 10월 1일입니다.\n
감사합니다.'''
elif response=="계약서":
to_subject = "[테스트] 점심 계약서."
to_content = f'''{toname}님 최종 계약서 내용입니다.\n
귀하는 2024년 12월 4일 ~ 2025년 4월 24일까지 점심을 사기로 하였습니다.\n
# 계약자 A: {toname}
# 계약자 B: 이순신'''
pyautogui의 prompt와 password 함수를 사용하여 스크립트 실행 시 사용자로부터 ID, 비밀번호, 받는 사람 정보를 실시간으로 입력받습니다. 또한 confirm 함수를 통해 선택된 항목에 따라 메일 본문(to_content)과 제목(to_subject)을 조건문(if-elif)으로 동적 생성합니다.
* 코드를 main()함수로 묶은 이유?
- 1. 모듈화 및 재사용성 (Import 방지)
- 파이썬에서는 다른 파일에서 이 코드를 import 할 때, 함수 밖의 코드들은 import 되는 순간 즉시 실행됩니다.
- 함수로 묶지 않았을 때: 다른 파일에서 이 코드를 가져오기만 해도 메일 자동화 프로그램이 강제로 실행되어 버립니다.
- 함수로 묶었을 때: main() 함수 안에 로직을 넣어두면, 다른 파일에서 import 해도 함수를 직접 호출(main())하기 전까지는 코드가 실행되지 않습니다. 즉, 필요할 때만 호출할 수 있는 도구로 만드는 것입니다.
- 2. 코드의 구조적 깔끔함
- 프로그램이 커지면 여러 개의 함수가 생기게 됩니다. main()은 프로그램의 "시작점(Entry Point)" 역할을 합니다.
- 코드를 읽는 사람(또는 미래의 본인)이 어디서부터 프로그램이 시작되는지 직관적으로 알 수 있습니다.
- 스크립트의 흐름(입력 -> 설정 -> 실행 -> 종료)을 한눈에 파악하기 좋습니다.
- 3. if __name__ == "__main__":의 마법
- 이 구문은 "이 파일을 직접 실행했을 때만 아래 코드를 실행하라"는 뜻입니다.
- 직접 실행할 때: __name__ 변수는 "__main__"이라는 값을 가집니다. 따라서 main()이 실행됩니다.
- 다른 파일에서 불러올 때: __name__ 변수는 파일 이름이 됩니다. 따라서 if 조건이 거짓이 되어 main()이 자동으로 실행되지 않습니다.
# 브라우저 구동 및 로그인
chrome_options = Options()
chrome_options.add_experimental_option("detach", True)
# 불필요한 에러메시지 노출 방지
chrome_options.add_experimental_option("excludeSwitches", ["enable-logging"])
service = Service(excutable_path=ChromeDriverManager().install())
driver = webdriver.Chrome(service=service, options = chrome_options)
time.sleep(2)
driver.implicitly_wait(5)
driver.maximize_window()
driver.get("https://nid.naver.com/nidlogin.login?mode=form&url=https://www.naver.com/")
# 아이디 입력창
id = driver.find_element(By.CSS_SELECTOR, "#input_item_id")
id.click()
pyperclip.copy(uid)
pyautogui.hotkey("ctrl","v")
time.sleep(2)
# 비밀번호 입력 창
pw = driver.find_element(By.CSS_SELECTOR, "#input_item_pw")
pw.click()
pyperclip.copy(pwd)
pyautogui.hotkey("ctrl","v")
time.sleep(2)
login_btn = driver.find_element(By.CSS_SELECTOR, "#log\\.login")
login_btn.click()
time.sleep(30)
- detach 옵션: 코드가 끝난 뒤 브라우저가 바로 닫히지 않고 열려 있게 합니다.
- 로그인 로직: CSS Selector(input_item_id, input_item_pw)를 사용해 아이디와 비밀번호 필드를 찾습니다. 이후 클립보드 복사 → 붙여넣기 방식으로 입력 후 로그인 버튼을 클릭합니다.
- time.sleep(30): 네이버의 보안 문자가 뜰 경우를 대비해 사용자가 직접 30초 동안 캡차(CAPTCHA)를 처리할 시간을 줍니다.
# 메일 쓰기 화면 창 넘어가기 및 메일쓰기 (메일 버튼, 메일 받는 사람, 메일 제목)
mail_btn = driver.find_element(By.CSS_SELECTOR, "li.shortcut_item a")
mail_btn.click()
time.sleep(2)
# 윈도우창 핸들링
windows = driver.window_handles
# 새로 열린 창으로 이동
driver.switch_to.window(windows[-1])
# 메일 버튼
write_btn = driver.find_element(By.CSS_SELECTOR, "a.button_write")
write_btn.click()
time.sleep(2)
# 메일 받는 사람
to_email = driver.find_element(By.CSS_SELECTOR, "#recipient_input_element")
to_email.click()
time.sleep(1)
pyperclip.copy(tomail)
pyautogui.hotkey("ctrl", "v")
# 메일 제목
subject_email = driver.find_element(By.CSS_SELECTOR, "#subject_title")
subject_email.click()
pyperclip.copy(to_subject)
pyautogui.hotkey("ctrl", "v")
time.sleep(2)
- 네이버 메일의 '메일 쓰기'는 새로운 탭(창)에서 열리므로 window_handles를 통해 마지막으로 열린 창으로 포커스를 이동합니다.
- CSS Selector를 사용해 메일쓰기버튼을 누른 후 메일 받는 사람, 메일 제목 필드를 찾습니다. 이후 클립보드 복사 → 붙여넣기 방식으로 입력합니다.
# iframe 제어 및 내용 입력 (메일 본문 작성)
# iframe으로 이동
WebDriverWait(driver, 10).until(
EC.frame_to_be_available_and_switch_to_it((By.CSS_SELECTOR, "#content > div.contents_area > div > div.editor_area > div > div.editor_body > iframe"))
)
time.sleep(2)
# 내용칸 로드후 다음 진행
mail_content = WebDriverWait(driver, 10).until(
EC.presence_of_element_located((By.CSS_SELECTOR, "div.workseditor-content"))
)
# 내용 붙여넣기
mail_content.click()
pyperclip.copy(to_content)
pyautogui.hotkey("ctrl","v")
time.sleep(2)
- 네이버 메일의 에디터는 iframe이라는 '페이지 안의 페이지' 구조로 되어 있습니다. 셀레늄은 기본 페이지에만 접근할 수 있으므로, 반드시 switch_to.frame을 통해 에디터 영역으로 진입해야 내용 작성이 가능합니다.
- iframe 내부의 본문 영역을 찾아 클릭하고, 앞서 복사해둔 내용을 Ctrl+V로 붙여넣습니다.
* iframe으로 전환 후 바로 클릭하고 붙여넣기 못하고 내용칸 로드될 때까지 기다려야하는 이유
- 1. 브라우저의 비동기 렌더링 (가장 큰 이유)
- 웹 브라우저는 페이지를 불러올 때 모든 요소를 한 번에 다 보여주지 않습니다. 특히 메일 서비스처럼 복잡한 에디터는 다음과 같은 순서로 작동합니다.
- 프레임(iframe) 로드: 먼저 큰 틀인 빈 박스(iframe)가 먼저 생성됩니다.
- 내부 엔진 로드: 그 안에서 자바스크립트가 실제 글을 쓸 수 있는 에디터 영역(div.workseditor-content)을 동적으로 생성하고, 각종 서식 툴바를 입힙니다.
- 웹 브라우저는 페이지를 불러올 때 모든 요소를 한 번에 다 보여주지 않습니다. 특히 메일 서비스처럼 복잡한 에디터는 다음과 같은 순서로 작동합니다.
이 과정에서 iframe이라는 '빈 껍데기'가 생성된 직후에 click()을 수행하면, 정작 내부에 글을 쓸 실제 영역인 div는 아직 브라우저에 존재하지 않을 확률이 매우 높습니다. 결과적으로 NoSuchElementException 에러가 발생하게 됩니다.
- 2. 동적 로딩과 JavaScript의 한계
- 네이버 메일 같은 현대적인 웹사이트는 대부분 SPA(Single Page Application) 방식을 사용합니다.
- iframe 태그가 HTML에 나타났다고 해서 그 내부의 기능이 완벽하게 초기화(Initialize)된 것은 아닙니다.
- "내용칸 로드 후 진행"한다는 것은, 단순히 요소가 존재하는지 확인하는 것을 넘어 "브라우저가 해당 영역에 사용자의 입력을 받을 준비를 마쳤는지(Ready 상태)"를 확인하는 필수적인 과정입니다.
- 네이버 메일 같은 현대적인 웹사이트는 대부분 SPA(Single Page Application) 방식을 사용합니다.
# 최종 전송
# 원래 창으로 돌아가기
driver.switch_to.default_content()
# 보내기 버튼
mail_send = driver.find_element(By.CSS_SELECTOR, "#content > div.mail_toolbar.type_write > div:nth-child(1) > div > button.button_write_task")
mail_send.click()
time.sleep(3)
driver.quit()
- default_content()를 호출해 다시 메인 페이지로 돌아와 '보내기' 버튼을 누릅니다.
- 그 후 diver.quit()로 종료합니다.
느낀점
어제 Selenium 개념은 알았지만 제대로 써보지 못해 오늘 네이버 메일 보내기 자동화로 Selenium을 써보았다. 확실히 요즘 반복된 업무를 자동화하는 게 나름 중요한 시대라고 생각되는데, 파이썬으로 자동화하는데 Selenium을 통해 편하게 할 수 있는 거 같아 나중에 기본 틀을 만들어두면 유용하게 사용할 수 있을 것 같다.
——————————————————————————
본 후기는 [한글과컴퓨터x한국생산성본부x스나이퍼팩토리] 한컴 AI 아카데미 (B-log) 리뷰로 작성 되었습니다.
'학습일지' 카테고리의 다른 글
| [스나이퍼팩토리] 한컴AI아카데미(26.06.11) JWT 기반 인증 (0) | 2026.06.11 |
|---|---|
| [스나이퍼팩토리] 한컴AI아카데미(26.06.10) Fast API (1) | 2026.06.10 |
| [스나이퍼팩토리] 한컴AI아카데미(26.06.08) Selenium (0) | 2026.06.09 |
| [스나이퍼팩토리] 한컴AI아카데미(26.06.05) 웹 스크랩핑(BeautifulSoup) (0) | 2026.06.05 |
| [스나이퍼팩토리] 한컴AI아카데미(26.06.04) urllib (0) | 2026.06.04 |