본문추출

가장 긴 것을 고르면 사이드바가 이깁니다

2026. 09. 14

본문 추출이 기사 대신 추천 기사 목록을 가져오고 있었습니다. 점수로 고르는 방식이 왜 뒤집혔는지, 근거의 3분의 2가 120자 요약이던 것을 어떻게 찾았는지, 그리고 테스트가 운영 DB에 쓰고 있던 것을 확인한 기록입니다.

프로젝트 배경

소속
통신사 B2B 영업조직 대상 사내 프로젝트
프로젝트
계정 플랜 자동 생성 에이전트
기간
2026.07 ~ (진행 중)
담당
AI 백엔드 · 데이터 수집부터 초안 생성까지 단독 담당

고객사별 계정 플랜을 공시·조달·뉴스 데이터로 자동 생성하는 사내 도구입니다. 뉴스는 초안의 주요 근거이며, 대상 고객사가 1만 2천 곳 규모여서 회사 한 곳에서 생긴 오염이 그대로 전체로 확대됩니다.

본문추출크롤링LLM근거테스트데이터파이프라인

1. 연구 배경

이 시스템은 고객사마다 네 개 페이지(산업 환경·가치사슬·고객 현황·정책 규제)의 초안을 생성합니다. 뉴스는 그 근거의 큰 축입니다. 검색 API가 주는 요약은 120자에 불과하므로 기사 주소를 직접 받아 본문을 추출합니다.

본문 추출은 이전에 한 번 정리한 영역입니다. 언론사마다 본문 컨테이너가 다르므로 표지 후보를 여러 개 두고, 각 표지로 잘라낸 결과 중 가장 긴 것을 고르는 방식이었습니다. 본문이 사이드바보다 길다는 전제입니다.

이번 회차에서 그 전제가 깨진 것을 확인했습니다.

2. 문제 정의

베타 대상 초안을 검토하던 중 한 매체의 기사에서 본문이 한 줄도 없이 같은 문단만 반복해서 나오는 것을 발견했습니다. 해당 매체 기사 6건을 확인한 결과 6건 전부 동일했습니다.

[미국 특징주] 콘아그라·캠벨·맥코믹 등 식품 업체 가격 줄인상
극초음속 기업 스트라토런치, 최대 5억달러 IPO 준비
[홍콩 대장주] 메이퇀 ③ …

기사 내용이 아니라 추천 기사 목록입니다. 일부가 섞여 들어온 것이 아니라 본문 전체가 사이드바로 대체된 상태였습니다.

부분 오염이라면 문장 단위로 걸러낼 수 있지만, 전량 대체는 그럴 수 없습니다. 해당 기사를 근거로 받은 초안은 존재하지 않는 사실을 인용하게 됩니다.

3. 기술적 난점

표지별 점수를 찍어 보니 원인이 분명했습니다.

표지추출 결과점수
itemprop="articleBody"실제 기사 본문971
<article>추천 기사 카드 묶음3,482

itemprop="articleBody" 는 본문을 정확히 집어냈습니다. 그런데 범용 표지인 <article> 이 중첩된 추천 카드들에 걸리면서 3배 이상 높은 점수를 받았습니다.

길이만의 문제가 아닙니다. 점수에는 문장 밀도도 들어가는데, 추천 카드의 제목들이 마침표로 끝나 이어 붙으면 문장처럼 보입니다. 길이와 문장 밀도 양쪽에서 사이드바가 본문을 이기는 구조였습니다.

"가장 긴 것이 본문"이라는 전제는 본문과 잡동사니를 비교할 때는 성립했지만, 잡동사니가 다른 기사 제목 수십 개로 채워진 경우에는 뒤집힙니다.

4. 대안 검토

1안. 점수 계산 보정

사이드바로 의심되는 패턴에 감점을 줍니다.

  • 장점: 기존 구조를 유지합니다.
  • 단점: 매체마다 사이드바 형태가 다르므로 패턴을 계속 추가해야 합니다. 새 매체가 들어올 때마다 같은 사고가 재발합니다.

2안. 표지 우선순위 존중

표지를 앞에서부터 시도하고 본문을 찾으면 거기서 멈춥니다.

  • 장점: 표지 목록은 이미 본문 전용 속성이 앞, 범용 <article> 이 맨 뒤로 정렬돼 있습니다. 순서에 의도가 담겨 있는데 점수 비교가 그것을 무시하고 있었습니다.
  • 단점: 앞선 표지가 잘못된 구간을 잡으면 뒤쪽 표지로 회복할 기회가 없습니다.

5. 설계 결정 및 근거

2안을 택했습니다.

근거는 표지 목록 자체에 있습니다. itemprop="articleBody" 는 본문을 표시하기 위해 존재하는 속성이고, <article> 은 문서의 어느 구획에나 붙을 수 있는 범용 태그입니다. 전자가 매칭됐다면 그것이 본문일 확률은 후자보다 훨씬 높습니다. 이미 알고 있는 정보를 점수라는 단일 지표로 뭉개고 있었던 셈입니다.

단점으로 적은 "회복 기회 없음"은 실제로는 크지 않습니다. 구체적 표지가 매칭됐는데 본문이 아닌 경우보다, 범용 표지가 엉뚱한 구획을 잡는 경우가 압도적으로 많습니다.

점수 비교는 같은 우선순위의 표지가 여러 번 등장할 때만 남겼습니다. id="content" 가 메뉴와 본문 양쪽에 붙는 사례가 있어 그때는 여전히 비교가 필요합니다.

6. 실험 결과

