파이썬 urljoin 상대 링크 변환: 슬래시, 상위 경로, 쿼리를 구분하기

수집한 href는 기준 문서 URL과 urljoin으로 결합하되, 경로 앞뒤 슬래시와 결합 후 호스트를 별도로 확인하세요.

확인일: 2026-10-25. Python 3.14 표준 라이브러리 예제이며, Windows Python 3.14.4에서 합성 입력으로 실행하고 assert 검사를 통과했습니다.

문자열 더하기로 링크를 만들면 왜 틀리나요?

HTML에서 찾은 href는 detail.html, ../summary.html, /download.csv처럼 서로 다른 기준을 가질 수 있습니다. 사이트 주소와 문자열로 붙이면 상위 경로나 쿼리 전용 링크를 잘못 해석할 수 있습니다. 이 글은 네트워크 수집 전에 링크를 계산하는 단계만 다루며 실제 사이트에 요청하지 않습니다. example.invalid와 other.invalid는 합성 주소입니다.

입력 기준 URL을 문서 주소로 고정합니다

기준은 https://example.invalid/reports/2026/index.html입니다. detail.html은 같은 디렉터리, ../summary.html은 한 단계 위, /download.csv는 사이트 루트에서 시작합니다. ?page=2는 현재 문서의 쿼리를 바꾸고 #table은 문서 내 위치를 붙입니다. 아래 사례별 예상 URL을 assert로 비교하므로 출력 문자열을 직접 대조할 수 있습니다.

직접 실행하는 예제

from urllib.parse import urljoin

base = "https://example.invalid/reports/2026/index.html"
cases = {
    "detail.html": "https://example.invalid/reports/2026/detail.html",
    "../summary.html": "https://example.invalid/reports/summary.html",
    "/download.csv": "https://example.invalid/download.csv",
    "?page=2": "https://example.invalid/reports/2026/index.html?page=2",
    "#table": "https://example.invalid/reports/2026/index.html#table",
    "//other.invalid/path": "https://other.invalid/path",
    "https://other.invalid/direct": "https://other.invalid/direct",
}
for href, expected in cases.items():
    actual = urljoin(base, href)
    assert actual == expected
    print(href, "->", actual)
assert urljoin("https://example.invalid/reports", "a.csv") == "https://example.invalid/a.csv"
assert urljoin("https://example.invalid/reports/", "a.csv") == "https://example.invalid/reports/a.csv"
assert urljoin(base, "") == base
print("상대 경로·쿼리·문서 내 위치·외부 주소·끝 슬래시 검사 통과")

검증한 출력

detail.html -> https://example.invalid/reports/2026/detail.html
../summary.html -> https://example.invalid/reports/summary.html
/download.csv -> https://example.invalid/download.csv
?page=2 -> https://example.invalid/reports/2026/index.html?page=2
#table -> https://example.invalid/reports/2026/index.html#table
//other.invalid/path -> https://other.invalid/path
https://other.invalid/direct -> https://other.invalid/direct
상대 경로·쿼리·문서 내 위치·외부 주소·끝 슬래시 검사 통과

기준 URL 끝의 슬래시가 중요한 이유

https://example.invalid/reports와 끝에 슬래시가 있는 /reports/는 상대 경로를 결합할 때 다릅니다. 앞의 reports는 마지막 경로 요소로 대체될 수 있고, 뒤의 reports/는 디렉터리 기준이 됩니다. 다운로드 목록이 디렉터리인지 문서인지 확인하지 않고 슬래시를 일괄 추가하면 링크가 달라질 수 있습니다. 실제 응답이 리디렉션됐다면 최종 문서 URL을 기준으로 판단해야 합니다.

urljoin이 외부 주소를 막아 주나요?

아닙니다. //other.invalid/path나 https://other.invalid/direct 같은 href는 기준 호스트를 다른 호스트로 바꿀 수 있습니다. 공식 문서도 비신뢰 URL과의 결합을 주의하라고 설명합니다. 결합 성공은 접근 허가나 안전성 검증이 아닙니다. 실제 요청 전에는 허용한 스킴·호스트·포트와 자격 증명 포함 여부를 별도로 검사하고, 인증 헤더가 외부 주소로 전달되지 않게 하세요.

빈 링크와 base 요소는 어떻게 다루나요?

빈 href는 이 예제에서 기준 URL 자체가 되므로 수집 목록에서 제외할지 정해야 합니다. fragment만 다른 링크도 같은 문서의 다른 위치일 수 있으므로 중복 기준을 합의하세요. HTML의 base href 요소가 있으면 브라우저가 사용하는 링크 기준이 문서 URL과 다를 수 있습니다. 이 코드는 HTML을 파싱하거나 base 요소를 자동 적용하지 않으며, 별도로 검토한 기준 URL을 받는 계산 예제입니다.

실제 수집에 연결할 때 남는 작업

urljoin은 HTML 엔티티를 해석하는 파서도, 브라우저와 동일한 모든 URL 처리 규칙을 보장하는 보안 장치도 아닙니다. HTML 파서가 추출한 링크와 실제 문서의 기준을 먼저 확인하세요. 쿼리에 토큰·개인정보가 있을 수 있으므로 결과 URL을 로그나 공개 원고에 그대로 붙이지 마세요. 접근 정책과 이용약관을 확인한 사이트에서만 요청해야 합니다.

자주 묻는 질문

urljoin이 HTTP 요청도 보내나요?

아닙니다. 아래 예제는 URL 문자열을 계산하고 비교할 뿐 외부 연결을 하지 않습니다.

모든 링크의 fragment를 제거해도 되나요?

필요에 따라 다릅니다. 같은 문서 중복 제거에는 유용할 수 있지만 문서 내 위치 정보가 필요한 업무에서는 보존해야 합니다.

공식 출처와 관련 글

공식 문서 확인일: 2026-10-25. 예제의 입력과 결과는 직접 작성한 합성 자료입니다.

AI 보조 작성. 위 로컬 예제만 실행 검증했으며 실제 업무 자료·외부 API·GUI 실행이나 사람 전문가 검수를 주장하지 않습니다.

댓글

이 블로그의 인기 게시물

API 키 저장 방법: 해싱과 암호화의 차이, Fernet 예제

파이썬 Google Sheets API 연동: 서비스 계정 권한과 읽기·쓰기

Pandas 결측치 처리: isna, fillna, dropna 선택 기준