[WebCrawling] BeautifulSoup 라이브러리

MINJEE·2023년 12월 7일
0

SMHRD_5_WebCrawling

목록 보기
3/4
post-thumbnail

BeautifulSoup

BeautifulSoup 라이브러리란?

  • 웹페이지의 HTML구조를 파싱, 파이썬 beautifulsoup 객체로 변환
  • 파이썬에서 HTML 및 XML 문서를 파싱하고 데이터를 추출하는 데 사용되는 라이브러리
  • HTML문서 내 데이터에 접근할 수 있게 변환해주는 라이브러리
  • requests로 가져온 data를 분석할 수 있게 객체화

◉ bs4모듈 설치 + BeautifulSoup 라이브러리 Import

! pip install bs4

import requests as req
from bs4 import BeautifulSoup as bs

◉ 파싱 (Parsing)

파싱이란?

  • 문자열 형태로 이루어진 문서를 구문 분석
  • 웹페이지에서 원하는 데이터를 추출, 가공하기 쉬운 상태로 변환하는 것
    • 예를 들면, 생선을 낚아서 최종적으로 통조림의 형태로 만들어야하는 데, 이 때의 손질하려는 절차
  • 웹페이지에 존재하는 데이터는 list, dictionary와 같은 자료구조와 다르다. 그래서 parser함수나 프로그램으로 다루기 쉬운 형태로 바꾸어주는데 이 과정을 '파싱'이라고 한다.
  • 문서를 파싱하면 요소(Element), 속성(Attribute), 텍스트 등을 검색하고 조작할 수 있다.
response = req.get(url)
soup = bs(response.text, 'lxml') # html코드를 파싱(분석)
type(soup) ## <class 'bs4.BeautifulSoup'>

BeautifulSoup함수

  • HTML 또는 XML 문서를 파싱하여 BeautifulSoup 객체를 생성
  • 매개변수 markup (첫번째 매개변수) : 파싱할 HTML 또는 XML 문서를 나타내는 문자열 또는 파일 객체
    • requests로 얻은 데이터의 텍스트가 여기에 해당
  • 매개변수 features (두번째 매개변수) : 파서의 종류를 문자열로 지정
    • 일반적으로 'html.parser'를 사용
    • 'lxml'이나 'html5lib'과 같은 외부 파서를 지정 가능
    • 생략하면 기본적으로 내장된 'html.parser'가 사용

◉ select_one()함수 와 select()함수

: BeautifulSoup 객체에서 CSS 선택자를 사용하여 원하는 요소를 선택하는 데 사용되는 함수
# select_one()함수 : 원하는 데이터 1개일 때 추출
soup.select_one('태그명')

# select()함수 : 원하는 데이터 추출(여러개 일 때)
soup.select('태그명')
soup.select('.클래스명')
soup.select('상위태그>하위태그')
soup.select('#아이디명')
soup.select('태그명[속성1=값1]')

select_one(CSS selector) 함수

  • CSS 선택자에 해당하는 첫 번째 요소를 선택
  • 선택된 요소를 BeautifulSoup 객체로 반환
  • 원하는 데이터 1개를 추출할 때 사용

select(CSS selector) 함수

  • CSS 선택자에 해당하는 모든 요소를 선택
  • 선택된 요소들을 리스트 형태로 반환
  • 각 요소를 BeautifulSoup 객체로 사용 가능

CSS Selector

  • 태그 선택자 (Tag Selector)
    • 태그이름 으로 요소 선택
    • ex. div는 모든 <div> 태그를 선택
  • ID 선택자 (ID Selector)
    • # 기호를 사용하여 #아이디속성값 으로 요소 선택
    • ex. #my-id는 id 속성이 "my-id"인 요소를 선택
  • 클래스 선택자 (Class Selector)
    • .(온점) 기호를 사용하여 .클래스속성값 으로 요소 선택
    • ex. .my-class는 class 속성이 "my-class"인 요소를 선택
    • 한 태그 안에 클래스가 여러 개 있을 수 있음. 그 때는 클래스명 사이 공백을 온점(.)으로 바꿔서 사용
    • ex. .my-class.second-class는 class 속성이 "my-class"와 "second-class" 둘 다 포함하고 공백으로 분리되어 있는 요소를 선택
  • 자식 선택자 (Child Selector)
    • > 기호를 사용하여 특정 요소의 직계 자식 요소 선택
    • ex. div > p는 <div> 태그의 직계 자식인 <p> 태그를 선택
  • 부모 선택자 (Parent Selector)
    • & 기호를 사용하여 특정 요소의 부모 요소 선택
    • ex. p &는 <p> 태그의 부모 요소를 선택
  • 인접 형제 선택자 (Adjacent Sibling Selector)
    • + 기호를 사용하여 특정 요소의 바로 뒤에 있는 형제 요소 선택
    • ex. h2 + p는 <h2> 태그 다음에 있는 <p> 태그를 선택
  • 일반 형제 선택자 (General Sibling Selector)
    • ~ 기호를 사용하여 특정 요소의 형제 요소들 선택
    • ex. h2 ~ p는 <h2> 태그 뒤에 있는 모든 <p> 태그를 선택
