<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>자동화 on To the Moon</title><link>https://junstellar.github.io/tags/%EC%9E%90%EB%8F%99%ED%99%94/</link><description>Recent content in 자동화 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/%EC%9E%90%EB%8F%99%ED%99%94/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><item><title>@멘션 한 번이면 클로드가 답한다 — 디스코드로 만든 팀 AI 비서</title><link>https://junstellar.github.io/p/discord-claude-mention-bot/</link><pubDate>Tue, 14 Jul 2026 12:00:00 +0900</pubDate><guid>https://junstellar.github.io/p/discord-claude-mention-bot/</guid><description>&lt;h2 id="시작은-슬랙이었다"&gt;시작은 슬랙이었다
&lt;/h2&gt;&lt;p&gt;팀 프로젝트 관리용으로 &amp;ldquo;메신저에서 클로드를 태그로 불러 쓰는&amp;rdquo; 환경을 만들고 싶었다.
슬랙에 클로드를 붙이는 공식 기능(Claude in Slack, 이른바 클로드 태그)이 이미 있어서
그걸 쓰려고 했는데 — &lt;strong&gt;팀 플랜을 요구했다.&lt;/strong&gt; 혼자 쓰거나 소규모로 굴리는 입장에선
기능 하나 때문에 요금제를 통째로 올리는 게 납득이 안 됐다.&lt;/p&gt;
&lt;p&gt;그래서 &amp;ldquo;그럼 내가 쓰는 메신저에 직접 붙이자&amp;quot;로 방향을 틀었고, 후보를 좁혔다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;카카오톡&lt;/strong&gt; — 탈락. 일반 채팅방에 붙일 봇 API가 아예 없다.
오픈빌더는 1:1 채널 챗봇 전용이고, 비공식 자동화는 계정 정지 위험이 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;텔레그램&lt;/strong&gt; — 공식 봇 API가 가장 간단하고 진입장벽이 낮다. 다만 대화가 한 줄기라
여러 주제를 동시에 굴리면 금방 뒤엉킨다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;디스코드&lt;/strong&gt; — 최종 선택.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;디스코드를 고른 이유는 명확했다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;스레드 구조.&lt;/strong&gt; 멘션 하나에 스레드가 하나 생기니 주제별로 문맥이 자연스럽게 분리된다.
이게 뒤에 나올 &lt;code&gt;--resume&lt;/code&gt; 세션 매핑과 정확히 맞아떨어진다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;채널 분리.&lt;/strong&gt; 업무/메모/일정처럼 용도별 채널을 나눠 쓸 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;첨부파일과 코드블록을 제대로 다룬다.&lt;/strong&gt; 파일 올리고 바로 &amp;ldquo;이거 분석해줘&amp;quot;가 된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;봇 권한을 세밀하게 준다.&lt;/strong&gt; 어떤 채널에서 뭘 할 수 있는지 서버 단위로 통제된다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;슬래시 명령이 1급 시민.&lt;/strong&gt; &lt;code&gt;/일정등록&lt;/code&gt; 같은 걸 자동완성·인자 검증까지 붙여 만들 수 있다.&lt;/li&gt;
&lt;li&gt;무료이고, PC·모바일 클라이언트가 다 있어서 나가서도 그대로 쓴다.&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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;디스코드 @멘션 → discord.py 봇 → claude -p (headless Claude Code) → 응답 전송
&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;ul&gt;
&lt;li&gt;&lt;code&gt;claude -p --output-format json&lt;/code&gt;은 응답과 함께 &lt;code&gt;session_id&lt;/code&gt;를 돌려준다.
이걸 디스코드 스레드 ID와 매핑해 저장해두고, 다음 메시지에 &lt;code&gt;--resume &amp;lt;session_id&amp;gt;&lt;/code&gt;를
붙이면 &lt;strong&gt;스레드마다 대화 문맥이 유지&lt;/strong&gt;된다.&lt;/li&gt;
&lt;li&gt;Claude Code에 구독(Pro/Max) 로그인이 되어 있으면 &lt;code&gt;claude -p&lt;/code&gt;는 구독량으로 처리되어
&lt;strong&gt;Anthropic API 결제가 필요 없다.&lt;/strong&gt; 슬랙 쪽에서 막혔던 요금 문제가 여기서 사라진다.&lt;/li&gt;
&lt;li&gt;headless 모드에서도 그 PC에 설정된 &lt;strong&gt;MCP 서버와 스킬을 전부 그대로 인식한다.&lt;/strong&gt;
내 PC에서 쓰던 환경이 그대로 디스코드로 나오는 셈이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="예상-못-한-수확-로컬-메모-동기화가-같이-풀렸다"&gt;예상 못 한 수확: 로컬 메모 동기화가 같이 풀렸다
&lt;/h2&gt;&lt;p&gt;옵시디언처럼 &lt;strong&gt;메모를 로컬 파일로 쌓아두고 있었는데&lt;/strong&gt;, 이걸 폰에서도 보려면
어느 서비스든 결국 유료 동기화 플랜을 권한다. 슬랙 때와 똑같은 벽이다.&lt;/p&gt;
&lt;p&gt;그런데 이 봇을 붙이고 나니 그 문제가 통째로 사라졌다.
메모 파일은 계속 내 PC에 로컬로 있고, 디스코드에서 멘션 한 번이면
클로드가 그 파일들을 읽어서 답한다. &lt;strong&gt;동기화를 할 필요가 없다 —
파일을 옮기는 게 아니라, 파일이 있는 곳에 질문을 보내는 구조이기 때문이다.&lt;/strong&gt;&lt;/p&gt;
&lt;p&gt;여기에 MCP를 얹으면 더 좋아진다. 레드마인 같은 사내 시스템을 MCP로 물려두면,
봇이 일정과 이슈를 조회해서 &lt;strong&gt;디스코드 채팅에 보기 좋게 정리해 뿌려준다.&lt;/strong&gt;
로컬 메모 + MCP 데이터를 한 자리에서 묶어 요약해주니, 별도 대시보드를 안 만들어도
&amp;ldquo;오늘 뭐 있지&amp;quot;가 채팅 한 줄로 끝난다.&lt;/p&gt;
&lt;h2 id="봇-기능"&gt;봇 기능
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;멘션 → 스레드 자동 생성 후 답변&lt;/li&gt;
&lt;li&gt;첨부파일 자동 다운로드 후 클로드가 Read 도구로 분석&lt;/li&gt;
&lt;li&gt;슬래시 명령(&lt;code&gt;/일정등록&lt;/code&gt;, &lt;code&gt;/일정목록&lt;/code&gt;, &lt;code&gt;/일정삭제&lt;/code&gt;)으로 일정 알림
(마감 전날 1회 + 당일 매시간 반복, 삭제 = 완료 처리)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img alt="디스코드에서 슬래시 명령으로 일정을 등록하고, 봇이 마감일과 D-day를 정리해 보여주는 화면" loading="lazy" sizes="(max-width: 767px) calc(100vw - 30px), (max-width: 1023px) 700px, (max-width: 1279px) 950px, 1232px" src="https://junstellar.github.io/images/discord-bot/claudetag-schedule-ko.png"&gt;&lt;/p&gt;
&lt;h2 id="보안-설계"&gt;보안 설계
&lt;/h2&gt;&lt;p&gt;봇이 있는 서버의 &lt;strong&gt;모든 멤버가 내 PC의 클로드를 쓰게 되므로&lt;/strong&gt; 도구를 잠가야 한다.
&lt;code&gt;--allowedTools&lt;/code&gt;를 읽기/검색 계열(Read, Glob, Grep, WebSearch, WebFetch)과
허용할 MCP 도구(&lt;code&gt;mcp__redmine__list_issues&lt;/code&gt; 등)로만 제한하고,
Bash·Write·Edit 같은 실행/쓰기 도구는 막았다.&lt;/p&gt;
&lt;h2 id="디스코드-봇-등록-절차"&gt;디스코드 봇 등록 절차
&lt;/h2&gt;&lt;p&gt;개발자 포털에서:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;New Application → Bot 메뉴에서 토큰 발급&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;MESSAGE CONTENT INTENT 스위치 켜기&lt;/strong&gt; — 안 켜면 봇이 메시지를 못 읽는다. 최다 실수 포인트.&lt;/li&gt;
&lt;li&gt;OAuth2 URL Generator에서 &lt;code&gt;bot&lt;/code&gt; 스코프 + 필요한 권한 체크&lt;/li&gt;
&lt;li&gt;생성된 URL로 서버 초대&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;다른 클로드 구독자도 자기 토큰과 자기 구독으로 돌릴 수 있게
zip(봇 코드 + &lt;code&gt;.env.example&lt;/code&gt; + 설치 가이드 + &lt;code&gt;setup.bat&lt;/code&gt;)으로 묶어뒀다.
setup.bat이 Python(winget)·discord.py 설치와 설정 파일 생성까지 자동화하고,
계정이 필요한 부분(디스코드 토큰 발급, 클로드 로그인)만 수동으로 남는다.&lt;/p&gt;
&lt;h2 id="막혔던-점과-해결"&gt;막혔던 점과 해결
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;스토어(MSIX) 설치 Python은 AppData가 가상화&lt;/strong&gt;되어 &lt;code&gt;%APPDATA%\Claude\claude-code\&lt;/code&gt;의
claude.exe를 못 읽는다 (PowerShell에선 보이는데 Python에선 FileNotFoundError).
→ claude.exe를 AppData 밖 폴더로 복사해서 해결. 단독 실행파일이라 복사만으로 동작.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;배치 파일에 한글을 넣으면 cmd가 명령을 깨뜨린다&lt;/strong&gt; (&lt;code&gt;chcp 65001&lt;/code&gt; 이후 파서가 바이트
오프셋을 잘못 읽어 &lt;code&gt;'laudeBot'은(는)...&lt;/code&gt; 같은 유령 오류 발생). → bat 파일은 ASCII만 사용.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;PowerShell 파이프로 한글을 넘기면 인코딩이 깨진다.&lt;/strong&gt; → 파이썬 subprocess에서
UTF-8 바이트로 stdin에 직접 쓰면 안전. 파이썬 실행도 &lt;code&gt;-u&lt;/code&gt;(unbuffered) + &lt;code&gt;PYTHONUTF8=1&lt;/code&gt; 필수
(안 하면 print가 버퍼에 갇혀 로그가 안 보이거나 이모지 출력에서 죽는다).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;봇을 AI 세션의 백그라운드 프로세스로 띄우면 세션이 끝날 때 봇도 같이 죽는다.&lt;/strong&gt;
→ bat 더블클릭으로 독립 콘솔에서 실행해야 한다. 콘솔 창 닫으면 봇 종료, 최소화는 OK.&lt;/li&gt;
&lt;li&gt;같은 봇 토큰으로 프로세스를 2개 띄우면 게이트웨이에 둘 다 붙어서 &lt;strong&gt;답장이 중복&lt;/strong&gt;된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="메모생각"&gt;메모/생각
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;결국 세 번의 &amp;ldquo;유료 플랜을 쓰세요&amp;quot;를 같은 방법 하나로 넘겼다.
슬랙 클로드 태그(팀 플랜), 메모 동기화(구독), API 결제 —
&lt;strong&gt;내 PC에서 도는 봇 하나&lt;/strong&gt;로 셋 다 정리됐다.&lt;/li&gt;
&lt;li&gt;핵심은 클로드를 어디 올려두는 게 아니라, 이미 내 PC에 갖춰둔 환경(MCP·스킬·로컬 파일)에
&lt;strong&gt;메신저라는 입구를 하나 뚫어준 것&lt;/strong&gt;이다. 그래서 새로 설정할 게 거의 없었다.&lt;/li&gt;
&lt;li&gt;다음에 해볼 것: 부팅 시 자동 시작 등록, 스레드별 도구 권한 분리.&lt;/li&gt;
&lt;/ul&gt;</description></item><item><title>유튜브 AI 트렌드를 매일 밤 자동으로 받아보기 — YouTube API + Gemini + 클로드 3단 폴백</title><link>https://junstellar.github.io/p/youtube-ai-trend-report-automation/</link><pubDate>Sun, 12 Jul 2026 10:30:00 +0900</pubDate><guid>https://junstellar.github.io/p/youtube-ai-trend-report-automation/</guid><description>&lt;p&gt;AI 소식은 유튜브에 제일 먼저 올라오는데, 매번 손으로 검색해서 훑는 건 시간 낭비다. 그래서 만들었다 — &lt;strong&gt;매일 밤 11시에 자동으로 돌아서, 아침에 일어나면 어제 하루치 AI 트렌드 리포트가 마크다운으로 놓여 있는&lt;/strong&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;span class="lnt"&gt; 9
&lt;/span&gt;&lt;span class="lnt"&gt;10
&lt;/span&gt;&lt;span class="lnt"&gt;11
&lt;/span&gt;&lt;span class="lnt"&gt;12
&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-fallback" data-lang="fallback"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;매일 밤 11시 (Windows 작업 스케줄러)
&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;YouTube Data API → 최근 24시간 Claude·ChatGPT·Gemini 영상 검색
&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;필터링 (AI 모델 이름 없는 영상 제거 + 차단 키워드)
&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;조회수 상위 N개 분석 — 3단 폴백
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ① Gemini가 영상 URL을 직접 시청 (기본)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ② 토큰 소진 시 → claude -p (자막+메타데이터 분석)
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; ③ 그래도 안 되면 → Gemini 텍스트 분석
&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;로컬 마크다운 리포트 저장 (reports/trend-날짜.md)
&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;&amp;ldquo;검색·발견&amp;quot;은 YouTube API가, &amp;ldquo;내용 분석&amp;quot;은 AI가&lt;/strong&gt; 맡는다. 검색 키워드는 3대 모델(Claude/ChatGPT/Gemini)을 뉴스·새 기능·활용법 앵글로 조합한 9개다:&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;/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;KEYWORDS&lt;/span&gt; &lt;span class="o"&gt;=&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="s2"&gt;&amp;#34;ChatGPT 업데이트&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Claude 업데이트&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Gemini 새 기능&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;OpenAI 발표&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 class="s2"&gt;&amp;#34;ChatGPT new feature&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Claude AI update&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Gemini update&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 class="s2"&gt;&amp;#34;ChatGPT 활용법&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;Claude Code&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 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;h2 id="가장-중요한-깨달음-gemini는-유튜브-url을-직접-먹는다"&gt;가장 중요한 깨달음: Gemini는 유튜브 URL을 직접 먹는다
&lt;/h2&gt;&lt;p&gt;처음엔 &amp;ldquo;영상 자막(CC)을 다운로드해서 분석해야 하나?&amp;ldquo;라고 생각했다. 그런데 여기 함정이 있다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;YouTube 공식 API의 자막 다운로드는 &amp;ldquo;내 채널 영상&amp;quot;만 된다.&lt;/strong&gt; 남의 영상 자막은 공식 API로 못 가져온다.&lt;/li&gt;
&lt;li&gt;그래서 보통 &lt;code&gt;youtube-transcript-api&lt;/code&gt; 같은 비공식 라이브러리를 쓰는데, 유튜브가 막으면 깨진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;그런데 Gemini API는 유튜브 URL 자체를 영상으로 그냥 받는다.&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;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;span class="lnt"&gt;11
&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="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;google&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="nn"&gt;google.genai&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;types&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;client&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;genai&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Client&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;api_key&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;GEMINI_API_KEY&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;resp&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;models&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;generate_content&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;model&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;gemini-2.5-flash&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 class="n"&gt;contents&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Content&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;parts&lt;/span&gt;&lt;span class="o"&gt;=&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;types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_data&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;FileData&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;file_uri&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;https://www.youtube.com/watch?v=...&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 class="n"&gt;types&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;Part&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;이 영상 핵심 3줄 요약 + 다룬 AI 키워드 뽑아줘&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 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="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;이 한 방으로 &amp;ldquo;자막 다운로드&amp;rdquo; 단계가 통째로 사라졌다. 구조가 훨씬 단순해졌다.&lt;/p&gt;
&lt;h2 id="검색-노이즈-잡기-조회수순보다-관련도순"&gt;검색 노이즈 잡기: 조회수순보다 관련도순
&lt;/h2&gt;&lt;p&gt;처음엔 &lt;code&gt;order=viewCount&lt;/code&gt;(조회수순)로 검색했더니, &amp;ldquo;Gemini&amp;quot;라는 단어에 &lt;strong&gt;인도 텔루구 TV 채널 &amp;ldquo;Sun Gemini&amp;rdquo;&lt;/strong&gt;, 아랍어 음식 챌린지 같은 게 조회수 높다고 딸려왔다.&lt;/p&gt;
&lt;p&gt;해결책 두 가지:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;order=relevance&lt;/code&gt;(관련도순)로 검색&lt;/strong&gt; → 대형 비(非)AI 채널이 밀려남. 그런 다음 그 결과들 안에서 조회수로 다시 정렬.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;필터 두 겹 추가&lt;/strong&gt;
&lt;ul&gt;
&lt;li&gt;차단 키워드(BLOCK): 제목/설명에 &lt;code&gt;Promo&lt;/code&gt;, &lt;code&gt;Serial&lt;/code&gt;, &lt;code&gt;NASA&lt;/code&gt; 등이 있으면 버림&lt;/li&gt;
&lt;li&gt;필수 키워드(REQUIRE): 제목/설명에 &lt;code&gt;claude / chatgpt / gpt / gemini / 클로드 / 제미나이&lt;/code&gt; 중 하나도 없으면 버림&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&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;/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;haystack&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;title&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;channel&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;desc&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;&amp;#34;&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&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="c1"&gt;# 1) 차단어 있으면 스킵&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;bw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;haystack&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;bw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;BLOCK_WORDS&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="k"&gt;continue&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# 2) AI 모델 이름 하나도 없으면 스킵&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="nb"&gt;any&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;rw&lt;/span&gt;&lt;span class="o"&gt;.&lt;/span&gt;&lt;span class="n"&gt;lower&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;haystack&lt;/span&gt; &lt;span class="k"&gt;for&lt;/span&gt; &lt;span class="n"&gt;rw&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;REQUIRE_ANY&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="k"&gt;continue&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;특히 두 번째 필터가 강력했다. 기간을 24시간으로 좁히니 AI 영상이 적어서 &amp;ldquo;NASA Moon Base Update&amp;rdquo; 같은 게 조회수로 올라왔는데, &amp;ldquo;AI 모델 이름 없으면 제거&amp;rdquo; 필터 한 방에 정치·일반뉴스 노이즈가 싹 사라졌다.&lt;/p&gt;
&lt;h2 id="무료-티어의-벽-그리고-3단-폴백"&gt;무료 티어의 벽, 그리고 3단 폴백
&lt;/h2&gt;&lt;p&gt;Gemini 무료 티어는 &lt;strong&gt;분당 입력 25만 토큰&lt;/strong&gt; 제한이 있다. 그런데 영상은 텍스트와 달리 토큰을 엄청 먹는다(초당 약 100~300토큰). 영상 2개만 분석해도 &lt;code&gt;429 RESOURCE_EXHAUSTED&lt;/code&gt;가 터졌다.&lt;/p&gt;
&lt;p&gt;기본 대응은 이렇다:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;저해상도 처리&lt;/strong&gt;로 토큰 절약: &lt;code&gt;media_resolution=&amp;quot;MEDIA_RESOLUTION_LOW&amp;quot;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;영상 사이 20초 대기&lt;/strong&gt;로 분당 한도 회피&lt;/li&gt;
&lt;li&gt;503 같은 일시 오류는 잠시 쉬고 재시도&lt;/li&gt;
&lt;li&gt;분석 개수(&lt;code&gt;TOP_N&lt;/code&gt;)를 무리하게 키우지 않기 (6~8개가 안전)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;그래도 토큰이 소진되는 날이 있다. 그래서 &lt;strong&gt;분석 엔진을 3단 폴백으로&lt;/strong&gt; 만들었다:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Gemini 영상 시청&lt;/strong&gt; — 기본. 영상을 직접 보니 가장 깊다.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;claude -p&lt;/code&gt; (헤드리스 클로드)&lt;/strong&gt; — Gemini 토큰이 떨어지면, 로컬에 설치된 Claude Code CLI를 파이프라인이 직접 호출한다. 영상 대신 제목·설명·자막(최대 8,000자)을 표준입력으로 넘겨 같은 형식으로 분석시킨다. CLI에 로그인만 되어 있으면 별도 API 키 없이 작동.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Gemini 텍스트 분석&lt;/strong&gt; — 클로드마저 안 되면 같은 텍스트 뭉치를 Gemini에 넘긴다. 텍스트라 토큰을 거의 안 쓴다.&lt;/li&gt;
&lt;/ol&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;/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="c1"&gt;# ① Gemini 영상 시청 → 429면 즉시 폴백&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;quota_hit&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gemini_video&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;video&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="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;quota_hit&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;context&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;build_text_context&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;video&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="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;claude_headless&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;claude_exe&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ② claude -p&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="n"&gt;text&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;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;gemini_text&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;client&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="c1"&gt;# ③ Gemini 텍스트&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;리포트에는 영상마다 어떤 엔진이 분석했는지(🎬 영상 시청 / 🤖 claude -p / 📝 텍스트) 표시되므로, 그날 토큰 사정이 어땠는지도 한눈에 보인다.&lt;/p&gt;
&lt;p&gt;교훈: &lt;strong&gt;영상 분석은 토큰이 비싸다.&lt;/strong&gt; &amp;ldquo;많이 보는 것&amp;quot;보다 &amp;ldquo;잘 추려서 몇 개만 깊게 보는 것&amp;quot;이 정답이고, 그마저 안 될 때를 대비한 값싼 차선책을 미리 깔아두면 파이프라인이 멈추지 않는다.&lt;/p&gt;
&lt;h2 id="자동-실행-windows-작업-스케줄러"&gt;자동 실행: Windows 작업 스케줄러
&lt;/h2&gt;&lt;p&gt;매일 밤 11시 실행은 PowerShell로 등록했다. PC가 꺼져 있었을 경우를 대비해 &lt;strong&gt;놓친 작업은 켜지면 바로 실행&lt;/strong&gt;(StartWhenAvailable) 옵션도 켰다.&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;/code&gt;&lt;/pre&gt;&lt;/td&gt;
&lt;td class="lntd"&gt;
&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-powershell" data-lang="powershell"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;New-ScheduledTaskAction&lt;/span&gt; &lt;span class="n"&gt;-Execute&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;run.bat&amp;#34;&lt;/span&gt; &lt;span class="n"&gt;-WorkingDirectory&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;...&amp;#34;&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$trigger&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;New-ScheduledTaskTrigger&lt;/span&gt; &lt;span class="n"&gt;-Daily&lt;/span&gt; &lt;span class="n"&gt;-At&lt;/span&gt; &lt;span class="mf"&gt;23&lt;/span&gt;&lt;span class="err"&gt;:&lt;/span&gt;&lt;span class="mf"&gt;00&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;$settings&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="nb"&gt;New-ScheduledTaskSettingsSet&lt;/span&gt; &lt;span class="n"&gt;-StartWhenAvailable&lt;/span&gt; &lt;span class="n"&gt;-ExecutionTimeLimit&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nb"&gt;New-TimeSpan&lt;/span&gt; &lt;span class="n"&gt;-Hours&lt;/span&gt; &lt;span class="mf"&gt;1&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="nb"&gt;Register-ScheduledTask&lt;/span&gt; &lt;span class="n"&gt;-TaskName&lt;/span&gt; &lt;span class="s2"&gt;&amp;#34;YouTubeTrendReport&amp;#34;&lt;/span&gt; &lt;span class="n"&gt;-Action&lt;/span&gt; &lt;span class="nv"&gt;$action&lt;/span&gt; &lt;span class="n"&gt;-Trigger&lt;/span&gt; &lt;span class="nv"&gt;$trigger&lt;/span&gt; &lt;span class="n"&gt;-Settings&lt;/span&gt; &lt;span class="nv"&gt;$settings&lt;/span&gt; &lt;span class="n"&gt;-Force&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;h2 id="열흘-돌려본-결과"&gt;열흘 돌려본 결과
&lt;/h2&gt;&lt;p&gt;리포트가 매일 꼬박꼬박 쌓이고 있다. 영상별로 3줄 요약·다룬 키워드·실용 팁 1개가 정리되어 나오는데, 가장 큰 효과는 &lt;strong&gt;&amp;ldquo;지금 무슨 키워드가 이슈인지&amp;quot;를 아침 5분 안에 파악할 수 있다&lt;/strong&gt;는 것. 새 모델 출시 같은 큰 뉴스가 터진 날은 리포트 상위가 온통 그 얘기로 채워지니, 유튜브를 직접 뒤지지 않아도 흐름을 놓치지 않는다.&lt;/p&gt;
&lt;p&gt;실제로 이 리포트 덕에 잡은 주제로 글도 하나 썼다 — 최근에 올린 &lt;a class="link" href="https://junstellar.github.io/p/qwen3-tts-local-korean-narration/" &gt;무료 로컬 TTS 트렌드 + Qwen3-TTS 구축기&lt;/a&gt;의 트렌드 조사가 바로 이 파이프라인의 확장판이다.&lt;/p&gt;
&lt;h2 id="배운-것-요약"&gt;배운 것 요약
&lt;/h2&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;영상 분석은 Gemini에 URL을 직접 던지면 된다.&lt;/strong&gt; 자막 다운로드는 옛날 방식.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;검색은 관련도순 + 이중 필터&lt;/strong&gt;(차단어 + AI 모델명 필수)가 노이즈 제거의 핵심.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;무료 티어 토큰 한도&lt;/strong&gt;를 감안해 &amp;ldquo;적게, 깊게&amp;rdquo; 분석하고, &lt;code&gt;claude -p&lt;/code&gt; → 텍스트 분석으로 이어지는 &lt;strong&gt;3단 폴백&lt;/strong&gt;을 깔아 파이프라인이 멈추지 않게.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;API 키는 절대 코드/채팅에 넣지 말고 &lt;code&gt;.env&lt;/code&gt;에.&lt;/strong&gt; (git에서도 제외)&lt;/li&gt;
&lt;li&gt;자동화의 마지막 조각은 결국 &lt;strong&gt;스케줄러&lt;/strong&gt;다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;코드 전체(trend.py, config.py, 스케줄러 등록 안내)는 GitHub에 공개했다: &lt;strong&gt;&lt;a class="link" href="https://github.com/junstellar/youtube-ai-trend-report" target="_blank" rel="noopener"
 &gt;junstellar/youtube-ai-trend-report&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;</description></item></channel></rss>