-
혼자 공부하는 데이터 분석 CH 02 웹 스크래핑Study/Data Analysis 2025. 9. 22. 10:53

목차
들어가며: API, JSON, XML, 그리고 웹 스크래핑으로 데이터 정복하기
1. API와 데이터 형식 (JSON, XML) 다루기
- 1-1. JSON 데이터 처리
- 1-2. XML 데이터 처리
- 1-3. API 호출 및 응답 처리
2. 웹 스크래핑으로 웹 데이터 가져오기
- 2-1. 데이터 로드 및 전처리
- 2-2. 웹 스크래핑 기초 (requests & BeautifulSoup)
- 2-3. 스크래핑 함수화 및 데이터프레임 병합
마치며: 데이터 수집과 가공, 그 다음은?
들어가며: API, JSON, XML, 그리고 웹 스크래핑으로 데이터 정복하기
안녕하세요! 데이터 분석의 핵심은 바로 '데이터'를 얻는 것입니다.
오늘은 웹에 흩어져 있거나 API를 통해 제공되는 다양한 형태의 데이터를 파이썬으로 가져오고 다루는 방법을 깊이 있게 알아보겠습니다. 특히 JSON, XML 같은 데이터 형식과 웹 스크래핑 기술에 초점을 맞출 예정입니다.
본격적인 실습에 앞서, 웹에서 데이터를 가져오는 두 가지 핵심 방법을 먼저 개념적으로 살펴보겠습니다.
- 방법 1: API 활용 (정식 창구 이용)
API는 프로그램끼리 데이터를 주고받기 위한 공식적인 규칙입니다.
정해진 요청을 보내면, 서버가 깔끔하게 포장된 데이터를 보내줍니다.
이때 데이터는 주로 JSON이나 XML형태로 오며, requests 라이브러리로 서버에 요청하고 pandas로 응답받은 데이터를 즉시 표(DataFrame)로 만들 수 있습니다. - 방법 2: 웹 스크래핑 (직접 정보 수집)
공식 창구인 API가 없을 때 사용하는 방법입니다.
웹사이트에 직접 찾아가서 HTML 코드 속에서 필요한 부분만 추출하는 기술입니다.
requests로 웹페이지의 HTML 전체를 가져온 뒤, BeautifulSoup이라는 탐색 도구로 원하는 정보를 쉽게 찾아낼 수 있습니다.
1. API와 데이터 형식 (JSON, XML) 다루기
API(Application Programming Interface)는 프로그램 간의 통신 규칙이며,
대부분 데이터를 JSON이나 XML형태로 주고받습니다. 이 두 가지 형식은 웹에서 데이터를 교환하는 데 가장 널리 사용됩니다.
1-1. JSON 데이터 처리
JSON(JavaScript Object Notation)은 가볍고 읽기 쉬운 데이터 교환 형식입니다.
파이썬의 딕셔너리와 리스트 구조와 매우 유사하여 다루기 편리합니다.
기본 JSON 변환
# 파이썬 딕셔너리 생성 d = {"name": "혼자 공부하는 데이터 분석"} print(d['name']) # 결과: 혼자 공부하는 데이터 분석 # 딕셔너리를 JSON 문자열로 변환 import json d_str = json.dumps(d, ensure_ascii=False) # 한글이 깨지지 않도록 ensure_ascii=False 설정 print(d_str) # 결과: {"name": "혼자 공부하는 데이터 분석"} print(type(d_str)) # 결과: <class 'str'> (JSON 문자열은 파이썬에서 문자열 타입) # JSON 문자열을 다시 딕셔너리로 변환 d2 = json.loads(d_str) print(d2['name']) # 결과: 혼자 공부하는 데이터 분석 (딕셔너리로 복원됨) print(type(d2)) # 결과: <class 'dict'>복잡한 JSON 구조 처리
JSON은 중첩된 딕셔너리나 리스트(배열)를 포함할 수 있습니다.
# 문자열 형태의 JSON 파싱 json_str1 = '{"name": "혼자 공부하는 데이터 분석", "author": "박해선", "year": 2022}' d3_1 = json.loads(json_str1) print(d3_1['name']) # 결과: 혼자 공부하는 데이터 분석 # 배열이 포함된 JSON 파싱 json_str2 = '{"name": "혼자 공부하는 데이터 분석", "author": ["박해선","홍길동"], "year": 2022}' d3_2 = json.loads(json_str2) print(d3_2['author'][1]) # 결과: 홍길동 (리스트처럼 인덱스로 접근) # JSON 배열 파싱 (여러 개의 객체가 리스트로 묶인 형태) d4_str = """ [ {"name": "혼자 공부하는 데이터 분석", "author": "박해선", "year": 2022}, {"name": "혼자 공부하는 머신러닝+딥러닝", "author": "박해선", "year": 2020} ] """ d4 = json.loads(d4_str) print(d4[0]['name']) # 결과: 혼자 공부하는 데이터 분석 (리스트의 첫 번째 요소에서 'name' 키 접근)판다스와 JSON 연동
pandas 라이브러리는 JSON 데이터를 DataFrame으로 쉽게 변환할 수 있는 강력한 기능을 제공합니다.
import pandas as pd # JSON 문자열(배열 형태)을 직접 데이터프레임으로 변환 df_from_json_str = pd.read_json(d4_str) print(df_from_json_str) # 결과: # name author year # 0 혼자 공부하는 데이터 분석 박해선 2022 # 1 혼자 공부하는 머신러닝+딥러닝 박해선 2020 # 파이썬 리스트/딕셔너리(d4)를 데이터프레임으로 변환 df_from_dict = pd.DataFrame(d4) print(df_from_dict) # 결과: # name author year # 0 혼자 공부하는 데이터 분석 박해선 2022 # 1 혼자 공부하는 머신러닝+딥러닝 박해선 20201-2. XML 데이터 처리
XML(Extensible Markup Language)은 HTML과 유사하게 태그를 사용하여 데이터를 구조화하는 형식입니다.
파이썬의 xml.etree.ElementTree 모듈로 처리할 수 있습니다.
단일 XML 요소 파싱
x_str = """ <book> <name>혼자 공부하는 데이터 분석</name> <author>박해선</author> <year>2022</year> </book> """ import xml.etree.ElementTree as et book = et.fromstring(x_str) # XML 문자열을 Element 객체로 변환 print(type(book)) # 결과: <class 'xml.etree.ElementTree.Element'> print(book.tag) # 결과: book (최상위 태그명) # 자식 요소의 텍스트 추출 (list()로 자식 요소들을 얻은 후 접근) book_childs = list(book) name_elem, author_elem, year_elem = book_childs print(name_elem.text) # 결과: 혼자 공부하는 데이터 분석 print(author_elem.text) # 결과: 박해선 # findtext()로 더 간편하게 특정 태그의 텍스트 추출 name = book.findtext('name') author = book.findtext('author') year = book.findtext('year') print(name, author, year) # 결과: 혼자 공부하는 데이터 분석 박해선 2022중첩된 XML 구조 파싱
findall() 메서드를 사용하면 특정 태그를 가진 모든 자식 요소를 리스트 형태로 찾을 수 있습니다.
x2_str = """ <books> <book> <name>혼자 공부하는 데이터 분석</name> <author>박해선</author> <year>2022</year> </book> <book> <name>혼자 공부하는 머신러닝+딥러닝</name> <author>박해선</author> <year>2020</year> </book> </books> """ books_root = et.fromstring(x2_str) print(books_root.tag) # 결과: books # findall()로 모든 <book> 요소를 찾아 반복 처리 for book in books_root.findall('book'): name = book.findtext('name') author = book.findtext('author') year = book.findtext('year') print(f"책 이름: {name}, 저자: {author}, 출판 연도: {year}") print() # 결과: # 책 이름: 혼자 공부하는 데이터 분석, 저자: 박해선, 출판 연도: 2022 # # 책 이름: 혼자 공부하는 머신러닝+딥러닝, 저자: 박해선, 출판 연도: 2020 # 판다스로 XML 직접 읽기 df_from_xml = pd.read_xml(x2_str) print(df_from_xml) # 결과: # name author year # 0 혼자 공부하는 데이터 분석 박해선 2022 # 1 혼자 공부하는 머신러닝+딥러닝 박해선 20201-3. API 호출 및 응답 처리
파이썬의 requests 라이브러리를 사용하여 HTTP 요청을 보내고 API 응답을 받을 수 있습니다.
requests 라이브러리로 API 호출
import requests # 도서관 정보나루 API URL (※ 실제 사용 시 '인증키' 부분을 본인의 인증키로 변경해야 합니다.) url = "http://data4library.kr/api/loanItemSrch?format=json&startDt=2021-04-01&endDt=2021-04-30&age=20&authKey=인증키" # API 호출 및 응답 받기 # 실제 API 호출 시 인증키가 필요하며, 여기서는 예시로만 보여드립니다. # r = requests.get(url) # data = r.json() # 응답을 JSON으로 파싱 # 예시 데이터 (API 호출 없이 구조만 보여주기 위함) data = { "response": { "docs": [ {"doc": {"no": 1, "bookname": "데이터 분석 입문", "authors": "김철수"}}, {"doc": {"no": 2, "bookname": "파이썬으로 데이터 과학", "authors": "박영희"}} ] } } # API 응답 구조 탐색 print(data) # 전체 응답 데이터 (예시 데이터 출력) # 결과: {'response': {'docs': [{'doc': {'no': 1, 'bookname': '데이터 분석 입문', 'authors': '김철수'}}, {'doc': {'no': 2, 'bookname': '파이썬으로 데이터 과학', 'authors': '박영희'}}]}} print(data['response']['docs']) # 실제 도서 정보가 담긴 부분 # 결과: [{'doc': {'no': 1, 'bookname': '데이터 분석 입문', 'authors': '김철수'}}, {'doc': {'no': 2, 'bookname': '파이썬으로 데이터 과학', 'authors': '박영희'}}]API 응답 데이터 처리
API 응답에서 필요한 데이터만 추출하여 DataFrame으로 만들고 파일로 저장할 수 있습니다.
# 예시 데이터 (위에서 사용한 data 변수) # 일반적인 반복문으로 데이터 추출 books = [] for d in data['response']['docs']: books.append(d['doc']) print(books) # 결과: [{'no': 1, 'bookname': '데이터 분석 입문', 'authors': '김철수'}, {'no': 2, 'bookname': '파이썬으로 데이터 과학', 'authors': '박영희'}] # 리스트 컴프리헨션으로 더 간결하게 처리 books_comprehension = [d['doc'] for d in data['response']['docs']] print(books_comprehension) # 결과: [{'no': 1, 'bookname': '데이터 분석 입문', 'authors': '김철수'}, {'no': 2, 'bookname': '파이썬으로 데이터 과학', 'authors': '박영희'}] # 데이터프레임으로 변환 books_df = pd.DataFrame(books) print(books_df.head()) # 결과: # no bookname authors # 0 1 데이터 분석 입문 김철수 # 1 2 파이썬으로 데이터 과학 박영희 # JSON 파일로 저장 (실제 실행 시 '20s_best_book.json' 파일이 생성됩니다.) # books_df.to_json('20s_best_book.json')
2. 웹 스크래핑으로 웹 데이터 가져오기
웹 스크래핑(Web Scraping)은 웹 페이지의 HTML을 분석하여 원하는 데이터를 추출하는 기술입니다.
requests로 웹페이지 내용을 가져오고 BeautifulSoup으로 파싱합니다.
2-1. 데이터 로드 및 전처리
먼저 스크래핑할 대상 도서 목록을 JSON 파일에서 로드합니다.
import gdown import pandas as pd # 구글 드라이브에서 JSON 파일 다운로드 (실제 파일이 존재하지 않으면 에러 발생) # gdown.download('https://bit.ly/3q9SZix', '20s_best_book.json', quiet=False) # 다운로드된 JSON 파일에서 데이터프레임 로드 # 여기서는 예시 데이터프레임을 직접 생성합니다. books_df = pd.DataFrame([ {'no': 1, 'ranking': 1, 'bookname': '우리가 빛의 속도로 갈 수 없다면', 'authors': '김초엽', 'publisher': '허블', 'publication_year': 2019, 'isbn13': 9791190090018}, {'no': 2, 'ranking': 2, 'bookname': '아몬드', 'authors': '손원평', 'publisher': '창비', 'publication_year': 2017, 'isbn13': 9791186259556} ]) print(books_df.head()) # 필요한 컬럼만 선택하여 새로운 데이터프레임 생성 books = books_df[['no','ranking','bookname','authors','publisher', 'publication_year','isbn13']] print(books.head())데이터프레임 인덱싱 기법
loc이나 iloc을 사용하여 데이터프레임에서 원하는 행과 열을 선택하는 다양한 방법입니다.
# loc을 이용한 특정 행과 컬럼 선택 print(books_df.loc[[0,1], ['bookname','authors']]) # 결과: # bookname authors # 0 우리가 빛의 속도로 갈 수 없다면 김초엽 # 1 아몬드 손원평 # loc을 이용한 범위로 행과 컬럼 선택 print(books_df.loc[0:1, 'bookname':'authors']) # 결과: # bookname authors # 0 우리가 빛의 속도로 갈 수 없다면 김초엽 # 1 아몬드 손원평 # 모든 행, 특정 컬럼 범위 선택 books_subset = books_df.loc[:, 'no':'isbn13'] print(books_subset.head()) # 짝수 번째 행만 선택 (스텝=2) print(books_df.loc[::2, 'no':'isbn13'].head()) # 결과: # no ranking bookname authors publisher publication_year isbn13 # 0 1 1 우리가 빛의 속도로 갈 수 없다면 김초엽 허블 2019 97911900900182-2. 웹 스크래핑 기초 (requests & BeautifulSoup)
requests로 웹페이지 HTML을 가져오고, BeautifulSoup으로 HTML 구조를 분석합니다.
SSL 에러 해결 (필요시)
일부 환경에서 발생하는 SSL 에러를 해결하기 위한 코드입니다.
# DH_KEY_TOO_SMALL 에러 발생 시 실행 (필요한 경우에만) import requests # requests.packages.urllib3.util.ssl_.DEFAULT_CIPHERS += 'HIGH:!DH:!aNULL' # try: # requests.packages.urllib3.contrib.pyopenssl.DEFAULT_SSL_CIPHER_LIST += 'HIGH:!DH:!aNULL' # except AttributeError: # pass웹페이지 요청 및 응답
requests.get() 함수로 특정 URL의 HTML 내용을 가져옵니다.
import requests isbn = 9791190090018 # '우리가 빛의 속도로 갈 수 없다면'의 ISBN url = 'http://www.yes24.com/Product/Search?domain=BOOK&query={}' # requests.get()으로 ISBN을 넣어 검색 페이지 요청 r = requests.get(url.format(isbn)) # print(r.text) # HTML 응답 내용이 길어서 주석 처리. 실제 실행 시 웹 페이지 HTML이 출력됩니다.BeautifulSoup을 이용한 HTML 파싱
BeautifulSoup은 HTML/XML 문서를 파싱하여 원하는 요소를 쉽게 찾을 수 있도록 돕습니다.
from bs4 import BeautifulSoup # HTML 파서로 파싱 soup = BeautifulSoup(r.text, 'html.parser') # 'a' 태그 중 class가 'gd_name'인 첫 번째 요소 찾기 prd_link = soup.find('a', attrs={'class':'gd_name'}) print(prd_link) # 결과: <a class="gd_name" href="/Product/Goods/74261416">우리가 빛의 속도로 갈 수 없다면</a> (전체 태그 출력) print(prd_link['href']) # 결과: /Product/Goods/74261416 (href 속성값 추출)상세 페이지 정보 추출
검색 결과에서 얻은 상세 페이지 URL로 이동하여 추가 정보를 추출합니다.
# 도서 상세 페이지로 이동 detail_url = 'http://www.yes24.com'+prd_link['href'] r_detail = requests.get(detail_url) soup_detail = BeautifulSoup(r_detail.text, 'html.parser') # 상품 상세정보 div(id가 infoset_specific) 찾기 prd_detail = soup_detail.find('div', attrs={'id':'infoset_specific'}) # 상세정보 테이블의 모든 tr 태그 추출 prd_tr_list = prd_detail.find_all('tr') # '쪽수, 무게, 크기' 정보 찾아서 추출 page_td_text = '' for tr in prd_tr_list: if tr.find('th').get_text() == '쪽수, 무게, 크기': page_td_text = tr.find('td').get_text() break print(page_td_text.split()[0]) # 결과: 360쪽 (첫 번째 단어만 추출)2-3. 스크래핑 함수화 및 데이터프레임 병합
스크래핑 로직을 함수로 만들고, pandas의 apply()나 merge()를 활용하여 데이터프레임에 적용합니다.
쪽수 추출 함수 정의
def get_page_cnt(isbn): url = 'http://www.yes24.com/Product/Search?domain=BOOK&query={}' r = requests.get(url.format(isbn)) soup = BeautifulSoup(r.text, 'html.parser') prd_info = soup.find('a', attrs={'class':'gd_name'}) if prd_info is None: # 검색 결과가 없으면 빈 문자열 반환 return '' detail_url = 'http://www.yes24.com'+prd_info['href'] r_detail = requests.get(detail_url) soup_detail = BeautifulSoup(r_detail.text, 'html.parser') prd_detail = soup_detail.find('div', attrs={'id':'infoset_specific'}) if prd_detail is None: # 상세 정보 div가 없으면 빈 문자열 반환 return '' prd_tr_list = prd_detail.find_all('tr') for tr in prd_tr_list: if tr.find('th').get_text() == '쪽수, 무게, 크기': return tr.find('td').get_text().split()[0] # 첫 단어(쪽수) 반환 return '' # 쪽수 정보를 찾지 못하면 빈 문자열 반환 # 함수 테스트 print(get_page_cnt(9791190090018)) # 결과: 360쪽 print(get_page_cnt(9791190090000)) # 결과: (존재하지 않는 ISBN 가정 시 빈 문자열 또는 에러 처리)apply() 함수로 일괄 처리
데이터프레임의 각 행에 위에서 정의한 함수를 적용하여 일괄적으로 쪽수 정보를 가져옵니다.
Pythontop10_books = books.head(10) # 예시로 상위 10개 도서만 사용 # 람다 함수를 사용하여 get_page_cnt 함수를 각 행의 'isbn13'에 적용 page_count_series = top10_books.apply(lambda row: get_page_cnt(row['isbn13']), axis=1) page_count_series.name = 'page_count' # 시리즈에 이름 부여 print(page_count_series) # 결과 (예시): # 0 360쪽 # 1 336쪽 # Name: page_count, dtype: object데이터프레임 병합 (merge())
스크래핑으로 얻은 쪽수 정보를 기존 도서 데이터프레임에 병합합니다.
# 인덱스 기준으로 병합 top10_with_page_count = pd.merge(top10_books, page_count_series, left_index=True, right_index=True) print(top10_with_page_count.head()) # 결과 (예시): # no ranking bookname authors publisher publication_year isbn13 page_count # 0 1 1 우리가 빛의 속도로 갈 수 없다면 김초엽 허블 2019 9791190090018 360쪽 # 1 2 2 아몬드 손원평 창비 2017 9791186259556 336쪽 # merge() 함수의 다양한 옵션들 (예제 데이터프레임으로 설명) df1 = pd.DataFrame({'col1': ['a','b','c'], 'col2': [1,2,3]}) df2 = pd.DataFrame({'col1': ['a','b','d'], 'col3': [10,20,30]}) # inner join (기본값): 공통된 키(col1)만 병합 (교집합) print(pd.merge(df1, df2, on='col1')) # 결과: # col1 col2 col3 # 0 a 1 10 # 1 b 2 20 # left join: 왼쪽 데이터프레임(df1) 기준으로 병합. 일치하는 값이 없으면 NaN print(pd.merge(df1, df2, how='left', on='col1')) # 결과: # col1 col2 col3 # 0 a 1 10.0 # 1 b 2 20.0 # 2 c 3 NaN # outer join: 모든 키를 포함하여 병합 (합집합) print(pd.merge(df1, df2, how='outer', on='col1')) # 결과: # col1 col2 col3 # 0 a 1.0 10.0 # 1 b 2.0 20.0 # 2 c 3.0 NaN # 3 d NaN 30.0
마치며: 데이터 수집과 가공, 그 다음은?
오늘은 웹에서 데이터를 수집하는 두 가지 강력한 방법인 API 활용과 웹 스크래핑에 대해 깊이 있게 알아보았습니다.
JSON과 XML 데이터를 파싱하고,
requests, BeautifulSoup, pandas 같은 라이브러리를 통해 데이터를 원하는 형태로 가공하는 과정을 실습했습니다.
이제 여러분은 웹의 방대한 데이터에 접근하고,
이를 파이썬으로 가져와 분석 가능한 형태로 만드는 기초 역량을 갖추게 되었습니다.
데이터 분석의 첫걸음인 데이터 수집과 전처리는 매우 중요하니, 오늘 배운 내용들을 꼭 반복 해보시기를 바랍니다.
다음 시간에는 이렇게 얻은 데이터를 실제로 분석하고 시각화하는 방법을 다뤄보겠습니다.
'Study > Data Analysis' 카테고리의 다른 글
혼자 공부하는 데이터 분석 CH 06 복잡한 데이터 표현하기 (0) 2025.09.26 혼자 공부하는 데이터 분석 CH 05 데이터 시각화 (3) 2025.09.25 혼자 공부하는 데이터 분석 CH 04 통계와 분포 (0) 2025.09.24 혼자 공부하는 데이터 분석 CH 03 데이터 클렌징 (1) 2025.09.23 혼자 공부하는 데이터 분석 CH 01 데이터 분석 (0) 2025.09.21