본문 바로가기
풀스택 개발/개발일지

티스토리 크롤링, 제목 영어로 번역, 마크다운 파일 생성 & 해시노드 업로드

by act2 2025. 2. 8.
728x90

티스토리에서 해시노드로 데이터 이전하기 성공

 

티스토리를 크롤링해서 해시노드로 데이터를 이전하는 코드를 작성했습니다.

 

해시노드에 한글 제목을 slug로 해서 업로드했을 때, 

약 7,000개의 글 중에서 5,000개 미만의 글이 업로드됐는데, 

아마도 이건 slug가 중복되어 그런 것이라 예상합니다.

 

티스토리를 크롤링하면, 제목이 한글로 추출되는데...

이렇게 하면 해시노드에 적용되는 slug 부분이 아주 엉망이 되어 버립니다. 

그래서 크롤링하면서, slug 부분은 제목을 영어로 번역하도록 코딩했습니다.

 

저장되는 마크다운 파일의 제목은 YYYY-MM-DD-제목.md로 저장됩니다.

이렇게 해서 zip파일로 압축한 후 업로드하면...

깔끔히 업로드되고, 글의 uri도 slug가 될 것입니다.

각 글에 들어가서 slug를 수정하려고 했는데, 이렇게 하니까...

약 7천 번의 수작업 소요를 줄일 수 있었습니다. 

 

import requests
from bs4 import BeautifulSoup
import os
import time
from datetime import datetime
import re
from googletrans import Translator


def get_page_content(url):
    headers = {
        'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36'}
    try:
        response = requests.get(url, headers=headers, timeout=10)
        return response.text if response.status_code == 200 else None
    except Exception as e:
        print(f"Error fetching {url}: {e}")
        return None


def translate_title(title):
    translator = Translator()
    try:
        translated = translator.translate(title, src='ko', dest='en')
        return translated.text
    except Exception as e:
        print(f"번역 오류: {e}")
        return title  # 번역 실패 시 원본 제목 반환


def html_to_markdown(html_content):
    soup = BeautifulSoup(html_content, 'html.parser')

    title_element = soup.find('meta', property='og:title')
    title = title_element['content'] if title_element else '제목 없음'

    # 제목 번역
    translated_title = translate_title(title)
    slug = re.sub(r'[^\w\-]', '', translated_title.lower().replace(' ', '-'))

    date_element = soup.find('meta', property='article:published_time')
    if date_element:
        raw_date = date_element['content'][:19]
        formatted_date = datetime.strptime(raw_date, "%Y-%m-%dT%H:%M:%S").strftime("%Y-%m-%d %H:%M:%S")
    else:
        formatted_date = "날짜 정보 없음"

    category_element = soup.select_one('.area_title strong.tit_category a')
    category = category_element.text.strip() if category_element else '분류 없음'

    tag_container = soup.find('dl', class_='list_tag')
    tags = []
    if tag_container:
        tags = [tag.text.strip() for tag in tag_container.find_all('a', rel='tag')]

    content = soup.find('div', class_='tt_article_useless_p_margin')
    markdown_content = ''
    if content:
        for element in content.find_all(['p', 'h2', 'h3', 'h4', 'img', 'ul']):
            if element.name == 'img':
                img_url = element.get('src')
                markdown_content += f"![이미지]({img_url})\n\n"
            elif element.name in ['h2', 'h3', 'h4']:
                level = element.name[1]
                markdown_content += f"{'#' * int(level)} {element.get_text().strip()}\n\n"
            elif element.name == 'ul':
                for li in element.find_all('li'):
                    markdown_content += f"- {li.get_text().strip()}\n"
                markdown_content += "\n"
            else:
                text = element.get_text().strip()
                if text:
                    markdown_content += f"{text}\n\n"

    return title, formatted_date, category, tags, slug, f"""---
layout: single
title: "{title}"
date: "{formatted_date}"
slug: "{slug}"
categories: "{category}"
tags: {tags}
---

{markdown_content.strip()}"""


def save_markdown(content, filename):
    base, extension = os.path.splitext(filename)
    counter = 1
    while os.path.exists(filename):
        filename = f"{base}_{counter}{extension}"
        counter += 1
    with open(filename, 'w', encoding='utf-8') as f:
        f.write(content)


base_url = "https://example.tistory.com"
output_dir = "markdown_output"
os.makedirs(output_dir, exist_ok=True)

page_number = 1
empty_page_count = 0
max_empty_pages = 10

while True:
    url = f"{base_url}/{page_number}"
    html_content = get_page_content(url)

    if not html_content:
        print(f"페이지 {page_number}에서 내용을 찾을 수 없습니다.")
        empty_page_count += 1
        if empty_page_count >= max_empty_pages:
            print(f"{max_empty_pages}개의 연속된 빈 페이지 발견. 크롤링 종료")
            break
    else:
        empty_page_count = 0
        title, formatted_date, category, tags, slug, markdown_content = html_to_markdown(html_content)

        try:
            file_date = datetime.strptime(formatted_date, "%Y-%m-%d %H:%M:%S").strftime("%Y-%m-%d")
        except ValueError:
            file_date = datetime.now().strftime("%Y-%m-%d")

        filename = f"{file_date}-{slug[:50]}.md"

        save_markdown(markdown_content, os.path.join(output_dir, filename))
        print(f"성공: {filename}")

    page_number += 1
    time.sleep(1)

print("크롤링 완료")
728x90