# 예시 : '주차'검색하여 나온 기사 제목들 추출
import requests as req
from bs4 import BeatufulSoup as bs

res_park = req.get('https://search.naver.com/search.naver?sm=tab_sug.top&where=news&query=%EC%A3%BC%EC%B0%A8&oquery=%EC%BA%A0%ED%95%91&tqi=iZ8Wdsp0YiRssCfxPtVssssss2d-270145&acq=%EC%A3%BC%EC%B0%A8&acr=2&qdt=0')
res_park ## Response [200]
soup_park = bs(res_park.text, 'lxml')
articles = soup_park.select('a.news_tit') # class명이 news_tit인 a태그들

◉ 같이 많이 사용하는 기타 내용

파일 생성 방법 (csv, excel, txt)

  • csv파일로 저장 :데이터프레임.to_csv(’파일경로.csv’, encoding=’euc-kr'|'cp949’)
  • excel파일로 저장 : 데이터프레임.to_excel(’파일경로.xlsx’, encoding=’euc-kr'|'cp949’)
  • txt파일로 저장
    내용을 쓸 파일 열기 :파일명 = open(’파일경로.txt’, ‘w’)
    내용 쓰기 : 파일명.write(’내용’) (여러 번 작성하려면, for loop을 이용해서 여러번 작성해야 함)
    파일 닫기 : 파일명.close()

iframe tag

: 기존 페이지 내에 새로운 페이지를 삽입하는 태그
  • 하나의 페이지 안에, 다른 페이지를 불러오는 것
  • 일부 쇼핑몰 웹사이트에서는 리뷰/상품정보를 iframe으로 구성하여 보여주는 경우 많음

time 라이브러리

: 간과 관련된 작업을 수행하기 위해 사용되는 표준 라이브러리
  • 너무 많은 요청이 들어오면, 사용자 인증하는 방법으로 웹브라우저 보호
  • 사용자 인증 방법 종류 : capture 또는 '로봇이 아닙니다' 클릭항목 등
  • time.sleep(초단위숫자)으로 시간을 지연시켜서, 일정한 시간 간격을 주어서 웹페이지에 요청

쿼리 스트링(Query String)

: 웹 요청 URL에 추가되는 매개변수의 형태로 사용되는 문자열
  • 주소 ? key1=value1 & key2=value2 형식으로 되어 있는 주소 형식
  • url의 끝에 물음표(?)를 포함하여 요청 매개변수(request parameter)를 전달하기 위한 방법
  • reqeusts.get(url, params={'key1':value1, 'key2':value2}) : requests의 get함수 안에 params에 딕셔너리 형태로 대입

예시 : 여러 페이지에 있는 리뷰들 가져오기

# 필요한 library Import
import requests as req
from bs4 import BeautifulSoup as bs
import time # 요청 할 때, 지연시간을 주기 위한 library

# 해당 url에 GET요청
url = 'https://oneroommaking.com/product/detail.html?product_no=1911&cate_no=61&display_group=1'
head = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/112.0.0.0 Safari/537.36'}
res = req.get(url, headers = head)
res # 응답메세지 확인 ## Response [200]

# BeautifulSoup객체로 파싱
soup = bs(res.text, 'lxml')
soup.select('div.review_list_v2__message_container')
# => 경로를 잘잡아주었는데, 빈 리스트로 반환
# 리뷰부분의 태그 위에 iframe태그 존재
# 요청한 원룸페이지가 아닌, 해당 리뷰서비스를 제공하는 cream페이지로 요청
# cream페이지는 요청한 원룸페이지의 iframe태그의 src(sorce)속성에 있음!

##########################################################

