본문 바로가기
카테고리 없음

[혼공학습단 12기] 혼자 공부하는 데이터 분석 with 파이썬 (2주차)

by 이네 (ine) 2024. 7. 15.
728x90
반응형

0. 이번 글 키워드 #웹 스크래핑 #뷰티플수프

1. 학습 목표

  • 웹 서비스 API에서 데이터를 가져오는 방법을 배운다
  • 웹 페이지를 웹 스크래핑하여 데이터를 가져오는 방법을 배운다 (2주차-2)

2. 도서 쪽수를 찾아서...

문제 상황: DB/공개 API에 도서 쪽수가 없는 상황이지만 알아내야 함.

웹 스크래핑 (web scrapping) or 웹 크롤링 (web crawling) : 프로그램으로 웹사이트의 페이지를 옮겨 가면서 데이터를 추출하는 작업

-> requests 패키지를 활용하여 웹사이트 HTML을 가져올 수 있다.

3. 검색 결과 페이지 가져오기

step 1) 사이트에서 검색 결과 페이지 HTML을 가져온다

step 2) gdown 패키지를 사용해 코랩으로 다운로드

step 3) read_json() 함수로 JSON 데이터를 데이터프레임으로 가져온다

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

 

3-1. 데이터프레임 행과 열 선택하기: loc 메서드

: 판다스가 제공, 원하는 행과 열을 조금 더 쉽게 선택 가능. [ ] 대괄호를 사용하여 목록을 받는다.

books_df.loc[[0,1], ['bookname', 'authors']]

Q) iloc은 무엇인가요?

A) loc 메서드는 인덱스와 열 이름을 사용. books_df의 행 인덱스는 0부터 시작하므로 인덱스와 인덱스 위치가 같다. 열의 경우 'no' 부터 위치가 0에서 시작하여 1씩 증가. 따라서 앞의 코드를 iloc으로 다시 쓰면 books_df.iloc[[0,1], [2,3]]과 같다.

books_df.loc[0:1, 'bookname': 'authors']

 리스트 대신 슬라이스 연산자 (:)를 이용한 코드. 🌟 파이썬의 슬라이싱과 다르게 마지막 항목도 포함. 🌟

books = books_df.loc[:, 'no':'isbn13']
books.head()

시작과 끝을 지정하지 않고 슬라이스 연산자를 사용하면 전체를 의미한다.

 

Q) 파이썬 슬라이싱처럼 스텝(step)을 지정할 수 있나요?

A) 아래는 2를 추가하여 하나씩 건너뛰며 행을 선택하도록 만든 코드다.

books_df.loc[::2, 'no' : 'isbn13'].head()

 

3-2. 검색 결과 페이지 HTML 가져오기: requests.get() 함수

-> request 패키지 import -> requests.get() 함수로 도서에 대한 검색 결과 페이지 HTML 불러옴.

import requests
isbn = 97911190090018
url = 'https://www.yes24.com/Product/Search?domain=Book&query{}'
r = requests.get(url.format(isbn))

print(r.text)

: 첫 번째 도서의 ISBN & Yes24 검색 결과 페이지 URL 위한 변수 정의, format () ISBN 변수값 -> URL 변수 전달

4. HTML에서 데이터 추출하기: 뷰티플수프

파이썬 프로그래머들은 웹페이지 / 웹 기반 API 호출하는데 request 패키지를 주로 사용.

비슷하게 HTML 안에 있는 내용을 찾을 때 뷰티플수프가 사용되는데, 이미 코랩에도 설치되어 있음.

 

Q) 뷰티플수프 말고 웹 스크래핑에 사용할 수 있는 다른 파이썬 패키지가 있는지?

A) 스크래피 (Scrapy)와 같은 스크래핑 패키지의 인기가 높다. 이 패키지는 request + beautiful soup 와 유사한데, 코랩에 설치되어 있지 않음.

* 스크래피 (scrapy)를 누르면 공식 사이트로 이동.

 

4-1. 크롬 개발자 도구로 HTML 태그 찾기

01. YES24 웹사이트에서 첫 번째 도서의 ISBN (97911190090018) 검색 후 

02. 윈도우는 F12, 맥 사용자는 command + option + i를 눌러 개발자 도구를 연다

03. 사진과 같은 절차를 거쳐 HTML 찾아준다

from bs4 import BeautifulSoup
soup = BeautifulSoup(r.text, 'html.parser')

