파이썬으로 HTML을 Markdown으로 변환하는 방법: 포괄적인 가이드

HTML을 깔끔하고 LLM에 적합한 Markdown으로 변환하는 Python

Page content

HTML을 Markdown으로 변환은 웹 콘텐츠를 대규모 언어 모델(LLM), 문서 시스템, 또는 Hugo와 같은 정적 사이트 생성기로 준비하는 현대 개발 워크플로우에서 기본적인 작업입니다.

HTML은 웹 브라우저를 위한 풍부한 스타일링과 구조를 제공하지만, Markdown은 텍스트 처리, 버전 관리, AI 소비에 이상적인 깔끔하고 읽기 쉬운 형식을 제공합니다. Markdown 구문에 익숙하지 않다면, Markdown Cheatsheet를 참고하여 종합적인 참조 자료를 확인해 보세요.

인포그래픽: HTML에서 Markdown으로 페이지 변환

이 종합적인 검토에서는 HTML을 Markdown으로 변환하는 데 사용되는 6가지 Python 패키지를 탐구하며, 실용적인 코드 예제, 성능 벤치마크, 실제 사례를 제공합니다. LLM 훈련 파이프라인을 구축하거나 블로그를 Hugo로 이전하거나 문서를 스크래핑하는 경우에도, 워크플로우에 적합한 도구를 찾을 수 있습니다.

대안적 접근: 복잡한 레이아웃에 대한 AI 기반 변환을 원하는 경우, LLM과 Ollama를 사용하여 HTML을 Markdown으로 변환을 고려해 보세요. 이 방법은 의미 있는 이해를 바탕으로 한 더 똑똑한 콘텐츠 추출을 제공합니다.

학습할 내용:

  • 6가지 라이브러리의 상세한 비교 및 각각의 장단점
  • 실제 웹 HTML 샘플을 사용한 성능 벤치마크
  • 일반적인 사용 사례에 대한 생산성 높은 코드 예제
  • LLM 전처리 워크플로우에 대한 최선의 실천 방법
  • 요구사항에 따라 특정 추천 사항

LLM 전처리를 위한 Markdown의 이유

도구에 들어가기 전에, Markdown이 LLM 워크플로우에서 특히 유용한 이유를 이해해 보겠습니다:

  1. 토큰 효율성: 동일한 콘텐츠에 대해 HTML보다 Markdown이 훨씬 적은 토큰을 사용합니다.
  2. 의미적 명확성: Markdown은 복잡한 태그 없이 문서 구조를 보존합니다.
  3. 가독성: 인간과 LLM 모두 Markdown 구문을 쉽게 파싱할 수 있습니다.
  4. 일관성: 표준화된 형식은 모델 입력의 모호성을 줄입니다.
  5. 저장: 훈련 데이터 및 컨텍스트 창의 파일 크기가 작아집니다.

Markdown의 유연성은 HTML 변환을 넘어선 더 많은 용도로 확장됩니다. 문서 워크플로우에서 Word 문서를 Markdown으로 변환를 수행하거나, Obsidian을 개인 지식 관리에 사용와 같은 지식 관리 시스템에서 사용할 수 있습니다.

TL;DR - 빠른 비교 매트릭스

시간이 부족한 경우, 6가지 라이브러리에 대한 종합적인 비교를 한눈에 볼 수 있는 표를 제공합니다. 이 표는 특정 요구사항에 맞는 도구를 빠르게 식별하는 데 도움이 됩니다:

기능 html2text markdownify html-to-markdown trafilatura domscribe html2md
HTML5 지원 부분적 부분적 전체 전체 전체 전체
타입 힌트 없음 없음 있음 부분적 없음 부분적
커스텀 핸들러 제한적 우수 좋음 제한적 좋음 제한적
테이블 지원 기본 기본 고급 좋음 좋음 좋음
비동기 지원 없음 없음 없음 없음 없음 있음
콘텐츠 추출 없음 없음 없음 우수 없음 좋음
메타데이터 추출 없음 없음 있음 우수 없음 있음
CLI 도구 없음 없음 있음 있음 없음 있음
속도 중간 느림 빠름 매우 빠름 중간 매우 빠름
활발한 개발 없음 있음 있음 있음 제한적 있음
Python 버전 3.6+ 3.7+ 3.9+ 3.6+ 3.8+ 3.10+
의존성 없음 BS4 lxml lxml BS4 aiohttp

빠른 선택 가이드:

  • 속도가 필요하다면? → trafilatura 또는 html2md
  • 커스터마이징이 필요하다면? → markdownify
  • 타입 안전성이 필요하다면? → html-to-markdown
  • 간단함이 필요하다면? → html2text
  • 콘텐츠 추출이 필요하다면? → trafilatura

