데이터 수집 및 전처리
크롤링 실습 2
복습 + pandas 추가 — 구글 뉴스 DataFrame으로 만들기
어제(Day10) 배운 구글 뉴스 크롤링 코드에 pandas가 추가됩니다. CSV로 저장하는 건 똑같지만, 수집한 데이터를 DataFrame이라는 표 형태로도 동시에 만들어두면 이후 분석과 전처리가 훨씬 편해집니다.
pd.DataFrame()으로 리스트를 표로 만들고, 정렬·필터·저장을 쉽게 할 수 있습니다.
어제 코드에서 달라진 점
3곳에 pandas 관련 코드가 추가됩니다. 나머지는 완전히 동일합니다.
!pip install requests beautifulsoup4 lxml -q import requests, csv import pandas as pd # ★ 추가: DataFrame 만들기 위해 from bs4 import BeautifulSoup from urllib.parse import quote_plus from google.colab import files from datetime import datetime # ★ 추가: 날짜 변환 위해 search_query = "마케팅" rss_url = f"https://news.google.com/rss/search?q={quote_plus(search_query)}&hl=ko&gl=KR&ceid=KR:ko" response = requests.get(rss_url, headers={"User-Agent": "Mozilla/5.0"}) soup = BeautifulSoup(response.text, "xml") items = soup.find_all("item") news_list = [] # ★ 추가: DataFrame용 빈 리스트 filename = f"news_{search_query}.csv" with open(filename, "w", newline="", encoding="utf-8-sig") as f: writer = csv.writer(f) writer.writerow(["제목", "출처", "날짜", "링크"]) for idx, item in enumerate(items[:10], start=1): title = item.find("title").text if item.find("title") else "" source = item.find("source").text if item.find("source") else "" pub_date = item.find("pubDate").text if item.find("pubDate") else "" link = item.find("link").text if item.find("link") else "" if pub_date: pub_date = datetime.strptime(pub_date, "%a, %d %b %Y %H:%M:%S GMT").strftime("%Y-%m-%d") news_list.append([title, source, pub_date, link]) # ★ 추가 writer.writerow([title, source, pub_date, link]) print(f"{idx}. [{source}] {title}") # ★ 추가: 리스트를 DataFrame(표)으로 변환 df_news = pd.DataFrame(news_list, columns=["title", "source", "pubDate", "link"]) print(df_news) # 표 형태로 확인 files.download(filename)
리스트에 쌓인 데이터를 엑셀 표처럼 구조화합니다.
columns에 각 열의 이름을 지정하면, 이후 df["title"]처럼 열 이름으로 데이터에 접근할 수 있습니다.
CSV에 저장하는 것과 별도로 파이썬 메모리 안에 표로 남아있기 때문에 바로 전처리·분석에 활용할 수 있습니다.
네이버 뉴스 공식 API 사용하기 NEW
구글 뉴스 RSS가 "비공식 통로"라면, 네이버 검색 API는 네이버가 공식으로 열어준 창구입니다. 키를 발급받아야 하지만, 더 안정적이고 법적으로도 안전합니다.
API 키 발급 방법
🔑 네이버 개발자 센터에서 API 키 발급하기
- developers.naver.com 접속 후 로그인
- 상단 메뉴 → Application → 애플리케이션 등록
- 애플리케이션 이름 입력 (예: "뉴스수집테스트")
- 사용 API → 검색 체크 → 등록
- 발급된 Client ID와 Client Secret을 코드에 붙여넣기
RSS 태그 vs API JSON 필드 비교
| RSS 태그 | API JSON 키 | 내용 |
|---|---|---|
<item> | data["items"] | 기사 하나 |
<title> | item["title"] | 기사 제목 |
<link> | item["link"] | 기사 링크 |
<pubDate> | item["pubDate"] | 발행 날짜 |
<description> | item["description"] | 기사 요약 |
| (없음) | item["originallink"] | 원본 언론사 링크 |
전체 코드
# ① API 키를 여기에 붙여넣으세요 (developers.naver.com 에서 발급) client_id = "나의 Client ID" client_secret = "나의 Client Secret" import requests import pandas as pd # ② 네이버 뉴스 검색 API 주소 url = "https://openapi.naver.com/v1/search/news.json" # ③ 신분증 역할: 내가 발급받은 키를 헤더에 담아서 보냄 headers = { "X-Naver-Client-Id": client_id, "X-Naver-Client-Secret": client_secret } # ④ 검색 조건 설정 params = { "query": "CRM 후기", # 검색어 (여기만 바꾸면 됨) "display": 30, # 가져올 기사 수 (최대 100) "sort": "date" # 정렬 기준: date(최신순) or sim(관련도순) } # ⑤ 요청 보내기 response = requests.get(url, headers=headers, params=params) data = response.json() # JSON → 파이썬 딕셔너리로 변환 # ⑥ 기사 목록 추출 rows = [] for item in data["items"]: rows.append({ "title": item["title"], "link": item["link"], "originallink": item.get("originallink"), # .get(): 없으면 None 반환 "pubDate": item.get("pubDate"), "description": item.get("description") }) # ⑦ DataFrame으로 변환해서 표 형태로 확인 df_news = pd.DataFrame(rows) print(df_news)
헷갈리는 부분 짚어보기
item["key"]는 해당 키가 없으면 에러가 발생합니다.
item.get("key")는 해당 키가 없으면 None을 반환하고 에러 없이 계속 실행됩니다.
실무에서는 데이터가 빠져있는 경우가 많기 때문에 확신이 없는 필드는 .get()을 쓰는 게 안전합니다.
API가 돌려주는 응답은 JSON 형식의 문자열입니다.
.json()을 호출하면 이 문자열을 파이썬 딕셔너리(dictionary)로 자동 변환해줍니다.
RSS에서는 XML을 BeautifulSoup으로 파싱했지만, JSON API는 이 한 줄로 끝납니다.
URL에 검색 조건을 넘기는 방법입니다. requests.get()에 params=로 넘기면
자동으로 URL에 붙여줍니다.
"display": 30은 30개 가져오기, "sort": "date"는 최신순 정렬,
"sort": "sim"은 관련도 높은 순으로 바꿀 수 있습니다.
수집된 데이터 전처리하기 (5단계) NEW
API로 데이터를 가져오면 날짜 형식이 이상하거나, HTML 태그가 섞여 있거나, 중복·결측치가 있습니다. 분석에 쓰기 전에 반드시 전처리(Preprocessing)를 거쳐야 합니다.
import pandas as pd import re # ① 날짜 형식 변환 # "Wed, 16 Apr 2026 09:00:00 +0900" → "2026-04-16" df_news["pubDate"] = pd.to_datetime(df_news["pubDate"]) df_news["pubDate"] = df_news["pubDate"].dt.strftime("%Y-%m-%d") # ② 날짜순 정렬 (최신 기사가 맨 위로) df_news = df_news.sort_values("pubDate", ascending=False) # ③ HTML 태그 제거 (<b>CRM</b> → CRM) df_news["title"] = df_news["title"].str.replace(r"<.*?>", "", regex=True) df_news["description"] = df_news["description"].str.replace(r"<.*?>", "", regex=True) # ④ 중복 기사 제거 (제목 기준, 링크 기준 각각 실행) df_news = df_news.drop_duplicates(subset=["title"]) df_news = df_news.drop_duplicates(subset=["link"]) # ⑤ 결측치 처리 (비어있는 셀 기본값으로 채우기) df_news["title"] = df_news["title"].fillna("제목 없음") df_news["description"] = df_news["description"].fillna("내용 없음") df_news["link"] = df_news["link"].fillna("링크 없음") # 결과 확인 df_news.head()
각 함수 완전 이해하기
pd.to_datetime()은 문자열로 된 날짜를 파이썬이 "진짜 날짜"로 인식하게 만듭니다.
그 다음 .dt.strftime("%Y-%m-%d")로 우리가 원하는 형태(2026-04-16)로 다시 문자열로 바꿉니다.
왜 두 단계를 거치냐? to_datetime으로 바꿔야 비로소 날짜 계산·정렬이 가능해지기 때문입니다.
DataFrame의 특정 열을 기준으로 정렬합니다.
ascending=False는 내림차순(큰 값이 위) → 날짜는 최신이 위로 올라옵니다.
ascending=True로 바꾸면 오름차순(오래된 기사가 위)이 됩니다.
네이버 API가 반환하는 제목에는 <b>CRM</b>처럼 HTML 태그가 섞여 있습니다.
r"<.*?>"는 정규표현식(regex)으로, <로 시작해서 >로 끝나는 모든 태그를 의미합니다.
이것을 ""(빈 문자열)로 바꾸면 태그가 깔끔하게 사라집니다.
같은 기사가 여러 번 수집되는 경우 중복을 제거합니다.
subset에 기준이 되는 열을 지정합니다. 제목이 같으면 제거, 링크가 같으면 제거 — 두 번 실행해서 모두 잡습니다.
기본적으로 첫 번째 항목만 남기고 나머지를 삭제합니다.
데이터에 빈 값(NaN)이 있으면 분석 시 오류가 생깁니다.
.fillna()로 빈 셀에 기본값을 채워서 오류를 예방합니다.
"제목 없음", "내용 없음"처럼 의미 있는 텍스트를 채우는 것이 나중에 데이터를 볼 때 편합니다.
전처리 단계 요약표
| 단계 | 코드 | 목적 |
|---|---|---|
| ① 날짜 변환 | pd.to_datetime() + .strftime() | 날짜를 다루기 쉬운 형태로 통일 |
| ② 날짜 정렬 | sort_values(ascending=False) | 최신 기사가 위로 |
| ③ 태그 제거 | .str.replace(r"<.*?>", "", regex=True) | HTML 태그 제거 |
| ④ 중복 제거 | drop_duplicates(subset=[...]) | 동일 기사 중복 제거 |
| ⑤ 결측치 처리 | .fillna("기본값") | 빈 셀 채우기 |
네이버 API로 블로그 링크 수집하기 회고조 실습
뉴스 API와 동일한 방식으로, URL 주소만 블로그 검색 엔드포인트로 바꾸면 됩니다. 경쟁사나 키워드에 대한 블로그 포스팅 링크를 한 번에 수집할 때 유용합니다.
뉴스:
/v1/search/news.json | 블로그: /v1/search/blog.json주소의 news 부분만 blog로 바꾸면 블로그 검색 결과를 가져올 수 있습니다.
# ① API 키 설정 client_id = "나의 Client ID" client_secret = "나의 Client Secret" import requests import pandas as pd # ② 블로그 검색 엔드포인트 (news → blog 로만 바뀜!) url = "https://openapi.naver.com/v1/search/blog.json" # ③ 인증 헤더 (뉴스 코드와 완전히 동일) headers = { "X-Naver-Client-Id": client_id, "X-Naver-Client-Secret": client_secret } # ④ 검색 조건 params = { "query": "CRM 후기", # 검색어를 여기서 바꾸세요 "display": 30, "sort": "date" } # ⑤ 요청 및 응답 response = requests.get(url, headers=headers, params=params) data = response.json() # ⑥ 블로그 링크만 수집 links = [] for item in data["items"]: links.append(item["link"]) # ⑦ DataFrame으로 만들어 확인 df_links = pd.DataFrame(links, columns=["url"]) print(df_links)
더 많은 정보를 함께 수집하고 싶다면
링크만 수집하는 대신, 블로그 제목·날짜·요약까지 한 번에 수집하는 확장 버전입니다.
rows = [] for item in data["items"]: rows.append({ "제목": item.get("title", ""), "링크": item.get("link", ""), "블로그명": item.get("bloggername", ""), "발행일": item.get("postdate", ""), "요약": item.get("description", "") }) df_blog = pd.DataFrame(rows) # HTML 태그 제거 (제목, 요약에 섞여있음) df_blog["제목"] = df_blog["제목"].str.replace(r"<.*?>", "", regex=True) df_blog["요약"] = df_blog["요약"].str.replace(r"<.*?>", "", regex=True) df_blog.head(10)
경쟁사 이름으로 검색 → 소비자가 직접 쓴 후기 링크 한 번에 수집
우리 제품명으로 검색 → 자사 브랜드 언급 블로그 모니터링
키워드로 검색 → 콘텐츠 기획 시 경쟁 포스팅 파악
뉴스 vs 블로그 API 비교
| 항목 | 뉴스 API | 블로그 API |
|---|---|---|
| 엔드포인트 | /search/news.json | /search/blog.json |
| 출처 | 언론사 기사 | 네이버/외부 블로그 |
| 고유 필드 | originallink, source | bloggername, postdate |
| 주요 용도 | 뉴스 모니터링, 트렌드 파악 | 소비자 후기, 브랜드 언급 수집 |
'부트캠프' 카테고리의 다른 글
| 멋쟁이사자처럼 부트캠프 그로스마케팅 4기 29일차_260420 (0) | 2026.04.20 |
|---|---|
| 멋쟁이사자처럼 부트캠프 그로스마케팅 4기 28일차_260417 (1) | 2026.04.17 |
| 멋쟁이사자처럼 부트캠프 그로스마케팅 4기 26일차_260415 (1) | 2026.04.15 |
| 멋쟁이사자처럼 부트캠프 그로스마케팅 4기 25일차_260414 (2) | 2026.04.14 |
| 멋쟁이사자처럼 부트캠프 그로스마케팅 4기 24일차_260413 (0) | 2026.04.13 |