-> 뷰티풀 수프 임포트 후 클래스 객체 생성. 첫 번째 매개변수는 파싱 (parsing) 할 HTML 문서, 두 번째는 파싱에 이용할 파서 (parser).

 

Q) 파싱과 파서란?

A) 파서는 입력 데이터를 받아 데이터 구조를 만드는 소프트웨어 라이브러리. 이런 과정을 파싱이라고 한다. html.parser는 파이썬에 내장된 HTML 파서다. 

 

4-2. 크롬 개발자 도구로 HTML 태그 찾기

: 태그 위치는 soup 객체의 find() 메서드를 사용하면 간편하게 찾을 수 있다. 첫 번째 매개변수에는 찾을 태그 이름을 지정하고, attrs 매개변수에는 찾으려는 태그의 속성을 딕셔너리로 지정하면 된다.

(예시) soup.find('div', attrs={'id':'search'})는 id 속성이 'search'인 <div> 태그를찾으라는 의미

prd_link = soup.find('a', sttrs={'class':'gd.name'})

: 도서 상세 페이지 링크가 있는 <a> 태그를 보면 class 속성이 'gd_name' 으로 지정되어 있다. 

print(prd_link)

: 링크가 포함되어 있는 prd_link는 뷰티플수프의 Tag 클래스 객체. 이 객체를 print() 함수에 출력하면 태그 안에 포함된 HTML 출력한다.

print(prd_link['href'])

 

4-3. 크롬 개발자 도구로 HTML 태그 찾기

# '우리가 빛의 속도로 갈 수 없다면'의 상세 페이지 가져오기
url = 'http://www.yes24.com'+prd_link['href']
r = requests.ger(url)

: 검색 결과 페이지를 가져왔을 때처럼 상세 페이지 주소를 만들어 requests.get() 함수를 호출.

print(r.text)

: 응답 객체 r을 사용해 가져온 HTML을 출력

--- 여기까지가 책의 상세페이지 가져오는 부분

 

-- 이제 쪽수가 담긴 HTML 위치 찾는 것

개발자 도구 -> select -> 쪽수 확인

뷰티플 수프 객체를 생성하고 find() 메서드로 품목정보 <div> 태그를 찾아서 출력

soup = BeautifulSoup(r.text, 'html.parser')
prd_detail = soup.find('div', attrs={'id': 'inforest_specific'})
print(prd_detail)

Q) <div> 태그 대신 그 아래 <table> 태그를 찾으면 안 되나?

A) 품목 정보는 앞에서 찾은 <div> 태그 아래 <"tb_nor tb_vertical" summary="품목정보 국내도서, 외국도서 "width"=100%"> 태그 안에 있다. 따라서 테이블을 다음처럼 찾을 수 있는데, 

prd_detail = soup.find('table', attrs={'class':'tb_nor tb_vertical'})

class 속성이 "tb_nor tb_vertical"인 <table> 태그가 상세 페이지 안에서 유일한 것인지 확신할 수 없다. 대신 id 속성이 "infoset_specific"인 <div> 태그는 품목정보를 나타내기 위해 사용한 것이라고 짐작 가능하다.

 

4-4. 테이블 태그를 리스트로 가져오기: find_all() 메서드

<div> 태그 안에 품목정보 테이블이 있는데, 테이블에서 '쪽수, 무게, 크기'에 해당하는 행인 <tr> 태그를 찾아 <td> 태그 안에 있는 텍스트 가져온다.

prd_tr_list = prd_detail.find_all('tr')
print(prd_tr_list)

: 뷰티플 수프의 find_all() 메서드를 사용하여 특정 HTML 태그를 모두 찾고 리스트로 반환한다.

 

4-5. 태그 안의 텍스트 가져오기: get_text() 메서드

<tr> 태그를 리스트로 추출 -> for 문을 이용하여 태그 안의 텍스트가 '쪽수, 무게, 크기'에 해당하는지 확인 -> 변수에 저장

for tr in prd_tr_list:
if tr.find('th').get_text() == '쪽수, 무게, 크기':
page_td = tr.find('td').get_text()
break

print(page_td)

* 파이썬 문자열 객체에서 split() 메서드를 사용하면 공백 기준 문자열을 나눠 리스트로 반환해준다

(예시) print(page_td.split()[0])

5. 전체 도서의 쪽수 구하기

