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:
207
docs/youtube-transcript-guide.md
Normal file
207
docs/youtube-transcript-guide.md
Normal 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 자동 실행 |
|
||||
Reference in New Issue
Block a user