IT/개발

Playwright로 Tistory 자동 포스팅 챌린지: 웹 자동화 실전 디버깅 가이드

zzun 2026. 4. 25. 22:42
반응형

현대 IT 환경에서 반복적이고 지루한 작업은 자동화의 주요 대상입니다. 특히 웹 기반 서비스와의 상호작용은 수많은 수동 작업을 요구하며, 이는 개발자와 실무자에게 큰 부담으로 작용하곤 합니다. 이런 맥락에서 웹 자동화(Web Automation) 기술은 단순한 편의를 넘어 업무 효율성과 생산성을 혁신하는 핵심 도구로 자리매김하고 있습니다.

이번 글에서는 최근의 Tistory 자동 포스팅 프로젝트 경험을 바탕으로, Playwright 라이브러리를 활용한 웹 자동화의 실제 구현 과정과 함께, 실무에서 마주할 수 있는 다양한 문제점과 효과적인 디버깅 전략을 심층적으로 다루고자 합니다. 이 경험을 통해 웹 자동화 프로젝트를 계획하거나 현재 진행 중인 분들께 실질적인 도움을 드릴 수 있기를 바랍니다.

웹 자동화는 단순한 클릭/입력을 넘어, 동적으로 변화하는 웹 환경에 능동적으로 대응하고, 예상치 못한 오류를 해결하는 복합적인 기술력을 요구합니다. 특히 로그인 절차, 동적 페이지 로딩, 그리고 미묘한 UI 변경 등은 자동화 스크립트를 무용지물로 만들 수 있는 주요 요인입니다. 이 글에서는 이러한 도전 과제를 Playwright를 통해 어떻게 극복했는지 단계별로 살펴보겠습니다.

Playwright: 웹 자동화의 강력한 동반자

Playwright는 Microsoft에서 개발한 Node.js, Python, Java, .NET을 지원하는 최신 웹 자동화 라이브러리입니다. Selenium, Puppeteer와 함께 웹 자동화 시장의 주요 플레이어로 꼽히며, 특히 다음과 같은 강력한 장점들을 제공합니다.

  • 크로스 브라우저 지원: Chromium, Firefox, WebKit (Safari의 엔진) 등 주요 브라우저를 모두 지원하여 동일한 스크립트로 다양한 환경에서 테스트 및 자동화가 가능합니다.
  • Headless 및 Headful 모드: GUI 없이 백그라운드에서 실행되는 Headless 모드와 실제 브라우저 창을 띄워 시각적으로 확인하며 디버깅할 수 있는 Headful 모드를 유연하게 전환할 수 있습니다.
  • 강력한 셀렉터: CSS 셀렉터, XPath, 텍스트, 역할(role), 플레이스홀더 텍스트 등 다양한 방식으로 요소를 선택할 수 있으며, 동적으로 변하는 웹 페이지에도 강건하게 대응할 수 있는 기능을 제공합니다.
  • 자동 대기(Auto-waiting): 요소가 나타나거나 활성화될 때까지 자동으로 기다려주므로, `time.sleep()`과 같은 명시적인 대기 시간을 줄여 스크립트의 안정성과 효율성을 높입니다.
  • 스크린샷 및 비디오 녹화: 문제 발생 시 스크린샷을 찍거나 전체 실행 과정을 비디오로 녹화하여 디버깅을 용이하게 합니다.

Tistory 자동 포스팅 프로젝트는 Playwright의 이러한 장점들을 최대한 활용하여 구현되었습니다.

기본 환경 설정 및 스크립트 골격

Playwright를 사용하기 위한 첫 단계는 Python 환경에 라이브러리를 설치하고 필요한 브라우저 드라이버를 구성하는 것입니다.

pip install playwright
playwright install chromium  # 또는 firefox, webkit

다음은 Tistory 자동 포스팅 스크립트의 핵심 구조를 나타내는 간소화된 코드입니다. 실제 스크립트에는 카카오 로그인 정보, 블로그 이름, 포스팅 제목 및 내용 등이 변수로 정의되어 사용됩니다.

from playwright.sync_api import sync_playwright
import time

# 사용자 정의 설정 (실제 값으로 대체 필요)
KAKAO_EMAIL    = 'your_kakao_email@kakao.com'
KAKAO_PASSWORD = 'your_kakao_password'
BLOG_NAME      = 'zzun'
TITLE          = '자동 포스팅 테스트'
TAGS           = 'Playwright,자동화,Python'
CONTENT        = '<p>이것은 Playwright로 자동 포스팅된 글입니다.</p>'

