페이지 변경 알림을 열었는데 달라진 것은 방문자 수뿐입니다. 반대로 문장은 그대로인데 내려받기 링크가 다른 파일을 가리킬 수도 있습니다. HTML 전체가 달라졌다는 사실, 본문 텍스트가 달라졌다는 사실, 독자가 실행할 행동이 바뀌었다는 사실은 서로 다릅니다. 이번에는 기본 합성 HTML 네 개에서 본문과 링크 목적지를 나눠 비교하고 추출 실패 반례 네 쌍을 추가했습니다. 의미를 이해하는 AI 검사가 아니라 비교할 영역과 값을 먼저 정하는 작은 실험입니다.

무엇이 바뀌면 알려야 하는지부터 정합니다.

전체 응답의 해시가 달라지면 바이트가 바뀐 것은 알 수 있습니다. 그러나 요청 시각, 추천 글 순서, 세션 값이 들어간 페이지는 핵심 내용이 같아도 매번 달라질 수 있습니다. 이런 변화를 모두 같은 경고로 보내면 정작 마감일이 바뀐 알림도 무심히 넘길 수 있습니다. 먼저 문서의 업무 목적과 잘못됐을 때 영향이 큰 부분을 적습니다.

공개 안내문이라면 대상·기한·신청 경로가 핵심일 수 있고, 다운로드 페이지라면 파일 주소와 버전이 중요할 수 있습니다. 이번 계약은 정확히 하나이며 비어 있지 않은 main 안의 일반 텍스트와 a 요소의 href 목록입니다. nav·aside·script·style은 제외합니다. 이미지 속 정보나 CSS로 보이는 문구, JavaScript가 나중에 삽입한 내용까지 비교했다고 읽으면 안 됩니다.

합성 입력 네 개는 배치·문장·링크 변경을 나눕니다.

기준 HTML에는 바깥 nav의 방문 수 10, main의 검사 제목, “기한은 10월 20일.” 문장, /v1을 가리키는 자료 링크가 있습니다. 두 번째 문서는 방문 수를 11로 바꾸고 태그 사이 공백을 더했습니다. 세 번째는 둘째 문서에서 20일을 21일로 바꿨습니다. 넷째는 문장을 유지한 채 자료 링크만 /v2로 바꿨습니다.

이렇게 한 번에 한 종류를 바꿔야 어떤 비교 층에서 차이가 발견되는지 알 수 있습니다. 방문 수도 바꾸고 본문도 고친 사례 하나만 있으면 텍스트 필터가 무엇을 제외했는지 설명하기 어렵습니다. 입력은 모두 코드에 들어 있고 실제 사이트를 크롤링하지 않습니다. 특정 서비스의 변경 감지 성능이나 정확도 수치를 측정한 것은 아닙니다.

본문 추출은 main이라는 이름만 보고 끝내지 않습니다.

HTML의 main은 문서의 주된 콘텐츠를 나타내는 요소입니다. 그러나 모든 사이트가 같은 방식으로 표시하지는 않습니다. 레이아웃용 div만 있는 사이트도 있고, main 안에 공지·도구·추천 영역을 함께 넣기도 합니다. 실제 문서를 처음 연결할 때는 선택한 범위의 제목과 첫 문단을 눈으로 확인하고, 빠지면 곤란한 표나 안내가 바깥에 있지 않은지 봐야 합니다.

이번 파서는 main의 시작과 끝 사이를 읽고 지정한 제외 태그 안의 텍스트를 건너뜁니다. Python HTMLParser의 이벤트를 이용하는 제한된 구현입니다. 깨진 HTML의 브라우저 보정 규칙이나 모든 중첩 오류를 재현하지 않습니다. 실제 운영 도구라면 검증된 DOM 파서와 사이트별 추출 규칙을 사용하고, 규칙이 바뀔 때 과거 예제도 다시 실행하는 편이 좋습니다.

공백 정규화는 일반 문단과 코드에서 다르게 생각해야 합니다.

이번 합성 문서는 일반 문단만 사용하므로 연속 공백을 하나로 접고 앞뒤 공백을 제거했습니다. 이 규칙에서 태그 사이 들여쓰기의 변화는 본문 변경으로 세지 않습니다. 그러나 Python 코드의 들여쓰기, pre 안의 공백, 원문에서 의미 있는 줄바꿈까지 같은 방식으로 접으면 실제 변경을 감출 수 있습니다.

문서 안에 코드·시·주소·정렬된 텍스트가 있다면 영역별로 규칙을 나눠야 합니다. 제공 비교 함수는 pre 또는 code가 들어 있으면 일반 문단 규칙을 적용해 동등하다고 결론 내리지 않고 verbatim-policy-required로 보류합니다. 원시 HTML은 따로 저장하고 비교용 문자열만 정규화합니다. 그래야 “같다고 나온 이유”를 나중에 설명할 수 있습니다. 같은 결과를 만들겠다고 원본 파일을 정규화한 값으로 덮어쓰면 증거가 사라지므로 비교와 저장을 분리합니다.