오염됐던 기사 4건을 다시 추출했습니다.

항목개선 전개선 후
사이드바 오염4 / 40 / 4
본문 길이0자971 ~ 2,312자

다른 매체 회귀도 확인해 기존 추출에 영향이 없음을 확인했습니다.

추출 과정에서 본문 영역 안에 들어 있는 UI 문구(AI 핵심 요약 beta 분석 중...)도 함께 제거했습니다. 매체가 본문 컨테이너 내부에 위젯을 넣어 둔 경우입니다.

7. 근거의 3분의 2가 요약이었습니다

같은 회차에 별도로 확인한 것입니다. 초안 품질이 떨어진다는 평가를 받아 원인을 찾던 중이었습니다.

수집 기록 117,606건을 집계했습니다.

항목값
회사당 관련 기사평균 10.8건
그중 원문 확보평균 3.6건
요약으로 대체평균 7.2건 (67%)

원문 확보분은 평균 1,502자인데, 나머지는 검색 API가 주는 120자 요약이 그대로 들어가고 있었습니다. 원문을 받을 기사 수에 상한이 걸려 있었고 그 값이 6이었습니다.

상한을 12로 올렸습니다. 다만 더 중요한 것은 두 번째 조치입니다.

끊긴 조각을 버립니다. 검색 API 요약은 120자 지점에서 기계적으로 잘리므로 문장 중간에서 끝납니다.

입력: …매출 1조 2천억원을 기록했다. 양극재 출하가 늘면서 특히 헝가리
출력: …매출 1조 2천억원을 기록했다.

끊긴 조각을 그대로 주면 모델이 그 뒤를 지어냅니다. 종결 부호까지만 남기고 조각은 버리도록 했습니다. 종결 부호가 하나도 없으면 통째로 버립니다.

처음에는 기존의 길이 제한 함수를 재사용했는데, 그 함수는 한도를 넘을 때만 자르므로 120자짜리 짧은 요약은 그냥 통과시켰습니다. 검증 과정에서 조각이 살아남는 것을 확인하고 전용 함수로 분리했습니다. 재사용이 항상 옳지는 않습니다 — 두 함수의 목적이 "길이 줄이기"와 "조각 제거"로 달랐습니다.

정책·규제 근거에도 같은 결함이 남아 있었습니다. 뉴스 근거와 다른 경로라 원문 수집이 아예 없었고, 소비하는 쪽에서 다시 200자로 자르고 있었습니다. 같은 방식으로 통일했습니다.

8. 테스트가 운영 DB에 쓰고 있었습니다

조달 관련 테스트 2건이 실패하고 있었습니다. 제가 건드리지 않은 파일이라 기존 실패로 넘기려다 실패 메시지를 확인했습니다.

삽입 0건 · 생성 False

"실패"가 아니라 "이미 있음"입니다. 테스트는 임시 폴더를 쓰도록 경로 변수를 바꿔 두는데, 연결 함수가 그 변수를 보지 않고 논리 이름으로만 연결하고 있었습니다.

pythonconnect("g2b")   # 설정한 경로가 아니라 기본 경로로 연결됩니다

그 결과 테스트가 운영 DB에 쓰고 있었습니다. 첫 실행에서 시험 자료를 넣고, 두 번째 실행부터는 이미 있는 행 때문에 삽입이 0건이 되어 실패한 것입니다.

운영 DB를 열어 확인했습니다.

(1, 'Example Co.', '123-45-67890', 'Kim', 'https://example.com')

테스트 픽스처가 운영 데이터에 들어 있었습니다. 다행히 그 테이블에는 이 행 하나뿐이어서 실제 자료 손실은 없었습니다.

경로 변수가 기본 위치와 다르면 그 경로로 연결하도록 고치고, 경로 지정 연결 함수를 DB 계층에 추가했습니다. 연결과 SQL은 한 계층에 모은다는 기존 규칙을 지키기 위해서입니다. 남아 있던 시험 행도 삭제했습니다.

"기존 실패"로 분류하고 넘어갔다면 운영 DB 오염을 계속 몰랐을 것입니다. 실패 메시지를 읽는 데 든 시간은 1분이었습니다.

9. 결론 및 시사점

세 건 모두 같은 성격입니다. 동작은 하는데 결과가 조용히 틀린 경우입니다.

  • 사이드바 오염은 예외를 던지지 않습니다. 본문 대신 다른 텍스트가 반환될 뿐입니다.
  • 120자 요약도 정상 값으로 취급됩니다. 근거가 얇아질 뿐 파이프라인은 끝까지 돕니다.
  • 테스트는 실패하고 있었지만 메시지를 읽지 않으면 원인이 드러나지 않습니다.

세 건 다 집계를 내기 전까지는 보이지 않았습니다. 기사 한 건을 눈으로 보면 그럴듯하고, 초안 한 장을 읽으면 문장이 자연스럽습니다. 6건을 한 번에 보고서야 전부 같은 사이드바라는 것이 드러났고, 117,606건을 세고서야 근거의 3분의 2가 요약이라는 것을 알았습니다.

수집 단계별 통과·탈락 건수를 기록해 둔 것이 여기서 값을 했습니다. 초안이 부실할 때 자료가 없어서인지 선별이 과도했는지를 구분하려고 넣은 장치인데, 이번에는 "자료는 받았지만 내용이 요약뿐"이라는 세 번째 경우를 드러냈습니다.

품질 문제를 인상으로 다루면 프롬프트를 고치게 됩니다. 이번 세 건 중 프롬프트로 해결되는 것은 하나도 없었습니다.