ToolScrapingChrome Extension

인프런 자료 수집기

·10 min read

우리가 가진 인프런 구독은 한달이라는 유효기간을 가지고 있다.

그리고 벌써 2주가 지나간다. 추석 지나가면 유효기간은 금방 사라질 것이다.

9 to 6의 강의와 대학교 진도까지

콘텐츠 과잉의 현대에서 멍하니 인프런 강의를 지켜볼 시간은 사치에 불과하다.

고로 나는 하라는 자율 공부 시간에 공부는 안하고

인프런 강의 자료를 수집하는 플러그인을 설계하는 과정에서 개발적인 배움을 정리하고자 한다.

Chrome Extension 아키텍처

우리에겐 한달 구독권이라는 자유이용권 티켓이 있다.

즉 복잡한 인증이나 보안 상관없이 원하는 강의 데이터에 손쉽게 접근이 가능하다는 것이다.

그렇다면 별도의 복잡한 크롤러 대신 사용자인 나와 함께 동작하는 플러그인 형태가 아주 손쉽게 구동이 된다.

일단 나의 목표는 다음과 같다.

- 강의 페이지에서 커리큘럼 정보, 강의별 수업 자료, 첨부파일 자료, 자막을 자동으로 수집하고 싶다.

- 버튼 하나로 모든 걸 다운받고 싶다.

강의 하나 클릭하니 모든게 저장되는 심플이즈 베스트다

어떻게 웹 페이지 데이터에 접근 할 것인가

Manifest라는 규격이 있다.

확장 프로그램 청사진 같은건데 현재 V3 버전이다.

주요 사항으로는 Service Worker라는 놈이 있다.

과거 V2 버전에서는 Background Page 라고 항상 실행되며 메모리 사용하는 기술이 있었는데 이거 대체하고 나온게 Service Worker다.

특이점으로는 이벤트 기반으로 필요할때만 동작하고 필요 끝나면 알아서 종료된다.

또한 DOM 구조에 대한 직접 접근도 못하고 메모리 영역도 별도로 관리된다.

보안 철학에 변화로 생긴 별도 영역이라는 것이다.

Service Worker의 특이점은 탭 상관없이 독립된 백그라운드 프로세스로 동작한다는 건데,

여러 탭에서 확장 프로그램을 써도 Service Worker는 하나라는 점이다.

즉 여러 탭이 열렸을 때 공통적인 작업에 대해서는 태스크를 관리하면서 볼일 다 보면 사라져가지고 메모리까지 관리되는 아주 편리한 기술이라는 것이다.

(추가로 Chrome API로 브라우저 기능에 대한 조작도 가능하다)

강의1, 2, 3 다 띄워놓고 각 페이지에서 보고, 수집하고, 전달하면, 공통영역에서 데이터를 안정적으로 처리가 가능한 것이다.

The message port closed before a response was received.

이건 내가 실수한건데

Service Worker랑 비동기 통신하면서 코드를 다음과 같이 작성했다.

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  // 비동기 응답
  (async () => {
    // 복잡한 로직등, 분기 처리 등
    sendResponse({ result: 'ok' });  // Port 이미 닫힘
  })();
  // 리스너 함수가 이미 종료됨 → Port 닫힘
});

바로 return True를 안박아 넣었기 때문인데

비동기 응답에서 Response를 받기 위해서는 통신을 유지해야하는데

return True를 마지막에 넣어줘야 Port를 열어두기 때문이다.

chrome.runtime.onMessage.addListener((request, sender, sendResponse) => {
  setTimeout(() => {
    sendResponse({ result: 'ok' });
  }, 1000);
  return true;  // Port를 열어둠
});

AWS CloudFront 서명 메커니즘

커리큘럼, 학습 자료, 첨부 파일...

애네들은 생각보다 구조가 단순하다.

그러다가 영상이랑 자막 쪽 건들이다가 생소한걸 봤는데

https://vod.inflearn.com/videos/{videoId}/drm/cmaf/subtitles/json
  ?Policy=eyJTdGF0ZW1lbnQiOlt7IlJlc...     ← 뭔가 Base64 같음
  &Signature=abc123xyz...                  ← 서명?
  &Key-Pair-Id=APKAITJXMBGW53HFS7RQ        ← AWS 키?
  &subtitleId=687bbf50cc50b23f3b7ae4ac

