-
Spring: Playwright exposeFunction (유튜브 라이브 채팅 읽기)컴퓨터/JAVA 2025. 3. 2. 23:24728x90반응형
Youtube.com 라이브에서 API 없이 실시간으로 채팅을 읽고 싶었다.
Spring Boot 3.4 + Java 23 환경에서 Virtual Thread를 켠 상태이다.
(새로운 동영상마다 scraper가 생성되는 방식)
visualvm 일단 유튜브 웹사이트가 동적으로 iframe을 불러온다.
처음에는 HTML에서 <yt-live-chat-text-message-renderer> element를 계속 읽으면 될 것이라고 생각했다.
하지만, 유튜브가 DOM을 재활용한다. (보이는 부분만 렌더링?)
그래서 이 방식은 어느 순간부터 새로운 메시지를 잘 읽지 못하고, ID도 재사용되는 듯해 uuid를 써서
메시지를 처리했는지 일일이 확인해야 했다.
그러면 어떻게 하면 제일 빠르게 읽을 수 있을까? 고민을 하다가 @MutationObserver를 알게 되었다.
DOM 트리 변경사항을 감지할 수 있는 브라우저 api 이기 때문에 가장 케이스에 맞다고 생각했다.
playwright에서는 exposeFunction (자바 콜백 연결)과 evaluate (JS 코드 실행)을 할 수 있다.
아래처럼 evaluate를 써서 iframe 객체에 mutationobserver를 만들고
새로운 메시지를 감지했을 때 자바에서 어떻게 처리할 것인가를 exposeFunction을 통해서 만든다.
username/message를 HTML에서 추출할 때 shadow 요소이기 때문에 shadow를 썼었는데 프로파일 결과 너무 느렸다.
xpath를 사용해서 하니 100배 이상 빨라졌다.
그리고 시간을 많이 잡아먹은 부분이 @Async 메서드 내부에서 추가로 runAsync를 통해 이중 비동기 구조를 만들었었다.
엔드포인트 함수 호출 -> runScape @Async에서 간단한 로직 후에 CompletableFuture 통해 scrape 과정을 나눴다.
그러면 Playwright의 exposeFunction과 MutationObserver를 사용해 새 채팅 메시지를 감지하고, 콜백을 통해서
Java 측에서 메시지를 잘 처리할 수 있다.
하지만 실제 브라우저에서는 새 메시지를 잘 감지했지만, Java 쪽 콜백이 실행이 안되었다.
스레드를 주기적으로 폴링 하거나, 다른 곳에서 스레드를 움직여? 줘야만 이벤트 콜백이 호출되었다.
스레드 설정 문제인가 싶어서 기본 ThreadPoolTaskExecutor 셋업 (VT off)으로 해도 결과가 같았다.
문제는 다음과 같았다.
1. Playwright 콜백 함수를 호출하려면, Java 쪽에서도 이벤트를 처리할 이벤트 루프가 비어있어야 한다.
2. 같은 메서드에서 CompletableFuture.get() or 무한 대기를 하는 코드가 있으면, 이 스레드가 잠겨 콜백을 못 받는다.
3. VT라고 해서 Playwright 네이티브 라이브러리는 콜백 처리를 위해 실제 OS 스레드가 필요한 것 같다.
(가상 스레드가 파킹 상태면 콜백 멈춤)
해결 방법은 간단하게
runAsync를 지우고 runScraper 함수에서 다 돌린다.
블로킹 호출도 제거하고, 필요한 경우에 외부에서 future를 get() 하거나 cancel() 하도록 설계했다.
15분 동안 1만명 라이브 채팅 읽었을 때 Perf: No double async methods for exposeFunction · Alfex4936/YoutubeLive-Chatx@752b803
Alfex4936 committed Mar 2, 2025
github.com
728x90'컴퓨터 > JAVA' 카테고리의 다른 글
멀티스레드 Phaser - flexible 동기화 장벽 (0) 2024.06.12 Spring boot: Github Action CI에 JaCoCo + CodeCov (0) 2023.12.03 Spring boot: 실시간 로그 수집하기 (Logstash, ELK) (1) 2023.10.31