한자 사전 DB

IT 지식 / Python·2026.09.07
PYTHON PROJECT
파이썬 한자사전 DB 만들기
Unicode Unihan 데이터에서 한자 획수를 추출하고
국립국어원 우리말샘 OpenAPI를 연결해 한자 학습 데이터를 만들어 봅니다.
한자가 적힌 학습 자료
한자 데이터와 Python을 결합해 나만의 한자 학습 프로그램을 만들 수 있습니다.
1. 한자사전 DB 프로젝트 구조

이번 프로젝트에서는 단순히 한자를 화면에 출력하는 것을 넘어, 실제 공개 데이터를 이용해 한자 학습용 데이터베이스를 만들어 봅니다.

데이터는 크게 두 가지 출처를 활용합니다.

1 Unicode Unihan

Unicode에서 제공하는 Unihan 자료를 이용하여 한자별 총 획수(kTotalStrokes)를 가져옵니다.

2 국립국어원 우리말샘 OpenAPI

우리말샘 검색 API를 이용해 한자어의 표제어, 한자, 뜻풀이, 품사 등의 정보를 검색합니다.

Unihan.zip Python 분석 JSON OpenAPI 한자 학습 프로그램
💡 이 프로젝트에서 연습하는 Python 기술
  • 텍스트 파일 읽기
  • 문자열 분리와 데이터 정제
  • Unicode 코드 포인트 처리
  • JSON 파일 저장
  • HTTP GET 요청
  • JSON·XML 데이터 파싱
  • dataclass를 이용한 데이터 구조화
  • threading을 이용한 API 비동기 처리
  • Tkinter GUI 프로그램 제작
노트북에서 Python 코드를 작성하는 모습
공개 데이터를 Python으로 가공하면 검색·게임·학습 프로그램에 활용할 수 있습니다.
2. Unicode Unihan 데이터 활용

첫 번째 데이터는 Unicode Consortium에서 제공하는 Unihan Database입니다.

Unihan 자료에는 CJK 통합 한자와 관련된 다양한 속성이 들어 있으며, 이번 실습에서는 그중 kTotalStrokes 값을 이용합니다.

Unihan.zip 다운로드
https://www.unicode.org/Public/UCD/latest/ucd/Unihan.zip
⚠ 압축을 먼저 풀어 주세요

다운로드한 Unihan.zip을 Python 프로그램과 같은 위치에 압축 해제하고 폴더 이름을 Unihan으로 지정하면 예제 코드를 그대로 실행하기 편리합니다.

Unihan 데이터의 기본 형태

Unihan 텍스트 파일의 데이터는 일반적으로 탭(Tab)으로 구분됩니다.

Unihan 데이터 구조
U+4E00    kTotalStrokes    1
U+4E01    kTotalStrokes    2
U+4E03    kTotalStrokes    2

첫 번째 값은 Unicode 코드 포인트, 두 번째는 속성 이름, 세 번째는 실제 값입니다.

항목 설명
코드 포인트 U+4E00 한자를 나타내는 Unicode 번호
필드 kTotalStrokes 한자의 총 획수
1 실제 획수
3. 한자별 총 획수 JSON 만들기

이제 Unihan 폴더 안의 모든 TXT 파일을 검색하여 kTotalStrokes 데이터만 추출해 보겠습니다.

extract_hanja_strokes.py
from __future__ import annotations

import argparse
import json
from pathlib import Path

FIELD_NAME = "kTotalStrokes"


