
Python


M-URL Checker는 납품한 웹매뉴얼 HTML이 고객사 서버에 최신 Version으로 정상 반영되었는지 자동으로 전수 검사하기 위해 개발한 Django 기반 사내 Web Application이다.
기존에는 수십 개 언어와 수천 개 HTML 페이지 중 일부를 사람이 직접 열어 확인했기 때문에 부분적으로만 배포된 상태를 놓칠 가능성이 있었다.
M-URL Checker에서는 QA 완료 시 HTML에 기록되는 Version 값을 기준으로 실제 고객사 서버의 모든 페이지를 검사하고, 어떤 언어와 페이지가 최신인지, 구버전인지, 아직 배포되지 않았는지까지 자동으로 분류하도록 구현했다.
개발 기간 : 2026.08.03 ~ 2026.08.14 / 이후 운영 기능 지속 개선
| Project | 웹매뉴얼 배포 상태 자동 검증 및 변경 이력 관리 시스템 |
| Role | 업무 분석 · 기획 · Architecture · Backend · Frontend · Test · 배포 · 운영 |
| Backend | Python 3.11 · Django 5.2 · MariaDB 11.3 |
| Frontend | Django Template · HTML · CSS · JavaScript |
| Network / Data | httpx · BeautifulSoup · XML / HTML Parsing · openpyxl |
| Deployment | IIS · wfastcgi · 사내망 운영 |
| Test | 자동화 Test 402건 · MariaDB Backend 기준 검증 |
| Performance | 29개 언어 · 약 1,953페이지 전수 검사 약 12초 |
| Result | 표본 육안 검사 → 전체 페이지 자동 검증 · 부분 배포 탐지 · 재업로드 대상 자동 추출 |
자동차 웹매뉴얼은 하나의 지역향만 해도 여러 언어와 수십 개의 HTML 페이지로 구성된다.
예를 들어 29개 언어에 언어별 약 55~78개의 페이지가 존재하면 검사 대상은 약 1,953개의 HTML이 된다.
1개 지역향 29개 언어 × 55 ~ 78 HTML Page ≈ 1,953개 HTML
납품 이후 실제 고객사 서버에 파일이 모두 반영되었는지를 확인하려면 각 언어의 페이지를 직접 열어 변경된 Version을 확인해야 했다.
하지만 사람이 1,953페이지를 전부 확인하는 것은 현실적으로 어렵기 때문에 일부 페이지만 표본으로 확인했고, 이 방식에서는 특정 언어나 일부 페이지만 이전 Version으로 남아 있는 부분 배포를 발견하기 어려웠다.
기존 방식 납품 완료 ↓ 언어별 몇 개 Page 직접 접속 ↓ 변경 내용 육안 확인 ↓ 표본이 정상이라면 완료 판단 문제 → 나머지 Page의 상태는 알 수 없음 → 특정 언어만 누락된 부분 배포 발견 어려움 → 검사 결과를 남길 Data가 없음
QA가 완료된 HTML에는 해당 배포본을 식별할 수 있는 값을 data-update 속성으로 기록하고 있었다.
<html data-update="H_WN260197_BJ1_Connect-L[EUR]AVT_260727">
M-URL Checker는 이 값을 검사 기준으로 사용한다.
사용자가 URL이나 언어를 일일이 입력할 필요 없이 검사값 한 줄만 입력하면 프로그램이 플랫폼, 제품, 지역향, 언어 정보를 해석해 실제 검사 URL과 대상을 자동으로 구성한다.
URL Check 값 입력
↓
입력값 Parsing
↓
Platform / Product / Region 분석
↓
언어 Master 조회
↓
기준 URL 조립
↓
목차 Parsing
↓
전체 검사 Page 생성
↓
고객사 Server 전수 검사
↓
data-update 비교
↓
상태 분류 및 결과 집계

