본문 바로가기

언어/python&웹 크롤링

[python&웹 크롤링] 9. BeautifulSoup 사용 및 웹 파싱 기초(1)

- 파이썬 모듈인 BeautifulSoup 파서사용법을 알아보자

- urljoin, find_all, select_one, next_sibiling, previous_sibiling 을 사용하여 파싱을 진행하겠다.

 

아나콘다 프롬프트 실행 후 section2를 활성화 시키자

pip install beautifulsoup4 명령어를 입력 후 패키지를 다운받는다.

conda list 명령어를 통해 설치가 완료됨을 확인 후

atom 명령어 실행 후 atom을 실행시킨다.

 

파일을 생성 후 다음 코드를 입력해보자

 

 

1
2
3
4
5
6
7
8
from urllib.parse import urljoin
 
baseUrl = "http://test.com/html/a.html" #주소를 객체에 저장한다
print(">>", urljoin(baseUrl, "b.html")) # >> http://test.com/html/b.html 치환시킴
print(">>", urljoin(baseUrl, "sub/b.html")) # >> http://test.com/html/sub/b.html
print(">>", urljoin(baseUrl, "../index.html")) # >> http://test.com/index.html
print(">>", urljoin(baseUrl, "../img/img.jpg")) # >> http://test.com/img/img.jpg
print(">>", urljoin(baseUrl, "../css/img.css")) # >> http://test.com/css/img.css
cs

download2-5-1.py

 

urllib.parse 컴포넌트 의 urljoin 메서드를 사용했다.

상대경로 입력 시 기본 도메인 이후가 변경된다.

아래 urljoin의 라이브러리를 확인해보자

출처 : docs.python.org/3/library/urllib.parse.html

 

url 경로를 치환시키는 법을 알아봤으면 다음으로는 html 코드를 BeautifulSoup를 통해 객체로 담아와보자

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
from bs4 import BeautifulSoup
import sys
import io
 
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8'#한글 인코딩을 위해서라면
sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8'#입력해야 합니다.
 
html = """
<html>
<body>
<h1>파이썬 BeautifulSoup 공부</h1>
<p>태그 선택자</p>
<p>두번째 태그 선택자</p>
</body>
</html>
"""
 
