[Python 재입문] 8. Python 딕셔너리 완전 정복 - requests API 요청/JSON 변환/Gemini AI 분석까지 실전 가이드

AgentKim·2026년 3월 13일
post-thumbnail

업로드중..

여기서는 "딕셔너리(dictionary)"라는 Python의 데이터 구조를 배웁니다.
딕셔너리를 익히면서, 동시에 공개 API에서 데이터를 가져와 AI에게 분석시키는 실전 패턴도 함께 익혀봅시다.


딕셔너리란 무엇인가

딕셔너리는 키(Key)와 값(Value)의 쌍을 저장하는 데이터 구조입니다.
{}로 감싸인 형식으로 표현되며, 각 항목은 "키": 값 형태입니다.

# 딕셔너리 정의 예시
programing_language = {
    "언어": "Python",
    "버전": 3,
    "특징": "간결하고 읽기 쉽다"
}
print(programing_language["언어"])   # Python
print(programing_language["버전"])   # 3
print(programing_language["특징"])   # 간결하고 읽기 쉽다

programing_language["버전"] = 3.10
print(programing_language["버전"])   # 3.10

print(programing_language.get("프로그래밍 언어", "알 수 없음"))  # 알 수 없음

리스트 vs 딕셔너리

# 리스트: 순서로 접근 (인덱스)
fruits = ["사과", "바나나", "체리"]
print(fruits[0])   # 사과

# 딕셔너리: 이름(키)으로 접근
person = {"이름": "김민준", "나이": 28, "직업": "개발자"}
print(person["이름"])  # 김민준
리스트딕셔너리
접근 방법인덱스 번호 ([0])키 이름 (["이름"])
형식[값1, 값2, ...]{"키1": 값1, "키2": 값2}
순서유지됨유지됨 (Python 3.7+)
활용순서 있는 목록이름이 있는 속성 집합

딕셔너리 기본 조작

값 읽기 — 두 가지 방법:

person = {"이름": "이서윤", "나이": 25}

# 방법1: 키로 직접 접근 (키가 없으면 KeyError 발생)
print(person["이름"])   # 이서윤

# 방법2: get() 사용 (키가 없으면 기본값 반환, 에러 없음)
print(person.get("직업", "알 수 없음"))  # 알 수 없음
print(person.get("나이", 0))             # 25

값 추가 / 수정:

person["직업"] = "디자이너"    # 새 키 추가
person["나이"] = 26             # 기존 키 수정
print(person)
# {"이름": "이서윤", "나이": 26, "직업": "디자이너"}

키 존재 확인:

if "이름" in person:
    print("이름 키가 있습니다.")

# 모든 키 목록 확인
print(list(person.keys()))    # ['이름', '나이', '직업']
# 모든 값 목록 확인
print(list(person.values()))  # ['이서윤', 26, '디자이너']

딕셔너리에 저장할 수 있는 값의 타입

값의 타입예시
문자열"Python", "JavaScript"
숫자3, 3.10
불리언True, False
리스트["간결함", "읽기 쉬움"]
튜플("간결함", "읽기 쉬움")
딕셔너리 (중첩){"특징": "간결하고 읽기 쉽다"}
함수lambda x: x * 2
인스턴스MyClass()

(튜플/함수/인스턴스는 별도 챕터에서 자세히 다룹니다)


Velog API로 AI에게 기술 트렌드를 분석시키는 코드 (velog_analyzer.py)

아래 샘플 코드를 velog_analyzer.py라는 이름으로 저장하세요.

💡 Velog API는 GraphQL을 사용합니다
URL 파라미터를 붙이는 REST 방식이 아니라,
쿼리(query)를 JSON 본문에 담아 POST 요청으로 보내는 방식입니다.
겉보기에 다르지만, "딕셔너리로 요청을 정의한다"는 핵심 구조는 동일합니다.

import requests
from langchain_google_genai import ChatGoogleGenerativeAI
from dotenv import load_dotenv
import os
import streamlit as st

# -----------------------------------------------
# Velog GraphQL API 엔드포인트 정의
# -----------------------------------------------
# Velog는 REST API 대신 GraphQL API를 사용합니다.
# GraphQL은 "원하는 데이터 항목을 쿼리로 직접 지정"하는 방식입니다.
# 엔드포인트는 하나이지만, 쿼리 내용에 따라 다른 데이터를 가져올 수 있습니다.
VELOG_API_URL = "https://v2.velog.io/graphql"