검사를 실행하면 언어별 모든 Page를 확인하고 각 결과를 상태별로 분류한다.
단순히 성공·실패만 보여주지 않고 어떤 Version이 발견되었는지, 몇 개 Page가 최신인지, 어떤 Page가 확인이 필요한지까지 표시한다.
검사 결과
전체 1,953 Page
↓
최신 Version
구 Version
값 불일치
Page 없음
속성 없음
접속 실패
언어 불일치
미배포
↓
언어별 / Page별 상세 결과
↓
재확인 또는 재업로드 대상 추출사용자는 결과를 보고 개별 Page를 다시 확인할 수 있으며, 화면에 표시된 결과는 Excel로 내보내거나 사내 메신저·메일에 전달하기 위한 형태로 복사할 수 있도록 했다.
검사 결과를 단순한 정상 / 오류 두 종류로 합치지 않고 실제 의미에 따라 8가지 상태로 분리했다.
| 상태 | 의미 |
|---|---|
| OK | 기준 Version과 일치 |
| STALE | 이전 Version이 남아 있음 |
| PAGE_MISSING | 목차에는 있으나 실제 Page가 없음 |
| MISMATCH | 예상하지 않은 Version 값 |
| LANG_MISMATCH | 언어 정보 불일치 |
| HTTP_ERROR | HTTP 요청 실패 |
| NO_ATTR | Version 판정 속성이 없음 |
| NOT_DEPLOYED | 해당 언어 자체가 아직 배포되지 않음 |
이 상태들을 하나의 “오류 합계”로 다시 묶지 않았다.
개별 상태의 합계가 전체 검사 건수와 항상 일치해야 결과 숫자를 신뢰할 수 있기 때문이다. 특히 속성이 없는 Page와 아직 배포 대상이 아닌 언어를 일반 오류와 섞으면 정상적인 상황도 계속 오류로 보고될 수 있다.
일반적인 Web Page라면 존재하지 않는 URL 요청에 HTTP 404가 반환되는 것을 기대할 수 있다.
하지만 검사 대상 Server에서는 존재하지 않는 Page를 요청해도 HTTP 200 또는 Redirect 이후 안내 Page를 반환하는 경우가 있었다.
실제 Page 존재 → HTTP 200 Page 없음 → HTTP 200 + 안내 Page 또는 Page 없음 → HTTP 302 → 안내 Page → HTTP 200
단순히 Status Code만 확인하면 존재하지 않는 Page도 정상으로 판단하게 된다.
특정 Error URL을 하드코딩하는 방법도 고려했지만 Server의 Error Page 구조가 변경되면 다시 오탐이 발생할 수 있었다.
대신 요청한 언어 경로 Prefix가 최종 Redirect URL에서도 유지되고 있는지를 기준으로 판단하도록 했다.
Request
/.../EUR/Bulgarian/page.html
↓
Redirect 발생
↓
Final URL 확인
/EUR/Bulgarian/ 유지
→ 실제 언어 영역 안에 있음
언어 경로가 사라짐
→ 안내 / Error Page로 이탈
→ 미배포 또는 Page Missing 판정Error Page의 이름이나 HTML 구조가 변경되어도 동작할 수 있도록 화면 모양이 아니라 URL 구조의 불변 조건을 검사 기준으로 사용했다.
검사 대상 Server에서 HTML 전체를 다운로드하면 한 번의 검사에 수백 MB의 Network Traffic이 발생할 수 있었다.
하지만 실제로 필요한 data-update 값은 문서 맨 앞의 <html> 요소에 존재한다.
처음에는 HTTP Range Request로 일부만 받는 방식을 검토했지만 대상 Server가 Range를 지원하지 않는 경우가 있었다.
그래서 HTTP Response를 Streaming 방식으로 읽다가 여는 HTML Tag를 찾는 순간 더 이상 Body를 받을 필요가 없으므로 연결을 종료하도록 구성했다.
HTTP Request
↓
Response Streaming
↓
앞부분부터 읽기
↓
<html ... data-update="..."> 발견
↓
필요한 값 Parsing
↓
Response 조기 종료
전체 HTML 다운로드 X| 항목 | 개선 전 | 개선 후 |
|---|---|---|
| 1,953 Page 전송량 | 약 390MB | 약 5MB |
| 전송량 감소 | – | 약 96% |
| 전체 검사 시간 | 약 14.2초 | 약 12.4초 |
JavaScript Rendering이 필요한 정보가 아니라 정적 HTML Attribute만 확인하면 되었기 때문에 Browser Automation이나 Playwright를 사용하지 않고 HTTP Request만으로 처리했다.
Page를 하나씩 순차적으로 요청하면 검사 시간이 지나치게 길어지기 때문에 httpx 기반 비동기 요청을 사용해 여러 Page를 동시에 검사하도록 했다.
1,953 Page
↓
검사 Queue
↓
동시 20 Request
↓
┌────┬────┬────┬────┐
Page Page Page Page ...
└────┴────┴────┴────┘
↓
결과 수집
↓
언어 / 상태별 집계동시 요청 수를 무조건 크게 잡지 않고 실제 대상 Server와 CDN의 응답 상태를 고려해 제한했다.
403 또는 429 응답이 연속적으로 발생하면 단순 Page Error가 아니라 요청 자체가 차단된 상황으로 판단하고 검사를 중단해 Server 장애와 Rate Limit을 구분하도록 했다.
사용자가 잘못된 Version 값을 입력한 상태에서 수천 개 Request를 모두 실행하면 마지막에야 잘못된 입력이라는 사실을 알게 된다.
이를 방지하기 위해 전체 검사 전에 대표 Page 한 건만 먼저 요청하는 Preflight 단계를 추가했다.
검사값 입력
↓
대표 URL 1건 생성
↓
Preflight Request
↓
입력값 / Page 조건 확인
├─ 이상 있음 → 즉시 중단
└─ 정상 → 전체 검사 시작약 200ms 정도의 사전 검사로 잘못된 입력 상태에서 수천 건의 불필요한 Request가 발생하는 것을 방지했다.
실제 업무에서는 하나의 지역향만 검사하는 것이 아니라 여러 제품과 지역향을 연속으로 확인해야 하는 경우가 많아 이후 묶음 검사 기능을 추가했다.


