본문 바로가기

데이터/데이터 분석

[혼공] ch 2. 데이터 수집하기

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 파일 확인하기