~ 앞서 했던 작업을 하나의 함수로 만들기 위한 순서 정리 ~

1. 온라인 서점 검색 결과 페이지 URL 생성

2. requests.get() 함수로 검색 결과 페이지의 HTML 불러옴

3. 뷰티플수프로 HTML 파싱

4. 뷰티플 수프의 find() 메서드로 <a> 태그를 찾아 상세 페이지 URL 추출

5. requests.get() 함수로 도서 상세 페이지의 HTML 불러옴

6. 뷰티플 수프로 HTML 파싱

7. 뷰티플수프의 find() 메서드로 '품목정보' <div> 태그 찾기

8. 뷰티플수프의 find_all() 메서드로 '쪽수'가 들어있는 <tr> 태그 찾기

9.  앞에서 찾은 테이블의 행에서 get_text() 메서드로 <td> 태그에 들어있는 '쪽수'를 가져옴

 

5-1. 데이터프레임 행 혹은 열에 함수 적용하기: apply() 메서드

: 데이터 프레임에 200개의 도서가 있고, head() 함수로 10개의 행만 가져온다. 각 행에 get_page_cnt() 함수를 적용하여 10개 도서의 쪽수를 한 번에 구하려고 하는데 각 행의 반복 작업을 수행하기 위해 데이터 프레임의 apply() 메서드를 이용한다.

* apply() 메서드는 행 또는 열에 함수를 일괄 적용할 수 있는데, axis 매개변수에 1을 지정하면 행에 기본값 0을 지정하면 열에 적용된다.

top10_books = books.head(10)
def get_page_cnt2(row):
isbn = row['isbn13']
return get_page_ctn(isbn)
page_count = top10_books.apply(get_page_cnt2, axis=1)
print(page_count)

Q) 함수를 두 번 만들지 않고 더 간결하게 작성할 수 없을까?

A) 람다(lambda) 함수를 사용하면, 함수 이름 없이 한 줄로 쓸 수 있다.

(예시) page_count = top_10_books.apply(lambda row: get_page_cnt(row['isbn13']), axis=1)

 

5-2. 데이터프레임과 시리즈 합치기: merge() 함수

: page_count 시리즈 객체를 top10_books 데이터프레임의 열로 합치는 과정, 이렇게 하면 도서명과 쪽수를 한눈에 볼 수 있다.

(1) page_count 시리즈 객체에 이름을 지정 -> 이 이름은 top10_books 데이터프레임에 추가될 때 열 이름으로 사용

(2) 시리즈 객체의 name 속성을 사용하면 이름을 간단하게 지정할 수 있다

 

판다스에서 두 데이터프레임을 합치거나 시리즈와 합칠 때 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

5. 웹 스크래핑할 때 주의할 점

1. 웹사이트에서 스크래핑을 허락했는가? (robots.txt 파일 참고)

: /member/, /Product/Goods/addModules/ 등과 같은 페이지는 스크래핑 도구로 접근하면 언제든 막힐 수 있음.

2. HTML 태그를 특정할 수 있는지 확인

: 태그 이름 or 속성 등 필요한 HTML 태그를 특정할 수 없다면 스크래핑으로 데이터 가져오는 데 어려움이 있다. 자바스크립트를 사용하여 데이터를 가져오는 경우도 있는데, 이런 경우 셀레니움 (Selenium) 같은 고급 도구를 이용해야 한다. 최후의 사용 수단 추천.

 

6 . 웹 스크래핑으로 HTML 수집하기

 

🌀 기본 과제 pg 150

#01. 다음과 같은 데이터프레임 df가 있을 때 loc 메서드의 결과가 다른 하나는 무엇인가요?

  col1 col2
0 a 1
1 b 2
2 c 3

1. df.loc[[0,1,2], ['col1', 'col2']] -> 표와 같음

2. df.loc[0:2, 'col1' : 'col2'] -> loc 인덱싱의 슬라이싱은 종료 인덱스를 포함

3. df.loc [:2, [True, True]] -> 행 인덱스 슬라이싱은 끝 인덱스를 포함, 열 인덱스는 모든 열을 선택

4. df.loc [::2, 'col1':'col2'] -> 행 인덱스에서 '::2' 는 행을 2개씩 건너뛰면서 선택, 0행과 2행만 선택

  col1 col2
0 a 1
2 b 3

* 4번 실행 결과

반응형