Selenium Python 404 링크 체크 방법 (자동화)

테스트 자동화

📌 이런 분들을 위한 글입니다

  • 🔧 QA 엔지니어 → 깨진 링크 탐지를 자동화해서 테스트 공수를 줄이고 싶은 분
  • 🔍 SEO 담당자 → 깨진 링크 방치로 인한 SEO 점수 하락을 막고 싶은 분
  • ⚙️ 테스트 자동화 엔지니어 → Selenium과 Python으로 실무 수준의 도구를 만들고 싶은 분

🔥 이 글을 읽으면 얻을 수 있는 것

  • SEO에 악영향을 주는 「깨진 링크」를 자동으로 탐지할 수 있다
  • 수동 링크 확인 작업이 완전히 필요 없어진다
  • 404뿐만 아니라 4xx/5xx 전체 상태 코드에 대응한 검사를 구현할 수 있다
  • QA·테스트 자동화·SEO 대책 3가지 모두에 활용 가능한 실무 수준의 코드를 얻을 수 있다

👤

글쓴이 소개:Selenium·Python·테스트 자동화를 실무에서 담당하고 있는 QA 엔지니어입니다. 이 글의 코드는 실제 업무에서 사용하는 도구를 기반으로 작성되었으며, GitHub에서 전체 소스코드를 공개하고 있습니다.

웹사이트 운영에서 은근히 골치 아픈 문제가 바로 깨진 링크(404 에러)의 존재입니다. 수동으로 확인하기엔 시간이 너무 오래 걸리고, 그렇다고 방치하면 SEO 평가와 신뢰성에도 영향을 미칩니다. 그 고민을 해결하기 위해 만든 QA 자동화 도구가 바로 LinkChecker입니다.

이 글에서는 Selenium × Python으로 구현한 링크 체커의 코드를, 설계 의도·각 메서드의 역할·자주 발생하는 문제까지 포함해 철저하게 해설합니다. 실행 결과 샘플과 CSV 출력 예시도 포함되어 있으니 바로 실행해 보세요.



00. 깨진 링크가 SEO에 미치는 영향

「깨진 링크는 사용자만 불편한 것」이라고 생각하기 쉽지만, 실은 SEO에 미치는 영향도 심각합니다. 깨진 링크는 사이트의 품질·검색 평가·사용자 경험 모두에 악영향을 줍니다.

⚠️ Google의 공식 입장: 크롤링할 수 없는 페이지나 깨진 링크가 많은 사이트는 사이트 전체의 품질 평가가 낮아질 수 있습니다. 깨진 링크 방치는 SEO에 있어 리스크입니다.
📉
크롤 효율 저하

Googlebot이 깨진 링크를 만나면 크롤 버짓을 낭비해 다른 페이지가 순회되기 어려워집니다

사이트 평가 하락

404 페이지가 많은 사이트는 Google로부터 「품질이 낮다」고 판단되어 SEO 평가가 낮아질 수 있습니다

😞
UX 악화·이탈률 상승

깨진 링크를 만난 사용자는 바로 이탈합니다. 이탈률 상승이 간접적으로 SEO에도 악영향을 줍니다

⚠️ 대책: 링크 끊김을 정기적으로 자동 확인하고 즉시 수정함으로써, SEO 평가 하락을 미연에 방지할 수 있습니다. 이 도구는 바로 그 자동화를 실현합니다.

01. 실행 결과 샘플(먼저 동작 확인!)

실제로 실행하면 URL·상태 코드·결과가 목록으로 출력됩니다. 깨진 링크를 한눈에 파악할 수 있는 형식입니다.

URL상태 코드결과
/top200✅ 정상
/about200✅ 정상
/careers404❌ 링크 끊김
/old-page410❌ 삭제된 페이지
/contact200✅ 정상

터미널에도 같은 형식으로 실시간 출력됩니다.