입력별 결과를 편집 판단에 연결하기

다음 표의 결과는 제공 실행 파일에서 확인한 값이며, 해석과 다음 확인은 그 결과를 이용하는 편집 예시입니다. 추가 시험을 수행한 결과로 세지 않습니다.

입력·관찰실행 결과 또는 선택판단 근거다음 확인
navigation-only원시 HTML은 달라졌지만 textEqual과 linksEqual은 참입니다.방문 10→11과 들여쓰기 변화는 이번 계약의 본문 밖 또는 정규화 대상입니다. 이 알림을 핵심 안내 변경과 같은 단계로 보내지 않습니다.원시 응답은 보관하고 정한 추출 규칙이 정말 필요한 영역을 포함하는지 먼저 확인합니다.
deadline-changed본문 다름·링크 같음입니다.20일에서 21일로 한 자리만 바뀌었지만 독자의 기한을 바꿀 수 있습니다. 차이 크기나 문자열 유사도와 업무 중요도는 별개입니다.변경된 문장과 승인 근거를 확인하고 요약·버튼 주변의 이전 날짜도 같이 찾습니다.
href-only본문 같음·링크 다름입니다.“자료”라는 글자는 유지돼 텍스트 비교만으로는 놓칩니다. /v1과 /v2의 파일 내용이 같은지 다른지도 이 결과만으로는 알 수 없습니다.목적지의 버전·접근 조건을 별도 확인합니다. 코드가 연결을 수집했다고 다운로드가 정상이라고 표시하지 않습니다.

실행 결과는 세 개의 서로 다른 신호를 보여 줍니다.

코드는 각 경우의 textEqual과 linksEqual을 기대값과 대조합니다. 참과 거짓의 조합이 예상대로 나왔다고 해서 그 페이지의 안내가 사실이라는 뜻은 아닙니다. “기한이 바뀌었다”는 발견과 “새 기한이 승인됐다”는 확인도 구분해야 합니다. 변경 알림을 받은 뒤에는 바뀐 문장의 출처와 변경 사유를 사람이 확인해야 합니다.

문구가 같아도 링크·대체텍스트·단위를 놓칠 수 있습니다.

이번 링크 비교는 href만 수집합니다. 링크 텍스트나 download 속성, 접근 조건을 종합적으로 검수하는 기능은 없습니다. 이미지 alt가 바뀌거나 표의 머리글이 달라지는 상황도 별도 비교 항목으로 설계할 수 있습니다. 특히 가격표의 단위가 원에서 달러로 바뀌면 숫자만 같아도 중요한 변경입니다. 어떤 값을 비교하는지 결과에 함께 남겨야 합니다.

모든 속성을 한꺼번에 비교하면 다시 잡음이 늘 수 있습니다. 분석 태그의 식별자와 독자가 쓰는 다운로드 주소를 같은 우선순위로 두지 마세요. 알림을 본문 변경, 행동 목적지 변경, 접근성 정보 변경, 추출 실패처럼 나누면 다음 확인 행동이 분명해집니다. “변경됨” 한 상태는 간단하지만 바뀐 부분을 찾는 일을 사람에게 다시 떠넘깁니다.

가령 공지 원문이 “검수 기한은 10월 20일”에서 “검수 기한은 10월 21일”로 바뀌었다면 편집 인계에는 원문 위치, 이전값, 현재값, 확인자, 다음 행동을 남길 수 있습니다. “내용 2% 변경”보다 무엇을 바꿔야 하는지 명확합니다. 실제 운영의 승인 여부가 없으면 단순 관찰로 남기고 기존 기한을 자동으로 수정하지 않습니다. 이 문장은 결과 사용 방법을 설명하는 예시입니다.

빈 본문은 “변경 없음”으로 처리하지 않습니다.

로그인 화면이나 오류 안내를 받았거나 사이트 구조가 바뀌면 본문 선택자가 아무것도 찾지 못할 수 있습니다. 두 번 모두 빈 문자열이면 단순 비교에서는 같다고 나옵니다. 그러나 그것은 내용이 유지됐다는 증거가 아닙니다. 실제 도입 시에는 기대 제목·최소 구조·접근 상태를 먼저 확인하고 추출 실패를 별도 상태로 남겨야 합니다.

최소 글자 수만으로도 충분하지 않습니다. 긴 보안 안내 페이지가 정상 기사보다 더 길 수 있기 때문입니다. HTTP 상태, 최종 주소, 문서 제목, main 존재 여부를 묶어 보고 초기 기준 문서와 비교합니다. 이번 재검수에서는 추출 계약의 실패 입력 네 쌍을 추가로 실행했습니다. main이 없는 동일한 두 문서, 빈 main 두 문서, main이 둘인 두 문서, pre 안의 공백만 다른 두 문서입니다. 모두 comparisonStatus=hold-extraction이며 textEqual과 linksEqual은 true가 아닌 null로 남았습니다. 정상 문서의 내용이 같다는 결론과 추출할 수 없어 비교를 멈춘 상태를 구분했습니다. 실제 네트워크 실패나 로그인 전환을 수집한 실험은 아닙니다.