def extract_total_strokes(unihan_dir: Path) -> dict[str, int]:
    """폴더 안의 모든 Unihan txt 파일에서 kTotalStrokes 값을 읽는다."""

    if not unihan_dir.is_dir():
        raise FileNotFoundError(
            f"Unihan 폴더를 찾을 수 없습니다: {unihan_dir}"
        )

    strokes: dict[str, int] = {}

    text_files = sorted(unihan_dir.glob("*.txt"))

    if not text_files:
        raise FileNotFoundError(
            f"txt 파일이 없습니다: {unihan_dir}"
        )

    for text_file in text_files:

        with text_file.open(
            "r",
            encoding="utf-8"
        ) as source:

            for line_number, raw_line in enumerate(
                source,
                start=1
            ):
                line = raw_line.rstrip("\r\n")

                if not line or line.startswith("#"):
                    continue

                parts = line.split("\t", 2)

                if len(parts) != 3:
                    continue

                if parts[1] != FIELD_NAME:
                    continue

                code_point, _, value = parts

                try:
                    character = chr(
                        int(
                            code_point.removeprefix("U+"),
                            16
                        )
                    )

                    stroke_count = int(value)

                except ValueError as error:
                    raise ValueError(
                        f"잘못된 데이터: "
                        f"{text_file}:{line_number}: {line}"
                    ) from error

                old_value = strokes.get(character)

                if (
                    old_value is not None
                    and old_value != stroke_count
                ):
                    raise ValueError(
                        f"획수가 서로 다릅니다: "
                        f"{code_point} "
                        f"({old_value}, {stroke_count})"
                    )

                strokes[character] = stroke_count

    if not strokes:
        raise ValueError(
            f"{FIELD_NAME} 데이터를 찾지 못했습니다: "
            f"{unihan_dir}"
        )

    return strokes


def main() -> None:

    parser = argparse.ArgumentParser(
        description=(
            "Unihan 자료에서 한자별 총 획수를 "
            "추출해 JSON으로 저장합니다."
        )
    )

    parser.add_argument(
        "--input",
        type=Path,
        default=(
            Path(__file__).resolve().parent
            / "Unihan"
        ),
        help="Unihan txt 폴더",
    )

    parser.add_argument(
        "--output",
        type=Path,
        default=(
            Path(__file__).resolve().parent
            / "hanja_strokes.json"
        ),
        help="출력 JSON 경로",
    )

    args = parser.parse_args()

    strokes = extract_total_strokes(args.input)

    args.output.parent.mkdir(
        parents=True,
        exist_ok=True
    )

    with args.output.open(
        "w",
        encoding="utf-8",
        newline="\n"
    ) as output:

        json.dump(
            strokes,
            output,
            ensure_ascii=False,
            indent=2
        )

        output.write("\n")

    print(
        f"완료: 한자 {len(strokes):,}자의 획수를 "
        f"{args.output}에 저장했습니다."
    )


if __name__ == "__main__":
    main()
핵심 코드 이해하기
1 모든 TXT 파일 찾기
Python
text_files = sorted(unihan_dir.glob("*.txt"))

glob("*.txt")를 이용해 Unihan 폴더 안에 있는 모든 TXT 파일을 찾습니다.

2 탭으로 데이터 분리하기
Python
parts = line.split("\t", 2)

한 줄을 탭 문자를 기준으로 나누어 코드 포인트, 필드명, 값을 각각 얻습니다.

3 Unicode 번호를 실제 한자로 변환
Python
character = chr(
    int(code_point.removeprefix("U+"), 16)
)

예를 들어 U+4E00에서 U+를 제거하고 16진수를 정수로 바꾼 다음 chr() 함수로 실제 문자 을 얻습니다.

4 JSON으로 저장
Python
json.dump(
    strokes,
    output,
    ensure_ascii=False,
    indent=2
)

ensure_ascii=False를 사용하면 한자가 Unicode 이스케이프 문자열이 아니라 실제 문자로 저장됩니다.

프로그램 실행
Terminal
python extract_hanja_strokes.py

입력 폴더와 출력 파일을 직접 지정할 수도 있습니다.

Terminal
python extract_hanja_strokes.py --input Unihan --output hanja_strokes.json
생성되는 JSON 예
{
  "一": 1,
  "丁": 2,
  "七": 2
}
💡 JSON으로 저장하는 이유

JSON은 Python에서 쉽게 읽을 수 있고 웹 프로그램이나 GUI 프로그램에서도 활용하기 편리합니다. 한 번 Unihan 자료를 JSON으로 변환해 두면 프로그램을 실행할 때마다 원본 TXT 파일 전체를 다시 분석할 필요가 없습니다.

4. 국립국어원 우리말샘 OpenAPI 활용

획수 정보만으로는 한자 학습 프로그램을 만들기 어렵습니다. 한자어의 표제어와 뜻풀이 같은 사전 정보도 필요합니다.

이때 활용할 수 있는 것이 국립국어원 우리말샘 OpenAPI입니다.