경쟁자들: 6가지 Python 패키지 비교

각 라이브러리에 대한 실용적인 코드 예제, 구성 옵션, 실제 사례를 자세히 살펴보겠습니다. 각 섹션에는 설치 지침, 사용 패턴, 강점과 한계에 대한 솔직한 평가가 포함됩니다.

1. html2text - 전통적인 선택

Aaron Swartz가 처음 개발한 html2text는 10년 이상 Python 생태계에서 사용되어 왔습니다. 이 라이브러리는 깔끔하고 읽기 쉬운 Markdown 출력을 생성하는 데 초점을 맞추고 있습니다.

설치:

pip install html2text

기본 사용법:

import html2text

# 컨버터 인스턴스 생성
h = html2text.HTML2Text()

# 옵션 설정
h.ignore_links = False
h.ignore_images = False
h.ignore_emphasis = False
h.body_width = 0  # 줄 바꿈 없음

html_content = """
<h1>Welcome to Web Scraping</h1>
<p>This is a <strong>comprehensive guide</strong> to extracting content.</p>
<ul>
    <li>Easy to use</li>
    <li>Battle-tested</li>
    <li>Widely adopted</li>
</ul>
<a href="https://example.com">Learn more</a>
"""

markdown = h.handle(html_content)
print(markdown)

출력:

# Welcome to Web Scraping

This is a **comprehensive guide** to extracting content.

  * Easy to use
  * Battle-tested
  * Widely adopted

