수집 → 정제 → 분석 → 시각화 → 논문 결과물
박수현 · aSSIST University · 2026
🎨 이미지 프롬프트(배경): "An end-to-end research pipeline visualized as flowing data — from web sources through processing stages to a final published paper figure, glowing teal and purple light, editorial illustration, 16:9, no text"
📖 도서 매핑: Ch.13~14 (Mini Project) + Ch.11 (감성분석) + Ch.15 (다음 여정)
회차 1~7 을 한 줄로 잇기
| 단계 | 회차 | 주요 도구 |
|---|---|---|
| 1 수집 | 3·5·6 | requests · BS4 · API · Selenium |
| 2 정제 | 4 | pandas |
| 3 비식별화 | 7 | re · Faker · Presidio (Microsoft 비식별화 라이브러리) |
| 4 텍스트 | 8 (오늘) | OpenAI / Claude / Ollama |
| 5 통계 | 4 | pandas · scipy |
| 6 시각화 | 4 | matplotlib · seaborn |
| 7 산출물 | 8 (오늘) | PDF · CSV · LaTeX |
각 단계는 "완료 여부"보다 재현 가능성(같은 입력에서 같은 출력이 나오는지) 을 기준으로 점검합니다. 실습에서는 1~7 단계를 한 번에 완성하기보다, 1→3→4→7처럼 작은 루프로 먼저 연결해 점진적으로 확장하세요.
research_project/
├── 0_data/
│ ├── raw/ # 절대 수정 X
│ ├── interim/ # 중간 산출물
│ └── processed/ # 분석 가능 형태
├── 1_collect/
│ ├── 01_api_call.py # 회차 5
│ ├── 02_scrape_static.py # 회차 3
│ └── 03_selenium_login.py # 회차 6
├── 2_process/
│ ├── 11_clean.py # 회차 4
│ └── 12_anonymize.py # 회차 7
├── 3_analyze/
│ ├── 21_llm_classify.py # 회차 8
│ └── 22_stats.ipynb # 회차 4
├── 4_figures/
│ ├── 31_figure1.py
│ └── 32_figure2.py
├── 5_outputs/
│ ├── figures/ # PDF/PNG
│ └── tables/ # CSV/LaTeX
├── .env # API 키 (절대 커밋 X)
├── requirements.txt
└── README.md
🎯 번호 prefix 로 실행 순서 명시. 누가 따라와도 재현 가능.
요약 · 분류 · 감성 · 주제
| 작업 | 입력 | 출력 | 용도 |
|---|---|---|---|
| 요약 | 긴 글 | 짧은 요약 | 공시·논문 초록 |
| 분류 | 짧은 글 | 카테고리 | 토픽·장르·스팸 |
| 감성분석 | 리뷰·댓글 | 긍정/부정/중립 (pos/neg/neu) + 강도 점수 | 평판·만족도 |
| 주제 추출 | 글 묶음 | 키워드·주제 N개 | 트렌드 분석 |
🎯 회차 5의 첫 호출 → 오늘은 1만 건 일괄 처리.

import os, asyncio, json, pandas as pd
from anthropic import AsyncAnthropic
from tqdm.asyncio import tqdm
client = AsyncAnthropic()
SEM = asyncio.Semaphore(10) # 동시 10건 — Rate Limit 안전
async def classify(text: str) -> dict:
async with SEM:
msg = await client.messages.create(
# Anthropic 모델 ID 는 날짜 suffix 형태 (예: claude-haiku-4-5-20251001)
# 또는 별칭 claude-haiku-4-5-latest — 강의일에 공식 페이지 확인
model="claude-haiku-4-5-latest",
max_tokens=200,
messages=[{
"role":"user",
"content": (
"리뷰를 JSON 으로 분석해.\n"
'{"sentiment":"positive|negative|neutral","topics":[],"summary":""}\n\n'
f"리뷰: {text}"
)
}],
)
try:
return json.loads(msg.content[0].text)
except Exception:
return {"sentiment":"unknown","topics":[],"summary":""}
async def run(texts):
return await tqdm.gather(*(classify(t) for t in texts))
if __name__ == "__main__":
df = pd.read_csv("data/processed/reviews_masked.csv")
results = asyncio.run(run(df["text"].tolist()))
df["sentiment"] = [r["sentiment"] for r in results]
df["topics"] = [json.dumps(r["topics"], ensure_ascii=False) for r in results]
df.to_csv("data/processed/reviews_labeled.csv", index=False, encoding="utf-8-sig")
🎯 1,000건 ≈ 30초 / $0.3~0.7 (Haiku, 리뷰 평균 약 200토큰 가정 — 입력 길이에 따라 달라짐).
df["sentiment"].value_counts().plot.bar()
리뷰 1,000건의 감성 비율을 한눈에 보여줍니다.

