: HTML과 XML문서를 파싱(parsing)하기 위한 라이브러리
웹 서버로부터 HTML 소스코드를 가져온 다음에는 HTML 태그 구조를 해석하기 위한 과정이 필요한데, 이 때 HTML 소스 코드를 해석하는 것을 파싱이라고 한다.
pip install BeautifulSoup4
from bs4 import BeautifulSoup
e.g.) soup = BeautifulSoup(html, 'html.parser')
soup.find(태그): 처음 나오는 태그로 찾기
soup.find_All(태그): 태그에 해당하는 모든 요소 찾아서 리스트로 반환
+)findAll로 쓰는 것도 가능하다! 자금은 잘 쓰지 않는 예전 방식.
soup.find(태그, attrs = {'class': css_selector}): 태그에 해당하는 선택자로 찾기
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)
soup.select_one(태그요소.선택자이름): css_selector에 해당하는 첫번째 태그 가져오기
soup.select(태그요소.선택자이름): css_selector에 해당하는 모든 태그 리스트 가져오기
{tag}.get_text(): 해당 태그의 텍스트를 가져오기
# 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. 서울시청 웹 크롤링 코드 예시>
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. 국립 중앙 박물관 관람 정보 웹 크롤링 코드 예시>
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 기사 웹 크롤링 코드 예시>
#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. 전자 신문 메인 기사 크롤링 코드 예시>
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. 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. 환율 정보 사이트 크롤링 코드 예시>
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. 주식 정보 사이트 크롤링 코드 예시>
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 선택자를 찾는 것이 어려웠다...보다 익숙해지면 더 신속하고 효과적으로 태그와 선택자를 활용할 수 있겠지?