{
  "id": "5f2c658a-bd01-4dcc-92f9-08c542309349",
  "url": "https://searchkorea.kr/sources/pbr-text-extraction-20261013",
  "version": 1,
  "publishedAt": "2026-10-12T16:11:31.134Z",
  "modifiedAt": "2026-10-12T16:11:31.134Z",
  "reviewer": "서치코리아 편집부 · Codex 편집 지원",
  "changeNote": "운영자의 2026-10-13 진행 요청에 따라 수정 원고 한 편을 발행한다. HTML 공식 문서와 경쟁사 주장의 범위, 자체 텍스트 추출 실험의 코드·입력·출력을 대조했다. Codex 편집 지원을 사용한 단독 운영자의 자체 검수이며 독립 전문가의 인적 검수나 실제 AI 인용·고객 성과 측정이 아니다.",
  "document": {
    "readerIntents": [],
    "readerTools": [],
    "kind": "source",
    "slug": "pbr-text-extraction-20261013",
    "title": "문장 중간의 p와 br를 비교한 텍스트 추출 기록",
    "summary": "같은 위치에서 br와 p로 나눈 직접 작성 입력은 단순 연결 규칙에서 어절 공백이 빠졌고, 경계 보충 규칙에서는 기준 문장과 일치했다. 네이버·Google·ChatGPT의 처리나 AI 인용 성과를 측정하지 않았다.",
    "body": "같은 위치에서 br와 p로 나눈 직접 작성 입력은 단순 연결 규칙에서 어절 공백이 빠졌고, 경계 보충 규칙에서는 기준 문장과 일치했다. 네이버·Google·ChatGPT의 처리나 AI 인용 성과를 측정하지 않았다.",
    "disclosure": "",
    "authorName": "서치코리아 편집부",
    "authorType": "Organization",
    "verificationSummary": "",
    "publicFiles": [
      {
        "purpose": "source",
        "key": "parsing-code",
        "title": "텍스트 추출 코드",
        "name": "parsing-experiment.py.txt",
        "type": "text/plain",
        "text": "\"\"\"Controlled local extraction demonstration, NOT an AI citation experiment.\"\"\"\nimport datetime\nimport hashlib\nimport json\nimport pathlib\nimport platform\nimport re\nimport sys\nfrom html.parser import HTMLParser\n\nROOT=pathlib.Path(__file__).parent\nSENTENCES=[\n    '이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다.',\n    '신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.'\n]\nEXPECTED=' '.join(SENTENCES)\nFRAGMENTS=['이 안내문은 신청 전에','준비할 자료 세 가지를 설명합니다.','신청자는 연락처, 요청 범위,','희망 일정을 제출합니다.']\nFIXTURES={\n    'complete-paragraphs':'\\n'.join('<p>'+s+'</p>' for s in SENTENCES),\n    'complete-sentences-br':'<p>'+'<br>'.join(SENTENCES)+'</p>',\n    'fragment-br':'<p>'+'<br>'.join(FRAGMENTS)+'</p>',\n    'fragment-paragraphs':''.join('<p>'+s+'</p>' for s in FRAGMENTS),\n}\n\nclass Extractor(HTMLParser):\n    def __init__(self, boundary_aware):\n        super().__init__(convert_charrefs=True)\n        self.boundary_aware=boundary_aware\n        self.parts=[]\n    def handle_data(self,data):\n        self.parts.append(data)\n    def handle_starttag(self,tag,attrs):\n        if self.boundary_aware and tag=='br':\n            self.parts.append('\\n')\n    def handle_endtag(self,tag):\n        if self.boundary_aware and tag=='p':\n            self.parts.append('\\n')\n    def output(self):\n        return ''.join(self.parts)\n\ndef normalize(text):return re.sub(r'\\s+',' ',text).strip()\ndef glyphs(text):return re.sub(r'\\s+','',text)\ndef main():\n    rows=[]\n    for fixture_id,markup in FIXTURES.items():\n        for extractor_id,aware in [('data-concat',False),('boundary-aware',True)]:\n            parser=Extractor(aware);parser.feed(markup);parser.close()\n            output=parser.output();normalized=normalize(output)\n            rows.append({'fixtureId':fixture_id,'extractorId':extractor_id,'htmlSha256':hashlib.sha256(markup.encode()).hexdigest(),'extractedText':output,'normalizedText':normalized,'normalizedEqualsExpected':normalized==EXPECTED,'nonWhitespaceCharactersPreserved':glyphs(output)==glyphs(EXPECTED),'exactSentenceMatches':sum(s in normalized for s in SENTENCES)})\n    result={'status':'observed-local-extraction-only','executedAt':datetime.datetime.now(datetime.timezone.utc).isoformat(),'pythonVersion':sys.version,'platform':platform.platform(),'scriptSha256':hashlib.sha256(pathlib.Path(__file__).read_bytes()).hexdigest(),'fixtureCount':len(FIXTURES),'extractorCount':2,'observationCount':len(rows),'expectedNormalizedText':EXPECTED,'rows':rows,'scope':'No network requests, no AI calls, no actual CMS sample, no crawling/indexing/citation measurement. Exact string matching is not semantic understanding.','checks':{'sameNonWhitespaceCharactersAllConditions':all(r['nonWhitespaceCharactersPreserved'] for r in rows),'boundaryAwareMatchesAllFixtures':all(r['normalizedEqualsExpected'] for r in rows if r['extractorId']=='boundary-aware')}}\n    for name,data in [('parsing-fixtures.json',{'status':'synthetic-neutral-fixtures','sentences':SENTENCES,'fixtures':FIXTURES}),('parsing-observed-results.json',result)]:\n        (ROOT/name).write_text(json.dumps(data,ensure_ascii=False,indent=2)+'\\n',encoding='utf-8')\n    assert all(result['checks'].values())\n    print(json.dumps({'observations':len(rows),'checks':result['checks']},ensure_ascii=True))\nif __name__=='__main__':main()\n",
        "license": "서치코리아 편집부 자체 작성 자료",
        "rights": "서치코리아가 해당 글의 설명을 검증하기 위해 작성한 중립 안내문, Python 코드와 실행 결과다. 공개 재현 자료이며 고객 정보·검색 서비스 응답·인용 실적을 포함하지 않는다."
      },
      {
        "purpose": "source",
        "key": "parsing-fixtures",
        "title": "HTML 입력 원문",
        "name": "parsing-fixtures.json",
        "type": "application/json",
        "text": "{\r\n  \"status\": \"synthetic-neutral-fixtures\",\r\n  \"sentences\": [\r\n    \"이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다.\",\r\n    \"신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.\"\r\n  ],\r\n  \"fixtures\": {\r\n    \"complete-paragraphs\": \"<p>이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다.</p>\\n<p>신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.</p>\",\r\n    \"complete-sentences-br\": \"<p>이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다.<br>신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.</p>\",\r\n    \"fragment-br\": \"<p>이 안내문은 신청 전에<br>준비할 자료 세 가지를 설명합니다.<br>신청자는 연락처, 요청 범위,<br>희망 일정을 제출합니다.</p>\",\r\n    \"fragment-paragraphs\": \"<p>이 안내문은 신청 전에</p><p>준비할 자료 세 가지를 설명합니다.</p><p>신청자는 연락처, 요청 범위,</p><p>희망 일정을 제출합니다.</p>\"\r\n  }\r\n}\r\n",
        "license": "서치코리아 편집부 자체 작성 자료",
        "rights": "서치코리아가 해당 글의 설명을 검증하기 위해 작성한 중립 안내문, Python 코드와 실행 결과다. 공개 재현 자료이며 고객 정보·검색 서비스 응답·인용 실적을 포함하지 않는다."
      },
      {
        "purpose": "source",
        "key": "parsing-results",
        "title": "전체 실행 결과",
        "name": "parsing-observed-results.json",
        "type": "application/json",
        "text": "{\r\n  \"status\": \"observed-local-extraction-only\",\r\n  \"executedAt\": \"2026-10-12T14:04:34.802005+00:00\",\r\n  \"pythonVersion\": \"3.12.10 (tags/v3.12.10:0cc8128, Apr  8 2025, 12:21:36) [MSC v.1943 64 bit (AMD64)]\",\r\n  \"platform\": \"Windows-10-10.0.19045-SP0\",\r\n  \"scriptSha256\": \"212d54f81d7c2e8ae8cda56a83ea0fbf6eb090b42559a5c270f8d4f97ec84405\",\r\n  \"fixtureCount\": 4,\r\n  \"extractorCount\": 2,\r\n  \"observationCount\": 8,\r\n  \"expectedNormalizedText\": \"이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다. 신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.\",\r\n  \"rows\": [\r\n    {\r\n      \"fixtureId\": \"complete-paragraphs\",\r\n      \"extractorId\": \"data-concat\",\r\n      \"htmlSha256\": \"1de02dfd507bdf69ec9938af4abf69be43106f6cad2d0ccc22c47b051de35632\",\r\n      \"extractedText\": \"이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다.\\n신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.\",\r\n      \"normalizedText\": \"이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다. 신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.\",\r\n      \"normalizedEqualsExpected\": true,\r\n      \"nonWhitespaceCharactersPreserved\": true,\r\n      \"exactSentenceMatches\": 2\r\n    },\r\n    {\r\n      \"fixtureId\": \"complete-paragraphs\",\r\n      \"extractorId\": \"boundary-aware\",\r\n      \"htmlSha256\": \"1de02dfd507bdf69ec9938af4abf69be43106f6cad2d0ccc22c47b051de35632\",\r\n      \"extractedText\": \"이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다.\\n\\n신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.\\n\",\r\n      \"normalizedText\": \"이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다. 신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.\",\r\n      \"normalizedEqualsExpected\": true,\r\n      \"nonWhitespaceCharactersPreserved\": true,\r\n      \"exactSentenceMatches\": 2\r\n    },\r\n    {\r\n      \"fixtureId\": \"complete-sentences-br\",\r\n      \"extractorId\": \"data-concat\",\r\n      \"htmlSha256\": \"3ec5bc821759fc6d5a1dae9def36177e8ee30d551dfa27e2967e12326e23e54b\",\r\n      \"extractedText\": \"이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다.신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.\",\r\n      \"normalizedText\": \"이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다.신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.\",\r\n      \"normalizedEqualsExpected\": false,\r\n      \"nonWhitespaceCharactersPreserved\": true,\r\n      \"exactSentenceMatches\": 2\r\n    },\r\n    {\r\n      \"fixtureId\": \"complete-sentences-br\",\r\n      \"extractorId\": \"boundary-aware\",\r\n      \"htmlSha256\": \"3ec5bc821759fc6d5a1dae9def36177e8ee30d551dfa27e2967e12326e23e54b\",\r\n      \"extractedText\": \"이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다.\\n신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.\\n\",\r\n      \"normalizedText\": \"이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다. 신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.\",\r\n      \"normalizedEqualsExpected\": true,\r\n      \"nonWhitespaceCharactersPreserved\": true,\r\n      \"exactSentenceMatches\": 2\r\n    },\r\n    {\r\n      \"fixtureId\": \"fragment-br\",\r\n      \"extractorId\": \"data-concat\",\r\n      \"htmlSha256\": \"16ba748c960f72601329f892d87ef81f11d0918379605906ae9e294448a831b4\",\r\n      \"extractedText\": \"이 안내문은 신청 전에준비할 자료 세 가지를 설명합니다.신청자는 연락처, 요청 범위,희망 일정을 제출합니다.\",\r\n      \"normalizedText\": \"이 안내문은 신청 전에준비할 자료 세 가지를 설명합니다.신청자는 연락처, 요청 범위,희망 일정을 제출합니다.\",\r\n      \"normalizedEqualsExpected\": false,\r\n      \"nonWhitespaceCharactersPreserved\": true,\r\n      \"exactSentenceMatches\": 0\r\n    },\r\n    {\r\n      \"fixtureId\": \"fragment-br\",\r\n      \"extractorId\": \"boundary-aware\",\r\n      \"htmlSha256\": \"16ba748c960f72601329f892d87ef81f11d0918379605906ae9e294448a831b4\",\r\n      \"extractedText\": \"이 안내문은 신청 전에\\n준비할 자료 세 가지를 설명합니다.\\n신청자는 연락처, 요청 범위,\\n희망 일정을 제출합니다.\\n\",\r\n      \"normalizedText\": \"이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다. 신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.\",\r\n      \"normalizedEqualsExpected\": true,\r\n      \"nonWhitespaceCharactersPreserved\": true,\r\n      \"exactSentenceMatches\": 2\r\n    },\r\n    {\r\n      \"fixtureId\": \"fragment-paragraphs\",\r\n      \"extractorId\": \"data-concat\",\r\n      \"htmlSha256\": \"f790630b56e27cdc3b37169015ca6c0b6b4506688249cc4aa23126e6129c7d48\",\r\n      \"extractedText\": \"이 안내문은 신청 전에준비할 자료 세 가지를 설명합니다.신청자는 연락처, 요청 범위,희망 일정을 제출합니다.\",\r\n      \"normalizedText\": \"이 안내문은 신청 전에준비할 자료 세 가지를 설명합니다.신청자는 연락처, 요청 범위,희망 일정을 제출합니다.\",\r\n      \"normalizedEqualsExpected\": false,\r\n      \"nonWhitespaceCharactersPreserved\": true,\r\n      \"exactSentenceMatches\": 0\r\n    },\r\n    {\r\n      \"fixtureId\": \"fragment-paragraphs\",\r\n      \"extractorId\": \"boundary-aware\",\r\n      \"htmlSha256\": \"f790630b56e27cdc3b37169015ca6c0b6b4506688249cc4aa23126e6129c7d48\",\r\n      \"extractedText\": \"이 안내문은 신청 전에\\n준비할 자료 세 가지를 설명합니다.\\n신청자는 연락처, 요청 범위,\\n희망 일정을 제출합니다.\\n\",\r\n      \"normalizedText\": \"이 안내문은 신청 전에 준비할 자료 세 가지를 설명합니다. 신청자는 연락처, 요청 범위, 희망 일정을 제출합니다.\",\r\n      \"normalizedEqualsExpected\": true,\r\n      \"nonWhitespaceCharactersPreserved\": true,\r\n      \"exactSentenceMatches\": 2\r\n    }\r\n  ],\r\n  \"scope\": \"No network requests, no AI calls, no actual CMS sample, no crawling/indexing/citation measurement. Exact string matching is not semantic understanding.\",\r\n  \"checks\": {\r\n    \"sameNonWhitespaceCharactersAllConditions\": true,\r\n    \"boundaryAwareMatchesAllFixtures\": true\r\n  }\r\n}\r\n",
        "license": "서치코리아 편집부 자체 작성 자료",
        "rights": "서치코리아가 해당 글의 설명을 검증하기 위해 작성한 중립 안내문, Python 코드와 실행 결과다. 공개 재현 자료이며 고객 정보·검색 서비스 응답·인용 실적을 포함하지 않는다."
      }
    ],
    "searchAliases": [],
    "provenance": {
      "relation": "original",
      "originUrl": "https://searchkorea.kr/sources/pbr-text-extraction-20261013",
      "originPublisher": "서치코리아 편집부",
      "upstreamSourceIds": [],
      "independence": "interested",
      "rationale": "편집부 자체 실험이며 외부 독립 검증이나 실제 검색 시스템 실험으로 표시하지 않는다."
    },
    "url": "https://searchkorea.kr/sources/pbr-text-extraction-20261013",
    "publisher": "서치코리아 편집부",
    "sourceType": "participant",
    "checkedAt": "2026-10-12T16:07:49.759Z",
    "expiresAt": "2027-01-13T00:00:00.000Z",
    "validityMode": "historical",
    "historicalScope": "2026년 10월 12일 직접 만든 중립 안내문과 두 가지 추출 규칙의 고정 실행 결과. 현재 검색엔진이나 네이버 편집기의 처리 방식을 대표하지 않는다.",
    "reviewDueAt": "2026-11-13T00:00:00.000Z",
    "locator": "parsing-observed-results.json의 fragment-br 및 fragment-paragraphs; parsing-experiment.py의 Extractor와 normalize",
    "originalPublishedAt": "",
    "contentHash": "ca7d86c2bb4869c71a7c31ae65de29841af5602637225d214cfe356985146053",
    "capture": {
      "versionId": "checked-2026-10-13",
      "capturedAt": "2026-10-12T16:07:49.759Z",
      "finalUrl": "https://searchkorea.kr/sources/pbr-text-extraction-20261013",
      "method": "file",
      "hashScope": "file",
      "retentionReason": "원본 코드·입력·출력 전체를 공개 첨부로 보존한다."
    },
    "rightsStatement": "편집부가 직접 작성한 코드·입력·결과의 공개 재현 자료다. 합성된 중립 문구이며 실제 고객이나 검색 관측 자료가 아니다."
  },
  "relationships": {
    "about": []
  }
}