=== 실행 결과 예시 ===
[확인 중] /top       → 200 OK
[확인 중] /about     → 200 OK
[확인 중] /careers   → 404 Not Found  ← 링크 끊김 감지!
[확인 중] /old-page  → 410 Gone       ← 삭제된 페이지 감지!

=== 요약 ===
총 링크 수: 89  /  에러 링크 수: 3
📸 스크린샷 저장 완료
📊 CSV 출력 완료 → Desktop/LinkChecker/

실제로 필자의 환경에서 실행한 결과입니다. 총 128개 링크 중 에러 링크 2개를 감지하고, CSV와 스크린샷이 자동으로 저장되었습니다.

Selenium 링크 체커 터미널 실행 결과 총 링크 수 128 에러 링크 수 2

▲ 실제 터미널 출력. 128건 확인 중 2건의 에러 링크를 감지. CSV와 스크린샷이 자동 저장됨

에러 감지 시 다음과 같이 에러 페이지의 스크린샷이 자동으로 저장됩니다.

404 에러 감지 시 자동 저장되는 스크린샷 예시

▲ 404 에러 감지 시 자동 저장되는 스크린샷

💡 실무에서의 활용: 실무에서는 이 결과를 로그나 CSV로 저장해 정기적으로 확인하는 경우가 많습니다. 이 글에서는 1페이지를 대상으로 하지만, 이 로직은 여러 페이지나 사이트 전체 확인에도 응용 가능합니다.

자동 생성되는 CSV 리포트(저장해서 재활용)

실무에서는 「결과를 남기는 것」이 필수입니다. 이 도구는 확인 완료 후 CSV를 자동 생성하므로, 버그 티켓 첨부·SEO 담당자와의 공유·수정 작업의 우선순위 결정에 바로 활용할 수 있습니다.

링크 텍스트,URL,상태 코드,스크린샷 경로,확인 시각
상단 페이지,/top,200,,2026-03-19 14:30:01
회사 소개,/about,200,,2026-03-19 14:30:03
채용 정보,/careers,404,screenshots/404_careers.png,2026-03-19 14:30:22
구 페이지,/old-page,410,screenshots/410_old-page.png,2026-03-19 14:30:24
문의하기,/contact,200,,2026-03-19 14:30:25

자동 생성된 CSV를 Excel로 열면 이렇게 표시됩니다. 링크 텍스트·URL·상태 코드·스크린샷 경로가 한눈에 정리되어 있어 그대로 버그 티켓에 첨부할 수 있습니다.

Selenium 링크 체커 CSV 리포트 Excel로 열기 에러 링크 목록 상태 코드 404

▲ 자동 생성된 CSV를 Excel로 열기. 링크 텍스트·URL·상태 코드·스크린샷 경로가 모두 정리되어 있음

📊 전체 결과 CSV
모든 링크 확인 결과를 저장. 통계·분석에 활용 가능
❌ 에러 전용 CSV
에러만 추출해 별도 파일로 저장. 수정 작업을 바로 시작할 수 있음

02. 왜 Selenium + requests 조합인가?

「Selenium만으로도 되지 않나?」라고 생각하는 분도 많을 텐데요. 사실 Selenium 단독으로는 HTTP 상태 코드를 직접 가져올 수 없다는 문제가 있습니다.

💡 실무에서의 정답

Selenium은 링크 추출·DOM 조작을 담당하고, 상태 코드 확인은 requests가 담당하는 것이 실무적인 역할 분담입니다.

도구잘하는 것못하는 것
SeleniumDOM 조작·JS 실행·Cookie 처리·페이지 렌더링HTTP 상태 코드 직접 취득
requestsHTTP 상태 코드 고속 확인·경량JS 인증·Cookie 처리·동적 콘텐츠

03. 대응하는 에러 상태 코드 일람

상태 코드의미대응
404페이지가 존재하지 않음✅ 감지·스크린샷
410페이지가 영구적으로 삭제됨✅ 감지·스크린샷
500서버 내부 에러✅ 감지·스크린샷
502/503/504게이트웨이·서비스 이용 불가✅ 감지·스크린샷
403접근 제한(페이지 자체는 존재)⏭️ 스킵(정상으로 처리)
200/301/302정상·리다이렉트✅ 정상으로 판정