def post_to_tistory():
    with sync_playwright() as p:
        # Headful 모드로 브라우저 실행 (디버깅 시 유용)
        browser = p.chromium.launch(headless=False)
        page = browser.new_page()

        # 1. 카카오 로그인 페이지로 이동
        page.goto('https://accounts.kakao.com/login')

        # 2. 로그인 정보 입력 및 클릭
        # 실제 셀렉터는 페이지 구조에 따라 변경될 수 있음
        page.fill('input#loginId--1', KAKAO_EMAIL)
        page.fill('input#password--2', KAKAO_PASSWORD)
        page.click('button[type='submit']')

        # 3. 티스토리 블로그 관리 페이지로 이동 (로그인 이후)
        page.goto(f'https://{BLOG_NAME}.tistory.com/manage')
        page.wait_for_load_state('networkidle') # 페이지 로드 완료 대기

        # 4. 새 글쓰기 페이지로 이동
        page.goto(f'https://{BLOG_NAME}.tistory.com/manage/newpost/')
        page.wait_for_selector('input#post-title', state='visible') # 제목 입력 필드 대기

        # 5. 제목, 본문, 태그 입력
        page.fill('input#post-title', TITLE)
        # Tistory 에디터는 iframe 내부에 있거나 복잡한 구조를 가질 수 있음
        # 여기서는 단순화를 위해 직접 HTML을 삽입하는 형태로 가정
        # 실제로는 에디터 내부의 iframe으로 이동하거나 JavaScript를 실행해야 할 수 있음
        page.evaluate(f'document.querySelector('div[role='textbox']').innerHTML = `{CONTENT}`')

        # 태그 입력
        tags_input_selector = 'input#tag-input'
        page.fill(tags_input_selector, TAGS)
        page.press(tags_input_selector, 'Enter') # 태그 입력 후 Enter로 추가
        time.sleep(1) # 태그 추가 반응 대기

        # 6. 발행 버튼 클릭
        # 발행 버튼 셀렉터는 UI에 따라 상이함. 스크린샷 디버깅 필요.
        publish_button_selector = 'button[data-tiara-action-name='발행']'
        page.click(publish_button_selector)
        page.wait_for_load_state('networkidle')

        print('블로그 포스팅 완료!')
        browser.close()

if __name__ == '__main__':
    post_to_tistory()

실전 디버깅: 웹 자동화의 숙명

웹 자동화는 웹 페이지의 미묘한 변화에도 민감하게 반응하여 오류를 발생시킵니다. 실제 Tistory 자동 포스팅 과정에서 마주했던 문제들과 그 해결책을 통해 실전 디버깅 노하우를 공유합니다.

1. 로그인 실패: 동적 UI와 셀렉터 문제

가장 흔한 문제는 로그인 실패입니다. Tistory는 카카오 계정으로 로그인하며, 카카오 로그인 페이지는 보안 및 UI 업데이트를 위해 자주 변경됩니다. 대화 내용에서도 '로그인 실패입니다. 카카오 로그인 페이지 구조가 바뀌었을 수 있어요.'라는 언급이 있었습니다.

  • 문제점: 이전에 잘 작동하던 `page.fill('input#email', KAKAO_EMAIL)`과 같은 셀렉터가 더 이상 유효하지 않을 때 발생합니다. 웹사이트 운영자가 입력 필드의 ID나 클래스명을 변경했거나, 필드 자체가 동적으로 생성되는 경우가 많습니다.
  • 해결책:
    1. 개발자 도구 활용: 브라우저의 개발자 도구(F12)를 열어 로그인 페이지의 HTML 구조를 직접 분석합니다. 변경된 ID나 클래스명을 확인하고, 더 안정적인 CSS 셀렉터나 XPath를 찾습니다. 예를 들어, `input[type='email']`이나 `div.login-form input[name='loginid']`와 같이 여러 속성을 조합하거나 부모-자식 관계를 이용한 셀렉터가 더 강건할 수 있습니다.
    2. 강건한 셀렉터 전략: ID는 가장 안정적이지만, 없을 경우 클래스명, name 속성, type 속성 등을 조합하거나, 텍스트 내용을 기반으로 하는 `page.get_by_text()`와 같은 Playwright의 Locator API를 활용합니다.
    3. `page.wait_for_load_state()`: 페이지 이동 후 JavaScript 로딩이 완료될 때까지 기다리는 것이 중요합니다. `networkidle` 또는 `domcontentloaded` 상태를 기다려야 요소가 완전히 렌더링될 시간을 확보할 수 있습니다.

2. 발행 여부 불확실 및 UI 요소 접근 실패