# -----------------------------------------------
# GraphQL 쿼리 정의 (딕셔너리)
# -----------------------------------------------
# GraphQL 쿼리는 "어떤 데이터를 얼마나 가져올지"를 정의하는 문자열입니다.
# trendingPosts: 이번 주 인기 게시글을 가져오는 쿼리
# limit: 가져올 게시글 수 (최대 20)
# offset: 건너뛸 게시글 수 (0 = 처음부터)
# timeframe: 기간 ("week" = 이번 주, "month" = 이번 달)
graphql_query = {
    "query": """
        query {
            posts(limit: 20) {
                id
                title
                short_description
                tags
                user {
                    username
                }
            }
        }
    """
}

# -----------------------------------------------
# Velog GraphQL API에 POST 요청을 보낸다
# -----------------------------------------------
# GraphQL은 항상 POST 요청입니다.
# REST의 GET 요청과 달리, 요청 본문(body)에 쿼리를 JSON으로 담아 보냅니다.
#
# REST GET:  requests.get(url, params={"page": 1})     → URL 파라미터로 전달
# GraphQL:   requests.post(url, json={"query": "..."}) → JSON 본문으로 전달
titles = []

try:
    response = requests.post(
        VELOG_API_URL,
        json=graphql_query,  # 딕셔너리를 JSON 본문으로 자동 변환하여 전송
        headers={"Content-Type": "application/json"}
    )

    if response.status_code == 200:
        # GraphQL 응답 구조: {"data": {"posts": [게시글1, 게시글2, ...]}}
        # REST와 달리 응답이 항상 "data" 키 안에 중첩되어 있습니다.
        result = response.json()
        posts = result.get("data", {}).get("posts", [])

        # 게시글 정보(posts)를 루프로 처리하여 제목만 추출합니다.
        # post는 개별 게시글 정보를 담은 딕셔너리입니다.
        for post in posts:
            title = post.get("title")  # 딕셔너리에서 "title" 키의 값을 취득
            if title:                  # 제목이 None이 아닌 경우만 추가
                titles.append(title)

        # 디버그용: 가져온 제목 수와 목록 출력
        print(f"=== Velog 인기 게시글 제목 ({len(titles)}건) ===")
        for title in titles:
            print(f"  - {title}")

    else:
        st.warning(f"Velog API 요청이 실패했습니다. 상태 코드: {response.status_code}")

except Exception as e:
    st.error(f"Velog 데이터 취득에 실패했습니다: {e}")

# -----------------------------------------------
# Gemini를 사용하여 Velog 게시글 제목을 분석한다
# -----------------------------------------------
load_dotenv()
google_api_key = os.getenv("GOOGLE_API_KEY")

if not google_api_key:
    st.error("Google API 키를 찾을 수 없습니다. .env 파일을 확인해주세요.")
    exit(1)

if not titles:
    st.error("Velog에서 게시글 제목을 가져오지 못했습니다. 잠시 후 다시 시도해주세요.")
    exit(1)

gemini = ChatGoogleGenerativeAI(
    google_api_key=google_api_key,
    model="gemini-2.5-flash"
)

prompt = f"""
다음은 Velog 이번 주 인기 게시글 제목 목록입니다.\n
이 제목들을 분석하여, AI 주도 개발 시대에,\n
초보자가 「기본 문법」을 익히는 의의를 알려주세요.\n\n
【Velog 인기 게시글 제목】\n\n
{titles}
"""

ai_response = gemini.invoke(prompt)

# -----------------------------------------------
# Gemini의 답변을 Streamlit으로 표시한다
# -----------------------------------------------
st.title("Velog 기술 트렌드 분석")
st.subheader(f"이번 주 인기 게시글 {len(titles)}건 기반 분석")
st.markdown("---")
st.markdown("#### 📋 분석에 사용한 게시글 제목")
for t in titles:
    st.markdown(f"- {t}")
st.markdown("---")
st.markdown("#### 🤖 AI 분석 결과")
st.markdown(ai_response.content)

# 기동 명령어:
# streamlit run velog_analyzer.py
# 종료:
# Ctrl + C

코드 해설 (velog_analyzer.py)

이번 코드의 주역은 "requests 모듈", "딕셔너리", 그리고 새로 등장한 "GraphQL" 입니다.


REST API vs GraphQL — 두 가지 요청 방식 비교

일반적으로 카카오/네이버 API는 모두 REST API 방식이었습니다.
Velog는 GraphQL 방식을 사용합니다. 겉보기에 다르지만, 본질은 같습니다.

REST APIGraphQL
요청 방식GET (URL 파라미터)POST (JSON 본문)
엔드포인트기능마다 다른 URLURL 하나 (/graphql)
원하는 필드 지정서버가 결정클라이언트가 직접 지정
예시GET /items?page=1POST /graphql + 쿼리 본문
# REST: URL 파라미터로 요청
requests.get("https://xxxxx.com/api/v2/items", params={"page": 1})

