Python

Lyoka료카·2023년 4월 4일
0

python 자료

목록 보기
2/3

BeautifulSoup 사용법

BeautifulSoup과 lxml이란?
BeautifulSoup이란 스크래핑을 하기위해 사용하는 패키지이다.
lxml은 구문을 분석하기 위한 parser이다.

response.text로 가져온 HTML문서는 단순히 String에 지나지 않으니, lxml을 통하여 의미있는 HTML문서로 변환하는 것이다.
response.text로 가져온 String은 lxml이라는 모듈의 해석에 의하여 의미있는 HTML 문서로 변환되고, 이렇게 변환된 HTML문서는 BeautifulSoup에 의해서 원하는 부분을 탐색할 수 있게 된다.
그래서 보통 BeautifulSoup이랑 lxml이랑 같이 쓰는 것 같다.

설치 방법

pip3 install beautifulsoup4
pip3 install lxml

사용법

import requests
from bs4 import BeautifulSoup

url = "https://comic.naver.com/webtoon/weekday"
response = requests.get(url)
response.raise_for_status()

soup = BeautifulSoup(response.text, "lxml")

예전에 하던 것처럼 requests를 통해 응답(response)를 받아오지만,
새롭게 추가된 것은
soup = BeautifulSoup(response.text, "lxml") 이라는 문장이다.
이 문장의 의미는 response.text를 통해 가져온 HTML 문서를 lxml parser를 통해서 BeautifulSoup 객체로 만들어 준 것이다.
따라서 soup은 해당 url의 모든 HTML 정보를 가지고 있게 된다.


1. '.'이용

'.'을 이용해서 보는 방법이 있다.
예를 들어 네이버 웹툰을 생각을 해보자면

import requests
from bs4 import BeautifulSoup

url = "https://comic.naver.com/webtoon/weekday"
response = requests.get(url)
response.raise_for_status()

soup = BeautifulSoup(response.text, "lxml")

1-1. soup.title

여기서 네이버 웹툰 웹사이트의 제목, 즉 title을 뽑아보자면
soup.title을 이용하여 뽑을 수 있다.

print(soup.title)
>>> <title>네이버 웹툰</title>

실제 네이버 타이틀을 보자면

따라서 soup 객체를 통해 네이버웹툰의 HTML의 바로 접근을 할 수 있다.

여기서 텍스트 정보만 뽑아 오고 싶다면 soup.title.get_text() 혹은 soup.title.string을 사용하면 된다.

print(soup.title.get_text())
print(soup.title.string)

>>>네이버 웹툰
>>>네이버 웹툰

이렇게 출력된다.

get_text()와 string의 차이

get_text()
get_text는 해당 element에 있는 모든 text를 추출한다.
모든 text를 추출해 하나의 String으로 만든다.

string
진짜 string만 추출한다.
줄바꿈, 공백 등은 제거된 string만 추출한다.
get_text()는 하위 태그의 모든 텍스트를 추출해오고, string은 해당하는 태그에 string이 있으면 그것만 가져온다.
string은 정확한 위치에서 가져오고, 만약 자식 태그가 둘 이상이라면 무엇을 가져와야할지 모르기에 None을 반환한다.
하지만 자식태그가 하나이고, 그 자식 태그에 .string 값이 있으면 해당하는 자식의 string을 추출한다.

1-2. soup.a

만약 아래있는 사진 속 body안에 있는 'a'태그를 가져오려면 어떻게 해야할까?

솔직히 이부분은 제가 아직 실행을 못했습니다.
계속 실행을 하려하면 None이 뜨거나 Error가 발생하여 계속 검색을 해봤지만 아직도 답을 찾지 못했습니다.

일단 설명을 해드리자면
먼저 코드를 보자면

print(soup.a)

>>> <a href="#menu" onclick="document.getElementById('menu').tabIndex=-1;document.getElementById('menu').focus();return false;"><span>메인 메뉴로 바로가기</span></a>

위에를 살펴보면 실행을 시켰을 때 a 태그가 출력되는 것을 확인할 수 있습니다.
되게 길어보이지만 결론적으로 말하면 HTML 문서에서 첫 번째로 a태그를 가지는 element 반환한다.
이때, soup.a.attrs(attribute)를 통해 이 element의 특성, 속성을 알 수 있다.

print(soup.a.attrs)

>>> {'href': '#menu', 'onclick': "document.getElementById('menu').tabIndex=-1;document.getElementById('menu').focus();return false;"}

딕셔너리 자료형으로 반환한다는 것을 알 수 있다.
따라서 딕셔너리를 통해서 검색을 할 수도 있다. a element의 href 속성 값 정보를 출력할 수 있다.

print(soup.a["href"])
print(soup.a.attrs["href"])

>>> #menu
>>> #menu

둘 다 똑같이 #menu를 반환한다.


2. find 이용

find를 통해 어떻게 HTML 정보를 가져올 수 있을까?
soup.find('a')를 하면 soup.a 와 마찬가지로 맨 처음으로 나오는 a 태그를 가지는 element를 반환하게 된다.

이때, 우리는 find에 또 다른 파라미터를 추가할 수 있는데,
앞에서는 attrs를 단순히 속성 정보를 읽어오기 위해 사용했지만,
find에서는 element를 조금 더 특정화 할 수 있는 파라미터로 이용할 수 있다.

'웹툰 올리기'의 class는 'Nbtn_upload' 임을 우리가 알 수 있다.