04. 환경 구축과 필요한 라이브러리

# 필요한 라이브러리 일괄 설치
pip install selenium requests
🐍
Python 3.8+

동작 확인 완료. Windows/Mac 크로스 플랫폼 대응

🌐
selenium 4.6+

ChromeDriver 자동 감지 대응. 수동 관리 불필요

📡
requests

HTTP 상태 코드 고속 확인에 사용. 대량 링크 처리에 대응


05. 클래스 구조와 설계

# 사용 방법은 단 3줄
checker = LinkChecker("https://example.com")
results = checker.run_check()
checker.close()
class LinkChecker:
    __init__            # 초기화·출력 경로·WebDriver 실행·에러 목록 초기화
    │
    ├── setup_output_directory   # 폴더 생성
    ├── setup_driver             # Chrome 옵션 설정·드라이버 실행
    │
    ├── run_check                # ★ 메인 루프(전체 제어)
    │   ├── get_all_links        # Selenium으로 링크 수집
    │   ├── check_link_status    # HTTP 상태 확인
    │   └── take_screenshot      # 에러 페이지 스크린샷 촬영
    │
    ├── save_results             # CSV 저장
    └── close                    # WebDriver 종료

06. __init__ 와 초기 설정

def __init__(self, base_url, output_dir=None):
    if output_dir is None:
        desktop_path = os.path.join(os.path.expanduser("~"), "Desktop")
        output_dir = os.path.join(desktop_path, "LinkChecker")

    self.base_url = base_url
    self.output_dir = output_dir
    self.setup_output_directory()
    self.setup_driver()
    self.error_links = []
⚠️ 주의: 봇 탐지 우회 기법은 사이트의 이용 약관에 따라 문제가 될 수 있습니다. 자사 사이트 또는 허가를 받은 사이트에만 사용하세요.

07. get_all_links — 링크 수집 전략

  1. 페이지 접속 & 초기 대기time.sleep(2)로 동적 콘텐츠 로딩을 기다림
  2. Cookie 팝업 처리handle_cookie_popup()으로 GDPR 동의 다이얼로그를 자동 클릭
  3. 전체 a 태그 취득find_elements(By.TAG_NAME, "a")로 HTTP로 시작하는 URL만 필터링
  4. 요소 정보 사전 저장(Stale Element 대책) — location/size/XPath 등을 dict에 저장
  5. 텍스트 없는 링크 보완title → alt → aria-label 순으로 폴백 취득
💡 Stale Element란: Selenium으로 요소를 취득한 후 DOM이 업데이트되면, 이전에 취득한 요소 참조가 무효화되는 현상입니다. 요소의 속성 정보를 dict에 미리 복사해 두는 것으로 회피할 수 있습니다.
# ❌ 나쁜 예:DOM이 변경되면 예외 발생
elements = driver.find_elements(By.TAG_NAME, "a")
do_something()
elements[0].click()  # ← StaleElementReferenceException!

# ✅ 좋은 예:필요한 정보를 먼저 dict에 모두 저장
element_data = {
    'location': element.location,
    'classes':  element.get_attribute("class") or "",
    'id':       element.get_attribute("id") or "",
    'xpath':    self.get_element_xpath(element)
}

08. check_link_status — 2단계 상태 코드 확인

# 먼저 HEAD 요청으로 경량 확인
response = requests.head(url, timeout=8, allow_redirects=True, headers=headers)
status_code = response.status_code

# 400번대(403/404 제외)는 GET으로 재확인
if 400 <= status_code < 500 and status_code not in [403, 404]:
    response = requests.get(url, timeout=8, allow_redirects=True, headers=headers)
    return response.status_code
💡 설계 의도: 「에러를 놓치는 것」보다 「오검출을 줄이는」 안전 측 설계입니다. 판정이 불분명한 페이지는 정상으로 처리하고 수동 확인 운영 흐름을 상정합니다.