# GraphQL: JSON 본문으로 쿼리를 전송
requests.post("https://v2.velog.io/graphql", json={"query": "{ trendingPosts(...) { title } }"})

GraphQL 쿼리 — "원하는 것만 골라 주문하는" 방식

REST API는 서버가 정해준 데이터를 통째로 받습니다.
GraphQL은 내가 원하는 필드만 쿼리로 지정해서 받을 수 있습니다.

graphql_query = {
    "query": """
        query {
            posts(limit: 20) {
                id
                title
                short_description
                tags
                user {
                    username
                }
            }
        }
    """
}

쿼리 구조 해설:

posts(limit: 20)        ← 함수 이름 + 인수
    limit: 20           ← 가져올 게시글 수 (최대 20)

{                       ← 가져올 필드 목록 (원하는 것만 지정)
    id
    title
    short_description
    tags
    user { username }   ← 중첩 객체도 가능
}

이 딕셔너리가 JSON으로 변환되어 POST 요청의 본문(body) 에 담깁니다.

💡 포인트: GraphQL에서도 딕셔너리가 핵심입니다.
{"query": "..."} 형태로 쿼리를 딕셔너리에 담아 requests.post(url, json=딕셔너리)로 전송합니다.


POST 요청과 JSON 본문

response = requests.post(
    VELOG_API_URL,
    json=graphql_query,
    headers={"Content-Type": "application/json"}
)
  • requests.post(): 데이터를 서버로 보낼 때 사용합니다.
  • json=딕셔너리: 딕셔너리를 자동으로 JSON 텍스트로 변환하여 본문에 담습니다.
  • Content-Type: application/json: "본문이 JSON 형식이다"라고 서버에 알려주는 헤더입니다.
# GET: URL 파라미터로 전달 (딕셔너리 → URL ?key=value 로 변환)
requests.get(url, params={"page": 1, "per_page": 100})

# POST: JSON 본문으로 전달 (딕셔너리 → JSON 문자열로 변환)
requests.post(url, json={"query": "..."})

GraphQL 응답 구조 — data 키 안에 중첩

result = response.json()
posts = result.get("data", {}).get("trendingPosts", [])

GraphQL 응답은 항상 data 키 안에 중첩되어 있습니다.

# REST API 응답 구조
[{"title": "게시글1"}, {"title": "게시글2"}, ...]

# GraphQL 응답 구조 (항상 "data" 키 안에 들어있음)
{
    "data": {
        "posts": [
            {"id": "...", "title": "게시글1", "tags": [...], "user": {"username": "..."}},
            {"id": "...", "title": "게시글2", ...},
            ...
        ]
    }
}

따라서 데이터에 접근하려면 두 번 get()을 사용합니다.

result = response.json()                      # 전체 응답
result.get("data", {})                        # "data" 키 접근 (없으면 빈 딕셔너리)
result.get("data", {}).get("posts", [])       # "posts" 접근 (없으면 빈 리스트)

체이닝(연결) 방식의 안전한 중첩 접근 패턴입니다.
중간에 None이 나와도 get(기본값) 덕분에 에러가 발생하지 않습니다.


post.get('title') — 딕셔너리에서 특정 키만 추출

titles = []
for post in posts:
    title = post.get("title")
    if title:
        titles.append(title)

post는 게시글 한 건의 정보가 담긴 딕셔너리입니다.
쿼리에서 요청한 필드(id, title, short_description, tags, user)만 포함되어 있습니다.

# post의 실제 내용 (딕셔너리)
post = {
    "id": "abc123",
    "title": "Python으로 AI 앱 만들기",       # ← 여기만 필요
    "short_description": "이 글에서는...",
    "tags": ["Python", "AI", "Streamlit"],
    "user": {"username": "velog_user"}
}

title = post.get("title")   # "Python으로 AI 앱 만들기"만 추출

if title: 조건을 추가한 이유는, 제목이 None인 게시글이 간혹 있을 수 있기 때문입니다 (Falsy 체크).


전체 데이터 흐름 정리

graphql_query = {"query": "{ posts(limit: 20) { title ... } }"}  ← 딕셔너리
    ↓ requests.post(url, json=딕셔너리)
POST 요청 전송 (JSON 본문)
    ↓ 서버에서 JSON 텍스트 반환
response.json()
    ↓
result = {"data": {"posts": [게시글1, 게시글2, ...]}}
    ↓ result.get("data", {}).get("posts", [])
posts = [{"title": "제목1", ...}, {"title": "제목2", ...}, ...]  ← 게시글 리스트
    ↓ for post in posts: title = post.get("title")
