
API 키를 넣으려 했더니, 키 자체가 필요 없어졌다 ── 잠들어 있던 YouTube 목록 엑셀 파일을 GAS로 되살린 이야기
요약
API 키 없이 YouTube Data API를 활용하기 위해 VBA와 Google Apps Script(GAS)를 연동하는 최적화 방법을 다룹니다. GAS를 중계 서버로 활용하여 복잡한 인증 과정을 생략하고, 호출 구조 개선을 통해 성능 저하를 최소화하는 실전 경험을 공유합니다.
핵심 포인트
- GAS를 활용해 API 키와 결제 수단 등록 없이 YouTube API 호출 가능
- VBA에서 GAS 웹 앱을 호출하는 방식으로 인증 및 보안 문제 해결
- HTTP 왕복 횟수를 줄이기 위해 루프 로직을 GAS 측으로 이전하여 성능 최적화
- 기존 VBA의 JSON 파싱 및 데이터 처리 로직을 그대로 유지하며 확장
Google Drive의 깊숙한 곳에서 ZIP 파일 하나가 나왔습니다. 내용은 「YouTube (최신 정보).xlsm」. 채널을 지정하면 해당 채널의 모든 동영상 제목, 조회수, 길이, 게시일을 목록으로 만들어 주는, 이전에 AI와 함께 만든 자작 엑셀 파일(Book)입니다.
구조는 제대로 되어 있어서, YouTube Data API를 VBA에서 직접 호출합니다. 매크로는 41개. 채널 정보 → 동영상 ID → 동영상 상세 정보로 이어지는 3단계 API 호출을 수행하며, 50개씩 묶어서 가져오고, 페이지 넘기기(Pagination)에도 대응하며, 반환된 JSON을 정규 표현식으로 풀어냅니다. 당시로서는 꽤 완성도 높은 결과물이었습니다.
다만, 모듈의 2행이 다음과 같이 되어 있었습니다.
Private Const API_KEY As String = "여기에 당신의 API 키를 붙여넣으세요"
키만 빼놓고 잠들어 있었던 것입니다. 이유는 기억납니다. API 키는 취득 시 신용카드 등록이 필요한 경우가 있어, 무료 할당량(Free tier)이 있더라도 돈의 냄새가 납니다. 나 자신은 괜찮더라도, 남에게 "이것 좀 써봐"라고 건네줄 수 있는 물건이 되지 못한다── 그래서 선반 위에 올려둔 채 방치했던 것입니다.
이번에 이 파일을 정리할 겸 다시 꺼내 보았습니다. 새로운 키를 넣으면 작동할 것이라고 생각하며 말이죠. 그런데 작업을 시작해 보니 이야기가 예상과는 다른 방향으로 흘러갔습니다. 키를 넣는 것이 아니라, 키 자체가 필요 없게 된 것입니다. 이 기사는 그 전말과 실측 기록입니다.
참고로 지난번에는 VBA에서 생성형 AI의 API를 호출하는 사례를 전 세계에서 찾아낸 이야기에 대해 썼습니다.
VBA에서 외부 API를 호출하는 이야기의 연장선이지만, 이번 상대는 생성형 AI가 아니라 YouTube입니다.
- 잠들어 있던 YouTube 목록 파일을, API 키를 단 한 글자도 입력하지 않고 부활시켰습니다. 중간에 GAS (Google Apps Script)를 한 층 끼워 넣었을 뿐입니다. GAS는 YouTube Data API를 표준 장착하고 있습니다. 키 취득 불필요, 과금 설정 불필요. 이를 웹 앱(Web App)으로 공개하면 URL이 하나 생성되며, VBA에서 그 URL을 호출할 수 있습니다.
- VBA 측의 개조는 통신이 모두 통과하는 함수 **1개(13행)**뿐입니다. 3단계 호출도, 50개 배치(Batch) 처리도, 정규 표현식을 이용한 JSON 해석도 예전 코드 그대로 작동했습니다.
- 다만 첫 실측 결과는 참패였습니다. 1,387개의 목록화에 107초가 걸렸습니다. 이전에는 키를 직접 호출했을 때 10초였던 처리입니다.
- 범인은 왕복 횟수였습니다. HTTP를 56회나 느린 중계 서버를 거쳐 호출하고 있었습니다. 루프를 GAS 측으로 옮겨 왕복을 1회로 만들었더니 13.8초가 되었습니다. "키도 카드도 필요 없다"는 대가가 직접 호출 대비 약 몇 초 차이입니다. 이 정도 교환이라면 얼마든지 지불할 용의가 있습니다.
먼저 이 파일의 구조를 설명하겠습니다. 나중에 중요한 내용이 되기 때문입니다.
YouTube의 채널 전체 동영상을 가져오려면 API를 3단계로 호출합니다.
channels
── 채널 ID로부터 "업로드 동영상 플레이리스트"의 ID를 얻음playlistItems
── 해당 플레이리스트를 50개씩 페이지 넘기며 모든 동영상 ID를 수집videos
── 동영상 ID를 50개씩 묶어서 전달하여 제목, 통계, 길이를 가져옴
파일은 이를 MSXML2.XMLHTTP로 정직하게 수행합니다. JSON 파서(Parser)는 사용하지 않고, VBScript.RegExp로 "title": "(.*?)"를 찾아내는 방식을 사용합니다. 일본어가 \u30d3\u30c7\u30aa와 같은 유니코드 표기로 반환되기 때문에, 이를 되돌리는 자체 함수까지 갖추고 있습니다. 참조 설정 없이 모두 CreateObject를 사용합니다. 즉, 어떤 PC로 가져가도 작동하는 구조입니다.
그리고 API를 호출하는 곳은 13군데가 있지만, 모두 이 하나의 함수를 통과합니다.
Function CallAPI(http As Object, url As String) As String
On Error Resume Next: http.Open "GET", url, False: http.Send: CallAPI = http.responseText: On Error GoTo 0
End Function
URL을 전달하면 본문이 반환되는, 그뿐인 통로입니다. 13곳의 호출이 전부 이곳을 통과한다는 이 일방통행 구조가 이번 개조를 거의 공짜로 만들어 주었습니다.
자, 부활시키려면 새로운 API 키를 받는 것이 정석입니다. 하지만 그렇게 하면 잠들어 있던 이유가 그대로 남습니다. 키 취득 절차를 넘을 수 있는 사람에게만 건넬 수 있는 도구인 상태 그대로 말입니다.
이때 떠오른 것이 GAS (Google Apps Script)였습니다. GAS에는 「Advanced Google Services」라는 메커니즘이 있어서, YouTube Data API가 표준 장비로 탑재되어 있습니다. 에디터 메뉴에서 하나를 활성화하기만 하면 됩니다. 키 취득 없음, 카드 등록 없음, 과금 없음.
그리고 GAS는 「웹 앱으로 배포 (Deploy as a web app)」하면, 단순한 URL이 하나 생성됩니다. URL이라면 VBA에서 호출할 수 있습니다. 즉──
VBA → GAS의 URL → (GAS가 자신의 권한으로 YouTube API를 호출) → JSON이 그대로 반환됨
GAS 측에 둔 것은 대략 30줄 정도의 전달자(Proxy)입니다. 골자만 보여드리겠습니다.
function doGet(e) {
// 암호 확인 및 통과할 엔드포인트 허용 리스트 확인 (생략)
const res = UrlFetchApp.fetch(
...
받은 경로를 본래의 YouTube API로 전달하고, 돌아온 JSON을 그대로 반환합니다. 본래 API와 동일한 형태로 반환되므로, 받는 쪽의 정규 표현식(Regular Expression)도 유니코드 복원도 전혀 수정할 필요가 없습니다.
VBA 측은 예시의 CallAPI 함수 안에 「GAS의 URL이 설정되어 있다면, 목적지를 본래 API에서 GAS로 교체한다」라는 분기를 추가했습니다. 전부 합쳐서 13줄입니다. 호출하는 쪽의 13곳은 수정하지 않았습니다.
의기양양하게 실행해 보았지만, 아무런 반응이 없습니다. 에러도 나지 않습니다. 결과만 비어 있습니다.
이 「에러조차 나지 않는다」는 점이 까다로웠습니다. CallAPI는 On Error Resume Next로 예외를 묵인하도록 만들어져 있어서, 통신이 실패해도 조용히 빈 문자열만 반환할 뿐입니다. 그래서 HTTP 오브젝트를 4종류 나열하여 같은 URL을 찔러보는 실험을 했습니다. 결과는 이렇습니다.
MSXML2.XMLHTTP 에러 -2147024891 액세스가 거부되었습니다
MSXML2.XMLHTTP.6.0 에러 -2147024891 액세스가 거부되었습니다
MSXML2.ServerXMLHTTP.6.0 성공 200
...
원인은 GAS의 특성에 있었습니다. GAS의 웹 앱은 호출하면 script.google.com에서 script.googleusercontent.com으로, **다른 도메인으로의 리다이렉트 (Redirect)**를 반환합니다. XMLHTTP는 Internet Explorer의 보안 존(Security Zone) 위에서 동작하는 구형 모델이라, 이를 위험하다고 판단하여 차단합니다. 보안 존을 따지지 않는 ServerXMLHTTP라면 그대로 통과합니다.
본래의 googleapis.com은 리다이렉트를 하지 않기 때문에, 키를 직접 호출하던 시절에는 한 번도 문제가 되지 않았던 함정입니다. **「지금까지 잘 작동하던 엑셀 파일이 GAS를 거치자마자 무반응이 된다」**는 형태로 나타나므로, 같은 작업을 하시는 분들은 기억해 두시면 손해 볼 일은 없을 것입니다. 해결 방법은 CreateObject의 문자열 하나를 바꾸는 것입니다.
교체 후 실행하니 통과되었습니다. 3단계 호출이 모두 연결되고, 일본어 제목도 깨지지 않고 나열됩니다. API 키 행은 「여기에 당신의 API 키를 붙여넣으세요」 상태 그대로입니다. 열쇠 구멍을 비워둔 채로, 자물쇠가 열린 것입니다.
곧바로 실측에 들어갔습니다. 대상은 영상 1,387개의 채널입니다. 이전에는 이 규모를 키 직접 호출 방식으로 약 10초 만에 가져왔었습니다. 이번에는──
107.38초.
10배 느립니다. 솔직히 말하면 여기서 조금 낙담했습니다. 하지만 원인을 따져보니 명확해졌습니다. 1,387개라면 영상 ID 수집에 28페이지, 상세 정보 취득에 28배치(Batch)가 필요하여, HTTP 왕복이 56회 발생합니다. GAS 중계는 1회 왕복할 때마다 리다이렉트를 1회 추가로 거치고, 여기에 GAS에서 본래 API로의 요청이 또 실행됩니다. 1회 왕복당 약 1.9초. 즉──
중계가 느린 것이 아니라, 느린 중계를 56번 호출하고 있었던 것입니다.
그렇게 알게 되면 해결 방법은 하나입니다. 56번의 왕복을 이쪽(VBA)에서 하니까 느린 것입니다. 그렇다면, 루프(Loop) 자체를 GAS 쪽으로 옮기면 됩니다.
GAS 전달자에 「채널 목록 모드」를 추가했습니다. 채널 ID를 하나 받으면, GAS가 스스로 페이지 넘기기를 수행하여 모든 영상 ID를 수집하고, 상세 정보 28배치는 UrlFetchApp.fetchAll을 사용하여 병렬로 가져옵니다. 또한 JSON 해석과 일시(Datetime)의 일본 표준시 변환까지 마친 뒤, 정렬된 탭 구분 텍스트를 한 번에 반환합니다. VBA 측은 URL을 딱 한 번만 호출하고, 돌아온 텍스트를 분할하여 시트에 붙여넣기만 하면 됩니다.
GAS에서 YouTube API로의 통신은 Google 서버 간의 대화이므로, 사용자의 회선을 거치지 않습니다. 왕복 56회가 1회가 되었습니다.
다시 실측해 보았습니다. 동일하게 1,387개로──
| 방식 | 실측 |
|---|---|
| API 키 직접 호출 (이전) | 약 10초 |
| GAS 중계 · 단순 버전 (1왕복 = 1회 호출) | 107.38초 |
| GAS 중계 · 루프 이식 버전 | 13.84초 |
첫 실행 시에는 16.4초였습니다. VBA의 컴파일(Compile)과 GAS 측의 기동(Startup) 시간이 포함되기 때문에, 2회차부터는 13초대로 안정화됩니다. 덧붙여 '최신 50건만 가져오기'라는 평소 방식도 측정해 보니, 4.49초였습니다. 체감상 거의 순식간입니다.
여기서 고백할 것이 있습니다. "왕복이 많으면 느리다"── 이 범인을 만나는 것은 사실 두 번째입니다.
지난해 말, 동일한 목록화 작업을 스프레드시트 버전과 Excel 버전으로 대결시켜 영상으로 만든 적이 있습니다.
당시 실측 결과, 1,300개가 넘는 채널에서 스프레드시트는 약 30초, Excel은 10초 미만이었습니다. 영상 속에서 저는 스프레드시트가 느린 범인을 "클라우드의 지연(Latency). PC와 서버를 수없이 왕복하는 통신 시간"이라고 지목했습니다.
이번의 107초 역시 동일한 범인이었습니다. API 키를 버리는 대신 중계를 끼워 넣었더니, 제가 만든 Excel 버전이 "왕복 때문에 느린 쪽"이 되어버렸습니다. 그리고 이번에는 왕복 그 자체를 줄임으로써 이를 만회했습니다. 같은 범인을 두 번 만났고, 두 번째에는 승리한 셈입니다.
또 하나, 숫자를 나열하면 보이는 것이 있습니다. 스프레드시트 버전도, 이번 GAS 중계 버전도, YouTube에서 데이터를 수집하는 것은 동일한 Google 서버입니다. 엔진은 같습니다. 차이점은 수집한 결과를 받아 정렬하는 쪽이 브라우저 너머의 스프레드시트인지, 내 손안의 Excel인지뿐입니다. 그 차이로 30초와 13초의 격차가 벌어집니다. 대량의 행을 처리하는 작업대로서는 내 손안의 스프레드시트 소프트웨어가 더 빠르다── 작년 대결의 결론은 엔진을 Google 쪽으로 몰아주어도 변하지 않았습니다.
최종적인 계산 결과를 적습니다.
API 키 직접 호출의 10초에 대해, GAS 중계는 1314초입니다. **약 34초의 추가 비용**이 발생합니다. 이는 GAS를 1회 왕복시키는 고정비이므로, 아마 이 이상은 줄어들지 않을 것입니다.
그 대신 사라진 것들을 나열합니다.
- API 키 취득 절차 (Google Cloud 프로젝트 생성부터 시작되는 그 과정)
- 신용카드 등록에 대한 심리적 장벽
- 파일에 키를 직접 적어 넣는 행위 자체 (키 입력란은 빈 상태로 유지됩니다)
즉, 열쇠와 카드를 버리는 대가로 지불하는 비용은 단 몇 초입니다. 이 파일을 잠들어 있게 했던 이유가 "돈 냄새가 나는 것은 남에게 줄 수 없다"는 것이었음을 떠올려 보면, 이는 완벽한 해결책입니다. 전달받은 사람이 할 일은 파일을 열고 버튼을 누르는 것── 그것뿐이 되었습니다.
관례에 따라 분류합니다.
사실: GAS의 YouTube Advanced Service는 키 취득 및 결제 설정 없이 활성화할 수 있다는 점. API 키 상수를 빈 상태로 둔 채 3단계 호출이 완수되었다는 점. 1,387개 실측 결과가 단순 버전 107.38초, 루프 이식 버전 13.84초(첫 실행 16.41초), 최신 50건 4.49초라는 점(모두 당일 밤 실측). MSXML2.XMLHTTP가 GAS의 리다이렉트(Redirect) 과정에서 "액세스가 거부되었습니다"(-2147024891)를 반환하고, ServerXMLHTTP.6.0에서는 통과한다는 점(4가지 오브젝트 비교 확인). 작년 12월 영상에서 스프레드시트 약 30초, Excel 10초 미만이라고 실측치를 공개했다는 점.
견해: 단순 버전의 느린 주원인을 "왕복 56회 × 중계 고정비"로 해석하고 있다는 점 (1회 왕복당 내역을 엄밀하게 분해한 것은 아님). 첫 실행과 두 번째 실행의 차이를 컴파일과 GAS 측의 기동 때문으로 돌리고 있다는 점. "이 이상은 줄어들지 않는다"는 고정비 구조에 기반한 추측이라는 점.
- GAS의 웹 앱(Web App)은 「모두에게 액세스 가능」으로 공개하는 형태이므로, URL을 알면 누구든 호출할 수 있습니다. 암호와 통과할 엔드포인트의 허용 리스트(이번에는 YouTube 읽기 계열 5종만)로 제한해 두었지만, 암호는 북(Book)을 열면 읽을 수 있는 위치에 있습니다.
정보가 유출되더라도 공개 데이터의 읽기만 가능하도록 구성해 두는 것이 전제입니다. - GAS의 무료 할당량은 YouTube API 환산 시 하루 1만 유닛입니다. 이번 전체 데이터 취득 1회가 약 57유닛이므로 개인 이용 시에는 문제가 없지만, URL을 배포하면 배포한 모든 사람이 동일한 할당량을 공유하게 됩니다. 널리 배포하려면 「각자가 자신의 계정으로 배포하는」 절차서를 만드는 것이 더 타당합니다. - 이 방식은 「요청을 보내면 응답이 돌아오는」 API를 위한 것입니다. 응답을 조금씩 흘려보내는 스트리밍(Streaming)은 GAS에서 중계할 수 없습니다.
- 키(Key)가 필요 없는 것은 YouTube처럼 GAS가 표준 서비스(Standard Service)로 보유하고 있는 경우의 이야기입니다. 그렇지 않은 API는 이와 다릅니다.
- 북(Book) 자체는 현 시점에서 미공개 상태입니다. VBA 매니저 등의 도구는 공개 리포지토리(Public Repository)에 있습니다.
이 기사의 내용은 5분 분량의 영상으로도 제작되었습니다. 합성 음성 나레이션과 슬라이드를 통해, 창고에서 나온 북이 다시 움직이기까지의 과정을 따라가는 형식입니다.
키를 넣을 생각으로 창고에서 꺼낸 북은, 열쇠 구멍을 비워둔 채 움직이고 있습니다.
3단 호출도, 50건 배치(Batch)도, 정규 표현식을 이용한 JSON 파싱(Parsing)도, 작성 당시 그대로 한 줄도 수정하지 않았습니다. 수정한 것은 통로 역할을 하는 함수 하나와, 건너편에 둔 전송 담당자뿐입니다. 잠들어 있던 반년 동안 북이 낡은 것이 아니라, 키라는 전제 자체가 먼저 낡아 있었던 것── 그것이 정답이었다고 생각합니다.
창고에는 아직 몇 권이 잠들어 있습니다.
그럼, 다음에 뵙겠습니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Qiita AI의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기