beautifulsoup

: HTML과 XML문서를 파싱(parsing)하기 위한 라이브러리

웹 서버로부터 HTML 소스코드를 가져온 다음에는 HTML 태그 구조를 해석하기 위한 과정이 필요한데, 이 때 HTML 소스 코드를 해석하는 것을 파싱이라고 한다.

BeautifulSoup 설치

pip install BeautifulSoup4

BeautifulSoup 사용

from bs4 import BeautifulSoup

e.g.) soup = BeautifulSoup(html, 'html.parser')

BeautifulSoup - find() 사용하기

  • soup.find(태그): 처음 나오는 태그로 찾기

  • soup.find_All(태그): 태그에 해당하는 모든 요소 찾아서 리스트로 반환
    +)findAll로 쓰는 것도 가능하다! 자금은 잘 쓰지 않는 예전 방식.

  • soup.find(태그, attrs = {'class': css_selector}): 태그에 해당하는 선택자로 찾기

BeautifulSoup - find() 예시 코드


from bs4 import BeautifulSoup

soup = BeautifulSoup(html, 'html.parser')

#처음 나오는 ul 태그로 찾기

first_ul = soup.find('ul')
print(first_ul)
print(first_ul.text) #태그 없이 텍스트 출력

#모든 요소를 li 태그로 찾기

first_all_li = first_ul.findAll('li')
print(first_all_li)
print(first_all_li[1]) #리스트 요소 중 2번째 요소 반환
print(first_all_li[1].text) #리스트 요소 중 2번째 요소의 텍스트만 반환

fot li in first_all_li:
	print(li.text) #리스트로 반환되었던 li태그 요소들 텍스트만 모두 반환
    
#class 선택자로 찾기
#second_ul = soup.find('ul', attrs = {'class' = 'comlang'}
second_ul = soup.find('ul', class = 'comlang')
print(second_ul)
print(second_ul.text)

BeautifulSoup - select() 사용하기

  • soup.select_one(태그요소.선택자이름): css_selector에 해당하는 첫번째 태그 가져오기

  • soup.select(태그요소.선택자이름): css_selector에 해당하는 모든 태그 리스트 가져오기

  • {tag}.get_text(): 해당 태그의 텍스트를 가져오기

BeautifulSoup - select() 예시 코드


# select_one(태그이름.선택자이름) - 1개 요소 찾기

first_ul = soup.select_one('ul.industry')
print(first_ul)
print(first_ul.text)

# select(태그이름.선택자이름) - 모든 요소 찾기

first_all_li = first_ul.select('ul.indusetry > li')
print(first_all_li)
print(first_all_li[1].text)

second_ul = soup.select_one('ul.comlang')
print(second_ul)
print(second_ul.text)

데이터 크롤링 시 주의할 사항

무한 크롤링: 특정 웹사이트를 쉬지 않고 크롤링 하는 행위
-> 해당 웹사이트의 자원을 독점하게 되어 다른 사람의 사이트 사용을 막는 불편을 준다. (일종의 디도스 공격으로 간주될 수 있다.)
-> 이러한 상황을 막기 위해 일부 웹사이트에서는 동일 IP로 쉬지 않고 접속을 시도할 경우 해당 IP의 접속을 막기도 한다. (e.g. 인스타그램)

=> 하나의 페이지를 크롤링 한 후 (time.sleep()) 등의 함수를 활용하여 1 ~ 2초 가량 정지했다가 다음 페이지를 이어서 크롤링 하는 것이 좋다.

+) 신문이나 책, 사진 등 저작권이 있는 자료를 통해 상업적 이득을 취하는 행위는 저작권법 위반으로 제재 대상임으로 주의해야 한다.

로봇 배제 표준

접근 방법: 웹사이트 주소 끝(.com 기준) 부분 뒤에 /robots.txt를 추가하면 관련 규정을 확인할 수 있다.
(위 주소에 규정이 없는 경우 대부분의 접근을 허용하는 것으로 간주한다.)

+) 로봇에 의한 접근이 허용되는 경우더라도 웹 서버에 무리를 주는 행위나 서비스 안정성을 해칠 수 있는 행위는 지양해야 한다.

+) 크롤링(또는 스크래핑)으로 취득한 자료를 임의로 배포하거나 변경하는 행위는 저작권을 침해할 수 있으므로 규정을 준수해야 한다.

E.g.

