eHelper: HTML 파싱 실패 시 Moodle API fallback을 붙인 과정
1. 처음 구조의 문제
eHelper의 첫 Chrome Extension 버전은 eCampus 페이지의 HTML을 직접 읽고 파싱하는 방식이었습니다.
빠르게 만들기에는 괜찮았지만, 계속 쓰기에는 불안한 부분이 있었습니다.

- 페이지를 여러 번 가져와야 해서 로딩이 느려진다
- HTML 구조가 바뀌면 파서가 깨질 수 있다
- 실패했을 때 원인을 구분하기 어렵다
- 사용자가 실패 로그를 보내기 어렵다
처음에는 이 정도가 어쩔 수 없는 한계라고 생각했습니다.
eCampus 화면에 보이는 값을 가져와야 했고, 공개된 API가 따로 없다고 봤기 때문입니다.
2. Moodle API를 찾았다
나중에 eCampus가 Moodle 기반이라는 점을 다시 보면서, Moodle에서 제공하는 API를 확인하게 됐습니다.
과목, 활동, 자료 같은 일부 정보는 화면 HTML을 파싱하지 않아도 구조화된 응답으로 받을 수 있었습니다.
처음에는 API를 우선으로 쓰고 HTML 파싱을 fallback으로 두는 구조도 생각했습니다.
하지만 실제로는 반대로 가는 게 맞았습니다.
Moodle API에는 한계가 있었습니다.
- 학교 Moodle 서버에서 허용한 함수만 호출할 수 있다
- Moodle Mobile service 기준이라 학교 커스텀 데이터가 빠질 수 있다
- 강의 시청 여부처럼 화면에서 확인되는 값이 API만으로는 애매하다
- 커스텀 API를 만들려면 학교 서버에 Moodle 플러그인을 설치해야 한다
- 플러그인 설치는 eCampus 서버 관리자 권한이 필요하다
그래서 Moodle API를 메인 수집 경로로 바꾸지는 않았습니다.
기존 HTML 파싱으로 화면 기준 데이터를 먼저 가져오고, 그게 실패했을 때 API로 가능한 만큼 복구하는 구조가 더 맞았습니다.
3. 파싱 우선, API fallback
개선 후 구조는 HTML 파싱을 먼저 시도하고, 실패한 경우에만 Moodle API를 사용하도록 만들었습니다.
Popup
↓
데이터 요청
↓
HTML 파싱 시도
├─ 성공: 화면 기준 데이터 사용
└─ 실패: Moodle API fallback
├─ 토큰 없음: 설정에서 API 로그인 안내
└─ 성공: API 응답 정규화
↓
공통 데이터 형태로 변환
↓
필터 / 정렬 / 대시보드 렌더링중요한 점은 UI가 데이터 출처를 따로 알지 않게 하는 것이었습니다.
HTML 파싱 결과든 API 응답이든 Popup으로 넘기는 형태는 같게 맞췄습니다.
그래야 필터, 정렬, 대시보드 렌더링 로직을 다시 고치지 않아도 됩니다.

