웹사이트나 링크 디렉터리를 운영하다 보면 생각보다 빨리 문제가 하나 생깁니다.
링크는 영원히 같은 주소에 머물지 않습니다.
도메인이 변경되기도 하고, 리다이렉트가 걸리기도 하고, 서버가 일시적으로 내려가기도 합니다. 브라우저에서는 정상적으로 열리는데 단순 HTTP 요청에서는 차단되는 경우도 있습니다.
몇십 개의 URL이라면 직접 확인할 수 있습니다.
하지만 관리해야 할 주소가 수백 개를 넘어가기 시작하면 이야기가 달라집니다.
이 글에서는 약 900개의 URL을 주기적으로 확인한다고 가정하고, 링크 상태를 자동으로 점검하는 시스템을 어떻게 구성할 수 있는지 정리해 보겠습니다.
1. 단순 HTTP 상태 코드만 믿으면 안 되는 이유
가장 쉬운 방법은 각 URL에 HTTP 요청을 보내는 것입니다.
예를 들어:
200 → 정상 301 / 302 → 리다이렉트 403 → 접근 제한 404 → 페이지 없음 5xx → 서버 오류 하지만 실제 웹에서는 이렇게 단순하지 않습니다.
어떤 사이트는 봇 요청에만 403을 반환합니다.
또 어떤 사이트는 최초 요청에서는 오류를 반환하지만 JavaScript가 실행된 브라우저에서는 정상적으로 열립니다.
따라서 단순 HTTP 검사만으로 최종 상태를 결정하면 오탐이 많아질 수 있습니다.
2. 1단계는 빠른 HTTP 검사
첫 단계에서는 가능한 한 가볍게 확인합니다.
예를 들면:
import requests def check_url(url): try: response = requests.get( url, timeout=10, allow_redirects=True ) return { "status": response.status_code, "final_url": response.url } except requests.RequestException: return { "status": None, "final_url": None } 여기서 중요한 것은 단순 상태 코드뿐 아니라 최종 URL도 함께 저장하는 것입니다.
예를 들어:
https://example001.com ↓ 301 ↓ https://example002.com 이런 패턴은 링크 디렉터리에서는 매우 중요합니다.
사용자에게 예전 주소를 계속 제공하고 있을 가능성이 있기 때문입니다.
3. HTTP 검사 실패 시 Headless Browser로 재확인
HTTP 검사에서 문제가 나온 모든 사이트를 바로 “접속 불가”로 처리하면 안 됩니다.
그다음 단계에서는 Playwright나 Chromium 같은 실제 브라우저 환경으로 재확인할 수 있습니다.
구조는 대략 다음과 같습니다.
URL ↓ HTTP 검사 ↓ 정상? ── Yes → 정상 처리 ↓ No Headless Browser ↓ 브라우저에서 열림? ↓ Yes → CHECK NEEDED No → ACCESS RESTRICTED / NOT OPENING 브라우저 검사는 HTTP 요청보다 비용이 많이 들기 때문에 모든 URL에 실행하는 것보다 실패한 URL에만 실행하는 방식이 효율적입니다.
4. 상태값은 기술 코드보다 운영 관점으로 설계하기
200, 403, ERR_CONNECTION_RESET 같은 값은 개발자에게는 유용하지만 운영자가 매일 확인하기에는 불편합니다.
그래서 실제 시스템에서는 사람이 바로 판단할 수 있는 상태로 변환하는 것이 좋습니다.
예:
NORMAL ADDRESS CHANGE CHECK NEEDED ACCESS RESTRICTED NOT OPENING 각 상태의 의미는 다음처럼 정의할 수 있습니다.
NORMAL
기존 주소에서 정상 접속.
ADDRESS CHANGE
기존 주소가 다른 도메인으로 지속적으로 이동.
CHECK NEEDED
자동 검사 결과가 애매해서 사람이 확인해야 함.
ACCESS RESTRICTED
서버는 존재하지만 현재 환경이나 네트워크에서는 접근이 제한됨.
NOT OPENING
HTTP와 브라우저 검사 모두 실패.
이런 식으로 상태값을 설계하면 기술적인 결과를 그대로 노출하는 것보다 운영 효율이 훨씬 좋아집니다.
5. 이전 검사 결과와 비교해야 하는 이유
매일 검사한다면 오늘 결과만 보는 것보다 어제 결과와 비교하는 것이 중요합니다.
예:
Yesterday: NOT OPENING Today: NORMAL 이 경우 단순히 NORMAL이라고 표시하는 것보다:
Recovered from previous-day issue 처럼 보여주는 편이 훨씬 유용합니다.
반대로:
Yesterday: NORMAL Today: ADDRESS CHANGE 라면 우선 확인해야 하는 항목으로 올릴 수 있습니다.
즉, 링크 모니터링 시스템은 단순 상태 검사기가 아니라 변화 감지 시스템이어야 합니다.
6. 결과 데이터 구조
실제 운영에서는 다음 정도의 컬럼이 있으면 편리합니다.
Category Site Name Status Current Domain Result Summary Last Checked Candidate 1 Candidate 2 Candidate 3 Reviewer Note 특히 주소가 변경됐다고 판단되는 경우에는 새로운 주소 후보를 바로 확정하기보다 몇 개의 후보를 저장한 뒤 사람이 최종 확인하도록 만들 수 있습니다.
자동화가 모든 것을 결정하도록 하는 것보다 이 방식이 훨씬 안전합니다.
7. 병렬 처리도 필요하다
URL이 900개라면 하나씩 순차 검사하는 방식은 느립니다.
대신 비동기 요청이나 제한된 동시 실행을 사용하는 것이 좋습니다.
예:
import asyncio import aiohttp async def fetch(session, url): try: async with session.get(url, timeout=10) as response: return url, response.status except Exception: return url, None 다만 동시 요청 수를 무작정 높이는 것도 좋지 않습니다.
- 대상 서버에 불필요한 부하 발생
- 로컬 네트워크 과부하
- 봇 탐지 가능성 증가
- 브라우저 메모리 사용량 증가
때문에 적절한 concurrency limit을 두는 편이 낫습니다.
8. 결국 사람이 확인해야 하는 단계가 필요하다
자동화에서 자주 하는 실수는 100% 자동 판정을 목표로 하는 것입니다.
하지만 웹사이트 상태는 생각보다 애매합니다.
예를 들어:
- VPN에서만 차단
- 특정 국가에서만 접근 제한
- 브라우저에서는 정상
- 로그인 후에만 정상
- 새 도메인으로 리다이렉트
- 일시적인 서버 오류
등이 존재합니다.
그래서 최종 단계는:
자동 검사 ↓ 이상 항목 분류 ↓ 사람이 수동 확인 ↓ 최종 상태 확정 구조가 현실적입니다.
실제 활용 사례: Radar
이런 링크 모니터링 구조가 필요한 대표적인 서비스가 Radar 같은 링크 디렉터리입니다.
Radar는 여러 종류의 웹사이트를 카테고리별로 정리해서 제공하는 주소모음·링크모음 플랫폼입니다.
링크 디렉터리는 일반적인 콘텐츠 사이트와 달리 외부 URL의 변경 여부가 서비스 품질에 직접적인 영향을 줍니다.
그래서 단순히 사이트를 많이 등록하는 것보다:
- 현재 주소가 유효한지
- 다른 도메인으로 변경됐는지
- 일시적인 오류인지
- 사람이 다시 확인해야 하는지
를 지속적으로 관리하는 구조가 중요합니다.
실제 서비스는 여기에서 확인할 수 있습니다.
Radar 주소모음안내사이트
https://radarjuso.com/
Radar에서는 AI, 커뮤니티, 쇼핑, 구인구직, 금융·재테크 등 다양한 분야의 웹사이트를 카테고리별로 탐색할 수 있습니다.
외부 사이트를 직접 운영하는 서비스가 아니라 여러 웹사이트 정보를 정리해서 제공하는 링크 디렉터리이기 때문에, URL 상태 관리와 주소 변경 감지는 운영에서 특히 중요한 문제입니다.
마무리
대규모 링크 모니터링 시스템에서 중요한 것은 단순히 200 OK를 찾는 것이 아닙니다.
핵심은 다음과 같습니다.
- 빠른 HTTP 검사
- 실패 사이트의 브라우저 재확인
- 리다이렉트 추적
- 이전 결과와 비교
- 운영 친화적인 상태 분류
- 제한된 병렬 처리
- 마지막 사람 검수
이 구조를 적용하면 수백 개 이상의 URL도 훨씬 체계적으로 관리할 수 있습니다.
그리고 실제 링크 디렉터리가 어떻게 구성되어 있는지 보고 싶다면 Radar도 참고할 수 있습니다.