09. take_screenshot — 증거 수집

📸
에러 페이지 스크린샷

404/500 등 에러 페이지를 캡처. 404_텍스트_타임스탬프.png로 저장

🔴
에러 전 스크린샷(BEFORE)

원래 페이지로 돌아가 문제 링크에 빨간 테두리 하이라이트+「ERROR LINK」배너를 JS로 주입해 캡처

# 요소에 빨간 테두리·글로우 이펙트 적용
element.style.cssText += `
    border: 3px solid #ff0000 !important;
    box-shadow: 0 0 15px rgba(255, 0, 0, 0.8) !important;
    z-index: 999999 !important;
`;

# 화면 상단에 고정 배너 추가
var label = document.createElement('div');
label.innerHTML = 'ERROR LINK: ' + linkText.substring(0, 30);
label.style.cssText = `
    position: fixed; top: 20px; left: 50%;
    background: #ff0000; color: white; padding: 10px 20px;
`;

실제로 생성되는 BEFORE 스크린샷입니다. 문제 링크가 빨간 테두리로 하이라이트되고 「ERROR LINK」 배너가 화면 상단에 자동 주입되어 어느 링크가 원인인지 한눈에 알 수 있습니다.

Selenium 링크 체커 에러 링크 빨간 테두리 하이라이트 ERROR LINK 배너 JS 자동 주입

▲ 에러 링크를 빨간 테두리로 하이라이트+「ERROR LINK」배너를 JS로 주입해 캡처. 어느 링크가 문제인지 한눈에 파악 가능


10. handle_cookie_popup — GDPR 대응

cookie_selectors = [
    "//button[contains(text(), 'Accept all cookies')]",
    "//button[contains(text(), 'Accept')]",
    "//button[contains(text(), 'Accetta')]",
    "//button[contains(@class, 'accept')]",
]
self.driver.execute_script("arguments[0].click();", button)

11. save_results — CSV 리포트 출력

# utf-8-sig = BOM 포함 UTF-8(Excel에서 깨짐 방지)
with open(csv_path, 'w', newline='', encoding='utf-8-sig') as csvfile:
    writer = csv.DictWriter(csvfile, fieldnames=results[0].keys())
    writer.writeheader()
    writer.writerows(results)

error_count = sum(1 for r in results if r['상태 코드'] in [404, 410, 500, 502, 503])
print(f"✅ 정상 링크  : {len(results) - error_count}건")
print(f"❌ 에러 링크: {error_count}건")
💡 실무 포인트: 에러 카운트를 로그로 남겨두면 이전 확인과의 비교가 간단해집니다. 「지난주보다 404가 3건 늘었다」와 같은 트렌드 관리도 가능해집니다.

12. 자주 발생하는 에러와 대처법

① ChromeDriver 버전 불일치

pip install --upgrade selenium

② TimeoutException — 페이지가 로딩되지 않음

self.driver.set_page_load_timeout(30)  # 15초 → 30초로 연장

③ a 태그가 0건밖에 취득되지 않음

time.sleep(5)  # 2초 → 5초로 늘려서 재시도

13. 대량 URL을 확인할 때의 고속화

① concurrent.futures로 병렬 처리

from concurrent.futures import ThreadPoolExecutor, as_completed

def check_links_parallel(links, max_workers=10):
    results = []
    with ThreadPoolExecutor(max_workers=max_workers) as executor:
        future_to_link = {
            executor.submit(check_link_status, link['url']): link
            for link in links
        }
        for future in as_completed(future_to_link):
            link = future_to_link[future]
            status = future.result()
            results.append({'url': link['url'], 'status': status})
    return results

② 타임아웃 설정과 재시도 처리

def check_with_retry(url, max_retries=3, timeout=8):
    for attempt in range(max_retries):
        try:
            response = requests.head(url, timeout=timeout, allow_redirects=True)
            return response.status_code
        except requests.exceptions.Timeout:
            if attempt < max_retries - 1:
                time.sleep(2)
    return 0