titles = ["제목1", "제목2", ..., "제목20"]  ← 제목만 추출한 리스트
    ↓ f-string으로 prompt에 삽입
prompt = "다음 제목들을 분석해주세요...\n{titles}"
    ↓ gemini.invoke(prompt)
ai_response.content  ← AI 분석 결과 (Markdown 텍스트)
    ↓ st.markdown()
브라우저에 기술 트렌드 분석 결과 표시

기타 구문 정리

구문역할
try: ... except Exception as e:에러 발생 시 except 블록 실행
if not google_api_key:API 키가 없으면(Falsy) 실행
if not titles:제목 리스트가 비어있으면 실행
exit(1)비정상 종료 (계속 실행 불가 시)
st.warning(msg)🟡 노란색 경고 박스 표시
st.error(msg)🔴 빨간색 에러 박스 표시
st.title(msg)가장 큰 제목 표시
st.subheader(msg)서브타이틀 표시
st.markdown(msg)Markdown 형식으로 텍스트 표시

응용: 다른 GraphQL 쿼리로 바꿔보기

쿼리의 파라미터를 바꾸면 다른 데이터를 가져올 수 있습니다.

# 게시글 20건 가져오기 (기본)
graphql_query = {
    "query": """
        query {
            posts(limit: 20) {
                id
                title
                short_description
                tags
            }
        }
    """
}

# 특정 태그의 게시글 검색
graphql_query = {
    "query": """
        query {
            posts(limit: 20, tag: "python") {
                id
                title
                short_description
            }
        }
    """
}

또한 국내 REST API와도 같은 코드 구조로 조합할 수 있습니다.

# 카카오 로컬 API (REST) — GET 방식
kakao_response = requests.get(
    "https://dapi.kakao.com/v2/local/search/keyword.json",
    headers={"Authorization": f"KakaoAK {os.getenv('KAKAO_API_KEY')}"},
    params={"query": "강남 맛집", "size": 15}
)

# 네이버 검색 API (REST) — GET 방식
naver_response = requests.get(
    "https://openapi.naver.com/v1/search/news.json",
    headers={
        "X-Naver-Client-Id": os.getenv("NAVER_CLIENT_ID"),
        "X-Naver-Client-Secret": os.getenv("NAVER_CLIENT_SECRET")
    },
    params={"query": "인공지능", "display": 100, "sort": "date"}
)

정리

개념한 줄 요약
딕셔너리{"키": 값} 형식으로 키-값 쌍을 저장하는 데이터 구조
dict["키"]키로 값에 접근. 키가 없으면 KeyError
dict.get("키", 기본값)키가 없어도 에러 없이 기본값 반환
REST API기능마다 다른 URL, GET 요청 + URL 파라미터 방식
GraphQL엔드포인트 하나, POST 요청 + JSON 본문(쿼리)으로 원하는 필드 지정
requests.get(url, params=딕셔너리)딕셔너리를 URL 파라미터로 자동 변환하여 GET 요청 전송
requests.post(url, json=딕셔너리)딕셔너리를 JSON 본문으로 변환하여 POST 요청 전송
HTTP 상태 코드 200요청 성공. 그 외는 에러
response.json()JSON 텍스트 → Python 딕셔너리/리스트로 변환
GraphQL 응답 구조항상 {"data": {"쿼리명": [...]}} 형태로 중첩
post.get('title')딕셔너리에서 특정 키의 값만 안전하게 추출

©2024-2026 MDRULES.dev, Hand-crafted & made with Jaewoo Kim.
이메일문의: jaewoo@mdrules.dev

AI강의/개발/기술자문, AI 업무 자동화 컨설팅  문의: https://talk.naver.com/ct/w5umt5

AI 프롬프트 및 워크플로우 설계 대행: https://mdrules.dev

📌 REST vs GraphQL 핵심 패턴 비교

# REST (Kakao, Naver, 공공데이터포털 등)
params = {"query": "파이썬", "size": 20}             # ① 딕셔너리로 파라미터 정의
response = requests.get(url, params=params)        # ② GET 요청
items = response.json()                            # ③ JSON → 리스트/딕셔너리

# GraphQL (Velog 등)
query = {"query": "{ trendingPosts(...) { title } }"}  # ① 딕셔너리로 쿼리 정의
response = requests.post(url, json=query)              # ② POST 요청
posts = response.json()["data"]["trendingPosts"]       # ③ data 키 안에서 추출
profile
AI-fluent liberal arts engineer | AX Consulting | AI Workflow Architect | LLM Engineer | Claude Code | AI강의/개발/기술자문 | AI 업무 자동화 컨설팅

0개의 댓글