템플릿 태그설명
User-agent: * Disallow: /모든(*) 로봇에게 루트 디렉토리 (/) 이하 모든 문서에 대한 접근 차단
User-agent: * Allow: /모든(*) 로봇에게 루트 디렉토리 (/) 이하 모든 문서에 대한 접근 허용
User-agent: * allow: /temp/모든(*) 로봇에게 특정 디렉토리 (/temp/)에 대한 접근 허용

+)일반적으로 공부용으로 자료를 활용하고 지우는 것은 용인된다.

+)구글 웹사이트의 로봇 배제 표준 예시

웹 크롤링 실습 예시

실습1. 서울시청 웹 크롤링하기

<실습1. 서울시청 웹 크롤링 코드 예시>

from bs4 import BeautifulSoup
import requests

html_url = "https://www.seoul.go.kr/main/index.jsp"
res = requests.get(html_url) #html 주소에서 request 받아오기
#print(res)
#print(res.text)
soup = BeautifulSoup(res.text, 'html.parser') # res.text는 html 코드 텍스트
#print(soup) #텍스트로 오던 아까와 달리 soup는 객체로 들어옴

all_nav = soup.select("nav > ul > li > a") # > 는 자식 선택자
#print(all_nav[1].text)
for i in all_nav:
    print(i.text)

<서울 시청 홈페이지의 html 코드>

<출력 결과>
서울소식
시민참여
분야별정보
서울소개
부서안내
정보공개
응답소
로그인
로그아웃
나의서울
전자우편

실습2. 국립 중앙 박물관 관람 정보

<실습2. 국립 중앙 박물관 관람 정보 웹 크롤링 코드 예시>

htmlurl = "https://www.museum.go.kr/site/main/home"
res2 = requests.get(htmlurl)
soup2 = BeautifulSoup(res2.text, 'html.parser')
#print(soup2) 
display_content = soup2.select("ul.main-info-area > li")
infos = soup2.select("ul.main-info-area > li")
#for i in infos:
#    print(i.text)

#관람 시간

times = soup2.select(".info-time > ul > li")
# html 코드에는 <div class = "info-txt info-time"으로 클래스 명이 2개 나타나 있는데, 둘 중 어느 것을 택하든 클래스 명으로서 기능한다. 
#print(times)
for i in times:
    print("이용시간: ", i.text.strip())

print()
#관람료

pay = soup2.select(".info-admission > ul > li") #배열로 나타남
#print(pay)
for i in pay:
    print("관람료: ", i.text.strip()) 

<국립 중앙 박물관 홈페이지의 html 코드>

<출력 결과>
이용시간: 월/화/목/금/일 10:00 ~ 18:00
이용시간: 수/토 10:00 ~ 21:00
이용시간: * 입장 마감은 폐관30분 전까지

관람료: 무료 특별전시는 유료

실습3. KBS 기사 크롤링 하기

<실습3. KBS 기사 웹 크롤링 코드 예시>


#html_url = "https://news.kbs.co.kr/news/pc/view/view.do?ncd=8117340"
html_url = "https://news.kbs.co.kr/news/pc/view/view.do?ncd=8118240" #url 다른 기사 가져와도 같은 코드로 크롤링 가능!
res = requests.get(html_url)
soup = BeautifulSoup(res.text, 'html.parser')
title = soup.select_one(".headline-title")
print("제목: ", title.text)

print()

contents = soup.select_one(".detail-body") #동적으로 하는 거라 앵커 리포트로 접근 불가
print("내용: ", contents.text.strip())

with open("news.txt", "w", encoding = "utf-8") as file:
    file.write(contents.text.strip()) #텍스트 파일로 기사 내용 저장

<KBS 기사 홈페이지의 html 코드>

<출력 결과>
제목: [충북&오늘] 디지털 관광주민증 발급…지역발전 행보 강화

내용: [앵커] 단양군과 보은군, 진천군이 지역 경제 활성화를 위한 주요 정책에 힘을 쏟고 있습니다. 디지털 관광주민 제도를 통한 생활인구 유치와 지역 발전
사업, 그리고 투자 유치인데요. 지역별 주요 소식, 진희정 기자가 보도합니다. [리포트] 디지털 관광주민증을 도입한 전국 자치단체 34곳 가운데
...(이하 생략)

실습4. 전자 신문 메인 기사 크롤링 하기

<실습4. 전자 신문 메인 기사 크롤링 코드 예시>

html_url = "https://www.donga.com/news/Society/article/all/20241203/130559522/1"
res = requests.get(html_url)
soup = BeautifulSoup(res.text, 'html.parser')