💡 목표 수치: 100건이면 보통 약 5분 → 10병렬 스레드로 약 30초로 단축 가능합니다.

14. 더 발전시키기 위한 개선 아이디어

병렬 처리로 고속화

concurrent.futures로 병렬 실행. 1000건 링크를 고속 확인 가능

🌐
전체 페이지 크롤 기능

내부 링크를 재귀적으로 따라가 사이트 전체를 일괄 확인 가능하게

🔁
재시도 처리 강화

타임아웃 시 자동 재시도로 오검출을 방지

🚫
제외 링크 설정

mailto:tel:을 스킵 목록에 추가해 불필요한 확인을 생략


15. 자주 겪는 문제 & 해결법

구현 중 실제로 겪었던 문제들을 정리했습니다. 같은 곳에서 막히는 분들께 도움이 되면 좋겠습니다.


① Selenium만으로는 HTTP 상태 코드를 가져올 수 없다

처음에 "Selenium만으로 링크 오류 검출이 가능하겠지!"라고 생각하고 코드를 작성하기 시작했습니다. 하지만 Selenium은 DOM 조작에는 능숙하지만, HTTP 상태 코드를 직접 가져오는 기능이 없습니다.

해결책으로 requests 라이브러리와 조합하는 방법을 찾았습니다.

# ❌ Selenium만으로는 HTTP 상태 코드를 가져올 수 없음
# ✅ requests와 조합하여 해결
response = requests.head(url, timeout=8, allow_redirects=True)
status_code = response.status_code

💡 포인트: Selenium은 링크 추출·DOM 조작, requests는 상태 코드 확인으로 역할을 분담하는 것이 실무에서의 정답입니다.


② StaleElementReferenceException 발생

링크를 가져온 후 DOM이 업데이트되면, 이미 가져온 요소의 참조가 무효화되어 StaleElementReferenceException 이 발생합니다. 처음에는 왜 오류가 나는지 전혀 이해할 수 없었습니다.

해결책은 요소 정보를 미리 dict에 저장해두는 것입니다.

# ❌ 나쁜 예: 나중에 요소를 조작하려고 하면 오류 발생
elements = driver.find_elements(By.TAG_NAME, "a")
do_something()
elements[0].click()  # ← StaleElementReferenceException!

# ✅ 좋은 예: 미리 dict에 정보를 저장
element_data = {
    'href': element.get_attribute("href"),
    'text': element.text,
}

💡 포인트: 요소 참조를 나중에 사용하는 것이 아니라, 필요한 정보를 가져온 직후에 dict에 저장하는 습관을 들이면 안정적으로 동작합니다.


③ a태그가 0건밖에 가져와지지 않는다

find_elements(By.TAG_NAME, "a") 를 실행해도 0건밖에 가져오지 못하는 경우가 발생했습니다. 원인은 JavaScript로 동적으로 생성되는 콘텐츠의 로딩 대기 시간이 부족했던 것이었습니다.

# ❌ 0건이 될 수 있음
driver.get(url)
elements = driver.find_elements(By.TAG_NAME, "a")

# ✅ 대기 시간을 늘려서 해결
driver.get(url)
time.sleep(5)  # 2초 → 5초로 늘림
elements = driver.find_elements(By.TAG_NAME, "a")

⚠️ 주의: time.sleep() 의 값은 사이트의 로딩 속도에 따라 조정이 필요합니다. 무거운 사이트에서는 10초 이상 필요한 경우도 있습니다.


④ HEAD 요청을 거부하는 서버가 있다

requests.head() 로 가벼운 체크를 시도했더니, 서버에 따라서는 HEAD 메서드를 거부하고 405 오류를 반환하는 경우가 있었습니다.

해결책은 HEAD가 실패하면 GET으로 재확인하는 2단계 체크로 만드는 것입니다.

# 먼저 HEAD로 가벼운 체크
response = requests.head(url, timeout=8)

