한 항목 안의 여러 주소 후보를 살피는 동안 오류가 나도 남은 후보를 읽을 수 있어야 한다. 동시에 끝내 선택하지 못한 항목은 실패에서 사라지면 안 된다. 이 두 판단을 작게 떼어, 주소 원문을 결과에 넣지 않는 합성 함수와 실행 기록으로 만들었다.
기준 주소와 후보 순서는 호출자가 정한다
이 모듈은 방문할 사이트나 이미지 속성을 찾아 주지 않는다. 신뢰할 설정에서 받은 절대 HTTP 계열 기준 주소와 순서가 정해진 문자열 목록만 받는다. 후보는 32개 이하, 기준 주소를 포함한 각 문자열은 Python 문자 수로 4,096자 이하이며 전체 계약을 반복 전에 검사한다. 앞 후보가 정상이어도 뒤에 잘못된 타입이 있으면 호출을 거절한다.
기준 주소가 잘못된 경우는 모든 후보의 공통 설정 문제이므로 호출 전체가 ValueError로 멈춘다. 반면 후보를 합치고 해석할 때 발생하는 ValueError는 그 후보의 parse_error로 남긴다. 설정 오류와 항목별 해석 오류를 같은 ‘계속 진행’ 정책으로 묶지 않았다. 이것은 독립 예제의 입력 계약이며 원래 수집기의 모든 입력 제한을 바꿨다는 뜻이 아니다.
처음 해석된 후보까지만 시도한다
빈 문자열은 empty, 해석은 됐지만 지원하지 않는 scheme은 unsupported_scheme으로 제외한다. 주소를 합치고 해석한 결과가 HTTP 계열이면 그 후보의 0부터 시작하는 번호를 선택하고 반복을 끝낸다. 선택 뒤의 후보는 시도하지 않았으며 그곳에 있을 오류를 진단했다고 말하지 않는다. attempted도 실제로 살펴본 후보까지만 센다.
출력에는 기준 주소나 선택된 주소, 원문이 들어 있을 수 있는 예외 문자열을 넣지 않는다. 대신 오류가 난 번호와 고정 사유를 남긴다. 이 출력은 비밀 입력 전체의 자동 익명화 증명도 아니다. 후보 수와 순서 같은 구조 자체가 민감한 업무라면 공유 전에 별도 검토해야 한다.
해석 오류만 해당 후보에 남기는 전체 코드
아래 구현은 Python 표준 라이브러리만 사용한다. urljoin으로 합친 결과를 urlsplit으로 살피며 그 구간의 ValueError만 처리한다. 예기치 않은 RuntimeError를 주입한 시험은 예외가 호출자에게 전파되는지 확인한다. 문제가 생겼을 때 무조건 다음 후보를 고르는 도구로 만들지 않았다.
selected_index가 있으면 호출자는 같은 원래 입력을 찾을 수 있다. 실제 접근 전에는 자신의 정책을 적용하고 다시 주소를 해석해야 한다. 이 함수가 안전한 저장 경로나 허용 호스트를 만들어 주지는 않는다. 코드를 복사하는 것보다 결과가 어떤 사실만 설명하는지 먼저 이해하는 것이 연결 과정에서 중요하다.
코드와 구현 살펴보기
독립 합성 전체 구현
"""Ordered syntax selection; this module never fetches a resource."""
from urllib.parse import urljoin, urlsplit
MAX_CANDIDATES = 32
MAX_CHARACTERS = 4096
def select_candidate(base, candidates):
"""Return an index and bounded diagnostics, never the input addresses."""
if type(base) is not str or not base or len(base) > MAX_CHARACTERS:
raise ValueError("invalid base contract")
parsed_base = urlsplit(base)
if parsed_base.scheme not in ("http", "https") or not parsed_base.hostname:
raise ValueError("base must be an absolute HTTP(S) address")
if type(candidates) is not list or len(candidates) > MAX_CANDIDATES:
raise ValueError("expected at most 32 ordered candidates")
if any(type(value) is not str or len(value) > MAX_CHARACTERS
for value in candidates):
raise ValueError("candidate must be a string of at most 4096 characters")
errors, skipped = [], []
chosen = None
attempted = 0
for index, raw in enumerate(candidates):
attempted += 1
value = raw.strip()
if not value:
skipped.append({"index": index, "reason": "empty"})
continue
try:
parsed = urlsplit(urljoin(base, value))
except ValueError:
errors.append({"index": index, "reason": "parse_error"})
continue
if parsed.scheme not in ("http", "https"):
skipped.append({"index": index, "reason": "unsupported_scheme"})
continue
chosen = index
break
return {
"selected_index": chosen,
"attempted": attempted,
"parse_errors": errors,
"skipped": skipped,
"unresolved_parse_failure": chosen is None and bool(errors),
"performs_fetch": False,
"security_approval": False,
}
앞 오류가 있어도 해결된 항목을 실패로 만들지 않는다
앞 후보가 해석에 실패하고 뒤 후보가 선택되면 parse_errors는 남지만 unresolved_parse_failure는 false다. 후보가 여러 개 잘못돼도 끝내 선택할 주소가 없을 때만 이 값이 true다. 이미지 단위 집계에서는 true를 실패 한 건으로 읽을 수 있으며 오류 배열의 길이를 실패 이미지 수로 쓰면 안 된다.
지원하지 않는 data 또는 blob 형태만 있는 경우에는 이 신호가 false이고 selected_index는 None이다. 선택 없음이 곧 모든 자료 확보를 뜻하지도, 항상 파싱 실패를 뜻하지도 않기 때문이다. 업무에서는 이 미선택 사유와 전송 결과를 따로 보고해야 한다. 선택 성공을 다운로드 성공으로 바꾸는 코드는 이 예제에 없다.
여섯 기록은 웹에서 재수집한 결과가 아니다
잘못된 앞 후보 뒤 정상 후보, 모두 잘못된 후보, 지원하지 않는 형태만 있는 경우, 상대주소, 정상 IPv6, 앞 후보에서 선택을 끝내는 경우의 여섯 결과를 만들었다. 아래 기록 선택기는 이 JSON을 읽는다. Python 실행과 네트워크 접근, 방문자 파일 업로드 기능은 없다. 선택 번호와 오류 번호가 달라지는 이유를 살펴보는 자료다.
한영 HTML의 실제 인라인 스크립트를 DOM 대역에 연결해 초기 표시 두 번과 상황 선택 열두 번을 확인한다. 이 검사는 렌더링 브라우저, 키보드 초점, 모바일 배치나 실제 이미지 표시 검수와 다르다. Python 함수는 소스 및 압축 추출본의 20개 시험으로 따로 확인하며, 원제품의 이전 41개 검사와 합쳐 하나의 검수 수로 만들지 않는다.
해석 성공을 접근 허가로 사용하지 않는다
security_approval과 performs_fetch는 항상 false다. 주소 파싱이 가능해도 허용하지 않은 네트워크 위치나 인증정보, 포트, 리다이렉트가 있을 수 있다. 이 예제는 DNS·접근 제어·SSRF 보호·응답 크기·이미지 디코딩을 구현하지 않는다. 같은 주소를 다시 요청하거나 다운로드 실패 뒤 다른 후보를 자동 선택하는 기능도 없다.
HTML 속성 추출과 srcset·JSON 속성의 해석도 호출자 몫이다. 공개 함수가 받는 것은 이미 순서가 정해진 목록이다. 실제 수집기는 후보 생성 규칙과 다운로드 경로를 가지고 있으므로 이 예제를 그대로 덮어써서는 안 된다. 파서, 접근 정책, 전송, 저장과 보고가 서로 어떤 입력과 결과를 주고받는지 먼저 맞춰야 한다.
실행과 재사용 범위를 파일에 함께 남겼다
ZIP에는 selector.py, 20개 시험, examples.py, 여섯 records.json, 사용법과 MIT 라이선스를 담았다. python -B -m unittest -v로 함수를 검사하고 python -B examples.py로 합성 기록을 다시 만들 수 있다. 예제 실행기만 현재 폴더의 records.json을 쓰며 선택 함수는 파일을 쓰지 않는다. 외부 패키지 설치나 실제 사이트 연결은 필요 없다.
입력 보존, 결과 객체 분리, 후보 수와 문자 수 한계, 잘못된 기준 주소, 정상 상대주소·IPv6·쿼리 괄호와 예상하지 못한 오류 전파도 시험했다. 수정·재배포·상업적 재사용은 라이선스 조건에 따라 가능하다. 실제 업체 수집 완성이나 과거 실패 복구를 대신하는 도구가 아니라, 오류를 어디서 멈추고 어떤 단위로 보고할지 직접 검토할 출발점으로 제공한다.