# 리뷰페이지의 첫 페이지의 5개 리뷰 가져오기
## 리뷰페이지의 첫 페이지에 해당하는 iframe 태그의 url로 접속(GET요청)
url_iframe = 'https://review7.cre.ma/oneroommaking.com/products/reviews?product_code=1911&iframe_id=crema-product-reviews-2&widget_style=&app=0&parent_url=https%3A%2F%2Foneroommaking.com%2Fproduct%2Fdetail.html%3Fproduct_no%3D1911%26cate_no%3D61%26display_group%3D1&nonmember_token=&secure_device_token=V28b93e0df4e31b8a2fd0bc9ea579b674c056a54680756d897def44e62288ba95bf36007177dc6f281df543a833595e47d&iframe=1'
res = req.get(url_iframe)
res ## Response [200]
## BeautifulSoup객체로 파싱
soup = bs(res.text,'lxml')
reviews = soup.select('div.review_list_v2__message.js-collapsed-review-content.js-translate-text')
reviews ## 리뷰가 잘 들어와 있는 것 확인 완료!

cnt = 1 # cnt : 1로 초기화, 1부터 카운터가 되도록 세팅
for i in range(len(reviews)):
    print(f'[{cnt}]') # 리뷰보기 편하게 숫자로 출력구분
    print(reviews[i].text.strip()) # 공백제거하여 출력
    print() # 결과 보기 좋게 개행 추가
    cnt += 1

##########################################################

# 1~5페이지 리뷰 가져오기 (for loop 사용)
f = open('review.txt', 'w', encoding='utf-8') # 파일을 생성하고, 열고, 작성할거라는 선언
for pageNo in range(1,6): # pageNo : 1,2,3,4,5
    url = f'https://review7.cre.ma/oneroommaking.com/products/reviews?app=0&iframe=1&iframe_id=crema-product-reviews-2&page={pageNo}&parent_url=https%3A%2F%2Foneroommaking.com%2Fproduct%2Fdetail.html%3Fproduct_no%3D1911%26cate_no%3D61%26display_group%3D1&product_code=1911&secure_device_token=V2c26c49798b678b5367dde9393aefc632a7ec4b849d05080fd331cef21fd252c8bfaf2e281921deb066e40748c3824d48&widget_env=100&widget_style='
    # url : 1페이지는 page부분이 생략되어 있음 (2페이지 이후의 url 아무거나 복사해서 페이지부분 포맷팅)
    res = req.get(url) # get요청
    print(f"[현재 페이지 번호 : {pageNo}]") # 현재 페이지 번호 출력
    soup = bs(res.text, 'lxml') # beautifulsoup 객체화
    reviews = soup.select('div.review_list_v2__message.js-collapsed-review-content.js-translate-text') # 해당 페이지의 리뷰 요소들 선택
    
    # for loop을 하나 더 사용하여 공백 제거 (strip()함수 사용)
    for i in range(len(reviews)):
        print(reviews[i].text.strip()) # 공백제거한 리뷰
        print() # 개행
        f.write(reviews[i].text.strip()+'\n') # 파일에 리뷰내용 작성
    print('-'*80)
    
    # 페이지 요청을 위한 시간 지연시키기
    time.sleep(1) # 웹페이지가 바뀔 때의 시간을 확보해 주기 위해!!(적절한 딜레이 시간 설정)
f.close() # 파일 닫기

##########################################################

# 1~5페이지에 있는 리뷰 가져오기 (쿼리 스트링 이용)
for pageNo in range(1,6):
    print(f"[{pageNo}]")
    url2 = 'https://review7.cre.ma/oneroommaking.com/products/reviews?app=0&iframe=1&iframe_id=crema-product-reviews-2&parent_url=https%3A%2F%2Foneroommaking.com%2Fproduct%2Fdetail.html%3Fproduct_no%3D1911%26cate_no%3D61%26display_group%3D1&product_code=1911&secure_device_token=V2fc98f6259c654f126d62766e4ecf3f776c5f2b2aabadcc811e8b6e4ab1e67c01b3553921dea8a20d687711b910f76fc6&widget_env=100&widget_style=#'
    res2 = req.get(url2, params={'page':pageNo})
        # params : dictionary로 정의
        # key : url의 'page='의 page로 정의
        # value : page=3일 때의 3
    soup2 = bs(res2.text, 'lxml') # beautifulsoup 객체화
    reviews2 = soup2.select('div.review_list_v2__message.js-collapsed-review-content.js-translate-text')
    for i in range(len(reviews2)):
        print(reviews2[i].text.strip())
        print()
    print('-'*80)
    time.sleep(2)
  • for loop을 이용한 리뷰 페이지 1~5의 리뷰 출력 결과
profile
개발, 분석 배운 내용 정리하기!

0개의 댓글