Switch to user Chrome CDP for YouTube transcript, fix auth and ads

- Replace Playwright standalone browser with CDP connection to user Chrome
  (bypasses YouTube bot detection by using logged-in Chrome session)
- Add video playback, ad detection/skip, and play confirmation before transcript extraction
- Extract transcript JS to separate resource files (fix SyntaxError in evaluate)
- Add ytInitialPlayerResponse-based transcript extraction as primary method
- Fix token refresh: retry on network error during backend restart
- Fix null userId logout, CLOB type hint for structured_content
- Disable XFCE screen lock/screensaver
- Add troubleshooting entries (#10-12) and YouTube transcript guide

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-09 21:01:49 +00:00
parent 9abb770e37
commit 4cde775809
10 changed files with 818 additions and 175 deletions

View File

@@ -0,0 +1,207 @@
# YouTube 트랜스크립트 추출 가이드
## 아키텍처
```
┌──────────────────────────────────────────────────┐
│ 사용자 Chrome 브라우저 │
│ (VNC에서 직접 YouTube 로그인된 상태) │
│ --remote-debugging-port=9222 │
│ --remote-allow-origins=* │
└──────────────┬───────────────────────────────────┘
│ CDP (Chrome DevTools Protocol)
┌──────────────▼───────────────────────────────────┐
│ PlaywrightBrowserService │
│ playwright.chromium().connectOverCDP() │
│ → 사용자 Chrome의 세션/쿠키를 그대로 사용 │
└──────────────┬───────────────────────────────────┘
┌──────────────▼───────────────────────────────────┐
│ YouTubeTranscriptService │
│ 1차: youtube-transcript-api 라이브러리 │
│ 2차: Playwright → 사용자 Chrome 탭으로 추출 │
└──────────────────────────────────────────────────┘
```
## 핵심: 왜 사용자 Chrome인가?
### Playwright 자체 브라우저의 한계
- Playwright가 실행하는 Chromium에는 `--enable-automation` 플래그가 붙음
- YouTube가 이를 감지하여 **봇으로 판정** → 자막 API 차단
- 쿠키를 로드해도, 직접 로그인을 시도해도 차단됨
- Google 로그인 자체가 "안전하지 않은 브라우저"로 거부됨
### 사용자 Chrome + CDP 방식
- VNC에서 일반 Chrome으로 YouTube에 로그인
- 백엔드가 CDP로 해당 Chrome에 연결하여 새 탭을 열어 작업
- **로그인 세션이 그대로 유지**되므로 봇 판정 우회
- Chrome 자체는 종료하지 않고 연결만 관리
## Chrome 실행 방법
### 필수 옵션
```bash
DISPLAY=:1 google-chrome \
--remote-debugging-port=9222 \
--remote-allow-origins=* \
--user-data-dir=/tmp/chrome-debug-profile \
--no-default-browser-check \
"https://www.youtube.com"
```
| 옵션 | 설명 |
|------|------|
| `--remote-debugging-port=9222` | CDP 접속 포트 |
| `--remote-allow-origins=*` | CDP WebSocket 연결 허용 |
| `--user-data-dir` | 프로필 디렉토리 (기본과 다른 경로 필요) |
### Chrome 프로필 복사 (기존 로그인 유지)
```bash
cp -r /home/opc/.config/google-chrome /tmp/chrome-debug-profile
```
### CDP 연결 확인
```bash
curl -s http://localhost:9222/json/version | python3 -m json.tool
```
## YouTube 로그인
1. VNC로 접속 (`vnc://공인IP:5901`)
2. Chrome에서 YouTube가 열려있는지 확인
3. YouTube에 Google 계정으로 로그인
4. 로그인 상태 유지 — Chrome을 닫지 않음
> Chrome을 닫으면 CDP 연결이 끊기고 백엔드가 재연결을 시도함.
> Chrome을 다시 시작하면 다시 로그인 필요.
## 트랜스크립트 추출 흐름
### 1차: youtube-transcript-api 라이브러리
```
videoId → TranscriptApi.listTranscripts()
→ 수동 자막(ko, en) 우선
→ 자동 생성 자막 fallback
```
- 서버 IP가 봇으로 판정되면 실패 (대부분 실패)
### 2차: Playwright + 사용자 Chrome
```
1. 사용자 Chrome에 새 탭 열기 (DOMCONTENTLOADED, 60초 타임아웃)
2. DOM 렌더링 대기 (3초)
3. 동영상 재생 시작 (playVideo)
- 쿠키 동의 팝업 닫기
- 마우스 호버 → 재생 버튼 클릭
4. 광고 대기 (waitForAdsToFinish, 최대 60초)
- 스킵 버튼 자동 클릭
- 광고 끝날 때까지 반복 확인
5. 실제 영상 재생 확인 (waitForVideoPlaying, 최대 15초)
- video.currentTime > 0.5 확인
- 일시정지면 재시도
6. ytInitialPlayerResponse에서 자막 URL 추출 + fetch (fetchTranscriptFromPageJs)
7. 실패 시 자막 패널 열기 (extractTranscriptFromPanel)
- 'Show transcript' 버튼 클릭
- 자막 세그먼트 DOM에서 텍스트 추출
- 최대 30초 반복 확인
8. 완료 후 about:blank로 이동 (탭 유지)
```
## 광고 처리
### 감지 방법
- `#movie_player.ad-showing` 클래스 확인 (가장 신뢰할 수 있음)
- `.ytp-ad-player-overlay` 오버레이 확인
- 광고 텍스트 배지 확인 (`.ytp-ad-text`)
### 스킵 버튼 셀렉터
```css
.ytp-skip-ad-button,
.ytp-ad-skip-button,
.ytp-ad-skip-button-modern,
.ytp-ad-skip-button-container button
```
### 주의사항
- 광고 스킵 후 두 번째 광고가 나올 수 있음 → `no_ad` 상태 확인까지 반복
- 광고 중에는 `NETWORKIDLE`에 도달하지 못함 → `DOMCONTENTLOADED` 사용
## 쿠키 관리
### CDP 방식에서는 쿠키 파일 불필요
- 사용자 Chrome에 직접 연결하므로 `cookies.txt` 불필요
- Chrome 세션의 쿠키가 자동으로 사용됨
### Chrome 쿠키 수동 export (참고용)
Chrome을 `--remote-debugging-port=9222`로 실행 후:
```python
import json, websocket, urllib.request
tabs = json.loads(urllib.request.urlopen("http://localhost:9222/json").read())
ws_url = tabs[0]["webSocketDebuggerUrl"]
ws = websocket.create_connection(ws_url)
ws.send(json.dumps({"id": 1, "method": "Network.getAllCookies"}))
result = json.loads(ws.recv())
cookies = result["result"]["cookies"]
ws.close()
```
> Chrome의 쿠키 DB(`~/.config/google-chrome/Default/Cookies`)는 암호화되어 있어 직접 읽기 어려움.
> CDP를 통해 복호화된 쿠키를 가져오는 것이 가장 확실한 방법.
## PM2 + Chrome 자동 시작
Chrome이 서버 재부팅 시에도 자동 시작되도록:
```bash
# pm2로 Chrome 관리하지 않음 (GUI 앱이라 적합하지 않음)
# 대신 VNC 시작 시 자동 실행하도록 ~/.vnc/xstartup에 추가:
```
`~/.vnc/xstartup`:
```bash
#!/bin/bash
exec startxfce4 &
# Chrome을 CDP 모드로 자동 시작
sleep 5
google-chrome --remote-debugging-port=9222 \
--remote-allow-origins=* \
--user-data-dir=/tmp/chrome-debug-profile \
--no-default-browser-check \
"https://www.youtube.com" &
```
## 트러블슈팅
### Chrome CDP 연결 실패
```
Chrome CDP 연결 실패: Chrome이 --remote-debugging-port=9222로 실행 중인지 확인하세요.
```
→ Chrome 재시작:
```bash
DISPLAY=:1 google-chrome --remote-debugging-port=9222 --remote-allow-origins=* \
--user-data-dir=/tmp/chrome-debug-profile --no-default-browser-check "https://www.youtube.com" &
```
### 자막 패널이 ghost-cards만 표시
- YouTube가 자막 데이터 로드를 차단한 상태
- Chrome에서 YouTube 로그인이 풀렸는지 VNC에서 확인
- 로그인 상태라면 Chrome 재시작 후 다시 로그인
### "안전하지 않은 브라우저" 로그인 거부
- Playwright 자체 브라우저에서 발생 (정상)
- **사용자 Chrome**에서만 로그인 가능
- CDP 방식으로 전환하면 해결
### 광고 중에 페이지가 닫힘
- YouTube 페이지 로드 시 `NETWORKIDLE` 사용하면 광고 때문에 타임아웃
- **`DOMCONTENTLOADED`** + 60초 타임아웃으로 변경하여 해결
## 관련 파일
| 파일 | 역할 |
|------|------|
| `PlaywrightBrowserService.java` | 사용자 Chrome에 CDP 연결, 탭 관리 |
| `YouTubeTranscriptService.java` | 자막 추출 로직 (API → Playwright fallback) |
| `youtube-transcript-extract.js` | 자막 패널 DOM 추출 JS |
| `youtube-transcript-from-page.js` | ytInitialPlayerResponse 기반 자막 추출 JS |
| `~/.vnc/xstartup` | VNC 시작 시 Chrome 자동 실행 |