[Learn more](https://example.com)

고급 구성:

import html2text

h = html2text.HTML2Text()

# 특정 요소 건너뛰기
h.ignore_links = True
h.ignore_images = True

# 포맷 조절
h.body_width = 80  # 80자로 줄 바꿈
h.unicode_snob = True  # 유니코드 문자 사용
h.emphasis_mark = '*'  # 강조에 * 사용
h.strong_mark = '**'

# 테이블 처리
h.ignore_tables = False

# 미리 포맷된 텍스트 보호
h.protect_links = True

장점:

  • 오랜 기간 동안 안정적인 성능 (15년 이상 개발)
  • 광범위한 구성 옵션
  • 경계 사례 처리 잘함
  • 외부 의존성 없음

단점:

  • 제한적인 HTML5 지원
  • 일관된 간격 생성이 어려움
  • 활발한 유지보수가 없음 (2020년 이후 주요 업데이트 없음)
  • 단일 스레드 처리만 가능

최적의 사용 사례: 간단한 HTML 문서, 오래된 시스템, 안정성이 가장 중요한 경우


2. markdownify - 유연한 선택

markdownify는 BeautifulSoup4를 사용하여 유연한 HTML 파싱과 커스텀 태그 처리를 제공합니다.

설치:

pip install markdownify

기본 사용법:

from markdownify import markdownify as md

html = """
<article>
    <h2>Modern Web Development</h2>
    <p>Building with <code>Python</code> and <em>modern frameworks</em>.</p>
    <blockquote>
        <p>Simplicity is the ultimate sophistication.</p>
    </blockquote>
</article>
"""

markdown = md(html)
print(markdown)

출력:


## Modern Web Development

Building with `Python` and *modern frameworks*.

> Simplicity is the ultimate sophistication.

커스텀 핸들러 사용:

from markdownify import MarkdownConverter

class CustomConverter(MarkdownConverter):
    """
    특정 태그 처리를 위한 커스텀 컨버터 생성
    """
    def convert_img(self, el, text, convert_as_inline):
        """커스텀 이미지 핸들러 (대체 텍스트 포함)"""
        alt = el.get('alt', '')
        src = el.get('src', '')
        title = el.get('title', '')

        if title:
            return f'![{alt}]({src} "{title}")'
        return f'![{alt}]({src})'

    def convert_pre(self, el, text, convert_as_inline):
        """강화된 코드 블록 처리 및 언어 감지"""
        code = el.find('code')
        if code:
            # 클래스 속성에서 언어 추출 (예: 'language-python')
            classes = code.get('class', [''])
            language = classes[0].replace('language-', '') if classes else ''
            return f'\n```{language}\n{code.get_text()}\n```\n'
        return f'\n```\n{text}\n```\n'

# 커스텀 컨버터 사용
html = '<pre><code class="language-python">def hello():\n    print("world")</code></pre>'
markdown = CustomConverter().convert(html)
print(markdown)

Markdown 코드 블록과 구문 강조에 대한 자세한 내용은 Markdown 코드 블록 사용을 참조하세요.

선택적 태그 변환:

from markdownify import markdownify as md

# 특정 태그 전체 제거
markdown = md(html, strip=['script', 'style', 'nav'])

# 특정 태그만 변환
markdown = md(
    html,
    heading_style="ATX",  # 헤딩에 # 사용
    bullets="-",  # 항목에 - 사용
    strong_em_symbol="*",  # 강조에 * 사용
)

장점:

  • BeautifulSoup4 기반 (강력한 HTML 파싱)
  • 서브클래싱을 통한 고도로 커스터마이징 가능
  • 활발한 유지보수
  • 좋은 문서

단점:

  • BeautifulSoup4 의존성 필요
  • 대규모 문서 처리 시 느림
  • 기본 테이블 지원 제한

최적의 사용 사례: 커스텀 변환 로직, BeautifulSoup4를 이미 사용하는 프로젝트


3. html-to-markdown - 현대적인 강력한 라이브러리

html-to-markdown은 완전히 타입 안전한, 현대적인 라이브러리로, 포괄적인 HTML5 지원과 광범위한 구성 옵션을 제공합니다.

설치:

pip install html-to-markdown

기본 사용법:

from html_to_markdown import convert

html = """
<article>
    <h1>Technical Documentation</h1>
    <table>
        <thead>
            <tr>
                <th>Feature</th>
                <th>Support</th>
            </tr>
        </thead>
        <tbody>
            <tr>
                <td>HTML5</td>
                <td>✓</td>
            </tr>
            <tr>
                <td>Tables</td>
                <td>✓</td>
            </tr>
        </tbody>
    </table>
</article>
"""

markdown = convert(html)
print(markdown)

고급 구성:

from html_to_markdown import convert, Options

# 커스텀 옵션 생성
options = Options(
    heading_style="ATX",
    bullet_style="-",
    code_language_default="python",
    strip_tags=["script", "style"],
    escape_special_chars=True,
    table_style="pipe",  # 테이블에 | 사용
    preserve_whitespace=False,
    extract_metadata=True,  # 메타 태그 추출
)

markdown = convert(html, options=options)

명령줄 인터페이스:

# 단일 파일 변환
html-to-markdown input.html -o output.md

# 옵션과 함께 변환
html-to-markdown input.html \
    --heading-style atx \
    --strip-tags script,style \
    --extract-metadata

# 배치 변환
find ./html_files -name "*.html" -exec html-to-markdown {} -o ./markdown_files/{}.md \;

장점:

  • HTML5 지원 완전 (세마틱 요소 포함)
  • 포괄적인 타입 힌트로 타입 안전
  • 테이블 처리 강화 (셀 병합, 정렬)
  • 메타데이터 추출 기능
  • 활발한 개발 및 현대적인 코드베이스

단점:

  • Python 3.9+ 필요
  • 더 큰 의존성
  • 학습 곡선이 가파름

최적의 사용 사례: 복잡한 HTML5 문서, 타입 안전 프로젝트, 프로덕션 시스템


4. trafilatura - 콘텐츠 추출 전문가

trafilatura는 단순한 HTML-to-Markdown 변환기 이상으로, 웹 스크래핑 및 기사 추출을 위해 설계된 지능형 콘텐츠 추출 라이브러리입니다.

설치:

pip install trafilatura

기본 사용법:

import trafilatura

# URL에서 다운로드 및 추출
url = "https://example.com/article"
downloaded = trafilatura.fetch_url(url)
markdown = trafilatura.extract(downloaded, output_format='markdown')
print(markdown)

참고: Trafilatura는 내장 URL 다운로드 기능을 포함하지만, 더 복잡한 HTTP 작업을 수행할 때 cURL Cheatsheet이 API 또는 인증된 엔드포인트 작업 시 도움이 될 수 있습니다.

고급 콘텐츠 추출:

import trafilatura
from trafilatura.settings import use_config

# 커스텀 구성 생성
config = use_config()
config.set("DEFAULT", "EXTRACTION_TIMEOUT", "30")

html = """
<html>
<head><title>Article Title</title></head>
<body>
    <nav>Navigation menu</nav>
    <article>
        <h1>Main Article</h1>
        <p>Important content here.</p>
    </article>
    <aside>Advertisement</aside>
    <footer>Footer content</footer>
</body>
</html>
"""

# 주요 콘텐츠만 추출
markdown = trafilatura.extract(
    html,
    output_format='markdown',
    include_comments=False,
    include_tables=True,
    include_images=True,
    include_links=True,
    config=config
)

# 메타데이터와 함께 추출
result = trafilatura.extract(
    html,
    output_format='markdown',
    with_metadata=True
)

if result:
    print(f"제목: {result.get('title', 'N/A')}")
    print(f"작성자: {result.get('author', 'N/A')}")
    print(f"날짜: {result.get('date', 'N/A')}")
    print(f"\n콘텐츠:\n{result.get('text', '')}")

배치 처리:

import trafilatura
from concurrent.futures import ThreadPoolExecutor
from pathlib import Path

def process_url(url):
    """URL에서 Markdown 추출"""
    downloaded = trafilatura.fetch_url(url)
    if downloaded:
        return trafilatura.extract(
            downloaded,
            output_format='markdown',
            include_links=True,
            include_images=True
        )
    return None

# 여러 URL 병렬 처리
urls = [
    "https://example.com/article1",
    "https://example.com/article2",
    "https://example.com/article3",
]

with ThreadPoolExecutor(max_workers=5) as executor:
    results = list(executor.map(process_url, urls))

for i, markdown in enumerate(results):
    if markdown:
        Path(f"article_{i}.md").write_text(markdown, encoding='utf-8')

장점:

  • 지능형 콘텐츠 추출 (불필요한 요소 제거)
  • 내장 URL 다운로드 및 강력한 오류 처리
  • 메타데이터 추출 (제목, 작성자, 날짜)
  • 언어 감지
  • 뉴스 기사 및 블로그 게시물에 최적화
  • C 기반 파싱으로 빠름

단점:

  • 일반 HTML에서 너무 많은 콘텐츠를 제거할 수 있음
  • 기사 추출에 초점 (일반 목적 아님)
  • 경계 사례에 대한 구성 복잡성

최적의 사용 사례: 웹 스크래핑, 기사 추출, LLM 훈련 데이터 준비


5. domscribe - 의미 보존 전문가

domscribe는 HTML을 Markdown으로 변환하면서 의미를 보존하는 데 초점을 맞추고 있습니다.

설치:

pip install domscribe

기본 사용법:

from domscribe import html_to_markdown

html = """
<article>
    <header>
        <h1>Understanding Semantic HTML</h1>
        <time datetime="2024-10-24">October 24, 2024</time>
    </header>
    <section>
        <h2>Introduction</h2>
        <p>Semantic HTML provides <mark>meaning</mark> to content.</p>
    </section>
    <aside>
        <h3>Related Topics</h3>
        <ul>
            <li>Accessibility</li>
            <li>SEO</li>
        </ul>
    </aside>
</article>
"""

markdown = html_to_markdown(html)
print(markdown)

커스텀 옵션:

from domscribe import html_to_markdown, MarkdownOptions

options = MarkdownOptions(
    preserve_semantic_structure=True,
    include_aria_labels=True,
    strip_empty_elements=True
)

markdown = html_to_markdown(html, options=options)

장점:

  • HTML5 의미 구조 보존
  • 현대 웹 컴포넌트 잘 처리
  • 깔끔한 API 설계

단점:

  • 초기 개발 중 (API 변경 가능성 있음)
  • 성숙한 대안에 비해 문서가 제한적
  • 커뮤니티 및 예제가 적음

최적의 사용 사례: HTML5 문서, 접근성 중심 프로젝트, HTML5 의미 구조 보존이 필수적인 경우

참고: domscribe는 다른 도구보다 테스트가 덜 되었지만, 의미 보존이라는 특정 분야에서 중요한 역할을 합니다.


6. html2md - 비동기 강력한 라이브러리

html2md는 고성능 배치 변환을 위해 비동기 처리를 설계했습니다.

설치:

pip install html2md

명령줄 사용법:

# 전체 디렉토리 변환
m1f-html2md convert ./website -o ./docs

# 커스텀 설정으로 변환
m1f-html2md convert ./website -o ./docs \
    --remove-tags nav,footer \
    --heading-offset 1 \
    --detect-language

# 단일 파일 변환
m1f-html2md convert index.html -o readme.md

프로그래밍 사용법:

import asyncio
from html2md import convert_html

async def convert_files():
    """비동기 배치 변환"""
    html_files = [
        'page1.html',
        'page2.html',
        'page3.html'
    ]

    tasks = [convert_html(file) for file in html_files]
    results = await asyncio.gather(*tasks)
    return results

# 변환 실행
results = asyncio.run(convert_files())

장점:

  • 비동기 처리로 고성능
  • 지능형 콘텐츠 선택자 감지
  • YAML frontmatter 생성 (Hugo에 이상적)
  • 코드 언어 감지
  • 병렬 처리 지원

단점:

  • Python 3.10+ 필요
  • CLI 중심 (유연한 API 부족)
  • 문서가 더 체계적이어야 함

최적의 사용 사례: 대규모 이전, 배치 변환, Hugo/Jekyll 이전


성능 벤치마킹

성능은 특히 수천 개의 문서를 LLM 훈련 또는 대규모 이전에 사용할 때 중요합니다. 라이브러리 간의 상대적인 속도 차이를 이해하면 워크플로우에 대한 잘 고려된 결정을 내릴 수 있습니다.

비교 성능 분석:

일반적인 사용 패턴에 기반하여, 이 라이브러리들이 세 가지 실제 시나리오에서 어떻게 비교되는지 확인해 보겠습니다:

  1. 간단한 HTML: 텍스트, 헤딩, 링크가 있는 기본 블로그 게시물 (5KB)
  2. 복잡한 HTML: 중첩된 테이블과 코드 블록이 있는 기술 문서 (50KB)
  3. 실제 웹사이트: 네비게이션, 푸터, 사이드바, 광고가 포함된 전체 웹페이지 (200KB)

다음은 직접 테스트할 수 있는 예시 벤치마크 코드입니다:

import time
import html2text
from markdownify import markdownify
from html_to_markdown import convert
import trafilatura

def benchmark(html_content, iterations=100):
    """변환 속도 벤치마킹"""

    # html2text
    start = time.time()
    h = html2text.HTML2Text()
    for _ in range(iterations):
        _ = h.handle(html_content)
    html2text_time = time.time() - start

    # markdownify
    start = time.time()
    for _ in range(iterations):
        _ = markdownify(html_content)
    markdownify_time = time.time() - start

    # html-to-markdown
    start = time.time()
    for _ in range(iterations):
        _ = convert(html_content)
    html_to_markdown_time = time.time() - start

    # trafilatura
    start = time.time()
    for _ in range(iterations):
        _ = trafilatura.extract(html_content, output_format='markdown')
    trafilatura_time = time.time() - start

    return {
        'html2text': html2text_time,
        'markdownify': markdownify_time,
        'html-to-markdown': html_to_markdown_time,
        'trafilatura': trafilatura_time
    }

일반적인 성능 특성 (대략적인 상대 속도):

패키지 간단한 (5KB) 복잡한 (50KB) 실제 사이트 (200KB)
html2text 중간 느림 느림
markdownify 느림 느림 가장 느림
html-to-markdown 빠름 빠름 빠름
trafilatura 빠름 매우 빠름 매우 빠름
html2md (비동기) 매우 빠름 매우 빠름 가장 빠름

주요 관찰:

  • html2mdtrafilatura는 복잡한 문서에 가장 빠르며, 배치 처리에 이상적입니다.
  • html-to-markdown은 생산성 사용에 있어 속도와 기능의 균형이 가장 좋습니다.
  • markdownify는 느리지만 가장 유연하며, 커스텀 핸들러가 필요한 경우 이 trade-off가 가치가 있습니다.
  • html2text는 나이가 많아 속도가 느리지만, 간단한 사용 사례에 안정적입니다.

참고: 수백 또는 수천 개의 파일을 처리할 때만 성능 차이가 크게 나타납니다. 간헐적인 변환은 어떤 라이브러리도 충분히 작동합니다. 기능과 커스터마이징 옵션에 집중하세요.

실제 사례

이론은 도움이 되지만, 실제 예시는 이러한 도구가 실제 환경에서 어떻게 작동하는지를 보여줍니다. 다음은 프로덕션 준비가 된 완전한 코드가 포함된 네 가지 일반적인 시나리오입니다. 자신의 프로젝트에 적응시킬 수 있습니다.

사례 1: LLM 훈련 데이터 준비

요구사항: 수천 개의 문서 페이지에서 깨끗한 텍스트 추출

추천: trafilatura + 병렬 처리

import trafilatura
from pathlib import Path
from concurrent.futures import ProcessPoolExecutor

def process_html_file(html_path):
    """HTML 파일을 마크다운으로 변환"""
    html = Path(html_path).read_text(encoding='utf-8')
    markdown = trafilatura.extract(
        html,
        output_format='markdown',
        include_links=False,  # 더 깨끗한 훈련 데이터를 위해 제거
        include_images=False,
        include_comments=False
    )

    if markdown:
        output_path = html_path.replace('.html', '.md')
        Path(output_path).write_text(markdown, encoding='utf-8')
        return len(markdown)
    return 0

# 10,000개의 파일을 병렬 처리
html_files = list(Path('./docs').rglob('*.html'))

with ProcessPoolExecutor(max_workers=8) as executor:
    token_counts = list(executor.map(process_html_file, html_files))

print(f"{len(html_files)}개의 파일 처리 완료")
print(f"총 문자 수: {sum(token_counts):,}")

사례 2: Hugo 블로그 이전

요구사항: WordPress 블로그를 Hugo로 이전하며 frontmatter 사용

추천: html2md CLI

Hugo는 인기 있는 정적 사이트 생성기로, 콘텐츠에 마크다운을 사용합니다. 더 많은 Hugo 관련 팁은 Hugo Cheat Sheet을 참고하고, Hugo 웹사이트에 구조화된 데이터 마크업 추가를 통해 SEO를 개선하는 방법을 배우세요.

# frontmatter와 함께 모든 게시물 변환
m1f-html2md convert ./wordpress-export \
    -o ./hugo/content/posts \
    --generate-frontmatter \
    --heading-offset 0 \
    --remove-tags script,style,nav,footer

또는 프로그래밍적으로:

from html_to_markdown import convert, Options
from pathlib import Path
import yaml

def migrate_post(html_file):
    """WordPress HTML을 Hugo 마크다운으로 변환"""
    html = Path(html_file).read_text()

    # HTML에서 제목과 날짜 추출
    from bs4 import BeautifulSoup
    soup = BeautifulSoup(html, 'html.parser')
    title = soup.find('h1').get_text() if soup.find('h1') else 'Untitled'

    # 마크다운으로 변환
    options = Options(strip_tags=['script', 'style', 'nav', 'footer'])
    markdown = convert(html, options=options)

    # Hugo frontmatter 추가
    frontmatter = {
        'title': title,
        'date': '2024-10-24',
        'draft': False,
        'tags': []
    }

    output = f"---\n{yaml.dump(frontmatter)}---\n\n{markdown}"

    # 저장
    output_file = html_file.replace('.html', '.md')
    Path(output_file).write_text(output, encoding='utf-8')

# 모든 게시물 처리
for html_file in Path('./wordpress-export').glob('*.html'):
    migrate_post(html_file)

사례 3: 커스텀 포맷팅을 위한 문서 스크래퍼

요구사항: 커스텀 코드 블록 처리를 위한 기술 문서 스크래핑

추천: markdownify와 커스텀 컨버터 사용

이 방법은 위키 시스템에서 문서를 이전하는 데 특히 유용합니다. 문서를 관리하고 있다면, DokuWiki - 자가호스팅 위키 및 대안을 참고하여 자가호스팅 문서 솔루션을 살펴보세요.

from markdownify import MarkdownConverter
import requests

class DocsConverter(MarkdownConverter):
    """기술 문서를 위한 커스텀 컨버터"""

    def convert_pre(self, el, text, convert_as_inline):
        """구문 강조가 포함된 코드 블록"""
        code = el.find('code')
        if code:
            # 클래스에서 언어 추출
            classes = code.get('class', [])
            language = next(
                (c.replace('language-', '') for c in classes if c.startswith('language-')),
                'text'
            )
            return f'\n```{language}\n{code.get_text()}\n```\n'
        return super().convert_pre(el, text, convert_as_inline)

    def convert_div(self, el, text, convert_as_inline):
        """특수 문서 블록 처리"""
        classes = el.get('class', [])

        # 경고 블록
        if 'warning' in classes:
            return f'\n> ⚠️ **경고**: {text}\n'

        # 정보 블록
        if 'info' in classes or 'note' in classes:
            return f'\n> 💡 **참고**: {text}\n'

        return text

def scrape_docs(url):
    """문서 페이지 스크래핑 및 변환"""
    response = requests.get(url)
    markdown = DocsConverter().convert(response.text)
    return markdown

# 사용 예
docs_url = "https://docs.example.com/api-reference"
markdown = scrape_docs(docs_url)
Path('api-reference.md').write_text(markdown)

사례 4: 뉴스레터를 마크다운 아카이브로 변환

요구사항: HTML 이메일 뉴스레터를 읽기 쉬운 마크다운으로 변환

추천: html2text와 특정 설정 사용

import html2text
import email
from pathlib import Path

def convert_newsletter(email_file):
    """HTML 이메일을 마크다운으로 변환"""
    # 이메일 파싱
    with open(email_file, 'r') as f:
        msg = email.message_from_file(f)

    # HTML 부분 추출
    html_content = None
    for part in msg.walk():
        if part.get_content_type() == 'text/html':
            html_content = part.get_payload(decode=True).decode('utf-8')
            break

    if not html_content:
        return None

    # 컨버터 설정
    h = html2text.HTML2Text()
    h.ignore_images = False
    h.images_to_alt = True
    h.body_width = 0
    h.protect_links = True
    h.unicode_snob = True

    # 변환
    markdown = h.handle(html_content)

    # 메타데이터 추가
    subject = msg.get('Subject', 'No Subject')
    date = msg.get('Date', '')

    output = f"# {subject}\n\n*날짜: {date}*\n\n---\n\n{markdown}"

    return output

# 뉴스레터 아카이브 처리
for email_file in Path('./newsletters').glob('*.eml'):
    markdown = convert_newsletter(email_file)
    if markdown:
        output_file = email_file.with_suffix('.md')
        output_file.write_text(markdown, encoding='utf-8')

시나리오별 추천

아직 어떤 라이브러리를 선택해야 할지 확신이 없나요? 이는 특정 사용 사례에 기반한 확정적인 가이드입니다. 이러한 추천은 각 라이브러리에 대한 실제 환경에서의 경험에서 비롯되었습니다.

웹 스크래핑 및 LLM 전처리

우승자: trafilatura

Trafilatura는 불필요한 요소를 제거하면서 깨끗한 콘텐츠를 추출하는 데 탁월합니다. 다음에 적합합니다:

  • LLM 훈련 데이터셋 구축
  • 콘텐츠 집합
  • 연구 논문 수집
  • 뉴스 기사 추출

Hugo/Jekyll 이전

우승자: html2md

비동기 처리 및 frontmatter 생성은 대규모 이전을 빠르고 쉽게 만듭니다:

  • 배치 변환
  • 자동 메타데이터 추출
  • YAML frontmatter 생성
  • 제목 레벨 조정

커스텀 변환 논리

우승자: markdownify

컨버터를 상속하여 완전한 제어를 얻을 수 있습니다:

  • 커스텀 태그 핸들러
  • 도메인 특화 변환
  • 특수 포맷팅 요구사항
  • 기존 BeautifulSoup 코드와 통합

타입 안전한 프로덕션 시스템

우승자: html-to-markdown

현대적이고 타입 안전하며 기능이 완비되어 있습니다:

  • 전체 HTML5 지원
  • 포괄적인 타입 힌트
  • 고급 테이블 처리
  • 활발한 유지보수

간단하고 안정적인 변환

우승자: html2text

“단순히 작동하는” 것이 필요한 경우:

  • 의존성 없음
  • 전투 테스트
  • 광범위한 설정
  • 넓은 플랫폼 지원

LLM 전처리를 위한 최고의 실천 방법

선택한 라이브러리에 관계없이 다음 최고의 실천 방법을 따르면 LLM 소비를 위한 최적화된 마크다운 출력을 보장할 수 있습니다. 이러한 패턴은 수백만 개의 문서를 처리하는 프로덕션 워크플로우에서 필수적이었습니다.

1. 변환 전 정리

변환 전에 원치 않는 요소를 제거하여 더 깨끗한 출력과 더 나은 성능을 얻으세요:

from bs4 import BeautifulSoup
import trafilatura

def clean_and_convert(html):
    """변환 전에 원치 않는 요소 제거"""
    soup = BeautifulSoup(html, 'html.parser')

    # 원치 않는 요소 제거
    for element in soup(['script', 'style', 'nav', 'footer', 'header', 'aside']):
        element.decompose()

    # 광고 및 추적 제거
    for element in soup.find_all(class_=['ad', 'advertisement', 'tracking']):
        element.decompose()

    # 정리된 HTML 변환
    markdown = trafilatura.extract(
        str(soup),
        output_format='markdown'
    )

    return markdown

2. 공백 정규화

다른 컨버터는 공백을 다르게 처리합니다. 출력을 일관되게 유지하기 위해 공백을 정규화하세요:

import re

def normalize_markdown(markdown):
    """마크다운 공백 정리"""
    # 여러 공백 줄 제거
    markdown = re.sub(r'\n{3,}', '\n\n', markdown)

    # 끝 공백 제거
    markdown = '\n'.join(line.rstrip() for line in markdown.split('\n'))

    # 끝에 단일 줄바꿈 추가
    markdown = markdown.rstrip() + '\n'

    return markdown

3. 출력 검증

품질 관리는 필수입니다. 변환 오류를 조기에 포착하기 위해 검증을 구현하세요:

def validate_markdown(markdown):
    """마크다운 품질 검증"""
    issues = []

    # HTML 잔여물 확인
    if '<' in markdown and '>' in markdown:
        issues.append("HTML 태그 감지됨")

    # 손상된 링크 확인
    if '[' in markdown and ']()' in markdown:
        issues.append("빈 링크 감지됨")

    # 과도한 코드 블록 확인
    code_block_count = markdown.count('```')
    if code_block_count % 2 != 0:
        issues.append("닫히지 않은 코드 블록")

    return len(issues) == 0, issues

4. 배치 처리 템플릿

대규모 문서 컬렉션을 처리할 때, 이 프로덕션 준비된 템플릿을 사용하세요. 적절한 오류 처리, 로깅, 병렬 처리가 포함되어 있습니다:

from pathlib import Path
from concurrent.futures import ProcessPoolExecutor
import trafilatura
import logging

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(__name__)

def process_file(html_path):
    """단일 HTML 파일 처리"""
    try:
        html = Path(html_path).read_text(encoding='utf-8')
        markdown = trafilatura.extract(
            html,
            output_format='markdown',
            include_links=True,
            include_images=False
        )

        if markdown:
            # 정규화
            markdown = normalize_markdown(markdown)

            # 검증
            is_valid, issues = validate_markdown(markdown)
            if not is_valid:
                logger.warning(f"{html_path}: {', '.join(issues)}")

            # 저장
            output_path = Path(str(html_path).replace('.html', '.md'))
            output_path.write_text(markdown, encoding='utf-8')

            return True

        return False

    except Exception as e:
        logger.error(f"{html_path} 처리 중 오류 발생: {e}")
        return False

def batch_convert(input_dir, max_workers=4):
    """디렉토리 내 모든 HTML 파일 변환"""
    html_files = list(Path(input_dir).rglob('*.html'))
    logger.info(f"{len(html_files)}개의 HTML 파일 발견")

    with ProcessPoolExecutor(max_workers=max_workers) as executor:
        results = list(executor.map(process_file, html_files))

    success_count = sum(results)
    logger.info(f"{success_count}/{len(html_files)}개의 파일 성공적으로 변환")

# 사용 예
batch_convert('./html_docs', max_workers=8)

결론

파이썬 생태계는 HTML-to-Markdown 변환을 위한 성숙하고 프로덕션 준비된 도구를 제공하며, 각각은 다른 시나리오에 최적화되어 있습니다. 선택은 특정 요구사항과 맞춰야 합니다:

  • 빠른 변환: html2text를 사용하세요. 간단하고 의존성이 없습니다.
  • 커스텀 논리: markdownify를 사용하세요. 서브클래싱을 통해 최대한의 유연성을 얻을 수 있습니다.
  • 웹 스크래핑: trafilatura를 사용하세요. 불필요한 요소를 제거하면서 주요 콘텐츠를 인텔리전트하게 추출합니다.
  • 대규모 이전: html2md를 사용하세요. 대규모 프로젝트에서 비동기 성능을 제공합니다.
  • 프로덕션 시스템: html-to-markdown을 사용하세요. 타입 안전성과 포괄적인 HTML5 지원이 있습니다.
  • 의미 보존: domscribe를 사용하세요. HTML5 의미 구조를 유지합니다.

LLM 워크플로우를 위한 추천

LLM 전처리 워크플로우를 위해 두 단계 접근 방식을 추천합니다:

  1. 초기 콘텐츠 추출을 위해 trafilatura 사용: 이는 내비게이션, 광고 및 불필요한 요소를 제거하면서 주요 콘텐츠를 보존합니다.
  2. 복잡한 문서에 정확한 구조 보존이 필요한 경우 html-to-markdown 사용: 기술 문서와 테이블, 코드 블록이 포함된 문서에 적합합니다.

이 조합은 실제 사례의 95%를 효과적으로 처리합니다.

다음 단계

이 도구들(예외 html2text)은 모두 활발히 유지보수되고 프로덕션 준비되어 있습니다. 다음을 수행하는 것이 좋습니다:

  1. 사용 사례에 맞는 2~3개의 라이브러리를 설치하세요.
  2. 실제 HTML 샘플로 테스트하세요.
  3. 일반 문서 크기로 성능을 벤치마킹하세요.
  4. 출력 품질을 기준으로 선택하세요. 속도만으로는 선택하지 마세요.

HTML-to-Markdown 변환을 위한 파이썬 생태계는 크게 성숙해졌으며, 각각의 목적에 맞는 선택은 모두 잘못된 선택이 아닙니다.

추가 자료

참고: 이 비교는 공식 문서, 커뮤니티 피드백 및 라이브러리 아키텍처 분석을 기반으로 합니다. 성능 특성은 일반적인 사용 패턴을 대표합니다. 특정 사용 사례에 대해서는 실제 HTML 샘플로 자체 벤치마킹을 수행하세요.

기타 유용한 기사