등과 같이 복잡한 URL이 하나 튀어나왔기 때문이다.

Policy 데이터를 가져다가 Base64로 디코딩을 하면 다음과 같이 표시된다.

{
  "Statement": [{
    "Resource": "https://vod.inflearn.com/videos/*/subtitles/*",
    "Condition": {
      "DateLessThan": {
        "AWS:EpochTime": 1735689600
      }
    }
  }]
}

CloudFront에서 보안 리소스 제공할때 URL에 시그니쳐 인증이 가미된 검증용 데이터를 같이 보내는 것이다.

URL 구조를 다시 보자면 Signature 항목이 있다.

우린 Policy로 접근 규칙을 정의하지만 실제로 해당 Policy의 진위 여부는 어떻게 증명 할 것인가?

그 해답이 바로 Signature다.

Key-Pair-Id는 서명에 사용한 키 ID 역할을 맡는다.

AWS에서 발급되어 서명 키에 대한 식별을 진행하는 것이다.

사실 이런 복잡한 보안 구조를 알 필요는 없다.

우리 손에는 인프런 자유이용권 티켓이 있기 때문에 그냥 우리 티켓들고 가서 서명좀 해달라고 하면 끝이다.

async function getSignedSubtitleUrl(unitId) {
  // 서명좀
  const response = await fetch(
    `https://ucc-api.inflearn.com/client/api/v1/videos/lectures/${unitId}?lang=ko`,
    { credentials: 'include' }  // 인증 쿠키 포함
  );
  const data = await response.json();
  return data.data.subtitles.ko.url;
}

어차피 통신 과정에서 새로 발급 받고 연결해서 데이터 가져오고 하기 때문에 중요한 내용은 아니지만,

나중에 AWS 설계시 참고하고자 잠시 알아봤다.

데이터 수집 구조

하나의 강의에는 3가지 API가 존재한다

노트, 첨부파일, 비디오(자막)

한개의 과정에는 보통 15~40개 강의가 존재한다.

최악을 생각해보자 40x3 = 120

병렬 처리가 필요하다.

커리큘럼에서 각 강의에 대한 데이터를 셋팅해둔다.

강의 기준으로 묶을까, 데이터 성격을 기준으로 묶을까

데이터의 품질과 보안을 위해 강의 단위로 처리를 진행한다.

다만 하나의 강의에서 첨부파일, 스크립트, 노트 데이터는 병렬적으로 가져온다.

const results = await Promise.allSettled([
  unit.hasBody ? this.fetchApiData(`/units/${unit.id}`) : null,
  unit.hasAttachment ? this.fetchApiData(`/units/attachment?unitId=${unit.id}`) : null,
  this.collectSubtitle(unit.id)
]);

Promise.all은 하나라도 실패하면 전체 실패처리를 하니 Promise.allSettled을 쓴다.

데이터 품질을 위해 강의 단위로 나누었지만, 개별 요청 일부 실패에 연연하지는 않는다.

results.forEach((result, index) => {
  if (result.status === 'fulfilled' && result.value) { // 개별 데이터 품질 검증
    // 저장 로직
  }
});

중요한건 일부라도 가져온 데이터의 품질이 검증이 되었는지 여부다.

await new Promise(resolve => setTimeout(resolve, 200));

강의 내부적으로는 병렬 처리가 들어가지만, 강의 데이터 사이에는 잠시 여유를 준다.

쉼없이 자료 긁고 인증 요청하면 그건 DDOS다.

동작 관련해서 수집된 html 기반 자료를 마크다운으로 변환하는 로직이나

저장시 데이터의 직렬화/역직렬화 등 부가적인 문제가 있었지만 굳이 담지는 않는다.

저는 그저 연구를 해봤을 뿐입니다.

개인 소장용으로 자료만 다운받았습니다.

인프런 사랑해요

← Previous
Spring AI란
Next →
AI한테 프론트엔드 디자인 맡기기