df.groupby([df["date"].dt.to_period("M"),
"sentiment"]).size()\
.unstack().plot.area()
월별 감성 변화 추이를 보여줍니다.

📌 도서 Ch.11 §6 — "시간이 데이터에 들어오면 시계열".


🎯 LLM 프롬프트 한 줄
"리뷰에서 칭찬 측면과 불만 측면을 각각 키워드 리스트로"
매번 손으로 만들지 않는다
import matplotlib.pyplot as plt
import seaborn as sns
from pathlib import Path
def paper_figure(plot_fn, *, name: str, outdir="5_outputs/figures",
size=(7, 4), title="", xlabel="", ylabel=""):
"""seaborn 기반 표준 figure 생성 + PNG·PDF 동시 저장."""
Path(outdir).mkdir(parents=True, exist_ok=True)
sns.set_theme(style="whitegrid", palette="colorblind",
rc={"axes.spines.top": False, "axes.spines.right": False})
fig, ax = plt.subplots(figsize=size)
plot_fn(ax)
if title: ax.set_title(title, fontsize=11)
if xlabel: ax.set_xlabel(xlabel)
if ylabel: ax.set_ylabel(ylabel)
fig.savefig(f"{outdir}/{name}.pdf", dpi=300, bbox_inches="tight")
fig.savefig(f"{outdir}/{name}.png", dpi=300, bbox_inches="tight")
plt.close(fig)
return f"{outdir}/{name}"
# 사용
paper_figure(
lambda ax: sns.barplot(data=df, x="rating", y="price", ax=ax, errorbar=("ci",95)),
name="fig1_price_by_rating",
title="Average book price by star rating",
xlabel="Star rating", ylabel="Price (£)",
)
🎯 한 번 만들면 Figure 10개 만들 때도 한 함수.
📌
errorbar=("ci", 95)는 seaborn ≥ 0.12 부터의 새 인자. 이전 버전은ci=95사용. 환경 확인:python -c "import seaborn; print(seaborn.__version__)".
import pandas as pd
# 1) 통계 표
agg = (df.groupby("rating")
.agg(N=("title","size"),
Mean=("price","mean"),
SD=("price","std"))
.round(2))
# 2) Markdown — README, 노션
print(agg.to_markdown())
# 3) LaTeX — 학회 논문
print(agg.to_latex(
caption="Price by star rating.",
label="tab:price_rating",
column_format="lrrr",
escape=True,
))
# 4) CSV — 부록·재현용
agg.to_csv("5_outputs/tables/table1_price_rating.csv",
encoding="utf-8-sig")
📌
to_latex()+\input{table1.tex}= LaTeX 본문 자동 삽입.⚙️
to_markdown()은tabulate패키지가 필요 —pip install tabulate
도서 Ch.13·14 의 프로젝트 사례
📖 도서 참조: Ch.13 — "프로젝트 1: 상품 가격 비교 분석기".


