Optimization#FE#최적화

노션에서 HTML 뽑았더니 코드가 난리났다

회사에서 공지사항을 매달 노션에 작성하고, HTML로 내보내서 Hygraph CMS에 올리는 작업을 반복하고 있었다.

2026년 4월 15일3분 읽기조회 59
공유

회사에서 공지사항을 매달 노션에 작성하고, HTML로 내보내서 Hygraph CMS에 올리는 작업을 반복하고 있었다.

파이프라인은 단순하다.

노션 작성 → HTML 추출 → Hygraph CMS 업로드

[ As -IS ]

문제는 노션이 뽑아주는 HTML에 불필요한 코드가 너무 많다는 것이다.


더러운 노션 HTML 코드.

노션은 배포용 HTML이 아니라, 편집기 내부 상태를 보존하기 위한 HTML을 내보낸다. 공지사항 한 문단을 추출하면 실제로 이렇게 생겼다.

<div style="display:contents" dir="auto">
  <p id="34169501-a942-809c-97a7-ef7ad389b207" class="">
    안녕하세요, 회원 여러분 !
  </p>
</div>

필요한 건 아래 이거 한줄인데 ㅜㅜ 💢

<p>안녕하세요, 회원 여러분 !</p>

제거해야 하는 것들

요소설명
id="34169501-a942-..."노션 내부 block id
data-* 속성노션 에디터 식별용
dir 속성불필요한 방향 속성
style 인라인 속성width: 703px 같은 것들
<div style="display:contents">의미 없는 래퍼
<head> 안 pdf/print CSS웹 배포와 무관한 코드
목차의 노션 링크notion.so/... 로 연결된 링크
이미지 감싼 <a> 태그클릭 시 로컬 이미지로 이동

이렇게 수정했다

제거 외에 손봐야 하는 부분도 있었다.

1. 목차 링크 → 페이지 내 앵커로 교체

노션 목차는 기본적으로 notion.so 링크로 연결된다. CMS에 올리면 당연히 작동하지 않는다.

<!-- before -->
<a href="https://www.notion.so/페이지id?pvs=21">나노바나나 완전 정복</a>

<!-- after -->
<a href="#나노바나나-완전-정복">나노바나나 완전 정복</a>

노션 내보내기 방식에 따라 두 가지 케이스가 있었다.

  • 방법 A: notion.so 링크가 직접 달려있는 경우 → href를 슬러그 기반 앵커로 교체
  • 방법 B: "목차" h3 아래 numbered-list의 <li> 텍스트만 있는 경우 → <li> 안에 앵커 링크를 직접 생성
# 방법 A
for a in soup.find_all("a", href=True):
    if "notion.so" in a["href"]:
        slug = f"section-{len(toc_items) + 1}"
        a["href"] = f"#{slug}"

# 방법 B
toc_h3 = next(
    (h3 for h3 in soup.find_all("h3") if "목차" in h3.get_text()), None
)
if toc_h3:
    for ol in toc_h3.find_all_next("ol", class_="numbered-list"):
        for li in ol.find_all("li"):
            slug = f"section-{idx}"
            a_tag = soup.new_tag("a", href=f"#{slug}")
            a_tag.string = li_text
            li.append(a_tag)

2. 이동 대상 h2에 id + 클래스 추가, CSS도 함께

목차 링크가 이동할 h2에 id를 붙여줘야 앵커가 작동한다. anchor-offset 클래스도 같이 달아서 스크롤 위치가 헤더에 가리지 않게 했다.

<h2 id="나노바나나-완전-정복" class="anchor-offset">나노바나나 완전 정복</h2>
.anchor-offset { scroll-margin-top: 100px; }

scroll-margin-top이 없으면 앵커로 이동했을 때 고정 헤더 뒤에 제목이 숨어버린다.

CSS는 clean_css() 함수 끝에 자동으로 추가된다.

3. 이미지 감싼 <a> 태그 제거

노션이 이미지를 <a>로 감싸는데, 클릭하면 로컬 파일 경로로 이동해버린다.

불필요한 a 태그 <a> 제거!!

for a in soup.find_all("a", href=True):
    if a.find("img"):
        a.unwrap()  # <a>만 벗기고 <img>는 남김

4. 외부 링크 → 새 탭으로 열기

http로 시작하는 외부 링크는 전부 새 탭에서 열리도록 처리했다.

<a href="https://..." target="_blank" rel="noopener noreferrer">링크</a>

rel="noopener noreferrer"는 보안상 같이 넣어줘야 한다.

noopener는 새 탭에서 원래 페이지에 접근하는 걸 막고, noreferrer는 referrer 정보 노출을 막는다.


이렇게 자동화했다

매달 반복되는 작업이라 Python 스크립트로 자동화했다. beautifulsoup4로 HTML을 파싱해서 위의 처리를 전부 한 번에 실행한다.

# 설치
pip install beautifulsoup4

# 실행
python notion-clean.py 입력파일.html 출력파일.html

같은 폴더에 넣고 쓰는 게 제일 편하다 ㅎㅎ!

📁 notice/
  ├── notion-clean.py
  └── notice.html

실행하면 정제된 HTML 파일이 생성되고 감소율이 출력된다.~~!

(아래 사진은 추후에 다른 파일 변경한 사진!)

image.png


수치로 보면

파일 리소스

항목전후
HTML 파일 크기39,792 bytes26,756 bytes
감소율032.8%

작업 시간

항목전후
건당 작업 시간1시간 이상1분 이내
월 누적 (3~5건 기준)3시간 ~ 5시간 이상5분 이내

매달 3시간 이상을 HTML 파일 열어서 코드 눈으로 찾아 지우는 데 썼다는 얘기다.

그리고 입사한지 1달도 안된 만큼 어떤걸 어떻게 수정해야 한다는 메뉴얼을 읽고 그대로 수정하려니 더더욱 공수가 많이 들었었다.

단순히 시간만 줄어든 게 아니라, 수동 작업에서 발생하던 누락(목차 링크 미교체, <a> 태그 일부 남아있음 등)이 사라졌다.


별도로 해야 하는 것

이미지 src는 스크립트가 건드리지 않는다.

Hygraph에 이미지를 먼저 업로드하고, 생성된 URL로 수동 교체가 필요하다.

(노션 이미지 URL은 인증 토큰이 포함된 임시 링크라 만료되기때문!!!)

<!-- 스크립트 실행 후 남아있는 상태 -->
<img src="image.png"/>

<!-- Hygraph URL로 직접 교체 -->
<img src="https://media.graphassets.com/..."/>

정리

  • beautifulsoup4 기반 스크립트로 자동화 가능!!!!! 시간 절약 아싸 ㅎㅎ
  • 노션 HTML 추출 시 포함되는 내부 식별용 코드(block id, data-*, 인라인 style 등)를 beautifulsoup4 기반 스크립트로 자동 제거!
  • 작동하지 않는 목차 노션 링크는 페이지 내 앵커로 교체, 이미지 <a> 제거 및 외부 링크 target="_blank" 처리까지 일괄 적용
  • 파일 크기 32.8% 감소, 건당 30분 이상 소요되던 수동 작업이 1분 이내로 단축되어 월 1.5~2.5시간 절감
  • 이미지 URL 교체만 수동 작업으로 남김

관련 포스트

댓글

방명록을 남겨주세요