HTML Parser인 bs4 라이브러리를 사용하고자 한다.
기존의 body를 이용해 모든 html 코드를 텍스트로 가져오는 과정을 거쳤는데, BeautifulSoup4를 통해 원하는 elements만 가져오도록 할 수 있다.
간단한 구현 코드는 다음과 같다!from bs4 import BeautifulSoup import requests res = resquests.get('www.example.com') res.text # 첫번째 인자로는 response의 body를 텍스트로 전달합니다. # 두번째 인자로는 "html"로 분석한다는 것을 명시해줍니다. soup = BeautifulSoup(res.text, 'html.parser') # 객체 soup의 .prettify()를 활용하면 분석된 HTML을 보기 편하게 반환해줍니다. print(soup.prettify()) # title 가져오기 print(soup.title) # head 가져오기 soup.head # body 가져오기 soup.body # <h1> 태그로 감싸진 요소 하나 찾기 soup.find('h1') # <p> 태그로 감싸진 모든 요소들 찾기 soup.find_all('p') # 태그 이름 가져오기 (찾은 요소를 객체로 변환한 변수에 name 적용할 것!) h1 = soup.find('h1') h1.name # 태그 내용 가져오기 h1.text
실습을 통해 BeautifulSoup을 사용해보고자 한다!
코드는 다음과 같다.# 스크래핑에 필요한 라이브러리를 불러와봅시다. from bs4 import BeautifulSoup as bf import requests # 예시 사이트에 요청을 진행하고, 응답을 바탕으로 BeautifulSoup 객체를 만들어봅시다. url = 'http://books.toscrape.com/catalogue/category/books/travel_2/index.html' res = requests.get(url) soup = bf(res.text, 'html.parser') # <h3> 태그에 해당하는 요소를 하나 찾아봅시다 soup.find('h3') # <h3> 태그에 해당하는 요소를 모두 찾아봅시다 soup.find_all('h3') book = soup.find('h3') # book_list에서 h3 특정 태그 내 우리가 원하는 제목(title)만 추출해봅시다. book.a.text # < 결과 > # - > "It's Only the Himalayas" # book_list에서 우리가 원하는 제목(title)을 모두 추출해봅시다. h3_results = soup.find_all('h3') for i in h3_results: print(i.a['title']) # < 결과 > ''' It's Only the Himalayas Full Moon over Noahâs Ark: An Odyssey to Mount Ararat and Beyond See America: A Celebration of Our National Parks & Treasured Sites Vagabonding: An Uncommon Guide to the Art of Long-Term World Travel Under the Tuscan Sun A Summer In Europe The Great Railway Bazaar A Year in Provence (Provence #1) The Road to Little Dribbling: Adventures of an American in Britain (Notes From a Small Island #2) Neither Here nor There: Travels in Europe 1,000 Places to See Before You Die '''
태그는 자신 이름 뿐만 아니라 고유한 속성 또한 가질 수 있다.
이중 id, class는 Locator로써 특정 태그를 지칭하는 데 사용된다.
- tagname : 태그 이름
- id : 하나의 고유 태그를 가리키는 라벨 (unique)
- class : 여러 태그를 묶는 라벨
<p>This element has only tagname</p> <p id="target">This element has tagname and id</p> <p class="targets">This element has tagname and class</p>
< 실습 코드는 다음과 같다.
soup = BeautifulSoup(res.text, 'html.parser') # id는 요소 하나를 지칭하는 Unique한 성질을 가졌고, 이를 이용해 단 1개의 태그를 쉽게 가져올 수 있다. # id 없이 div 태그 찾기 soup.find('div') # id가 ASDASDA인 div 태그 찾기 soup.find('div', id = 'ASDASDA') # class는 유사한 요소들을 구분 지을 수 있고, 이를 이용해 해당하는 태그 1개를 쉽게 가져올 수 있다. # class가 'page-header'인 div 태그 찾기 find_result = soup.find('div', 'page-header') # text만 깔끔하게 가져오기 (공백, 줄 제거 등) find_result.태그명.text.strip()
# 다음 User-Agent를 추가해봅시다.
user_agent = {"User-Agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.97 Safari/537.36"}
# 라이브러리 호출
import requests
from bs4 import BeautifulSoup
>
url = 'https://hashcode.co.kr/'
res = requests.get(url, user_agent)
soup = BeautifulSoup(res.text, 'html.parser')
>
# 질문의 제목을 모아서 가져오기
questions = soup.find_all('li', 'question-list-item')
question_list = []
for quest in questions:
print(quest.find('div', 'question').find('div','top').h4.text)
question_list.append(quest.find('div', 'question').find('div','top').h4.text)
# 질문의 빈도를 체크하는 dict를 만든 후, 빈도를 체크해봅시다.
frequency_check = {}
for i in question_list:
if i not in frequency_check:
frequency_check[i] = 1
else:
frequency_check[i] += 1
frequency_check
# 결과 ex) {'파이썬 프로젝트 중 오류': 1, '구조체 사용하여 다항식의 덧셈 구현하는 문제': 1}
##### Pagination
많은 정보를 인덱스로 구분하는 기법으로 주어진 page 별 추출하는 기법
# Pagination이 되어있는 질문 리스트의 제목을 모두 가져와봅시다.
# 과도한 요청을 방지하기 위해 1초마다 요청을 보내봅시다.
url = "https://hashcode.co.kr/?page={}"
import time
# 1~5페이지 상대로
for i in range(1, 6):
res = requests.get(url.format(i), user_agent)
soup = BeautifulSoup(res.text, 'html.parser')
questions = soup.find_all('li', 'question-list-item')
for quest in questions:
print(quest.find('div', 'question').find('div', 'top').h4.text)
# 1초의 interval 주기
time.sleep(1)
웹 페이지는 크게 정적 웹사이트와 동적 웹사이트로 나뉜다.
이를 살펴보면 다음과 같다.
- 정적 웹사이트
HTML 내용이 고정된 것
같은 주소 요청 시 같은 응답, 완전한 응답 얻을 수 있음
앞서 학습한 BeautifulSoup으로 get 가능
- 동적 웹사이트
HTML 내용이 변하는 것
응답 후 HTML이 렌더링 될 때까지의 지연시간이 존재해 곧바로 정보 추출이 힘듬
다양한 키보드 입력, 마우스 클릭 등 상호작용 존재
ex) 피드 빠르게 변화되는 SNS, 어플 등
-> 웹 브라우저를 파이썬으로 조작하는 방식 必🧨 동적 웹사이트 동작 방식
✔️ 웹 브라우저는 HTTP 통신을 보내고 받는데 사용.
✔️ 웹 브라우저에선 JavaScript라는 프로그래밍 언어가 동작.
✔️ 비동기 처리(요청에 따른 응답 대기 없이)를 통해 필요 데이터를 채움-> 비동기 처리의 경우 렌더링 과정에서 데이터 처리가 이루어지기에 데이터가 완전치 않은 경우가 발생해 기존 request로 요청하면 안된다.
✨ 동적 웹사이트에 request 요청 시 발생하는 문제 및 해결방안
- 완전치 않은 데이터에 요청을 보내면 불완전한 응답을 받게 되어 결과에 문제 발생
-> 임의로 시간을 지연한 후 데이터 처리가 끝난 후 정보를 가져오도록 함
- 마우스 클릭, 키보드 입력 등은 수시로 이루어지기에 request로 진행하기 힘듬
-> UI Action 문제는 웹 브라우저를 파이썬을 조작함으로써 해결 가능
-> 웹 브라우저를 자동화하는 Selenium 사용
< Selenium 예제 코드 >
from selenium import webdriver
# 크롬 브라우저 활용
driver = webdriver.Chrome()
# 응답 후 지연시간 부여 (문제 1 해결)
driver.implicitly_wait(10)
driver.get(url)
# UI와 상호작용 가능, 태그 이름을 통해 요소를 가져온 후 문자열을 키보드로 입력 진행 가능 (문제 2 해결)
elem = driver.find_element_by_tag_name('hello-input')
elem.send_keys('Hello')