이 가이드에서는 다음을 확인할 수 있습니다:
- 웹사이트를 마크다운으로 스크래핑하는 의미와 그 유용성.
- 정적 및 동적 사이트의 웹 페이지 HTML을 마크다운으로 변환하는 주요 방법.
- Python을 사용하여 웹페이지를 마크다운으로 스크래핑하는 방법.
- 이 솔루션의 한계점과 Bright Data를 통해 이를 극복하는 방법.
자, 시작해 보겠습니다!
“웹사이트를 마크다운으로 스크래핑한다”는 것은 무엇을 의미할까요?
“웹사이트를 마크다운으로 스크랩”한다는 것은 해당 콘텐츠를 마크다운으로 변환하는 것을 의미합니다.
더 구체적으로 말하면, 웹 페이지의 HTML을 가져와 마크다운 데이터 형식으로 변환하는 것을 의미합니다.
예를 들어, 사이트에 접속하여 개발자 도구를 열고 HTML을 복사합니다:

그런 다음 HTML-to-Markdown 변환기에 붙여넣습니다:

출력 결과는 웹 스크래핑을 통해 얻고자 하는 마크다운 문서와 유사하게 보일 것입니다. 이제 목표는 이 과정을 자동화하는 것이며, 바로 이 글이 다루는 내용입니다!
[추가] 왜 마크다운인가?
다른 형식(예: 일반 텍스트) 대신 마크다운을 선택한 이유는 무엇일까요? 데이터 형식 벤치마크에서 확인했듯이, 마크다운은 LLM(대규모 언어 모델) 입력에 가장 적합한 형식 중 하나이기 때문입니다. 주요 이유는 다음과 같습니다:
- 페이지 구조와 정보(링크, 이미지, 제목 등)를 대부분 보존합니다.
- 간결하여 토큰 사용량이 적고 AI 처리 속도가 빠릅니다.
- LLM은 일반 HTML보다 마크다운을 훨씬 잘 이해하는 경향이 있습니다.
이것이 최고의 AI 스크래핑 도구들이 기본적으로 마크다운을 처리하는 이유입니다.
HTML을 마크다운으로 변환하는 방법
이제 사이트를 마크다운으로 스크래핑한다는 것은 단순히 페이지의 HTML을 마크다운으로 변환하는 것임을 알게 되었습니다. 높은 수준에서 이 과정은 다음과 같습니다:
- 사이트에 연결합니다.
- HTML을 문자열로 가져옵니다.
- HTML-to-Markdown 라이브러리를 사용하여 마크다운 출력을 생성합니다.
문제는 모든 웹 페이지가 동일한 방식으로 제공되지 않는다는 점입니다. 대상 페이지가 정적인지 동적인지에 따라 처음 두 단계가 크게 달라질 수 있습니다. 필요한 단계를 확장하여 두 시나리오를 모두 처리하는 방법을 살펴보겠습니다!
단계 #1: 사이트에 연결하기
정적 웹 페이지의 경우 서버가 반환하는 HTML 문서가 브라우저에 표시되는 내용과 정확히 일치합니다. 즉, 모든 요소가 서버가 생성한 HTML에 고정되어 내장되어 있습니다.
이 경우 HTML을 가져오는 것은 간단합니다. HTTP 클라이언트로 페이지 URL에 GET HTTP 요청 을 수행하기만 하면 됩니다:

반면 동적 웹사이트에서는 대부분의(또는 일부) 콘텐츠가 AJAX를 통해 가져와져 자바스크립트로 브라우저에 렌더링됩니다. 즉 웹 서버가 반환하는 초기 HTML 문서에는 최소한의 내용만 포함됩니다. 클라이언트 측에서 자바스크립트가 실행된 후에야 페이지에 전체 콘텐츠가 채워집니다:

이러한 경우 단순한 HTTP 클라이언트로 HTML을 가져올 수 없습니다. 대신 브라우저 자동화 도구처럼 페이지를 실제로 렌더링할 수 있는 도구가 필요합니다. Playwright, Puppeteer, Selenium 같은 솔루션은 브라우저를 프로그래밍 방식으로 제어하여 대상 페이지를 로드하고 완전히 렌더링된 HTML을 가져올 수 있게 합니다.
2단계: HTML을 문자열로 가져오기
정적 웹 페이지의 경우 이 단계는 간단합니다. 웹 서버가 GET 요청에 응답할 때 이미 완전한 HTML 문서가 문자열 형태로 포함되어 있습니다. Python의 Requests 같은 대부분의 HTTP 클라이언트는 이를 직접 접근할 수 있는 메서드나 필드를 제공합니다:
url = "https://quotes.toscrape.com/"
response = requests.get(url)
# 페이지의 HTML 콘텐츠를 문자열로 접근
html = response.text
동적 웹사이트의 경우 훨씬 까다롭습니다. 이번에는 서버가 반환한 원시 HTML 문서 자체에 관심이 없습니다. 대신 브라우저가 페이지를 렌더링하고 DOM이 안정화된 후 최종 HTML에 접근해야 합니다.
이는 일반적으로 개발자 도구(DevTools)를 열고 <html> 노드에서 HTML을 복사하는 수동 작업과 일치합니다:

문제는 페이지 렌더링이 완료된 시점을 파악하는 것입니다. 일반적인 전략은 다음과 같습니다:
-
DOMContentLoaded이벤트 대기: 초기 HTML이 파싱되고 지연 로드된<script>가 로드 및 실행될 때 발생합니다. 이 이벤트를 기다리는 것이 Playwright의 기본 동작입니다. -
load이벤트 대기: 스타일시트, 스크립트, iframe, 이미지(지연 로딩된 이미지 제외)를 포함한 전체 페이지가 로드되었을 때 발생합니다. -
networkidle이벤트 대기: 지정된 시간(예: Playwright의 경우500ms) 동안 네트워크 요청이 없을 때 렌더링 완료로 간주합니다. 실시간 업데이트 콘텐츠가 있는 사이트에서는 절대 발생하지 않아 신뢰할 수 없습니다. - 특정 요소 대기: 브라우저 자동화 프레임워크가 제공하는 사용자 정의 대기 API를 사용하여 특정 요소가 DOM에 나타날 때까지 기다립니다.
페이지가 완전히 렌더링되면 브라우저 자동화 도구가 제공하는 특정 메서드/필드를 사용하여 HTML을 추출할 수 있습니다. 예를 들어 Playwright에서는:
html = await page.content()
3단계: HTML-to-Markdown 라이브러리로 Markdown 출력 생성
HTML을 문자열로 가져온 후에는 다양한 HTML-to-Markdown 라이브러리 중 하나에 전달하기만 하면 됩니다. 가장 널리 사용되는 라이브러리는 다음과 같습니다:
| 라이브러리 | 프로그래밍 언어 | GitHub 스타 |
|---|---|---|
markdownify |
Python | 1.8k+ |
turndown |
JavaScript/Node.js | 10k+ |
Html2Markdown |
C# | 300+ |
commonmark-java |
Java | 2.5k+ |
html-to-markdown |
Go | 3k+ |
html-to-markdown |
PHP | 1.8k+ |
웹사이트를 마크다운으로 스크래핑하기: 실용적인 파이썬 예제
이 섹션에서는 웹사이트를 마크다운으로 스크래핑하는 완전한 파이썬 코드 조각을 확인할 수 있습니다. 아래 스크립트는 앞서 설명한 단계를 구현합니다. 이 로직을 자바스크립트나 다른 프로그래밍 언어로 쉽게 변환할 수 있다는 점을 참고하세요.
입력은 웹 페이지의 URL이며, 출력은 해당 마크다운 콘텐츠가 될 것입니다!
정적 사이트
이 예제에서는 다음 두 라이브러리를 사용합니다:
requests: GET 요청을 수행하고 페이지 HTML을 문자열로 가져옵니다.markdownify: 페이지 HTML을 마크다운으로 변환합니다.
둘 다 다음 명령어로 설치하세요:
pip install requests markdownify
대상 페이지는 정적 “스크래핑할 인용문” 페이지입니다. 다음 코드 조각으로 목표를 달성할 수 있습니다:
import requests
from markdownify import markdownify as md
# 스크래핑할 페이지의 URL
url = "http://quotes.toscrape.com/"
# requests를 사용해 HTML 콘텐츠 가져오기
response = requests.get(url)
# HTML을 문자열로 가져옴
html_content = response.text
# HTML 콘텐츠를 마크다운으로 변환
markdown_content = md(html_content)
# 마크다운 출력 인쇄
print(markdown_content)
선택적으로, 다음 코드로 내용을 .md 파일로 내보낼 수 있습니다:
with open("page.md", "w", encoding="utf-8") as f:
f.write(markdown_content)
스크립트 실행 결과는 다음과 같습니다:

출력된 마크다운을 복사하여 마크다운 렌더러에 붙여넣으면 다음과 같이 표시됩니다:

이것이 “스크래핑할 인용문” 페이지의 원본 콘텐츠를 단순화한 버전처럼 보이는 점에 주목하세요:

미션 완료!
동적 사이트
여기서는 다음 두 라이브러리를 활용합니다:
playwright: 제어된 브라우저 인스턴스에서 대상 페이지를 렌더링합니다.markdownify: 렌더링된 페이지의 DOM을 마크다운으로 변환합니다.
위의 두 종속성을 다음 명령어로 설치하세요:
pip install playwright markdownify
그런 다음 다음 명령어로 Playwright 설치를 완료하세요:
python -m playwright install
대상은 ScrapingCourse.com 사이트의 동적 “JavaScript Rendering”페이지입니다:

이 페이지는 클라이언트 측에서 AJAX를 통해 데이터를 가져와 JavaScript로 렌더링합니다:

동적 웹사이트를 아래와 같이 마크다운으로 스크래핑하세요:
from playwright.sync_api import sync_playwright
from markdownify import markdownify as md
with sync_playwright() as p:
# 헤드리스 브라우저 실행
browser = p.chromium.launch()
page = browser.new_page()
# 동적 페이지의 URL
url = "https://scrapingcourse.com/javascript-rendering"
# 페이지로 이동
page.goto(url)
# 첫 번째 제품 링크 요소가 채워질 때까지 최대 5초 대기
page.locator('.product-link:not([href=""])').first.wait_for(timeout=5000)
# 완전히 렌더링된 HTML 가져오기
rendered_html = page.content()
# HTML을 마크다운으로 변환
markdown_content = md(rendered_html)
# 결과 마크다운 출력
print(markdown_content)
# 브라우저 닫기 및 리소스 해제
browser.close()
위 코드 조각에서는 가장 신뢰할 수 있는 옵션 4(“특정 요소 대기”)를 선택했습니다. 구체적으로 다음 코드 줄을 살펴보세요:
page.locator('.product-link:not([href=""])').first.wait_for(timeout=5000)
이 코드는 .product-link 요소( <a> 태그)의 href 속성이 비어 있지 않을 때까지 최대 5000밀리초(5초) 동안 대기합니다. 이는 페이지의 첫 번째 제품 요소가 렌더링되었음을 나타내기에 충분한 시간으로, 데이터가 가져오고 DOM이 안정화된 상태임을 의미합니다.
결과는 다음과 같습니다:

자, 이제 웹사이트를 스크래핑하여 마크다운으로 변환하는 방법을 배웠습니다.
이러한 접근법의 한계와 해결책
이 블로그 포스트의 모든 예시는 근본적으로 공통점이 있습니다: 스크래핑하기 쉽게 설계된 페이지를 대상으로 한다는 점입니다!
안타깝게도 대부분의 실제 웹 페이지는 웹 스크래핑 봇에 그렇게 개방적이지 않습니다. 오히려 많은 사이트가 CAPTCHA, IP 차단, 브라우저 지문 인식 등 스크래핑 방지 장치를 구현하고 있습니다.
다시 말해, 단순한 HTTP 요청이나 Playwright의 goto() 명령이 의도한 대로 작동할 것이라고 기대할 수 없습니다. 대부분의 실제 웹사이트를 대상으로 할 때 다음과 같은 403 Forbidden 오류를 마주칠 수 있습니다:

또는 오류/인간 인증 페이지가 표시될 수 있습니다:

또 다른 고려 사항은 대부분의 HTML-to-Markdown 라이브러리가 원시 데이터 변환을 수행한다는 점입니다. 이는 원치 않는 결과를 초래할 수 있습니다. 예를 들어, 페이지에 HTML에 직접 포함된 <style> 또는 <script> 요소가 있다면, 해당 콘텐츠(각각 CSS 및 JavaScript 코드)가 Markdown 출력에 포함됩니다:

이는 일반적으로 바람직하지 않으며, 특히 마크다운을 데이터 처리를 위해 LLM에 입력할 계획이라면 더욱 그렇습니다. 결국 이러한 텍스트 요소들은 불필요한 잡음만 추가할 뿐입니다.
해결책은 무엇일까요? 보호 기능과 무관하게 모든 사이트에 접근하여 LLM에 바로 활용 가능한 마크다운을 생성하는 전용 웹 언락커 API를 활용하는 것입니다. 이를 통해 추출된 콘텐츠가 깨끗하고 구조화되어 하류 AI 작업에 즉시 사용 가능하도록 보장합니다.
웹 언락커를 통한 스크래핑에서 마크다운으로 변환
Bright Data의 Web Unlocker는 클라우드 기반 웹 스크래핑 API로, 모든 웹 페이지의 HTML을 반환할 수 있습니다. 이는 페이지에 적용된 스크래핑 방지 또는 봇 방지 보호 기능과 상관없이, 페이지가 정적인지 동적인지와 관계없이 가능합니다.
이 API는 1억 5천만 개 이상의 IP로 구성된 프록시 네트워크를 기반으로 하여, Bright Data가 전체 차단 해제 인프라, 자바스크립트 렌더링, CAPTCHA 해결, 확장성 관리 및 유지보수 업데이트를 처리하는 동안 사용자는 데이터 수집에 집중할 수 있습니다.
사용법은 간단합니다. 올바른 인수를 포함한 POST HTTP 요청을 Web Unlocker에 전송하면 완전히 차단 해제된 웹 페이지가 반환됩니다. LLM(대규모 언어 모델)에 최적화된 마크다운 형식으로 콘텐츠를 반환하도록 API를 구성할 수도 있습니다.
초기 설정 가이드를 따르고, 몇 줄의 코드만으로 웹 언락커를 사용해 웹사이트를 스크래핑하여 마크다운으로 변환하세요:
# pip install requests
import requests
# Bright Data 계정의 올바른 값으로 대체하세요
BRIGHT_DATA_API_KEY= "<YOUR_BRIGHT_DATA_API_KEY>"
WEB_UNLOCKER_ZONE = "<YOUR_WEB_UNLOCKER_ZONE_NAME>"
# 대상 URL로 대체
url_to_scrape = "https://www.g2.com/products/bright-data/reviews"
# 필요한 헤더 준비
headers = {
"Authorization": f"Bearer {BRIGHT_DATA_API_KEY}", # 인증용
"Content-Type": "application/json"
}
# 웹 언락커 POST 페이로드 준비
payload = {
"url": url_to_scrape,
"zone": WEB_UNLOCKER_ZONE,
"format": "raw",
"data_format": "markdown" # 마크다운 형식으로 응답 받기
}
# Bright Data 웹 언락커 API에 POST 요청 수행
response = requests.post(
"https://api.brightdata.com/request",
json=payload,
headers=headers)
# 마크다운 응답 가져와 출력
markdown_content = response.text
print(markdown_content)
스크립트를 실행하면 다음과 같은 결과가 출력됩니다:

이번에는 G2에 차단되지 않고 원하는 대로 실제 마크다운 콘텐츠를 얻었음을 확인하세요.
완벽합니다! 웹사이트를 마크다운으로 변환하는 것이 이보다 더 쉬울 수는 없습니다.
참고: 이 솔루션은 CrawlAI, Agno, LlamaIndex, LangChain 등 75개 이상의 AI 에이전트 도구와의 통합을 통해 이용 가능합니다. 또한 Bright Data Web MCP 서버의 scrape_as_markdown 도구를 통해 직접 사용할 수도 있습니다.
결론
이 블로그 글에서는 웹페이지를 마크다운으로 변환하는 이유와 방법을 살펴보았습니다. 논의된 바와 같이, 안티 스크래핑 보호 기능이나 부적합한 마크다운 결과 같은 문제로 인해 HTML을 마크다운으로 변환하는 것이 항상 쉬운 것은 아닙니다.
Bright Data의 Web Unlocker는 클라우드 기반 웹 스크래핑 API로, 모든 웹페이지를 LLM 최적화 마크다운으로 변환할 수 있습니다. 이 API는 수동 호출하거나 AI 에이전트 구축 솔루션에 직접 통합하거나 Web MCP 통합을 통해 활용할 수 있습니다.
Web Unlocker는 Bright Data의 AI 인프라에서 제공하는 수많은 웹 데이터 및 스크래핑 도구 중 하나일 뿐임을 기억하세요.
지금 바로 Bright Data 무료 계정에 가입하여 AI 활용이 가능한 웹 데이터 솔루션을 경험해 보세요!