💬 Antigravity / ChatGPT 입력 collect.py
📋 prompt prompts/ch13_prompts.md 📄 example examples/ch13/price_compare/collect.py
Python 모듈 examples/ch13/price_compare/collect.py 를
만들어 줘.
목적: 네이버 쇼핑 검색 API 래퍼.
한 키워드에 대해 최대 200건까지 페이지네이션으로
수집하는 fetch_shop + 여러 키워드를 순회하는
fetch_many 헬퍼 제공.
요구사항:
1. 라이브러리: requests, pandas, python-dotenv,
표준 os, time
2. .env 의 NAVER_CLIENT_ID, NAVER_CLIENT_SECRET 로
공통 헤더 HEADERS 구성
3. 엔드포인트 상수:
BASE = "https://openapi.naver.com/v1/search/shop.json"
4. 함수
fetch_shop(query: str, max_items: int = 200,
delay: float = 0.2) -> pd.DataFrame:
- display=100 으로 시작, start 를 1 부터
min(max_items, 1000)+1 까지 display 간격 반복
- params: query, display, start, sort="sim"
- r.raise_for_status()
- items = r.json().get("items", [])
- items 비면 중단, rows 누적, time.sleep(delay)
- 누적 ≥ max_items 되면 중단
- 마지막에 pd.DataFrame(rows[:max_items]) 반환
5. 함수 fetch_many(keywords: list[str],
each: int = 100):
- 키워드별 fetch_shop 호출
- keyword 컬럼 추가 후 pd.concat 합침
6. __main__ 블록: fetch_shop("무선 이어폰",
max_items=50)
호출 후 [["title","lprice","mallName","link"]] 출력
에러 처리:
- HTTP 오류 → 메시지 후 현재까지의 rows 반환
- 429 한도 초과 → 60초 대기 후 1회 재시도,
실패 시 현재까지 반환
- 환경변수 누락 → 안내 후 빈 DataFrame 반환
환경: Python 3.11, venv. 코드만, 한국어 주석 최소.
collect.py (수집) / process.py (정제) / analyze.py (분석) 의 책임 분리 → 디버깅·재사용 쉬움display=100, max_items=200 두 파라미터로 학습용과 실습 모드를 전환합니다💡 응용:
- 네이버 쇼핑 → 카카오·구글 등 다른 API
- "무선 이어폰" → 본인 연구 키워드
-fetch_many(["키워드1", "키워드2", ...])
로 다중 키워드 자동 수집
📖 출처:
ch13_prompts.md§2 — 수집 모듈.
📖 도서 참조: Ch.14 — "프로젝트 2: 리뷰 감성분석 대시보드".


💬 Antigravity / ChatGPT 입력 analyze.py · analyze_batch
📋 prompt prompts/ch14_prompts.md 📄 example examples/ch14/review_sentiment/analyze.py
같은 analyze.py 에 배치 함수
analyze_batch(reviews: list[str]) -> list[dict]
를 추가해 줘.
목적: 여러 리뷰를 한 번의 LLM 호출로 처리해
속도·비용을 줄인다.
요구사항:
1. 리뷰 리스트를 "1. ...\n2. ..." 형식으로
번호 매긴 단일 문자열 numbered 로 합친다.
2. 상단에 BATCH_TPL 상수:
"""
아래 리뷰들 각각을 위와 동일한 스키마로
분석해 [{"idx": 1, ...}, ...] 형태의
JSON 배열로 돌려주세요.
리뷰:
{numbered}
"""
3. 호출:
client.chat.completions.create(
model="gpt-4o-mini",
messages=[
{role:"system", content:SYSTEM},
{role:"user",
content:BATCH_TPL.format(numbered=numbered)}
],
response_format={"type":"json_object"},
temperature=0,
)
4. JSON 파싱:
data = json.loads(resp.choices[0].message.content)
응답이 배열 직접이 아니라 최상위 key 로
감싸져 올 수 있다. 다음 순서로 추출:
- data.get("items")
- 없으면 data.get("results")
- 그래도 없으면 list(data.values())[0]
5. 반환 타입은 list[dict].
각 dict 는 analyze_one 과 동일 스키마 + idx
에러 처리:
- JSON 파싱 실패 시 각 입력 리뷰마다
기본 dict (neutral / not_mentioned) 리스트 반환
- API 호출 실패 시 동일 기본값 리스트 반환
환경: Python 3.11, venv. 코드만, 한국어 주석 최소.
| 단건 호출 (1만 건) | 배치 호출 (500 × 20건) | |
|---|---|---|
| 호출 수 | 10,000 회 | 500 회 |
| 시간 | 약 50 분 | 약 5 분 |
| 비용 | 약 $1.0 | 약 $0.4 |
items / results / 다른 키 → 3단 fallback 필수idx 를 명시 요청 → 매핑 정확⚠️ 개인정보 보호 순서 (회차 7 연결):
마스킹 → analyze_batch → 결과 매핑 → 폐기
📖 출처:
ch14_prompts.md§3 — LLM 전처리 파이프라인.
yfinance (회차 5)📌 댓글 감성 과 다음날 가격 변동 의 상관 — 학위논문 토픽 인기.