우리말샘 OpenAPI 안내
https://opendict.korean.go.kr/service/openApiInfo
💡 실습에서 확인할 내용
  • 인증키를 이용한 OpenAPI 요청
  • GET 방식으로 검색 API 호출
  • req_type=json으로 JSON 응답 요청
  • num과 start를 이용한 페이지 처리
  • 한자어 검색 결과를 WordCard 객체로 변환
⚠ API 인증키는 공개하지 마세요

블로그나 GitHub에 프로그램을 공개할 때는 실제 인증키를 Python 소스에 직접 작성하지 않는 것이 좋습니다.

아래 예제에서는 OPENDICT_API_KEY 환경 변수에서 인증키를 읽도록 구성합니다.

5. Python에서 OpenAPI 호출하기
기본 설정
Python
import json
import os
import urllib.error
import urllib.parse
import urllib.request

API_KEY = os.environ.get(
    "OPENDICT_API_KEY",
    ""
)

SEARCH_URL = (
    "https://opendict.korean.go.kr/api/search"
)

PAGE_SIZE = 100
MAX_START = 1000

인증키는 환경 변수에서 가져오고, 검색 주소는 SEARCH_URL 상수에 저장합니다.

검색 요청 보내기
Python
def request_search(params):
    query = urllib.parse.urlencode(params)

    request = urllib.request.Request(
        f"{SEARCH_URL}?{query}",
        headers={
            "User-Agent": "hanja-card-game-open"
        },
    )

    try:
        with urllib.request.urlopen(
            request,
            timeout=25
        ) as response:

            raw = response.read().decode("utf-8")

    except urllib.error.URLError as error:
        raise RuntimeError(
            "우리말샘에 연결하지 못했습니다."
        ) from error

    return raw

urllib.parse.urlencode()는 Python 딕셔너리를 URL의 쿼리 문자열 형식으로 변환합니다.

검색 파라미터
Python
params = {
    "key": API_KEY,
    "q": "중",
    "req_type": "json",
    "part": "word",
    "sort": "dict",
    "advanced": "y",
    "target": "1",
    "method": "include",
    "type1": "word",
    "type2": "chinese",
    "type3": "general",
    "letter_s": "1",
    "letter_e": "8",
    "num": "100",
    "start": "1",
}
항목 역할
key OpenAPI 인증키
q 검색할 단어
req_type 응답 형식 지정
num 한 페이지에서 요청할 결과 개수
start 검색 결과 시작 위치
type2 한자어 검색 조건에 활용
6. 한자 낱말카드 데이터 만들기

API에서 받은 데이터를 프로그램 전체에서 편리하게 사용하기 위해 dataclass로 카드 구조를 정의할 수 있습니다.

Python
from dataclasses import dataclass


@dataclass
class WordCard:
    word: str
    hanja: str
    definition: str
    pos: str
    category: str
    link: str

    @property
    def front(self) -> str:
        return self.hanja or self.word

    @property
    def meaning(self) -> str:
        return self.definition

하나의 WordCard 객체에는 표제어, 한자, 뜻풀이, 품사, 분류, 사전 링크를 저장할 수 있습니다.

문장에서 한자만 추출하기
Python
def extract_hanja(text: str) -> str:

    chars = [
        char
        for char in text
        if (
            "\u3400" <= char <= "\u9fff"
            or "\uf900" <= char <= "\ufaff"
        )
    ]

    return "".join(chars)

문자열에 한글과 한자가 함께 들어 있는 경우 Unicode 범위를 검사해 한자 문자만 추출합니다.

개념 예
입력 데이터 → 표제어 + 원어 + 뜻풀이

한자 추출

WordCard 객체 생성

카드 게임의 학습 데이터로 사용
중복 카드 제거
Python
def unique_cards(cards):
    seen = set()
    unique = []

    for card in cards:
        key = (
            card.word,
            card.hanja,
            card.definition
        )

        if key in seen:
            continue

        seen.add(key)
        unique.append(card)

    return unique

API 검색 결과에는 같은 표제어나 뜻풀이가 여러 번 나타날 수 있으므로 표제어·한자·뜻풀이를 조합한 키를 이용해 중복을 제거합니다.

7. Tkinter 한자 카드 게임으로 확장하기