4. 왜 API 우선이 아니었나
API 우선 구조가 더 깔끔해 보일 수는 있습니다.
하지만 eHelper에서는 화면 기준 데이터가 더 중요했습니다.
예를 들어 강의 시청 여부는 API fallback 결과만으로 완료/미완료를 단정하기 어려웠습니다.
그래서 API fallback으로 불러온 강의는 완료/미완료 대신 확인필요 상태로 표시했습니다.
또 API를 사용하려면 사용자가 설정에서 eCampus API 로그인을 해야 했습니다.
토큰이 없으면 개발자용 오류 로그를 보여주는 대신, 설정에서 API 로그인을 해달라는 안내를 띄우도록 분리했습니다.
이 안내도 같이 넣었습니다.
- 처음 API 연결 후 새로고침하면 eCampus 로그인이 풀릴 수 있다
- 그 경우 eCampus에 다시 로그인하고 사용하면 된다
- 비밀번호를 바꾸지 않는 이상 API 토큰 문제는 자주 생기지 않는다
즉, API는 기본 경로가 아니라 HTML 파싱이 깨졌을 때 대시보드를 완전히 비워두지 않기 위한 보조 경로였습니다.
5. 실패 기준도 다시 잡았다
처음에는 과목 수집 결과가 0개면 실패로 잡았습니다.
하지만 실제로 과제나 퀴즈가 없는 과목도 있을 수 있습니다.
그래서 0개 결과는 실패로 보지 않게 바꿨습니다.
대신 실제 HTML fetch 실패나 파싱 예외처럼 수집 경로가 깨진 경우에만 실패로 처리했습니다.
실패 로그도 현재 접속 중인 페이지 전체가 아니라, 수집 중 실제로 가져온 관련 HTML 조각을 담도록 바꿨습니다.
course:activitiesassignment:indexquiz:indexlecture:report
이렇게 해야 나중에 로그를 봤을 때 어떤 페이지의 어떤 영역이 깨졌는지 확인할 수 있습니다.
6. 로그 복사 기능도 추가했다
파싱이 깨졌을 때 문제는 실패 자체보다, 어떤 HTML에서 깨졌는지 알기 어렵다는 점이었습니다.
사용자에게 개발자 도구를 열고 콘솔 로그를 찾아 보내달라고 하기는 어렵습니다.
처음에는 실패하면 자동으로 메일을 보내는 방식도 생각했습니다.
하지만 Chrome Extension 안에 메일 API 키나 SMTP 정보를 넣는 것은 맞지 않았고, 별도 서버를 두는 것도 지금 단계에서는 과했습니다.
서버를 두면 실패 로그를 자동으로 받을 수는 있습니다.
하지만 eCampus 데이터에는 과목명, 활동명, 제출 상태처럼 개인 학습 정보가 섞일 수 있습니다.
이런 데이터를 제 서버로 자동 전송하는 구조는 사용자 입장에서도 부담이 있고, 저도 저장과 관리 책임을 져야 합니다.
그래서 일부러 서버를 두지 않았습니다.
로그는 사용자가 직접 확인하고, 필요할 때만 복사해서 보내는 방식으로 제한했습니다.
그래서 실패를 UI에 확실히 띄우고, 사용자가 로그를 복사해서 보내기 쉽게 만들었습니다.
- 수집 실패 시 상단에 경고 배너 표시
- 배너와 footer에
로그 복사버튼 제공 - 닫기 버튼으로 경고를 숨길 수 있게 처리
- 성공적으로 다시 수집되면 이전 오류 로그 제거
- 토큰 없음처럼 사용자가 해결할 수 있는 상태에서는 로그 복사 버튼 숨김
복사되는 로그에는 시간, 현재 URL, 실패 과목, 실패 이유, 관련 HTML 조각을 담았습니다.
이렇게 하면 사용자는 버튼만 누르면 되고, 저는 실제로 깨진 HTML을 보고 파서 수정 범위를 확인할 수 있습니다.
7. 바뀐 점
이 변경으로 데이터 수집 기준이 조금 더 명확해졌습니다.
- 정상 케이스에서는 HTML 파싱으로 화면 기준 데이터를 그대로 쓴다
- 파싱 실패 시 Moodle API fallback으로 가능한 데이터를 복구한다
- API 토큰 없음은 사용자 안내로 처리한다
- API fallback 강의는
확인필요로 표시한다 - 0개 결과는 정상 케이스로 둔다
- 실패 로그에는 실제 수집 중 가져온 HTML 조각을 담는다
- 사용자가 오류 로그를 복사해서 보내기 쉽게 만든다
완전히 API 기반으로 바꾼 것은 아닙니다.
Moodle API가 있어서 HTML 파싱을 바로 버릴 수 있었던 것도 아닙니다.
이번 개선은 파싱을 대체한 작업이 아니라, 파싱이 실패했을 때 무너지는 범위를 줄인 작업에 가깝습니다.
ehelper.vercel.appeHelperEnjoyed this article? Check out more projects and posts on my portfolio.
Explore this project