[혼공분석] 2주차: 데이터 수집하기
1주차 우수 혼공족이 되는 기쁨을 담아 2주차 공부 시작 ~
주변에.. 메머드 익스프레스가 없어.. 꿀커피를 마시며 하지는 못하지만..3주차에 꼭..!! 가서 먹으리.. 기다려라 꿀커피..
우수 혼공족이 되니 더 열심히 해야겠다는 생각만,, 아자아자 !!
Chapter 02. 데이터 수집하기
02-1. API 사용하기
API
: 두 프로그램이 서로 대화하기 위한 방법을 정의한 것
→ 인증된 URL만 있으면 언제든지 필요한 데이터에 편리하게 접근할 수 있는 방식
→ 가장 편리하게 데이터를 수집할 수 있는 방식
HTTP
: 인터넷에서 웹 페이지를 전송하는 기본 통신 방법
→ 웹 서버 플그램과 웹 브라우저 간의 통신에서 사용하는 프로토콜
*프로토콜: 통신 규약
HTML
: 웹 브라우저가 화면에 표시할 수 있는 문서의 한 종류 , 웹 페이지를 위한 표준 언어(마크업 언어)
* 마크업 언어: 문서가 화면에 표시되는 형식을 나타내거나 데이터의 논리적인 구조를 명시하기 위한 규칙을 정의한 언어의 일종 _나무위키
* 태크: HTML 명령어의 집합
웹 기반 API
: HTTP 프로토콜을 사용해 만든 API
→ CSV, JSON, XML 형태로 데이터 전달 (HTML 구조보다 비교적 단순)
JSON
: 자바스크립트를 기반으로 한 범용적인 포맷
→ 대부분의 프로그래밍 언어는 JSON 형태의 텍스트 읽고 쓸 수 있음
→ 웹 기반 API는 전송하려는 파이썬 객체를 JSON 문자열로 변환
→ JSON 문자열을 파이썬 프로그램에 사용하기 위해 파이썬 딕셔너리로 변환
→ 키와 값을 콜론으로 연결 (파이썬 딕셔너리와 아주 유사한 형태)
*파이썬 딕셔너리: 중괄호 안에 키: 값 형식으로 콤마로 구분하여 저장
d = {"name": "혼자 공부하는 데이터 분석"} #키:name, 값:혼자 공부하는 데이터 분석
print(d['name'])
데이터를 전송하기 위해 문자열로 변환하는 과정이 왜 필요한가?
→ 웹 기반 API가 사용하는 HTTP 프로토콜은 텍스트 기반
→ HTTP 프로토콜로 데이터 전송 시 텍스트로 변환 필요
json.dumps( ) 함수로 파이썬 객체를 JSON 문자열로 변환하기
* 직렬화: 프로그램 상의 객체를 저장하거나 읽을 수 있는 형태로 변환하는 것
import json
d_str = json.dumps(d, ensure_ascii=False)
print(d_srt)
json.dumps( ) 함수는 아스키 문자 외의 다른 문자를 16진수로 출력
→ 한글이 제대로 출력되지 않음
→ ensure_ascii=False 로 지정하여 원래 저장된 문자 그대로 출력하게 하여 해결
print(type(d_str)) # type( )함수: 데이터 타입 확인
문자열 확인
json.loads( ) 함수로 JSON 문자열을 파이썬 객체로 변환하기
* 역직렬화: 직렬화된 정보를 다시 프로그램에서 실행 가능한 객체로 변환하는 것
d2 = json.loads(d_str)
print(d2['name'])
print(type(d2))
딕셔너리 확인
json.loads( )함수에 json 문자열 직접 전달
d3 = json.loads('{"name": "혼자 공부하는 데이터 분석", "author": "박해선", "year": 2022}')
print(d3['name'])
print(d3['author'])
print(d3['year'])
json.loads( )함수에 리스트를 포함한 json 문자열 직접 전달
d3 = json.loads('{"name": "혼자 공부하는 데이터 분석", "author": ["박해선", "홍길동"], "year": 2022}')
print(d3['author'][1])
json.loads( )함수에 json 배열 전달
d4_str = """ # 세겹따옴표로 긴 문자열 줄바꿈하여 입력 가능
[
{"name": "혼자 공부하는 데이터 분석", "author": "박해선", "year": 2022},
{"name": "혼자 공부하는 머신러닝+딥러닝", "author": "박해선", "year": 2020}
]
"""
d4 = json.loads(d4_str)
print(d4[0]['name'])
read_json( ) 함수로 JSON 문자열을 데이터프레임으로 변환하기
import pandas as pd
pd.read_json(d4_str)
pd.DataFrame(d4)
XML
: 컴퓨터와 사람이 모두 읽고 쓰기 편한 문서 포맷을 위해 고안
→ HTML은 웹 페이지를 표현하는 데는 뛰어나지만, 구조적이지 못해 API에서는 적절하지 않음
→ 엘리먼트들이 계층 구조를 이루며 정보 표현
→ 엘리먼트의 시작과 끝은 태그로 나타냄
fromstring( ) 함수로 XML 문자열을 파이썬 객체로 변환하기
x_str = """
<book> #부모 엘리먼트, 부모 노드
<name>혼자 공부하는 데이터 분석</name> #자식 엘리먼트
<author>박해선</author>
<year>2022</year>
</book>
"""
import xml.etree.ElementTree as et
book = et.fromstring(x_str)
print(type(book))
단순 파이썬 객체가 아닌 ElemenTree 모듈 아래 정의된 Element 클래스 객체
print(book.tag) #엘리먼트 이름 확인
findtext( ) 함수로 자식 엘리먼트 확인하기
해당하는 자식 엘리먼트를 탐색하여 자동으로 텍스트를 반환
→ 자식 엘리먼트의 순서에 상관없이 탐색하여 반환하기 때문에 안전
name = book.findtext('name')
author = book.findtext('author')
year = book.findtext('year')
print(name)
print(author)
print(year)
x2_str = """
<books>
<book>
<name>혼자 공부하는 데이터 분석</name>
<author>박해선</author>
<year>2022</year>
</book>
<book>
<name>혼자 공부하는 머신러닝+딥러닝</name>
<author>박해선</author>
<year>2020</year>
</book>
</books>
"""
#파이썬 객체로 변환
books = et.fromstring(x2_str)
print(books.tag)
#findall( ) 메서드와 for 문을 함께 사용하여 동일한 이름을 가진 여러 개의 자식 엘리먼트 탐색
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()
read_xml( ) 함수로 XML을 데이터프레임으로 변환하기
*판다스 1.3.0 버전부터 지원
pd.read_xml(x2_str)
API 호출하는 URL 작성
호출 URL과 파라미터 - ? 문자로 연결
파라미터와 값 - = 문자로 연결
파라미터와 파라미터 - & 문자로 연결
→ HTTP GET 방식
파라미터format: 미지정 시, XML 문서로 반환startDt: 검색 시작 일자endDt: 검색 종료 일자age: 연령대authKey: 인증키
→ 호출 주소에서 2021년 4월 1일 부터 2021년 4월 30일까지 20대의 대출 도서 조회
* 쿼리 스트링: ? 문자 뒤 연결된 파마리터와 값들
도서관 정보나루 인증키 승인대기중이라 승인완료 후 실습 재개 예정.. ㅠ
request 패키지로 API 호출하기
import requests
url = "http://data4library.kr/api/loanItemSrch?format=json&startDt=2021-04-01&endDt=2021-04-30&age=20&authKey=인증키"
r = requests.get(url)
data = r.json()
print(data)
data
books = []
for d in data['response']['docs']:
books.append(d['doc'])
books
books_df = pd.DataFramse(books)
books_df
books_df.to_json('20s_best_book.json') #json 파일로 저장
02-2. 웹 스크래핑 사용하기
웹 스크래핑(웹 크롤링)
: 웹 상트의 페이지를 옮겨 가면서 데이터를 추출하는 작업
! 웹 사이트에서 스크래핑을 허락했는지 확인하기
! HTML 태그를 특정할 수 있는지 확인하기
검색 결과 페이지 가져오기
import gdown
gdown.download('https://bit.ly/3q9SZix', '20s_best_book.json', quiet=False)
import pandas as pd
books_df = pd.read_json('20s_best_book.json')
books_df.head()
books = books_df[['no', 'ranking', 'bookname', 'authors', 'publisher',
'publication_year', 'isbn13']]
books.head()
loc 메서드로 데이터프레임 행과 열 선택하기
books_df.loc[[0, 1], ['bookname', 'authors']] # 첫 번째, 두 번째 행의 도서명과 저자 추출
books_df.iloc[[0, 1], [2, 3]] # iloc 메서드는 인덱스의 위치 사용
books_df.loc[0:1, 'bookname':'authors'] # 슬라이스 연산자 이용하여 슬라이싱
books = book_df.loc[:, 'no':'isnb13'] # 전체 행과 'no' 열에서 'isnb13' 열까지
books.head()
requests.get( ) 함수로 검색 결과 페이지 HTML 가져오기
import requests
isbn = 9791190090018 # '우리가 빛의 속도로 갈 수 없다면' ISBN
url = 'http://www.yes24.com/Product/Search?domain=BOOK&query={}'
r = requests.get(url.format(isbn))
url 중괄호에 isbn 값
print(r.text) #requests.get() 함수가 반환한 응답 객체 사용해 도서 검색 결과 페이지 HTML 출력
뷰티플수프 패키지로 HTML에서 데이터 추출하기
from bs4 import BeautifulSoup
soup = BeautifulSoup(r.text, 'html.parser')
BeautifulSoup(파싱할 HTML 문서, 파서)
파서: 입력 데이터를 받아 데이터 구조를 만드는 소프트웨어 라이브러리
파싱: 파서의 과정
find( ) 메서드로 태그 위치 찾기
→ 지정된 이름을 가진 첫 번째 태그 찾음
prd_link = soup.find('a', attrs={'class':'gd_name'})
print(prd_link)
* 개발자 도구 창 이용하기
soup. find(태그 이름, attrs={태그 속성}
print(prd_link['href']) # 링크 주소인 href 속성 값
도서 상세 페이지 HTML 가져오기
url = 'http://www.yes24.com'+prd_link['href']
r = requests.get(url)
print(r.text)
soup = BeautifulSoup(r.text, 'html.parser')
prd_detail = soup.find('div', attrs={'id':'infoset_specific'})
print(prd_detail)
find_all( ) 메서드로 테이블 태그를 리스트로 가져오기
→ 지정된 이름을 가진 모든 태그 찾음
prd_tr_list = prd_detail.find_all('tr')
print(prd_tr_list)
get_text( ) 메서드로 태그 안의 텍스트 가져오기
for tr in prd_tr_list:
if tr.find('th').get_text() == '쪽수, 무게, 크기': #th가 '쪽수, 무게, 크기'인 지 확인
page_td = tr.find('td').get_text() #참인 경우 td 변수의 텍스트 내용 추출
break
print(page_td)
print(page_td.split()[0])
apply( ) 메서드 사용하여 데이터프레임 행 또는 열에 함수 적용하기
top10_books = books.head(10)
def get_page_cnt2(row):
isbn = row['isbn13']
return get_page_cnt(isbn)
page_count = top10_books.apply(get_page_cnt2, axis=1) # 판다스 시리즈 객체로 저장
print(page_count)
apply( 실행할 함수, axis = 0(or 1))
→ 기본값: 0 (각 열에 대한 함수 적용)
→ 1 (각 행에 대한 함수 적용)
merge( ) 함수로 데이터프레임과 시리즈 합치기
merge( ) 함수 매개변수

on 매개변수: 합칠 때 기준이 되는 열 지정, df1과 df2에 모두 존재해야 함
→ 열의 값이 같은 행끼리 합쳐짐
pd.merge(df1, df2, on='col1')

how 매개변수 (기본값 inner: 값이 같은 행만 합쳐짐)
→ left: 첫 번째 데이터프레임 기준으로 두 번째 데이터프레임 합침
→ right: 두 번째 데이터프레임 기준으로 첫 번째 데이터프레임 합침
→ outer: 두 데이터프레임 모든 행 유지하며 합침
*NaN = 결측치
pd.merge(df1, df2, how='left', on='col1')

pd.merge(df1, df2, how='right', on='col1')

pd.merge(df1, df2, how='outer', on='col1')

left_on 과 right_on 매개변수: 합칠 기준이 되는 열의 이름이 서로 다를 경우 각기 지정
left_index 와 right_index 매개변수: 합칠 기준이 인덱스일 경우 왼쪽 또는 오른쪽 지정
pd.merge(df1, df2, left_on='col1', right_on='col1')

pd.merge(df1, df2, left_on='col2', right_index=True)

page_count.name = 'page_count' # name 속성 사용하여 이름 지정
print(page_count)
top10_with_page_count = pd.merge(top10_books, page_count, left_index=True, right_index=True)
top10_with_page_count