
여기서는 "딕셔너리(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("프로그래밍 언어", "알 수 없음")) # 알 수 없음
# 리스트: 순서로 접근 (인덱스)
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_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
이번 코드의 주역은 "requests 모듈", "딕셔너리", 그리고 새로 등장한 "GraphQL" 입니다.
일반적으로 카카오/네이버 API는 모두 REST API 방식이었습니다.
Velog는 GraphQL 방식을 사용합니다. 겉보기에 다르지만, 본질은 같습니다.
| REST API | GraphQL | |
|---|---|---|
| 요청 방식 | GET (URL 파라미터) | POST (JSON 본문) |
| 엔드포인트 | 기능마다 다른 URL | URL 하나 (/graphql) |
| 원하는 필드 지정 | 서버가 결정 | 클라이언트가 직접 지정 |
| 예시 | GET /items?page=1 | POST /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 } }"})
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=딕셔너리)로 전송합니다.
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": "..."})
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 형식으로 텍스트 표시 |
쿼리의 파라미터를 바꾸면 다른 데이터를 가져올 수 있습니다.
# 게시글 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 키 안에서 추출