여러 검사값을 Text 형태로 한 번에 붙여넣으면 프로그램이 값을 Parsing해 실제 검사 조합을 생성한다.
여러 Check 값 Paste
↓
입력값 Parsing
↓
검사 조합 생성
↓
동일 조합 중복 확인
↓
Preflight
↓
조합별 순차 검사
↓
통합 결과
↓
Excel Export특히 같은 제품·지역 조합인데 서로 다른 검사 Version이 동시에 입력된 경우에는 프로그램이 임의로 최신값을 선택하지 않는다.
충돌 상태로 사용자에게 보여주고 직접 검사 대상을 선택하도록 해 자동화가 불확실한 상황에서 임의 판단을 하지 않도록 했다.
묶음 검사의 초기 설계에서는 사용자가 납품 Folder를 선택하면 내부 File에서 검사값을 자동으로 추출하도록 구현했다.
기술적으로는 가능했고 실제 기능도 구현했지만, 실사용 환경을 다시 확인하면서 전제가 틀렸다는 것을 알게 되었다.
| 처음 생각한 전제 | 실제 업무 환경 |
|---|---|
| 사용자 PC에 납품 File이 있다 | 대용량 File Server에 존재 |
| 납품 ZIP만 모아둔 Folder가 있다 | 작업 Folder 안에 여러 File과 함께 존재 |
| Folder가 가장 자연스러운 Input이다 | 실제 검사값은 Tool 출력 · 다운로드 페이지 · 메일에서 복사 |
실제 요구사항은 “Folder를 선택하고 싶다”가 아니라 “여러 검사값을 한 번에 처리하고 싶다”는 것이었다.
Folder 선택은 해결 방법 중 하나였을 뿐인데 이를 요구사항 자체로 받아들였던 것이다.
결국 Folder 입력은 폐기하고 Text Paste 방식으로 변경했다. 이미 구현해 둔 Parsing, 충돌 판정, Preflight, 검사 List 구조는 그대로 재사용했다.
처음 요구 이해
"Folder를 선택한다"
↓
Folder Batch 구현
실환경 확인
실제 목적
"여러 검사값을 한꺼번에 넣는다"
↓
Input 방식만 교체
↓
Text Paste Batch기술적으로 가능한 기능과 실제 업무에 필요한 기능이 반드시 같지는 않다는 점을 확인한 사례였다.
현재 배포 상태를 확인하는 기능 외에도, 고객사 Server의 Version이 언제 변경되었는지 확인할 수 있도록 자동 수집 기능을 추가했다.