'최종 URL이 `manage/newpost/`로 남아있어 실제 발행 여부가 불확실합니다.' 또는 '발행 버튼이 0개로 패널이 열리지 않았습니다.'와 같은 문제는 주로 두 가지 원인에서 비롯됩니다.

  • 문제점:
    1. 비동기 처리: 발행 버튼 클릭 후 서버 응답이나 페이지 전환이 지연되어 스크립트가 다음 단계를 너무 빨리 시도하는 경우입니다.
    2. 동적 UI 렌더링: 발행 버튼이 특정 조건(예: 제목/내용 입력 완료)에서만 활성화되거나, 패널이 JavaScript를 통해 뒤늦게 로딩되는 경우, 스크립트가 요소를 찾지 못할 수 있습니다.
  • 해결책:
    1. `page.wait_for_selector()`: 특정 요소가 페이지에 나타나고 'visible' 상태가 될 때까지 명시적으로 기다립니다. 예를 들어, 발행 버튼이나 발행 성공 메시지를 기다리는 식으로 활용합니다. `page.wait_for_selector('button[data-tiara-action-name='발행']', state='visible', timeout=10000)`와 같이 `timeout`을 지정하여 무한 대기를 방지할 수 있습니다.
    2. 스크린샷 디버깅: 문제가 발생한 직후 `page.screenshot(path='error_screenshot.png')`를 통해 현재 페이지의 상태를 시각적으로 확인합니다. 이를 통해 발행 버튼이 실제로 보이지 않는지, 다른 오버레이가 가리고 있는지 등을 파악할 수 있습니다.
    3. 명시적 대기 (`time.sleep()`): 최후의 수단으로 `time.sleep(초)`를 사용할 수 있지만, 이는 페이지 로딩 시간에 따라 스크립트의 성능과 안정성을 저해할 수 있으므로 `wait_for` 계열 메서드를 우선적으로 고려해야 합니다.
    4. `Locator` API 활용: Playwright의 `Locator`는 요소가 존재할 때까지 자동으로 대기하는 기능이 내장되어 있어 더욱 안정적인 상호작용을 가능하게 합니다. `page.get_by_role('button', name='발행').click()`와 같이 역할을 기반으로 요소를 찾는 것이 UI 변경에 더 강할 수 있습니다.

3. 환경별 문제: 인코딩 오류

대화에서 'Windows 콘솔이 이모지를 처리 못해서 발생하는 오류입니다. 파일 상단에 인코딩 설정을 추가합니다.'라는 내용은 환경에 따른 문제를 보여줍니다.

  • 문제점: Python 스크립트나 콘솔 환경에서 특정 문자(특히 이모지나 특수 문자)를 처리하지 못해 발생하는 `UnicodeEncodeError` 등의 인코딩 오류입니다.
  • 해결책: Python 스크립트 파일 상단에 `# -*- coding: utf-8 -*-`을 추가하여 파일 인코딩을 명시적으로 선언합니다. 또한, 운영체제의 콘솔 인코딩 설정이 UTF-8로 되어 있는지 확인하는 것이 좋습니다.

웹 자동화의 지속적인 관리와 유지보수

웹 자동화 스크립트는 한 번 개발했다고 해서 끝나는 것이 아닙니다. 웹 서비스는 지속적으로 업데이트되고 변화하므로, 자동화 스크립트 또한 주기적인 유지보수가 필수적입니다. 이를 위한 몇 가지 팁입니다.

  • 모듈화 및 설정 분리: 로그인 정보, 블로그 이름, 포스팅 내용과 같은 변수들을 스크립트 상단에 모아두거나 별도의 설정 파일(예: `.env` 파일)로 분리하여 관리합니다. 이는 스크립트 가독성을 높이고 변경 시 용이하게 합니다.
  • 버전 관리 시스템 활용: Git과 같은 버전 관리 시스템을 사용하여 스크립트의 변경 이력을 추적하고, 오류 발생 시 이전 버전으로 쉽게 되돌릴 수 있도록 합니다.
  • CI/CD 파이프라인 연동: 가능하다면 CI/CD(Continuous Integration/Continuous Deployment) 파이프라인에 자동화 스크립트를 통합하여 정기적으로 실행하고, 실패 시 알림을 받을 수 있도록 설정합니다.
  • 로깅 및 알림: 스크립트 실행 과정과 결과, 그리고 오류 발생 시 상세한 로그를 기록하고, 이메일, Slack 등으로 알림을 받도록 구성하여 문제 발생 시 신속하게 대응할 수 있도록 합니다.

결론: 자동화는 효율성의 핵심

Playwright를 이용한 Tistory 자동 포스팅 프로젝트는 웹 자동화의 강력한 가능성을 보여주는 동시에, 실제 웹 환경에서 마주하는 다양한 난관과 이를 극복하는 과정을 명확하게 보여주었습니다. 로그인 방식의 변화, 동적 UI 요소의 접근 문제, 그리고 환경적인 인코딩 이슈까지, 이 모든 것이 웹 자동화 스크립트를 견고하게 만드는 데 필요한 실전 경험이자 학습의 기회였습니다.

웹 자동화는 단순 반복 작업에서 벗어나 개발자와 실무자가 더욱 가치 있는 업무에 집중할 수 있도록 돕는 핵심적인 기술입니다. Playwright와 같은 강력한 도구를 활용하고, 위에서 언급된 디버깅 전략과 유지보수 팁을 적용한다면, 여러분도 복잡한 웹 서비스를 효율적으로 자동화할 수 있을 것입니다. 자동화를 통해 더 스마트하고 생산적인 업무 환경을 구축하시길 바랍니다.

반응형