# 400번대가 반환되면 GET으로 재확인
if 400 <= response.status_code < 500:
    response = requests.get(url, timeout=8)

💡 포인트: HEAD → GET 폴백 구성으로 서버 차이에 의한 오탐지를 방지할 수 있습니다.


⑤ ChromeDriver 버전 불일치

Chrome을 업데이트한 후 스크립트를 실행하니 갑자기 동작하지 않게 됐습니다. 원인은 Chrome과 ChromeDriver의 버전이 맞지 않게 된 것이었습니다.

# ✅ selenium 4.6 이상으로 업그레이드하면 자동 해결
pip install --upgrade selenium

💡 포인트: Selenium 4.6 이상은 ChromeDriver를 자동으로 관리해주기 때문에 이 문제가 근본적으로 해결됩니다. 수동 버전 관리가 필요 없어지므로 먼저 최신 버전으로 업그레이드하는 것을 권장합니다.

16. 정리

스크립트를 실행하면 screenshots 폴더에 에러 페이지 스크린샷(404_)과 에러 전 스크린샷(BEFORE_)이 세트로 자동 저장됩니다. 파일명에 링크 텍스트와 타임스탬프가 포함되어 있어 나중에 찾아보기도 쉽습니다.

Selenium 링크 체커 screenshots 폴더 404 에러 SS BEFORE 이미지 자동 저장 파일 목록

▲ 자동 저장되는 screenshots 폴더 내용. 에러 페이지 스크린샷(404_)과 에러 전 스크린샷(BEFORE_)이 세트로 저장됨

  • Selenium은 링크 추출·DOM 조작requests는 상태 확인이라는 역할 분담이 실무의 정답
  • 404뿐만 아니라 4xx/5xx 전체를 대상으로 하는 것이 실무적인 접근
  • 증거로서 에러 전후의 스크린샷을 자동 생성 가능
  • 결과는 Excel 대응 CSV로 자동 출력되어 버그 티켓에 바로 첨부 가능
  • SEO 개선 → 링크 끊김을 정기 감시해 검색 평가 하락을 방지 가능
  • CI/CD에 통합 → 릴리스 전에 자동으로 링크 확인이 실행되는 품질 게이트 구축 가능
  • 정기 감시 → cron이나 작업 스케줄러와 조합해 주간·월간으로 자동 실행 가능

이 도구의 3가지 활용 장면

🔍
SEO 개선에 활용

링크 끊김은 Google 평가를 낮추는 요인. 주간·월간으로 정기 실행해 항상 건전한 사이트를 유지

🧪
QA 테스트에 활용

릴리스 전 링크 품질 확인을 테스트 스위트에 통합. 증거 스크린샷도 자동 생성됨

⚙️
CI/CD에 통합 가능

GitHub Actions나 Jenkins에 통합하면 매 배포 전에 자동으로 링크 확인이 실행되는 품질 게이트 구축 가능

📅
정기 감시 가능

cron이나 Windows 작업 스케줄러와 조합해 주간·월간으로 자동 실행. 문제를 조기에 발견 가능

🚀 향후 확장 예시

  • 전체 페이지 크롤 대응 → 내부 링크를 재귀적으로 따라가 사이트 전체를 일괄 확인
  • 병렬 처리(concurrent.futures) → 1000건의 URL을 고속 처리
  • 정기 실행 + Slack 알림 → cron으로 자동 실행하고 에러 감지 시 Slack에 즉시 알림
💡 글쓴이 한마디: 이 도구는 「링크 끊김을 찾는다」는 단일 목적에 특화된 설계가 훌륭합니다. Selenium의 복잡성을 클래스 안에 가두어, 호출 측은 심플하게 유지되기 때문에 팀 전체에 배포하거나 수정하기도 쉬운 구조입니다. SEO·QA·CI/CD 모두에 활용할 수 있으니 GitHub의 소스코드도 꼭 참고해 보세요!
제목과 URL을 복사했습니다