필터를 추가하는 순간 무엇을 놓칠 수 있는지도 같이 적습니다. aside를 제외하면 광고나 추천 글의 잡음은 줄지만, 사이트가 중요한 주의사항을 aside에 넣었다면 그 부분을 놓칩니다. script를 제외하면 분석 코드의 변화는 무시할 수 있지만 JSON-LD의 날짜나 조건도 비교 목록에서 빠집니다. 따라서 본문 추출 규칙과 구조화 데이터 검사는 서로 다른 선택자로 관리하는 편이 좋습니다.

변경표는 짧게 만들고 원문은 따로 보관합니다.

실무 기록에는 확인 시각, 최종 URL, 추출 규칙 버전, 이전·현재 본문 해시, 바뀐 항목과 사람의 판정을 둡니다. difflib 같은 도구로 줄 차이를 표시할 수 있지만 문자열의 유사도가 사실의 중요도를 뜻하지는 않습니다. 날짜 숫자 한 자리 변경은 작게 보여도 행동을 바꿀 수 있고, 문장 순서 변경은 크게 보여도 사실이 같을 수 있습니다.

원시 응답을 보관할 때는 개인정보·세션 값이 섞이지 않도록 접근 범위를 정합니다. 공개 글이라도 내려받은 HTML에 사용자별 추천이나 토큰이 들어 있을 수 있습니다. 보관 기간과 책임자를 정한 뒤 필요한 증거만 남기세요. 원문 전체를 무조건 영구 저장하는 것이 좋은 변경 기록의 조건은 아닙니다.

본문을 문장 단위로 비교할지 블록 단위로 비교할지도 문서 특성에 맞춥니다. 표에서 행을 정렬한 변화와 값이 바뀐 변화를 구분하려면 키에 따른 표 비교가 필요할 수 있습니다. 코드를 한 줄씩 비교할 때는 공백이 의미를 가질 수 있습니다. 이 작은 일반 문단 예제를 모든 종류의 문서에 그대로 확장하지 말고 해당 형식의 원문 보존 규칙을 먼저 세우세요.

이 예제를 확장할 때는 새로운 실패 문서를 먼저 만듭니다.

본문 유무·영역 개수·공백 의미를 나눈 네 반례는 이번 실행 자료의 contractCases에 입력과 이유를 남겼습니다. main이 없거나 여러 개라는 판정은 이 예제가 한 영역만 받는다는 제한입니다. HTML 표준의 모든 유효·무효 문서를 판정하는 규칙이 아닙니다. 스크립트 삽입, 표, 숨겨진 문구와 브라우저가 보정하는 깨진 HTML은 여전히 별도 예제로 검토해야 합니다. 특히 textContent는 숨김 여부와 관계없이 텍스트를 포함할 수 있으므로 브라우저에서 실제로 보이는 문구와 별도 점검해야 합니다.

개선 여부는 알림 수가 줄었다는 숫자 하나보다 중요한 변경을 놓치지 않는지로 판단합니다. 먼저 놓치면 안 되는 문장과 링크의 목록을 정하고, 알림이 난 뒤 사람이 무엇을 확인했는지 기록합니다. 입력 범위와 정규화 규칙이 드러나는 비교기는 단순한 변경 점수보다 해석하기 쉽습니다. 다만 독자의 이해도나 사이트 운영 성과를 이번 실험에서 측정한 것은 아닙니다.

운영 중에는 같은 입력을 다시 추출했을 때 결과가 안정적인지도 확인합니다. 오늘의 규칙이 지난주 저장 문서를 다르게 읽는다면 실제 원문이 바뀐 것인지 파서 설정이 바뀐 것인지 구분해야 합니다. 규칙 버전과 원시 입력을 함께 남기는 이유입니다. 이번 비교는 고정 HTML을 읽으므로 이런 장기 반복 안정성을 측정한 결과는 아닙니다.

이 글의 입력·실행 자료

합성 자료의 실제 검증일은 2026-10-15입니다. 글의 예약 발행일과 구분합니다. 코드의 fixtureCreatedOn은 예제 작성일이고 executedAt은 재실행할 때의 실제 UTC 시각입니다. Python 3 표준 라이브러리를 사용합니다. 실제 고객정보나 새 Gemini 응답을 입력하지 않았습니다.

python3 html-content-semantic-diff-lab.py

확인한 공식 원문

2026-10-15에 열어 확인했습니다. 제공 예제의 규칙과 공식 문서의 일반 기능 설명을 구분해 읽습니다.

함께 대조할 기존 기록