💬 ChatGPT / Claude 입력 stock_sentiment.py
📋 prompt prompts/ch12_prompts.md 📄 example examples/ch12/scrape_discussion.py
주가 시계열과 토론 댓글 감성을 일별로 결합해 산점도
+ 병합 CSV 두 장을 만드는 stock_sentiment.py 를
만들어 줘.
입력 두 개:
- prices.csv (date, close) # yfinance 결과
- discussions.csv (date, text) # 토론 댓글 본문
요구사항:
1. 라이브러리: pandas, matplotlib, openai,
python-dotenv, hashlib, pathlib
2. 단계:
(a) text → sentiment_of(text) 로 score(-1.0~1.0)
만 추출 (다른 슬라이드의 LLM 감성 헬퍼 호출)
(b) date 별 score 평균 → daily_sentiment
(c) prices 와 inner-join (date)
(d) close 의 일간 수익률 ret = pct_change()
(e) 감성 vs 다음날 수익률(ret.shift(-1)) 산점도
— matplotlib, 1200×800, 300dpi (학회용)
(f) Pearson 상관계수 1줄 콘솔 출력
3. LLM 결과 캐싱:
- cache_llm/ 디렉토리에 sha256(date+text) 를 파일명
- 적중 시 API 안 부르고 캐시 read
- 미스 시 API 호출 후 캐시 write
4. 진행 표시: tqdm 로 모든 댓글 1건씩
출력:
- charts/sentiment_vs_return.png
- charts/sentiment_vs_return.csv (병합 결과)
에러: 한 댓글 LLM 실패 → score=0.0 채우고 계속.
환경: Python 3.11, venv. 코드만, 한국어 주석 최소.
💡 연구 응용: 토론 감성 → 다음날 수익률. "여론 선행 지표" 가설 검증의 미니 사례. 댓글의 PII (작성자명 등) 는 회차 7 헬퍼로 비식별화 후 호출.
본인 연구 주제로 1개
| # | 슬라이드 | 내용 (1분 분량) |
|---|---|---|
| 1 | 연구 질문 | "X 가 Y 에 영향을 주는가?" |
| 2 | 데이터 출처 | 사이트·기간·건수, 윤리 점검 |
| 3 | 방법 | 수집 → 정제 → 분석의 한 줄 다이어그램 |
| 4 | 결과 | 핵심 Figure 1장 (300 DPI) |
| 5 | 한계·다음 단계 | 표본·재현성·확장 |
| 시간 | 활동 |
|---|---|
| 0~5 | 환영 + 진행 안내 |
| 5~85 | 발표 8명 × (5분 발표 + 5분 Q&A) = 80분 |
| 85~90 | 마무리 + 다음 단계 |
8회차는 마무리이자 새로운 시작점
📖 도서1 Ch.12·13 — 에이전트가 도구를 스스로 선택하는 패턴이 다음 단계의 자연스러운 행보.


📌 학위논문 → arXiv 검색 → 본인 분야 최신 논문 5편 정해 매주 1편씩.
| 회차 | 핵심 한 줄 |
|---|---|
| 1 | HTML/CSS/JS · HTTP · 정적/동적 · DOM = 어휘 |
| 2 | F12 5대 패널 — Elements + Network 가 80% |
| 3 | requests + BS4 + safe_get + 페이지네이션 |
| 4 | pandas 5동사 + seaborn 5종 + 윤리 5계명 + 판례 4건 |
| 5 | API 우선 · 공공데이터 · LLM 첫 호출 |
| 6 | Selenium 4단계 · Wait · 로그인 → 쿠키 이전 |
| 7 | 비식별화 5기법 · k-익명성 · LLM 입력 마스킹 |
| 8 | 통합 파이프라인 · LLM 4작업 · Figure/Table 자동 |
"도구를 잘 쓰는 것만큼, 데이터를 매너 있게 다루는 자세도 중요합니다."
"데이터를 다루는 기술 만큼, 다루는 태도 가 곧 연구자의 품격으로 이어집니다."

"오늘 익힌 30줄 코드가 5년 뒤에도 의미 있도록 —
코드만큼 태도를, 결과만큼 방법을 함께 남기는 연구가 되시길 응원합니다."다음 기회에 또 뵙겠습니다 👋