1. API 사용하기
1) API란
- API(Application Programming Interface)는 두 프로그램이 서로 대화하기 위한 방법을 정의한 것
웹 페이지를 전송하기 위한 통신 규약 : HTTP
- 웹 사이트는 웹 페이지를 서비스하기 위해 웹 서버(Web server) 소프트웨어를 사용한다. 대표적인 웹 서버 프로그램으로는 NGINX, Apache 등이 있다 이런 웹 서버 프로그램은 웹 브라우저와 통신할 때 HTTP 프로토콜을 사용한다.
- HTTP(Hyper Text Transfer Protocol)는 인터넷에서 웹 페이지를 전송하는 기본 통신 방법이다.
- 웹 브라우저가 웹 서버에 웹 페이지를 요청하고, 웹 서버는 요청에 맞는 웹 페이지를 웹 브라우저에게 전송한다.
웹 페이지 문서 : HTML
- HTML(Hypertext Markup Langauge)은 웹 브라우저가 화면에 표시할 수 있는 문서의 한 종류이자 웹 페이지를 위한 표준 언어
- 웹 기반 API는 웹 서버와 웹 브라우저가 대화하는 방식과 비슷하다. HTTP 프로토콜을 사용하지만 HMTL을 주고받는 것이 아니라 일반적으로 CSV, JSON, XML 같은 파일을 사용한다.
2) 파이썬에서 JSON 데이터 다루기
- JSON은 JavaScript Object Notation의 약자이다. 키(key)와 값(value)를 콜론(:)으로 연결한다.
d = {"name":"혼자 공부하는 데이터 분석"} ##json 형식은 키와 값에 큰 따옴표를 사용
print(d['name'])
- 웹 기반 API로 데이터를 전달할 때는 파이썬 딕셔너리가 아니라 텍스트로 전달해야 한다.
파이썬 객체를 JSON 문자열로 변환하기 : json.dumps() 함수
- 웹 기반 API는 전송하려는 파이썬 객체를 json.dumps() 함수를 사용하여 JSON 문자열로 변환하여 전송
- 파이썬의 json 패키지를 사용해 딕셔너리 d를 json 형식에 맞는 문자열로 바꾸기
- json 패키지에서 파이썬 객체를 JSON 형식에 맞는 텍스트로 바꿀 때는 json.dumps() 함수를 사용
- ensure_ascii 매개변수를 false로 지정하여 원래 저장된 문자를 그대로 출력
-> 딕셔너리 d에 한글이 포함되어 있기 때문. json.dumps() 함수는 아스키 문자 외에 다른 문자를 16진수로 출력
import json
d_str = json.dumps(d,ensure_ascii=False)
print(d_str)
print(type(d_str))
JSON 문자열을 파이썬 객체로 변환하기 : json.load() 함수
- JSON 문자열을 파이썬 프로그램에서 사용하려면 다시 파이썬 딕셔너리로 바꾸어야 한다.
- json.loads() 함수를 사용하면 JSON 문자열을 파이썬 객체로 변환할 수 있다.
직렬화(serialization) | 프로그램 상의 객체를 저장하거나 읽을 수 있는 형태로 변환 |
역직렬화(deserialization) | 직렬화된 정보를 다시 프로그램에서 실행가능한 객체로 변환 |
d2 = json.loads(d_str)
print(d2['name'])
print(type(d2))
- JSON 배열로 여러 개의 도서를 포함시킬 수 있는 더 복잡한 구조 만들기
- 도서 두권을 JSON 객체로 표현하고 이를 대괄호 안에 콤마(,)로 나열
- 이 문자열을 json.loads() 함수를 사용해 파이썬 객체로 변환
d4_str = """
[
{"name": "혼자 공부하는 데이터 분석", "author": "박해선", "year": 2022},
{"name": "혼자 공부하는 머신러닝+딥러닝", "author": "박해선", "year": 2020}
]
"""
d4 = json.loads(d4_str)
print(d4[0]['name'])
JSON 문자열을 데이터프레임으로 변환하기 : read_json() 함수
- 판다스는 JSON 문자열을 읽어서 데이터프레임으로 변환하는 read_json() 함수를 제공한다.
import pandas as pd
pd.read_json(d4_str)
- JSON을 데이터프레임으로 바꾸는 또 다른 방법은 JSON 문자열을 파이썬 객체로 만든 다음 DataFrame 클래스를 사용하는 것이다.
3) 파이썬에서 XML 데이터 다루기
- XML은 eXtensible Markup Language의 약자이다.
- XML은 엘리먼트(element)들이 계층 구조를 이루면서 정보를 표현한다.
XML 문자열을 파이썬 객체로 변환하기 : fromstring() 함수
x_str = """
<book>
<name>혼자 공부하는 데이터 분석</name>
<author>박해선</author>
<year>2022</year>
</book>
"""
- xml.etree.ElementTree 모듈의 fromstring() 함수를 사용해 x_str 문자열을 XML로 변환
- fromstring() 함수가 반환하는 객체는 ElementTree 모듈 아래에 정의된 Element 클래스의 객체이다.
import xml.etree.ElementTree as et
book = et.fromstring(x_str)
print(type(book))
- book 객체는 x_str에서 가장 먼저 등장하는 부모 엘리먼트인 <book>에 해당한다. book 객체의 tag 속성을 출력하면 엘리먼트 이름을 쉽게 확인할 수 있다.
print(book.tag)
자식 엘리먼트 확인하기 : findtext() 메서드
- XML 문서에서 추출하고 싶은 것은 도서명(name), 저자(author), 발행 연도(year)이다.
- <book>엘리먼트의 자식 엘리먼트를 구한 다음, 각각의 자식 엘리먼트에 담긴 텍스트를 읽을 수 있다.
book_childs = list(book) #리스트로 변환할 때는 list() 함수 사용
print(book_childs)
- book_childs 리스트 각 항목을 name, author, year 변수에 할당하고 text 속성으로 엘리먼트에 있는 텍스트를 출력
name, author, year = book_childs
print(name.text)
print(author.text)
print(year.text)
- book 객체의 findtext() 메서드를 사용하면 해당하는 자식 엘리먼트를 탐색하여 자동으로 텍스트를 반환할 수 있다.
name = book.findtext('name')
author = book.findtext('author')
year = book.findtext('year')
print(name)
print(author)
print(year)
여러 개의 자식 엘리먼트 확인하기 : findall() 메서드와 for문
- <books>안에 포함된 두개의 <book> 엘리먼트를 찾아서 도서명, 저자, 발행 연도를 출력
- 동일한 이름을 가진 여러 개의 자식 엘리먼트를 찾을 때는 findall() 메서드와 for문을 함께 사용
x2_str = """
<books>
<book>
<name>혼자 공부하는 데이터 분석</name>
<author>박해선</author>
<year>2022</year>
</book>
<book>
<name>혼자 공부하는 머신러닝+딥러닝</name>
<author>박해선</author>
<year>2020</year>
</book>
</books>
"""
- fromstring() 함수를 사용하여 부모 엘리먼트 확인
books = et.fromstring(x2_str)
print(books.tag)
for book in books.findall('book'):
name = book.findtext('name')
author = book.findtext('author')
year = book.findtext('year')
print(name)
print(author)
print(year)
print()
4) API로 20대가 가장 좋아하는 도서 찾기
파이썬으로 API 호출하기 : requests 패키지
- URL은 HTTP GET 방식으로 파라미터 값을 전달하기 때문에 URL을 requests.get() 함수에 전달한다.
- get() 함수가 반환하는 값은 API 호출의 결과를 담고 있는 request 패키지의 Response 클래스 객체이다.
- json() 메서드는 웹 서버로부터 받은 JSON 문자열을 파이썬 객체로 변환하여 반환
import requests
url = "http://data4library.kr/api/loanItemSrch?format=json&startDt=2021-04-01&endDt=2021-04-30&age=20&authKey=API키"
r = requests.get(url)
data = r.json()
print(data)
- 판다스 데이터프레임으로 손쉽게 변환하기 위해 data 딕셔너리의 구조를 data['response']['docs']에 매핑된 리스트를 순회하면서 doc 키에 매핑된 딕셔너리를 추출한 후 빈 리스트에 추가
books = []
for d in data['response']['docs'] :
books.append(d['doc'])
- 판다스 DataFrame 클래스에 이 리스트를 넘긴다.
books_df = pd.DataFrame(books)
books_df
- books_df의 내용을 JSON으로 변환하여 저장 (to_json() 메서드 이용)
books_df.to_json('20s_best_book.json')
2. 웹 스크래핑 사용하기
웹 크롤링(web crawling)
- 프로그램으로 웹사이트의 페이지를 옮겨 가면서 데이터를 추출하는 작업
1) 검색 결과 페이지 가져오기
- Yes24 사이트에서 검색 결과 페이지 HTML 하나 가져오기
import gdown
gdown.download('https://bit.ly/3q9SZix', '20s_best_book.json', quiet=False)
- 판다스 데이터프레임으로 불러온 후 head() 메서드로 처음 다섯개 행을 출력
import pandas as pd
books_df = pd.read_json('20s_best_book.json')
books_df.head()
- 위 데이터프레임에서 'no'열 부터 'isbn13'열까지만 선택해서 새로운 데이터프레임 만들기
books_df.loc[[0,1], ['bookname','authors']]
데이터프레임 행과 열 선택하기 : loc 메서드
- 판다스가 제공하는 loc 메서드를 사용하면 원하는 행과 열을 조금 더 쉽게 선택할 수 있다.
- loc는 대괄호를 사용하여 행의 목록과 열의 목록을 받는다.
books_df.loc[0:1, 'bookname':'authors']
- loc 메서드의 첫 번째 매개변수로 행 인덱스 0과 1을 리스트로 전달하고 두 번째 매개변수로 열 이름 'bookname','authors'를 리스트로 전달
검색 결과 페이지 HTML 가져오기 : requests.get() 함수
- requests 패키지를 임포트하고 request.get() 함수로 첫 번째 도서에 대한 검색 결과 페이지 HTML 가져오기
- 첫 번째 도서의 ISBN과 Yes24 검색 결과 페이지 URL을 위한 변수를 정의한다.
- 그 다음 requests.get() 함수를 호출할 때 파이썬 문자열의 format() 메서드를 사용해 isbn 변수에 저장된 값을 url 변수에 전달한다ㅏ.
import requests
isbn = 9791190090018 # '우리가 빛의 속도로 갈 수 없다면'의 ISBN
url = 'http://www.yes24.com/Product/Search?domain=BOOK&query={}'
r = requests.get(url.format(isbn))
print(r.text)
2) HTML에서 데이터 추출하기
- HTML 안에 있는 내용을 찾을 때는 부티플수프(Beautiful Soup)가 널리 사용된다.
- 뷰티플수프 패키지에서 BequtifulSoup 클래스를 임포트 해야 한다. 그 다음 이 클래스의 객체를 생성한다.
- 첫 번째 매개변수는 파싱(parshing)할 HTML 문서이고 두 번째는 파싱에 사용할 파서(parser)이다.
- 파서는 입력데이터를 받아 데이터 구조를 만드는 소프트웨어 라이브러리를 의미한다. 이러한 과정을 파싱이라고 한다.
from bs4 import BeautifulSoup
soup = BeautifulSoup(r.text, 'html.parser')
태그 위치 찾기 : find() 메서드
- 태그 위치는 soup 객체의 find() 메서드를 사용하면 간편하게 찾을 수 있다.
- 첫 번째 매개변수에는 찾을 태그 이름을 지정하고, attrs 매개변수에는 찾으려는 태그의 속성을 딕셔너리로 지정하면 된다.
- class 속성이 "gd_name"인 <a> 태그 추출
prd_link = soup.find('a',attrs={'class':'gd_name'})
- 링크가 포함되어 있는 prd_link는 뷰티플수프의 Tag 클래스 객체이다.
print(prd_link)
- prd_link를 딕셔너리처럼 사용해 태그 안의 속성을 참조할 수 있다. 이 방법으로 링크 주소인 href속서의 값을 얻을 수 있다.
print(prd_link['href'])
도서 상세 페이지 HTML 가져오기
- 상세 페이지 주소를 만들어 requests.get() 함수를 호출
# '우리가 빛의 속도로 갈 수 없다면'의 상세 페이지 가져오기
url = 'http://www.yes24.com'+prd_link['href']
r = requests.get(url)
print(r.text)
- 뷰티플 수프 객체를 생성하고 find() 메서드로 품목정보 <div> 태그를 찾아서 출력
soup = BeautifulSoup(r.text, 'html.parser')
prd_detail = soup.find('div', attrs={'id':'infoset_specific'})
print(prd_detail)
테이블 태그를 리스트로 가져오기 : find_all() 메서드
- 앞서 찾은 <div> 태그 안에 품목정보 테이블이 들어있다. 이 테이블에서 '쪽수,무게,크기'에 해당하는 행인 <tr> 태그를 찾아 <td> 태그 안에 있는 텍스트를 가져오기
- 뷰티플수프의 find_all() 메서드를 사용하면 특정 HTML 태그를 모두 찾아서 리스트로 반환해준다.
prd_tr_list = prd_detail.find_all('tr')
print(prd_tr_list)
태그 안의 텍스트 가져오기 : get_text() 메서드
- for문으로 prd_tr_list를 순회하면서 <th> 태그 안의 텍스트가 '쪽수,무게,크기'에 해당하는지 검사. 원하는 행을 찾으면 <td> 태그 안에 담긴 텍스트를 page_td 변수에 저장하면된다.
- <td> 안에 있는 텍스트를 가져오려면 Tag 객체의 get_text() 메서드를 사용하면 된다.
- 파이썬 문자열 객체에서 split() 메서드를 호출하면 공백을 기준으로 문자열을 나누어 리스트로 반환해줌
for tr in prd_tr_list:
if tr.find('th').get_text() == '쪽수, 무게, 크기':
page_td = tr.find('td').get_text()
break
print(page_td)
print(page_td.split()[0])
3) 전체 도서의 쪽수 구하기
- get_page_cnt() 함수는 ISBN 정수 값을 받아 쪽수를 반환
def get_page_cnt(isbn):
# Yes24 도서 검색 페이지 URL
url = 'http://www.yes24.com/Product/Search?domain=BOOK&query={}'
# URL에 ISBN을 넣어 HTML 가져옵니다.
r = requests.get(url.format(isbn))
soup = BeautifulSoup(r.text, 'html.parser') # HTML 파싱
# 검색 결과에서 해당 도서를 선택합니다.
prd_info = soup.find('a', attrs={'class':'gd_name'})
if prd_info == None:
return ''
# 도서 상세 페이지를 가져옵니다.
url = 'http://www.yes24.com'+prd_info['href']
r = requests.get(url)
soup = BeautifulSoup(r.text, 'html.parser')
# 상품 상세정보 div를 선택합니다.
prd_detail = soup.find('div', attrs={'id':'infoset_specific'})
# 테이블에 있는 tr 태그를 가져옵니다.
prd_tr_list = prd_detail.find_all('tr')
# 쪽수가 들어 있는 th를 찾아 td에 담긴 값을 반환합니다.
for tr in prd_tr_list:
if tr.find('th').get_text() == '쪽수, 무게, 크기':
return tr.find('td').get_text().split()[0]
return ''
get_page_cnt(9791190090018)
데이터프레임 행 혹은 열에 함수 적용하기 : apply() 메서드
- 가장 인기 있는 10권의 도서의 쪽수를 가져오기 위해 head() 함수로 10개 행만 가져와 데이터프레임을 만든다.
top10_books = books.head(10)
- 각 행의 반복 작업을 수행하기 위해서 데이터프레임은 apply() 메서드를 제공한다.
- apply() 메서드의 첫 번째 매개변수는 실행할 함수이다. 따라서 데이터프레임의 한 행을 받아 get_page_cnt() 함수를 사용해 쪽수 구하는 함수를 만들어야 한다.
def get_page_cnt2(row):
isbn = row['isbn13']
return get_page_cnt(isbn)
- get_page_cnt2() 함수를 apply() 메서드에 다음과 같이 사용한다. 여기서는 각 행에 함수를 적용해야 하므로 axis 매개변수를 1로 지정한다.
page_count = top10_books.apply(get_page_cnt2, axis=1)
print(page_count)
- 각 행에 적용한 get_page_cnt2() 함수의 결괏값은 page_count 변수에 판다스 시리즈 객체로 저장된다.
데이터프레임과 시리즈 합치기 : merge() 함수
- 추출한 page_count 시리즈 객체를 top10_books 데이터프레임의 열로 합치기
- 먼저 page_count 시리즈 객체에 이름을 지정해준다. 이 이름은 top10_books 데이터프레임에 추가될 때 열 이름으로 사용된다.
page_count.name = 'page_count'
print(page_count)
- top10_books 데이터프레임과 page_count 시리즈를 합친다.
- 판다스에서 두 프레임을 합치거나 데이터프레임과 시리즈를 합칠 때 merge() 함수를 사용할 수 있다.
- 첫 번째 매개변수와 두 번째 매개변수는 합칠 데이터프레임이나 시리즈 객체이다.
- 두 객체의 인덱스를 기준으로 합칠 경우 left_index와 right_index 매개변수를 True로 지정한다.
top10_with_page_count = pd.merge(top10_books, page_count,
left_index=True, right_index=True)
top10_with_page_count
웹 스크래핑 주의할점 - 웹사이트 robots.txt 파일 확인하기
'데이터 > 데이터 분석' 카테고리의 다른 글
[혼공] ch 6. 복잡한 데이터 표현하기 (0) | 2024.07.09 |
---|---|
[혼공] ch 5. 데이터 시각화하기 (0) | 2024.07.09 |
[혼공] ch 4. 데이터 요약하기 (1) | 2024.07.05 |
[혼공] ch 3. 데이터 정제하기 (0) | 2024.07.04 |
[혼공] ch 1. 데이터 분석을 시작하며 (1) | 2024.07.03 |