<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Playwright on To the Moon</title><link>https://junstellar.github.io/tags/playwright/</link><description>Recent content in Playwright on To the Moon</description><generator>Hugo -- gohugo.io</generator><language>ko-kr</language><lastBuildDate>Mon, 27 Jul 2026 21:00:00 +0900</lastBuildDate><atom:link href="https://junstellar.github.io/tags/playwright/index.xml" rel="self" type="application/rss+xml"/><item><title>네이버 블로그 완전 자동 발행 만들기 — Claude Code + Playwright 실전기</title><link>https://junstellar.github.io/p/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%99%84%EC%A0%84-%EC%9E%90%EB%8F%99-%EB%B0%9C%ED%96%89-%EB%A7%8C%EB%93%A4%EA%B8%B0-claude-code--playwright-%EC%8B%A4%EC%A0%84%EA%B8%B0/</link><pubDate>Mon, 27 Jul 2026 21:00:00 +0900</pubDate><guid>https://junstellar.github.io/p/%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%B8%94%EB%A1%9C%EA%B7%B8-%EC%99%84%EC%A0%84-%EC%9E%90%EB%8F%99-%EB%B0%9C%ED%96%89-%EB%A7%8C%EB%93%A4%EA%B8%B0-claude-code--playwright-%EC%8B%A4%EC%A0%84%EA%B8%B0/</guid><description>&lt;h1 id="네이버-블로그-완전-자동-발행-만들기--claude-code--playwright-실전기"&gt;네이버 블로그 완전 자동 발행 만들기 — Claude Code + Playwright 실전기
&lt;/h1&gt;&lt;p&gt;네이버 블로그는 티스토리나 GitHub Pages와 달리 &lt;strong&gt;공식 발행 API가 없다&lt;/strong&gt;. 그래서 &amp;ldquo;매일 아침 글이 자동으로 올라가는 네이버 블로그&amp;quot;를 만들려면 브라우저 자동화(Playwright)로 스마트에디터 ONE을 사람처럼 직접 조작해야 한다. 오늘 Claude Code와 함께 이 파이프라인을 처음부터 끝까지 구축했고, 실제 첫 글 발행까지 성공했다. 그 과정과 시행착오를 정리한다.&lt;/p&gt;
&lt;p&gt;&lt;img alt="자동 발행된 실제 네이버 블로그 글" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://junstellar.github.io/images/naver-auto/published-desktop.png"&gt;
&lt;em&gt;사람 손 한 번 안 대고 발행된 실제 결과물. 제목, 본문, 카드뉴스 이미지 7장까지 전부 자동으로 들어갔다.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="전체-구조"&gt;전체 구조
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;span class="lnt"&gt;4
&lt;/span&gt;&lt;span class="lnt"&gt;5
&lt;/span&gt;&lt;span class="lnt"&gt;6
&lt;/span&gt;&lt;span class="lnt"&gt;7
&lt;/span&gt;&lt;span class="lnt"&gt;8
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="err"&gt;매일&lt;/span&gt; &lt;span class="err"&gt;생성되는&lt;/span&gt; &lt;span class="err"&gt;콘텐츠&lt;/span&gt; &lt;span class="err"&gt;폴더&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;C&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;\&lt;span class="n"&gt;Project&lt;/span&gt;\&lt;span class="n"&gt;Trend&lt;/span&gt;\&lt;span class="n"&gt;YYYY&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;MM&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;DD&lt;/span&gt;\&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;├─&lt;/span&gt; &lt;span class="n"&gt;naver_post&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;json&lt;/span&gt; &lt;span class="c1"&gt;# 제목 + HTML 본문 (이미지 위치 마커 포함)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;├─&lt;/span&gt; &lt;span class="n"&gt;blog&lt;/span&gt;&lt;span class="o"&gt;-&lt;/span&gt;&lt;span class="n"&gt;preview&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;html&lt;/span&gt; &lt;span class="c1"&gt;# 발행 미리보기 = 발행 원본 (없으면 json에서 자동 생성)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;└─&lt;/span&gt; &lt;span class="n"&gt;instagram&lt;/span&gt;\&lt;span class="o"&gt;*.&lt;/span&gt;&lt;span class="n"&gt;png&lt;/span&gt; &lt;span class="c1"&gt;# 카드뉴스 이미지 7장&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;upload&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;py&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;Playwright&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# 로그인 세션 재사용 → 에디터 조작 → 발행&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="err"&gt;↓&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;Windows&lt;/span&gt; &lt;span class="err"&gt;작업&lt;/span&gt; &lt;span class="err"&gt;스케줄러&lt;/span&gt; &lt;span class="c1"&gt;# 매일 오전 9시 자동 실행&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;핵심 설계 포인트 세 가지:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;발행 원본을 HTML 미리보기 파일로 단일화&lt;/strong&gt; — &lt;code&gt;blog-preview.html&lt;/code&gt;을 브라우저로 열면 발행될 모습을 그대로 볼 수 있고, 자동화는 이 파일의 &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;을 제목으로, &lt;code&gt;&amp;lt;img&amp;gt;&lt;/code&gt; 위치 그대로 이미지를 삽입한다. &amp;ldquo;미리보기 = 발행 결과&amp;quot;가 되니 검수가 쉽다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;중복 발행 방지 플래그&lt;/strong&gt; — 발행 성공 시 날짜 폴더에 &lt;code&gt;.naver_uploaded&lt;/code&gt; 파일을 남긴다. 스케줄러가 재실행돼도 같은 글이 두 번 올라가지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;실패 시 증거 남기기&lt;/strong&gt; — 예외가 나면 스크린샷과 로그를 자동 저장한다. 브라우저 자동화는 UI 변경으로 언제든 깨질 수 있어서 이게 필수다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="로그인-비밀번호-없이-세션만-저장"&gt;로그인: 비밀번호 없이 세션만 저장
&lt;/h2&gt;&lt;p&gt;자동화 스크립트에 비밀번호를 넣는 건 절대 금물. Playwright에 내장된 &lt;code&gt;--save-storage&lt;/code&gt; 옵션을 쓰면 &lt;strong&gt;사람이 직접 로그인한 세션(쿠키)만&lt;/strong&gt; 파일로 저장해 재사용할 수 있다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;python -m playwright open --channel chrome --save-storage&lt;span class="o"&gt;=&lt;/span&gt;auth.json https://nid.naver.com/nidlogin.login
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;열리는 브라우저에서 직접 로그인(&amp;lsquo;로그인 상태 유지&amp;rsquo; 체크)하고 창을 닫으면 &lt;code&gt;auth.json&lt;/code&gt;에 세션이 저장된다. 스크립트에서는 이렇게 불러온다:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;storage_state&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;auth.json&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;주의: &lt;code&gt;auth.json&lt;/code&gt;은 비밀번호는 없지만 세션 쿠키가 들어있어 &lt;strong&gt;그 자체가 로그인 열쇠다&lt;/strong&gt;. 절대 Git에 커밋하거나 공유하면 안 되고, 유출 의심 시 네이버 보안설정에서 전체 기기 로그아웃하면 즉시 무효화된다. 스크립트가 실행될 때마다 세션을 다시 저장해 만료도 방지했다.&lt;/p&gt;
&lt;h2 id="스마트에디터-조작의-핵심-기법"&gt;스마트에디터 조작의 핵심 기법
&lt;/h2&gt;&lt;p&gt;&lt;img alt="자동화가 조작하는 스마트에디터 ONE 글쓰기 화면" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://junstellar.github.io/images/naver-auto/editor.png"&gt;
&lt;em&gt;자동화 스크립트가 여는 스마트에디터 ONE. 이 화면의 제목·본문·사진 버튼·발행 버튼을 코드가 사람처럼 조작한다.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;본문 텍스트: 클립보드 HTML 붙여넣기.&lt;/strong&gt; 글자를 한 자씩 타이핑하는 것보다 훨씬 빠르고 서식(&lt;code&gt;&amp;lt;b&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;br&amp;gt;&lt;/code&gt;)도 유지된다. 브라우저 컨텍스트에 클립보드 권한을 주고, JS로 클립보드에 HTML을 넣은 뒤 Ctrl+V를 누른다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt; 1
&lt;/span&gt;&lt;span class="lnt"&gt; 2
&lt;/span&gt;&lt;span class="lnt"&gt; 3
&lt;/span&gt;&lt;span class="lnt"&gt; 4
&lt;/span&gt;&lt;span class="lnt"&gt; 5
&lt;/span&gt;&lt;span class="lnt"&gt; 6
&lt;/span&gt;&lt;span class="lnt"&gt; 7
&lt;/span&gt;&lt;span class="lnt"&gt; 8
&lt;/span&gt;&lt;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;ctx&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;browser&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;new_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;permissions&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;clipboard-read&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;clipboard-write&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;])&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;body&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&amp;#34;&amp;#34;async (el, html) =&amp;gt; {
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; const item = new ClipboardItem({
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;text/html&amp;#39;: new Blob([html], {type: &amp;#39;text/html&amp;#39;}),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; &amp;#39;text/plain&amp;#39;: new Blob([plain], {type: &amp;#39;text/plain&amp;#39;}),
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; });
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt; await navigator.clipboard.write([item]);
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="s2"&gt;}&amp;#34;&amp;#34;&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;html&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;keyboard&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;press&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;Control+V&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;이미지: 파일 선택 대화상자 가로채기.&lt;/strong&gt; 이미지 버튼을 클릭하면 뜨는 파일 선택창을 Playwright가 가로채 파일 경로를 직접 넣는다.&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;span class="lnt"&gt;2
&lt;/span&gt;&lt;span class="lnt"&gt;3
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-python" data-lang="python"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;with&lt;/span&gt; &lt;span class="n"&gt;page&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;expect_file_chooser&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="k"&gt;as&lt;/span&gt; &lt;span class="n"&gt;fc&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="n"&gt;frame&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;locator&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;button[data-name=&amp;#39;image&amp;#39;]&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;first&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;click&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;fc&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;value&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;set_files&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;str&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;img_path&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;&lt;strong&gt;에디터는 iframe 안에 있다.&lt;/strong&gt; 네이버 블로그 글쓰기 화면은 &lt;code&gt;iframe#mainFrame&lt;/code&gt; 안에서 돌아가므로 &lt;code&gt;page.frame_locator(&amp;quot;iframe#mainFrame&amp;quot;)&lt;/code&gt;로 접근해야 한다.&lt;/p&gt;
&lt;h2 id="삽질-기록-이게-제일-중요"&gt;삽질 기록 (이게 제일 중요)
&lt;/h2&gt;&lt;p&gt;실전에서 만난 문제들. 순서대로 다 겪었다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Playwright 내장 Chromium이 실행 불가&lt;/strong&gt; — &amp;ldquo;side-by-side configuration is incorrect&amp;rdquo; 오류. 재설치해도 안 됐고, 결국 &lt;code&gt;channel=&amp;quot;chrome&amp;quot;&lt;/code&gt; 옵션으로 PC에 설치된 진짜 Chrome을 쓰게 해서 해결했다. 이 방법이 봇 감지 회피에도 오히려 유리하다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;FrameLocator&lt;/code&gt;에는 &lt;code&gt;evaluate()&lt;/code&gt;가 없다&lt;/strong&gt; — iframe 안에서 JS를 실행하려면 &lt;code&gt;frame.locator(&amp;quot;body&amp;quot;).evaluate(...)&lt;/code&gt;처럼 요소를 잡고 실행해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&amp;ldquo;취소&amp;rdquo; 버튼이 두 개&lt;/strong&gt; — 이어쓰기 팝업의 &amp;lsquo;취소&amp;rsquo;를 &lt;code&gt;get_by_role(&amp;quot;button&amp;quot;, name=&amp;quot;취소&amp;quot;)&lt;/code&gt;로 찾았더니 서식 도구의 &amp;lsquo;취소선&amp;rsquo; 버튼과 겹쳐 strict mode 오류. 정확한 클래스(&lt;code&gt;button.se-popup-button-cancel&lt;/code&gt;)로 지정해야 한다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배치 파일이 로그 폴더 없이 리다이렉트&lt;/strong&gt; — &lt;code&gt;&amp;gt;&amp;gt; logs\runner.log&lt;/code&gt;인데 logs 폴더가 없으면 스크립트가 시작조차 안 된다. &lt;code&gt;if not exist logs mkdir logs&lt;/code&gt; 한 줄로 해결.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;블로그 미개설 계정&lt;/strong&gt; — 계정은 있는데 블로그를 개설한 적이 없으면 글쓰기 페이지가 아예 안 뜬다. 자동화 전에 블로그 개설부터.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="스케줄링과-검증"&gt;스케줄링과 검증
&lt;/h2&gt;&lt;div class="highlight"&gt;&lt;div class="chroma"&gt;
&lt;table class="lntable"&gt;&lt;tr&gt;&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code&gt;&lt;span class="lnt"&gt;1
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-gdscript3" data-lang="gdscript3"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;schtasks&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;Create&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;TN&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;NaverBlogUpload&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;TR&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;run_upload.bat&amp;#34;&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;SC&lt;/span&gt; &lt;span class="n"&gt;DAILY&lt;/span&gt; &lt;span class="o"&gt;/&lt;/span&gt;&lt;span class="n"&gt;ST&lt;/span&gt; &lt;span class="mi"&gt;09&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="mi"&gt;00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/td&gt;&lt;/tr&gt;&lt;/table&gt;
&lt;/div&gt;
&lt;/div&gt;&lt;p&gt;headed 브라우저를 띄우는 방식이라 PC가 켜져 있고 로그인된 상태여야 실행된다.&lt;/p&gt;
&lt;p&gt;&lt;img alt="작업 스케줄러 등록 확인" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://junstellar.github.io/images/naver-auto/scheduler.png"&gt;
&lt;em&gt;매일 오전 9시, 다음 실행을 기다리는 작업 스케줄러.&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;발행 후에는 &amp;ldquo;성공했다고 믿지 말고 확인&amp;quot;이 원칙. 공개된 글 URL을 실제로 가져와서 제목·본문 키워드·이미지 개수(스마트에디터 이미지 리소스 7개)까지 대조해 검증했다.&lt;/p&gt;
&lt;p&gt;&lt;img alt="모바일에서 본 발행 글 (TOP1~TOP3 구간)" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://junstellar.github.io/images/naver-auto/published-mobile-full.png"&gt;
&lt;em&gt;모바일 화면의 TOP1~TOP3 구간 — 카드 이미지가 본문 사이사이 의도한 위치에 정확히 들어갔다.&lt;/em&gt;&lt;/p&gt;
&lt;h2 id="자주-묻는-질문-실제로-부딪힌-것들"&gt;자주 묻는 질문 (실제로 부딪힌 것들)
&lt;/h2&gt;&lt;p&gt;&lt;strong&gt;Q. 크롬에 네이버 계정이 여러 개 로그인돼 있는데 괜찮나?&lt;/strong&gt;
상관없다. Playwright는 평소 쓰는 크롬 프로필과 완전히 분리된 깨끗한 창을 띄운다. 세션 저장 단계에서 로그인한 그 계정 하나만 &lt;code&gt;auth.json&lt;/code&gt;에 저장되고, 이후 발행은 계속 그 계정으로만 된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. 비밀번호가 어딘가에 저장되는 건 아닌가?&lt;/strong&gt;
아니다. 로그인은 네이버 공식 페이지에서 사람이 직접 하고, 저장되는 건 세션 쿠키뿐이다. 다만 그 쿠키 파일 자체가 로그인 열쇠이므로 비밀번호처럼 다뤄야 한다. 유출이 의심되면 네이버 보안설정에서 전체 기기 로그아웃하면 즉시 무효화된다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. 오전 9시에 PC가 꺼져 있으면?&lt;/strong&gt;
그날은 발행되지 않는다. headed 브라우저를 띄우는 방식이라 PC가 켜져 있고 로그인된 상태여야 한다. 중복 방지 플래그 덕분에 나중에 수동으로 실행해도 안전하다.&lt;/p&gt;
&lt;p&gt;&lt;strong&gt;Q. 세션은 언제까지 유효한가?&lt;/strong&gt;
매일 실행이 이어지는 동안에는 스크립트가 실행 때마다 세션을 갱신해 계속 유지된다. 하지만 PC를 며칠 꺼두면 만료된다 — 실제로 6일 건너뛰었더니 만료돼서 재로그인이 필요했다. 스크립트는 로그인 페이지로 리다이렉트되는 걸 감지해 &amp;ldquo;세션 만료&amp;rdquo; 로그를 남기고 멈추도록 해뒀다.&lt;/p&gt;
&lt;p&gt;한 가지 함정: 세션 갱신 저장을 아무 때나 하면 안 된다. 만료된 상태에서 갱신 저장이 실행되면 &lt;strong&gt;로그아웃 상태의 쿠키로 세션 파일을 덮어써버린다&lt;/strong&gt;. 반드시 &amp;ldquo;로그인 확인 후에만 저장&amp;quot;하도록 가드를 넣어야 한다. (직접 당해보고 알았다.)&lt;/p&gt;
&lt;h2 id="정리"&gt;정리
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;네이버 블로그 자동 발행 = &lt;strong&gt;세션 저장(auth.json) + 클립보드 HTML 붙여넣기 + 파일 선택창 가로채기 + 스케줄러&lt;/strong&gt; 네 가지 조합이면 된다.&lt;/li&gt;
&lt;li&gt;비밀번호는 어디에도 저장하지 않는다. 세션 파일만 쓰고, 그 파일을 비밀번호처럼 다룬다.&lt;/li&gt;
&lt;li&gt;브라우저 자동화는 반드시 깨진다는 전제로 만들 것 — 실패 스크린샷, 로그, 중복 방지 플래그가 본체만큼 중요하다.&lt;/li&gt;
&lt;li&gt;자동 발행이 되더라도 콘텐츠 품질은 별개 문제다. 네이버가 저품질로 판단하지 않도록 글 자체의 완성도를 관리하는 건 여전히 사람 몫.&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>