title = soup.select_one(" .view_head > div.inner > section.head_group > h1")
print("제목: ", title.text)
print()
issue_date = soup.select_one("ul.news_info > li > button")
print("업데이트 날짜: ",issue_date.text.strip())
print()
article = soup.select_one("div.main_view > section.news_view")
print("기사본문: ", article.text.strip())

<해당 전자신문 메인 기사 홈페이지의 html 코드>

<출력 결과>
제목: 연세대 논술시험 효력 인정…학교 측 “합격자 발표·2차시험 진행”

업데이트 날짜: 업데이트
2024-12-03 18:472024년 12월 3일 18시 47분

기사본문: 서울고법, 효력 정지했던 1심 뒤집어…“공정성 중대 훼손 없어”

11월 21일 오전 서울 서대문구 연세대 모습. 뉴스1
서울고등법원이 시험 문제 유출 논란에 휩싸인 2025학년도 연세대 수시모집 자연계열 논술시험의 효력을 인정했다. (이하 생략)

실습5. 명언 사이트 크롤링 하기

<실습5. Quotes to Scrape 사이트 크롤링 코드 예시>

#명언 사이트 크롤링 하기
html_url = "https://quotes.toscrape.com/"
res = requests.get(html_url)
soup = BeautifulSoup(res.text, 'html.parser')
quote = soup.select(".quote > .text")
#print(len(quote)) #리스트의 길이 반환, 10

# for i in quote:
#     print(i.text.strip())

text = [i.text.strip() for i in quote]
#print(text) #명언 텍스트만 모아서

speak = soup.select(".author") 
author = [i.text.strip() for i in speak]
#print(author) #명언 말한 인물 이름만 모아서

#같은 인덱스의 명언과 인물 각각 하나씩 포함한 zip 형태로 묶어 list 형태로 만듦
zipped = list(zip(text, author)) 
#print(zipped) 

for text, speak in zipped:
    print(f"말한사람: {speak} \n내용: {text}")
    print()

<Quotes to Scrape 홈페이지의 html 코드>

<출력 결과>
말한사람: Albert Einstein
내용: “The world as we have created it is a process of our thinking. It cannot be changed without changing our thinking.”

말한사람: J.K. Rowling
내용: “It is our choices, Harry, that show what we truly are, far more than our abilities.”
...(이하 생략)

실습6. 환율 정보 크롤링 하기

<실습6. 환율 정보 사이트 크롤링 코드 예시>

html_url = "https://finance.naver.com/marketindex/"
res = requests.get(html_url)
soup = BeautifulSoup(res.text, 'html.parser')
usd = soup.select_one("a.head.usd > div.head_info.point_dn > span")
print("USD: ", usd.text)

jpy = soup.select_one("a.head.jpy > div.head_info.point_dn > span")
print("JPY: ", jpy.text)

eur = soup.select_one("a.head.eur > div.head_info.point_dn > span")
print("EUR: ", eur.text)

cny = soup.select_one("a.head.cny > div.head_info.point_dn > span")
print("CNY: ", cny.text)

<환율 정보 사이트의 html 코드>

<출력 결과>
USD: 1,435.30
JPY: 920.27
EUR: 1,496.16
CNY: 197.24

실습7. 주식 정보 크롤링 하기

<실습7. 주식 정보 사이트 크롤링 코드 예시>

def stock(code):
    html_url = f"https://finance.naver.com/item/main.naver?code={code}"
    res = requests.get(html_url)
    soup = BeautifulSoup(res.text, 'html.parser')

    company = soup.select_one(" .wrap_company > h2 > a")
    print("회사명: ", company.text.strip())

    price = soup.select_one(" .today > .no_today .blind")
    print("종가: ",price.text.strip())

    aprice = soup.select(".today > .no_exday .blind")
    print("전일대비: ", aprice[0].text.strip())


#함수화시키기
stock("035720") 
#찾고 싶은 기업의 웹사이트 주소 뒤의 번호를 입력하기
#이 번호는 카카오 주식 회사

+) ccs 선택자에서 .blind 가 무슨 의미인지 기억이 안 난다. 해당 사이트에는 blind라는 클래스명을 가진 태그가 없는데...이후 질문할 것.

<주식 정보 사이트의 html 코드>

<출력 결과>
회사명: 카카오
종가: 36,300
전일대비: 150

후기

생각보다 CCS 선택자를 찾는 것이 어려웠다...보다 익숙해지면 더 신속하고 효과적으로 태그와 선택자를 활용할 수 있겠지?

0개의 댓글