attrs에서 "class" 속성의 값이 "Nbtn_upload"을 가지는 a태그의 element를 가져올 수 있다.

어떻게 출력하냐면

print(soup.find('a', attrs={"class": "Nbtn_upload"}))

>>> <a class="Nbtn_upload" href="/mypage/myActivity" onclick="nclk_v2(event,'olk.upload');">웹툰 올리기</a>

이렇게 '웹툰 올리기'의 HTML 정보를 가져온다.
attrs에 딕셔너리 자료형으로 {"class": "Nbtn_upload"}를 추가해주면 된다.

또한, 옆에 인기순위를 가져올 수도 있다.
'li' 태그를 가지는 element중에 class 명이 rank01인 것을 가져와 보겠다.

print(soup.find('li', attrs={"class": "rank01"}))

>>> <li class="rank01">
<a href="/webtoon/detail?titleId=641253&amp;no=376" onclick="nclk_v2(event,'rnk*p.cont','641253','1')" title="외모지상주의-376화 일해회 (2계열사) [05]">외모지상주의-376화 일해회 (2계열사) [05]</a>
<span class="rankBox">
<img alt="변동없음" height="10" src="https://ssl.pstatic.net/static/comic/images/migration/common/arrow_no.gif" title="변동없음" width="7"/> 0
					
				
				</span>
</li>

실제로 해당하는 HTML 문서를 보면 저런 식으로 되어 있음을 알 수 있다.
저렇게 찾아낸 HTML 또한, soup 객체로 활용할 수 있다.

rank1 = soup.find('li', attrs={"class": "rank01"})
print(rank1.a)

>>> <a href="/webtoon/detail?titleId=641253&amp;no=376" onclick="nclk_v2(event,'rnk*p.cont','641253','1')" title="외모지상주의-376화 일해회 (2계열사) [05]">외모지상주의-376화 일해회 (2계열사) [05]</a>

이런 식으로 사용 할 수 있습니다.
다른방식으로 태그없이 사용가능한 방법이지만
하지만 이 방법은 가급적이면 안 쓰는 것을 추천합니다.

위와 같은 경우에는 해당 클래스 명을 가지는 특정 태그 element가 하나 밖에 없어서 가능한 일이다.


3. find_all 이용

find_all을 사용하면 모든 정보를 가져올 수 있다.
예를 들어 '쇼미더럭키짱!'으로 연습을 해보면

import requests
from bs4 import BeautifulSoup

url = "https://comic.naver.com/webtoon/list?titleId=783054&weekday=thu"
response = requests.get(url)
response.raise_for_status()

soup = BeautifulSoup(response.text, "lxml")

cartoons = soup.find_all('td', attrs={"class": "title"})
for cartoon in cartoons:
    print(cartoon.get_text())

cartoons는 td태그를 가지고, class가 title인 모든 정보를 리스트 형태로 가지고 있다.
이후 for 문을 이용하여 하나씩 뽑아 출력하면 제목이 정상적으로 출력되는 것을 알 수 있다.

네이버 웹툰 메인 페이지에서 출력

import requests
from bs4 import BeautifulSoup

url = "https://comic.naver.com/webtoon/weekday"
response = requests.get(url)
response.raise_for_status()

soup = BeautifulSoup(response.text, "lxml")

cartoons = soup.find_all('a', attrs={"class": "title"})
for cartoon in cartoons:
    print(cartoon.get_text())

당연히 리스트니까 []참조를 통해서 가져올 수도 있다.
ex) cartoons[0].get_text()


4. 응용

네이버 웹툰 홈페이지에 있는 정보에 대한 제목과 링크를 가져오는 코드를 한 번 작성해보겠습니다.

import requests
from bs4 import BeautifulSoup

url = "https://comic.naver.com/webtoon/weekday"
response = requests.get(url)
response.raise_for_status()

soup = BeautifulSoup(response.text, "lxml")

# 'a'태그를 가지고 class명이 title인 element를 모두 가져와 cartoons에 저장함
cartoons = soup.find_all('a', attrs={"class": "title"})

# cartoons에 저장된 element를 하나씩 뽑아내 출력함
for cartoon in cartoons:

    # cartoon에서 텍스트를 읽어옴, 제목을 읽어오는 것
    title = cartoon.get_text()
    
    # cartoon에서 링크를 읽어옴, 딕셔너리 형태로 참조함
    link = cartoon["href"]
    
    # 얻어온 제목과 링크를 프린트함. 이때, link는 완전한 형태가 아니라서 https://comic.naver.com을 추가해줌
    print(title, "https://comic.naver.com" + link)

이렇게 출력된다 합니다.

참고로 link 값은

이런 식으로 되어있어서 우리가 완벽하게 만들어 줘야합니다.


5.요약

soup객체는 .(dot)을 통해서 참조할 수 있지만, 대부분의 경우 우리가 모르는 웹 사이트를 참조하기 때문에, find를 이용하는 것이 좋다.

find는 하나의 element만 가져오고, 다수의 element는 가져오지 않는다. 이때, 다수의 element중에서도 맨 처음 나오는 한 개의 element만 가지고 온다.

이때, find_all을 사용하면 해당하는 element를 모두 가져와 리스트 형태로 저장할 수 있다.

find와 find_all의 사용법은 다음과 같다.
ex) div태그 아래 class 명이 hello인 경우
soup.find("div", attrs={"class": "hello"}
soup.find_all("div", attrs={"class": "hello"}


profile
고딩 프로그래머

0개의 댓글