자동 수집은 하루 여러 차례 진행하지만 매번 모든 상태를 Snapshot으로 저장하지 않는다.
이전 값과 비교해 실제 Version이 변경된 경우에만 History 한 행을 추가하도록 했다.
자동 수집
↓
현재 Server 값 확인
↓
직전 값과 비교
↓
값 변경?
├─ NO → 저장하지 않음
└─ YES
↓
History 1 Row 기록매 수집 시점의 전체 Snapshot을 저장하면 장기간 운영 시 수백만 건의 Data가 쌓일 수 있기 때문에 변화 자체를 Event로 저장하는 방식을 선택했다.
또한 일정 기간 동안 계속 미배포 상태인 조합은 자동 수집 대상에서 휴면 처리해 의미 없는 반복 Request도 줄였다.
검사 결과는 화면 확인에서 끝나지 않고 Excel로도 Export할 수 있도록 했다.


Excel에는 검사 기본 정보, 상태별 집계, 언어별 결과, Page별 상세 내용을 구분해 저장한다.
검사 완료
↓
Excel Export
↓
요약
├─ 검사 대상
├─ Version
├─ 검사 시간
└─ 상태별 Count
상세
├─ 언어
├─ Page
├─ 입력값
└─ 검사 상태
전달용
└─ 업무 공유에 필요한 결과 정리이를 통해 결과를 다시 수기로 정리하지 않고 바로 보관하거나 다른 담당자에게 전달할 수 있도록 했다.
단일 검사는 수 초 수준이지만 묶음 검사처럼 대상이 많아지면 몇 분 동안 실행되는 작업도 생겼다.
일반적으로 Django의 장시간 Background Job에는 Celery와 Redis 같은 Queue 시스템을 사용할 수 있지만, 이 Application은 제한된 사내망 IIS 환경에서 운영된다.
기능 규모에 비해 별도의 Broker와 Worker를 운영하는 비용이 더 크다고 판단해 Thread + Database Polling 방식을 선택했다.
POST /run/
↓
CheckRun 생성
↓
Preflight 동기 실행
↓
Browser에 즉시 Response
↓
Background Thread
↓
본 검사 실행
↓
진행률 DB 기록
↓
Frontend 700ms Polling
↓
화면 Progress 갱신중단 요청도 Process Memory가 아니라 Database에 기록하도록 해 IIS Multi Process 환경에서도 상태를 공유할 수 있게 했다.
오랫동안 Heartbeat가 갱신되지 않은 RUNNING 작업은 조회 시점에 정리해 Server 재시작이나 비정상 종료 후 Zombie 상태가 남지 않도록 처리했다.
결과적으로 별도의 Celery · Redis · ASGI Infrastructure 없이 기존 IIS + wfastcgi 환경 안에서 운영할 수 있도록 했다.
개발 초기에는 SQLite로 시작했지만 실제 사내 운영 환경에 맞춰 MariaDB로 전환했다.
전환 과정에서 Windows MariaDB의 Table Name 처리 방식과 Django가 관리하는 Model State 사이의 Case 불일치가 발생했다.
특히 일반 조회에서는 바로 드러나지 않고 Test Database를 초기화하는 과정에서 문제가 나타나 원인을 찾는 데 시간이 필요했다.
Django State
↓
대소문자를 포함한 Table Name
MariaDB
↓
소문자 Table Name
↓
일반 기능은 일부 정상
↓
Test Flush 단계에서 불일치
↓
연쇄 Test Failure최종적으로 Table Name을 명시적으로 통일하고 Migration 상태를 정리해 MariaDB 환경에서도 전체 Test가 동일하게 동작하도록 맞췄다.
검사 결과뿐 아니라 언어 Master와 History 같은 운영 Data도 관리하기 때문에, 사용자 실수 한 번으로 기존 데이터가 대량 삭제되지 않도록 몇 가지 방어 조건을 두었다.
개발 과정에서는 기능뿐 아니라 실제 사용 후 UI 판단도 여러 차례 수정했다.
예를 들어 안내 Message의 종류를 구분하기 위해 파란색과 Pink 계열 Box를 나눠 사용한 적이 있었다.
하지만 실제 사용에서는 Pink Box가 설명이 아니라 오류 Warning처럼 인식되었다.
초기
Blue
→ 일반 안내
Pink
→ 사용 방법 안내
실사용
Pink
→ "문제가 발생했나?" 로 인식
↓
변경
안내는 Blue / Gray 계열
오류 Color는 오류 상황에만 사용
구분은 Color보다
위치 + 문구 + Icon으로 처리디자인 의도보다 실제 사용자가 어떻게 해석하는지가 더 중요하다는 판단으로 초기 UI 규칙을 철회했다.
M-URL Checker는 기능 구현과 함께 설계 결정과 운영 절차도 별도 문서로 관리했다.
| 문서 | 내용 |
|---|---|
overview.md | 전체 기능 및 Application 구조 |
decisions.md | 설계 결정, 선택 근거, 폐기한 접근 기록 |
data_format.md | data-update 등 외부 Data Format |
| 기능별 Design 문서 | History · Batch 등 세부 기능 설계와 변경 이력 |
| Deploy 문서 | IIS 실서버 배포 및 사전 Rehearsal 절차 |
특히 최종 구현만 기록하지 않고 시도했지만 폐기한 방식과 이유까지 남겼다.
같은 문제를 다시 만났을 때 왜 이전 방법을 사용하지 않았는지 다시 조사할 필요가 없도록 하기 위해서다.
현재 자동화 Test는 약 402건이며 개발용 SQLite가 아닌 실제 운영과 동일한 MariaDB Backend에서도 전체 Test를 확인하도록 했다.
| 항목 | Before | After |
|---|---|---|
| 검사 범위 | 일부 Page 표본 육안 확인 | 최대 약 1,953 Page 전수 검사 |
| 언어 | 언어별 개별 확인 | 29개 언어 일괄 확인 |
| 소요 시간 | 수십 분 또는 전수 확인 불가 | 약 12초 |
| Network | 전체 HTML Download 시 약 390MB | 약 5MB |
| 부분 배포 | 발견하기 어려움 | 언어 / Page 단위 자동 탐지 |
| 산출물 | 수기 확인 | 결론 · 재확인 목록 · Excel |
| History | 없음 | 자동 수집 · 변경 Event 기록 |
가장 큰 변화는 단순히 검사 시간이 짧아진 것이 아니었다.
이전에는 “아마 정상적으로 올라간 것 같다”는 수준으로 확인해야 했다면, 현재는 어떤 Page가 어떤 Version이고 어떤 URL을 다시 확인해야 하는지 근거를 포함해 답할 수 있게 되었다.
납품 완료
↓
M-URL Checker
↓
1,953 Page 전수 검사
↓
Version 분포 분석
↓
부분 배포 / 누락 탐지
↓
재업로드 대상 추출
↓
Excel / 업무 Message 전달
↓
이후 자동 History로 변경 추적M-URL Checker를 개발하면서 가장 중요했던 것은 URL을 많이 요청하는 기능 자체가 아니라 무엇을 정상이라고 판단할 것인지 명확한 기준을 만드는 것이었다.
HTTP 200이 반드시 정상 Page를 의미하지 않았고, 속성이 없다고 모두 오류도 아니었으며, 아직 배포 대상이 아닌 언어와 실제 Page 누락도 서로 다른 상태였다.
따라서 각각의 상황을 구분해 상태를 정의하고, 모든 상태의 합이 전체 검사 건수와 일치하도록 만들어야 검사 결과 자체를 신뢰할 수 있었다.
또한 전수 검사를 만들면서 단순히 요청을 병렬화하는 것뿐 아니라 실제 필요한 Byte가 어디까지인지, 실패가 Page 오류인지 Server 차단인지, 잘못된 입력을 언제 차단할지까지 고려해야 했다.
묶음 검사에서는 구현한 기능을 실제 업무 환경과 비교한 뒤 폐기하기도 했다. 기술적으로 동작하는 기능보다 실제 사용자가 가지고 있는 입력과 Workflow를 기준으로 요구사항을 다시 정의하는 것이 더 중요하다는 점을 경험했다.
업무 문제 확인
↓
검사 기준 정의
↓
Data / URL 구조 설계
↓
전수 검사 구현
↓
Network 최적화
↓
상태 모델 세분화
↓
Batch / History 확장
↓
안전장치 / Test
↓
IIS 실환경 배포
↓
실제 사용 Feedback
↓
설계 수정 및 지속 운영Project M-URL Checker는 수작업으로는 전수 확인이 어려웠던 웹매뉴얼 배포 검증 업무를 자동화하고, Network 처리부터 상태 판정, Batch 검사, 변경 이력, 운영 안전장치까지 실제 업무 Workflow로 확장한 Web Application이라는 점에서 의미가 있다.
Django : https://www.djangoproject.com/
httpx : https://www.python-httpx.org/
다른 프로젝트 보기 : Project 전체 보기