우리말샘에서 가져온 데이터를 Tkinter와 연결하면 간단한 한자 낱말카드 학습 프로그램으로 확장할 수 있습니다.

학습 모드

카드 앞면에는 한자 또는 표제어를 표시하고, 카드를 클릭하면 뜻풀이를 확인하도록 구성할 수 있습니다.

퀴즈 모드

현재 카드의 뜻이나 표제어를 정답으로 지정한 뒤 다른 카드에서 오답 후보를 가져와 4지선다 문제를 만들 수 있습니다.

검색 기능

사용자가 검색창에 중, 학, 한, 국, 中 등의 검색어를 입력하면 OpenAPI에서 관련 한자어를 가져와 새로운 카드 묶음을 구성할 수 있습니다.

보기 항목 만들기
Python
import random


def pick_choices(
    correct: str,
    pool: list[str],
    count: int = 4
) -> list[str]:

    options = [correct]

    candidates = [
        item
        for item in pool
        if item and item != correct
    ]

    random.shuffle(candidates)

    for item in candidates:

        if item not in options:
            options.append(item)

        if len(options) == count:
            break

    random.shuffle(options)

    return options

정답 하나를 먼저 넣고 나머지 카드에서 오답을 선택한 뒤 random.shuffle()로 보기 순서를 섞습니다.

API 검색은 별도 스레드에서 실행
Python
worker = threading.Thread(
    target=self._search_worker,
    args=(text,),
    daemon=True
)

worker.start()
⚠ GUI 프로그램에서 중요한 부분

네트워크 API 요청은 응답을 기다리는 시간이 필요합니다. Tkinter의 메인 스레드에서 직접 긴 네트워크 요청을 실행하면 프로그램 화면이 잠시 멈춘 것처럼 보일 수 있습니다.

따라서 API 검색은 별도의 threading.Thread에서 실행하고, 화면 변경은 Tkinter의 after()를 이용해 메인 스레드에서 처리하는 구조가 적합합니다.

전체 프로그램 흐름
검색어 입력 OpenAPI 요청 JSON 분석 WordCard 생성 중복 제거 Tkinter 카드 출력
실행 방법
Terminal
python hanja_card_game_open.py

OpenAPI를 사용하지 않고 로컬 데이터를 이용하는 카드 게임이 있다면 다음과 같이 별도로 실행할 수 있습니다.

Terminal
python hanja_card_game.py
프로젝트 폴더 구성 예
Folder Structure
hanja_project/
│
├── Unihan/
│   ├── Unihan_DictionaryIndices.txt
│   ├── Unihan_DictionaryLikeData.txt
│   └── ...
│
├── extract_hanja_strokes.py
├── hanja_strokes.json
├── hanja_card_game_open.py
└── hanja_card_game.py
💡 학습 순서 추천

처음부터 GUI 전체를 만들려고 하기보다 Unihan 데이터 추출 → JSON 확인 → OpenAPI 호출 → WordCard 생성 → Tkinter 연결 순서로 하나씩 완성하는 것이 좋습니다.

📌 한자사전 DB 프로젝트 핵심 정리
  • Unicode Unihan 자료에서 한자의 다양한 속성을 얻을 수 있다.
  • kTotalStrokes 필드를 이용하면 한자별 총 획수를 추출할 수 있다.
  • Unicode 코드 포인트는 int()와 chr()를 이용해 실제 문자로 변환할 수 있다.
  • 추출한 데이터는 JSON으로 저장하면 다른 Python 프로그램에서 활용하기 편리하다.
  • 국립국어원 우리말샘 OpenAPI를 이용해 한자어와 뜻풀이를 검색할 수 있다.
  • API 응답은 WordCard와 같은 객체로 변환하면 프로그램 관리가 쉬워진다.
  • 중복 데이터를 제거한 뒤 Tkinter 카드 게임의 학습 자료로 활용할 수 있다.
  • GUI에서 네트워크 요청을 처리할 때는 별도 스레드를 활용할 수 있다.
  • API 인증키는 공개 소스코드에 직접 넣지 않고 환경 변수로 관리하는 것이 좋다.

프로그램·홈페이지·강의가 필요하신가요?

프로그램 판매, 무료 다운로드, 홈페이지 제작, IT 강의 상담을 도와드립니다.

상담 신청하기