soup = BeautifulSoup(html, 'html.parser'# soup 객체에 html를 넣어준다
 
print("soup type : ", type(soup)) # <class 'bs4.BeautifulSoup'>
print('prettify : ', soup.prettify()) # prettify 매서드는 html코드를 자동정렬 해준다
 
h1 = soup.html.body.h1                      #soup 객체의 html > body > h1 에 접근하여 담아온다.
 
print('h1 type : ', type(h1)) # <class 'bs4.element.Tag'>
print('h1 : ', h1) # h1에 담긴 h1 태그가 출력된다
print(h1.string) # string은 해당 태그 안의 내용만을 가져온다.
print('\n')
 
p1 = soup.html.body.p
print('p1 : ',p1) # p1은 p태그가 출력된다. html 코드에 p가 2개이상인데, 이땐 가장 상위의 것을 가져온다.
print('\n')
 
p2 = p1.next_sibiling # next_sibiling은 해당 태그의 바로 다음 요소를 나타낸다.
print('p2 : ', p2) # None이 출력된다. 두번째 p 태그가 담겨야 하는데 왜일까? 그 이유는 사이에 개행인 \n가 안보이게 존재하여 None을 담게되는것이다.
print('\n')
 
p2 = p1.next_sibling.next_sibling # 위의 문제점을 해결하기 위해 next_sibiling을 두번 사용한다.
print('p2 : ', p2) # 두번째 p 태그가 담긴것을 확인할 수 있다.
print('\n')
 
p3 = p1.previous_sibling.previous_sibling # previous_sibling은 해당 태그의 바로 이전 요소를 나타낸다.
print('p3 : ', p3) # p3는 p1 객체의 이전 요소를 담고있다. 여기도 역시 previous_sibling을 두번 사용했다.
print('\n')
 
print('h1 내용 : ', h1.string) # string은 해당 태그 안의 내용만을 가져온다.
print('p1 내용 : ', p1.string) # string은 해당 태그 안의 내용만을 가져온다.
print('p2 내용 : ', p2.string) # string은 해당 태그 안의 내용만을 가져온다.
print('p3 내용 : ', p3.string) # string은 해당 태그 안의 내용만을 가져온다.
cs

download2-5-2.py

 

코드 우측 주석 설명을 참조하고 위 코드를 실행해서 실제 실행 내용을 확인해보자.

위 방법은 단점이 한가지가 있는데,  p1= soup.html.body.p 같은 형식으로 객체에 담으면 p태그의 객체가 여러개 존재하여도 가장 상위의 p태그 하나밖에 담아오질 못한다.

그러므로 크롤링 할 때 잘 활용되지는 않는다.

그럼, 같은 태그가 여러개 존재 할때 묶어서 한번에 담아오는 방법이 필요할 것이다.

아래 코드를 통해 어떻게 할 수 있는지 확인해보자.

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
from bs4 import BeautifulSoup
import sys
import io
 
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
 
html = """
<html><body>
    <ul>
        <li><a href="http://www.naver.com">naver</a></li>
        <li><a href="http://www.daum.net">daum</a></li>
        <li><a href="http://www.daum.co.kr">daum</a></li>
        <li><a href="http://www.google.com">google</a></li>
        <li><a href="http://www.tistory.com">tistory</a></li>
    </ul>
</body></html>
"""
soup = BeautifulSoup(html, "html.parser")
 
links = soup.find_all("a"# find_all을 사용하여 a태그 모두를 담아온다. find_all은 파라미터로 전달된 태그 모두를 리스트 형태로 담는다.
print('links : ', links) # a태그 5개가 리스트의 형태로 들어감을 확인할 수 있다.
print('\n')
 
= soup.find_all("a", string="daum"# 태그 안 내용이 daum인 노드만 가져온다.
print('a : ', a) # 위 html에서 string이 daum인 두 태그를 가져온다.
print('\n')
 
##### b = soup.find("a")와 같이 find로 "a"태그를 찾게되면 html 순서 상 가장 상위의 a태그 하나만을 가져온다.
 
= soup.find_all("a", limit=3# limit 제한을 걸어 상위 3개만 가져올 수 있다.
print('b : ', b) #b [<a href="http://www.naver.com">naver</a>, <a href="http://www.daum.net">daum</a>, <a href="http://www.daum.co.kr">daum</a>]
print('\n')
 
= soup.find_all(string=["naver""google"]) # 파라미터에 태그를 넣어주지 않았으므로 string이 "naver", "google"인 것 모두 찾아서 c에 리스트 형태로 담아준다.
print('c : ', c) # ['naver', 'google']
print('\n')
 
#links 리스트를 for문을 돌려 출력
for a in links :    
    href = a.attrs['href']  #어트리뷰트 속성은 https://www.crummy.com/software/BeautifulSoup/bs4/doc/ 에서 확인가능하다. 키를 파라미터로 전달하면 dictionary 형태로 객체에 담긴다.
    txt = a.string
    print('txt >> ', txt, 'href >> ', href)
cs

download2-5-3.py

 

해당 코드의 주석을 참고 후 실행해보자.

find_all 매서드를 사용하여 태그 전체를 가져와 파싱할 수 있다.

html 코드 파싱을 조금 살펴봤으니 선택자를 통한 파싱 방법을 확인해보겠다.

 

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#css 선택자
#https://www.w3schools.com/cssref/css_selectors.asp 레퍼런스 참고
 
from bs4 import BeautifulSoup
import sys
import io
 
sys.stdout = io.TextIOWrapper(sys.stdout.detach(), encoding = 'utf-8')
sys.stderr = io.TextIOWrapper(sys.stderr.detach(), encoding = 'utf-8')
 
html = """
 <html><body>
 <div id="main">
  <h1>강의목록</h1>
  <h1>강의목록2</h1>
  <ul class="lecs">
   <li>Java 초고수 되기</li>
   <li>파이썬 기초 프로그래밍</li>
   <li>파이썬 머신러닝 프로그래밍</li>
   <li>안드로이드 블루투스 프로그래밍</li>
  </ul>
 </div>
 </body><html>
"""
 
soup = BeautifulSoup(html,"html.parser"# soup객체에 저장
h1 = soup.select("div#main > h1"# select 매서드를 통해 div 태그의 id=main 내 h1 전체를 담는다
h2 = soup.select_one("div#main > h1"# select_one 매서드를 통해  div 태그의 id=main 내 가장 상단 h1을 담는다.
print('h1 : ', h1)
print('h2 : ', h2)
# print(h1.string) # h1는 list object는 속성이기 때문에 string으로 출력 불가
print('h2 : ', h2.string) # h2는 리스트 형태가 아니기 때문에 스트링으로 바로 가져올 수 있다.
print('\n')
 
for z in h1 :
    print(z.string) #다음과 같이 for문을 통해 list형태를 string으로 출력 시킬 수 있다.
 
list_li = soup.select("div#main > ul.lecs > li"# select 매서드를 통해 div 태그의 id=main 내 클래스명이 lecs인 ul태그 내 li 전체를 가져온다.
 
for li in list_li :
    print("li >>>", li.string) #다음과 같이 for문을 통해 list형태를 string으로 출력 시킬 수 있다.
cs

download2-5-4.py

 

주석을 참고하면서 내용을 실행해보자.

선택자를 통하여 요소에 쉽게 접근이 가능하고, 값을 가져와서 출력 및 조작이 가능할 것이다.


Today :
Yesterday :
Total :