상세 컨텐츠

본문 제목

17. BeautifulSoup

Python

by evaseo 2021. 5. 6. 17:35

본문

1.    의미

(1)     HTML XML 파일로부터 데이터를 뽑아 내기 위한 파이썬 라이브러리

(2)     웹 크롤링(Web crawling) 또는 스크래핑(Scraping)은 웹 페이지들을 긁어와서 데이터를 추출

(3)     웹 크롤러는 자동화된 방식으로 웹 페이지들을 탐색하는 컴퓨터 프로그램

 

2.    설치

(1)     anaconda3이 설치되어 있다면 별도의 설치 불필요

(2)     BeautifulSoup: 명령 프롬프트에서 ‘pip install beautifulsoup4‘로 설치

(3)     HTML해석기 lxml 설치: pip install lxml

 

3.    사용순서

(1)     xml, html의 문서나 url을 텍스트 객체 생성

(2)     텍스트 객체를 BeautifulSoup 생성자로 BeautifulSoup객체 생성 = 파싱

1)       형식: BeautifulSoup(문서명/텍스트 객체명, ‘해석기’, from_encoding=문서의 인코딩방식)

2)       파싱: HTML 문서 내에서 내가 원하는 데이터를 특정 패턴이나 순서로 추출해 가공하는 것

(3)     원하는 자료 추출

 

4.    작동원리

(1)     HTML 해석기는 문자열들(입력된 글자들 전부)을 취해서 일련의 이벤트로 변환

(2)     문서의 최초 해석 상태: “<html> 태그 열기”, “<head> 태그 열기”, “<title> 태그 열기”, “문자열 추가”, “<title> 태그 닫기”, “<p> 태그 열기”,….

(3)     BeautifulSoup는 문서의 최초 해석 상태를 재구성하는 도구들을 제공

 

5.    http 요청 모듈

(1)     requests

1)       데이터를 보낼 때 딕셔너리 형태로 보냄

2)       요청 메소드(get / post) 를 명시

        requests.get(url, headers=headers, cookies=cookies)
:
유저의 정보를 주고 사이트의 정보를 얻는다.

        requests.post(url, headers=headers, cookies=cookies)

headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36'}

i.     header: 접속하는 사람/프로그램에 대한 정보

ii.    크롤링을 방지하는 사이트에서 만약 User-Agent를 점검하는 로직이 있다면, header로 해결가능

3)       get으로 parameter를 넘기고 post data를 보내는 방법: 딕셔너리를 인자로 넘기기

4)       없는 페이지를 요청해도 에러를 띄우지 않는다.

from bs4 import BeautifulSoup
import requests

crawling_url = 'http://www.vogue.co.kr/category/fashion/'
html = requests.get(crawling_url).text/content
bs = BeautifulSoup(html, 'html.parser')

 

(2)     urllib

1)       데이터를 보낼 때 인코딩하여 바이너리 형태로 보낸다. 그래서 csv 파일에 저장할 때는 'wb' 형식으로 작성해야 함!

2)       데이터의 여부에 따라 get/post 요청을 구분

3)       없는 페이지 요청 시 에러

from bs4 import BeautifulSoup
import urllib.request

url = 'https://www.melon.com/chart/week/index.htm'
req = urllib.request.Request(url)
html = urllib.request.urlopen(req).read()
# urllib.request.urlopen(url).read()와 같다.
soup = BeautifulSoup(html, 'html.parser')

 

(3)     url을 읽어올 때(html) 한글이 깨진다면

1)       html text read() 뒤에 .decode(‘utf-8’)을 붙여준다.

2)     'from_encoding=문서의 인코딩방식' 속성: BeautifulSoup 생성자로 객체 생성 시 속성으로 명시하면 된다.

 

(4)     접속 상태 확인: html.data.status_code

(5)     encoding방법 확인: html.data.encoding

import requests 
data = requests.get(url)
print(data.status_code, ' ', data.encoding) 
#status_code: 성공하면 200, 실패하면 400, 500번 대​
실행결과

(6)  페이지 소스 코드 확인: html.data.text

(7)     parse 클래스: 한글을 프로그램이 해석할 수 있게 인코딩해주는 패키지

1)       from urllib import parse 로딩

2)       parse.quote(“인코딩 대상”)

from urllib import parse # 한글 인코딩용

para = "이순신"
para = parse.quote(para)
print(para)
print(type(para))
# https://ko.wikipedia.org/wiki?q=이순신 과는 차이가 있음. 꼭 para를 인코딩 해줘야 함
url = "https://ko.wikipedia.org/wiki/" + para
# print(url) # para를 인코딩했기 때문에 https://ko.wikipedia.org/wiki/%EC%9D%B4%EC%88%9C%EC%8B%A0로 나옴. 
# https://ko.wikipedia.org/wiki/이순신이면 에러​
실행결과

 

[참고] python: urllib vs. requests (velog.io)

 

<soup = BeautifulSoup(문서명/텍스트 객체명, ‘해석기’, from_encoding=문서의 인코딩방식)>

 

6.    속성

(1)     soup.태그.name: 해당 태그명 반환

from bs4 import BeautifulSoup

html_page="""
        <html><body>
        <h1>제목태그</h1>
        <p>웹 문서 읽기</p>
        <p>원하는 자료 선택</p>
        </body></html>
        """
print(type(html_page)) #<class 'str'>
soup = BeautifulSoup(html_page, 'html.parser') #BeautifulSoup객체 생성
print(type(soup)) #<class 'bs4.BeautifulSoup'>이므로 BeautifulSoup관련 명령 사용 가능
print()

h1 = soup.html.body.h1 #soup에는 html이 있고 html자식으로 body가 있고 body자식으로 h1
print("h1: ", h1)
print("h1의 value: ", h1.string)
print("h1의 name: ", h1.name)
print('type(h1.string):', type(h1.string))
실행결과

(2)     클래스/id 속성이 있는 해당 태그들 중에서 첫번째 태그의 클래스/id 명 반환

1)       soup.태그명[‘class/id’]

2)       soup.태그명.attrs: dict타입으로 {‘id’/’class’:’ 클래스/id ’ ‘href’…}

 

(3)     soup.태그.string

1)       해당 태그의 value의 문자열 반환

2)       type: NavigableString

3)       해당 태그안에 여러 개의 태그가 있다면 추출 불가 -> None반환

 

(4)     soup.태그.strings

1)       해당 태그안에 여러 개의 태그가 있을 때 문자열 추출 가능

2)       for문 사용

3)       공백들도 추출

공백 추출 예시

(5)     soup.태그.stripped_strings

1)       soup.태그.strings으로 추출 시 생기는 문자열 앞뒤의 공백을 제거

2)       전체 공백은 적용 불가

공백 제거 예시

7.    NavigableString 클래스

(1)     태그안의 문자열을 저장하는 클래스

(2)     Comment 객체

1)       주석<!-- -->의 내용 문자열

2)       특별한 유형의 NavigableString

from bs4 import BeautifulSoup
markup = """
        <p>내용</p>
        <b><!--Hey, buddy. Want to buy a used parser--></b>
        """
soup = BeautifulSoup(markup, 'lxml')
print('soup.p.string:', soup.p.string)
print('type(soup.p.string):', type(soup.p.string))
print()
print('soup.b.string', soup.b.string)
print('type(soup.b.string):', type(soup.b.string))​
실행결과

8.    태그추출

(1)     속성이용

1)       첫 번째 태그를 반환: soup.태그

2)       태그들의 경로를 따라 마지막 태그를 반환: soup.태그1.태그2….

3)       자손태그들을 리스트로 반환: soup.태그.contents

        BeautifulSoup의 첫번째 자손은 <html>~</html>

        문자열에는 사용불가

4)       for문을 이용하여 자손 반환: soup.태그.children

from bs4 import BeautifulSoup
markup = """
        <p>내용</p>
        <b><!--Hey, buddy. Want to buy a used parser--></b>
        """
soup = BeautifulSoup(markup, 'lxml')
print('soup.p.contents:', soup.p.contents)
print()
for c in soup.children:
    print(c)​
실행결과

5)       가족트리로 태그 추출

        복수개의 태그 추출은 단일 태그 추출 명령어에 s를 붙여 사용

        복수개의 태그 추출은 for문 사용

        부모태그: soup.태그.parent

        형제태그

i.     앞의 형제: soup.태그. previous _sibling

ii.    뒤의 형제: soup.태그.next_sibling

iii.   라인이 다른 두 태그는 sibling명령어를 두 번 사용해야함

    => 쉼표, 라인들도 sibling으로 간주

from bs4 import BeautifulSoup

html_page="""
        <html><body>
        <h1>제목태그</h1>
        <p>웹 문서 읽기</p>
        <p>원하는 자료 선택</p>
        </body></html>
        """
soup = BeautifulSoup(html_page, 'html.parser')
p1 = soup.html.body.p #최초의 p를 만남
print("p1: ", p1)
print("p1: ", p1.string)
print()

p22 =  p1.next_sibling
print("p22: ", p22)
print("p22: ", p22.string)
print()

p2 = p1.next_sibling.next_sibling
print("p2: ", p2)
print("p2: ", p2.string)​
실행결과

from bs4 import BeautifulSoup

html_page="""
        <html><body>
        <h1>제목태그</h1>
        <p>웹 문서 읽기</p><p>원하는 자료 선택</p>
        </body></html>
        """
soup = BeautifulSoup(html_page, 'html.parser')
p1 = soup.html.body.p #최초의 p를 만남
print("p1: ", p1)
print("p1: ", p1.string)
print()

p22 =  p1.next_sibling
print("p22: ", p22)
print("p22: ", p22.string)
print()

p2 = p1.next_sibling.next_sibling
print("p2: ", p2)
print("p2: ", p2.string)​
실행결과

 

6)       요소추출: 최초의 해석상태를 기준으로 앞, 뒤 요소를 추출

        앞의 요소: soup.태그.next_element

        뒤의 요소: soup.태그. previous_element

from bs4 import BeautifulSoup
markup = """
        <p><i>내용</i></p>
        <b><!--Hey, buddy. Want to buy a used parser--></b>
        """
soup = BeautifulSoup(markup, 'lxml')
print('soup.i.next_element:', soup.i.next_element)
print('soup.i.next_element.next_element:', soup.i.next_element.next_element)
print('soup.i.next_element.next_element.next_element:', soup.i.next_element.next_element.next_element)
print('soup.i.next_element.next_element.previous_element:', soup.i.next_element.next_element.previous_element)​
실행결과

(2)     함수

1)       해당 속성을 가진 태그를 반환, 여러 개의 태그 중 첫번째 태그를 반환: find(name, attrs, recursive, text, **kwargs)

        soup.find(태그명): 해당 태그의 모든 요소를 리스트로 반환

from bs4 import BeautifulSoup

html_page2="""
        <html><body>
        <h1 id = 'title'>제목태그</h1>
        <p>웹 문서 읽기</p>
        <p id = 'my'>원하는 자료 선택</p>
        </body></html>
        """
soup2 = BeautifulSoup(html_page2, 'html.parser')  
print(soup2.p,' ', soup2.p.string) #직접 최초 태그 선택 가능
print()
print(soup2.find('p')) #<p>웹 문서 읽기</p>
print()
print(soup2.find('p').string) #웹문서 읽기
print()
print(soup2.find('p', id='my').string) #원하는 자료 선택       
실행결과

 

②  str(추출자료).find(문자열A): 추출자료 문자열 중에서 문자열A가 위치한 번호 반환

# 구글 검색 기능 이용: 검색 결과의 개수만큼 브라우저 창을 띄우기

import requests
from bs4 import BeautifulSoup
#import webbrowser
import time

def searchFunc():
    base_url = "https://www.google.co.kr/search?q={0}"
    
    sword = base_url.format("파이썬") # 한글일 때 plain_text.text가 잘 안넘어올 수도 있다
    #print(sword)
    
    
    # 보안때문에 import requests사용할 때는 headers를 사용
    headers = {'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/80.0.3987.122 Safari/537.36'}
    plain_text = requests.get(sword, headers = headers)
    
    #print(plain_text)
    #print(plain_text.text)
    
    soup = BeautifulSoup(plain_text.text, 'lxml')
    #print(soup)
    
    # #rso > div:nth-child(1) > div:nth-child(1) > div(.tF2Cxc) > div.yuRUbf > a 
    link_data = soup.select('div > div.yuRUbf > a')
    #print(link_data)
    
    for link in link_data[:3]:
        print('link:')
        print(link)
        print()
        print('print(link):', type(link), ' ', 'type(str(link)):', type(str(link))) # <class 'bs4.element.Tag'>   <class 'str'>
        print()
        print("str(link).find('https'):", str(link).find('https'),' ', "str(link).find('ping') - 2:", str(link).find('ping') - 2) 
        # ping에서 앞으로 두칸까지만 필요하니깐
        # 66   123/66   114/66   89
        print()
        print('str(link):', str(link))
        print()
        print("str(link).find('https'):", str(link).find('https'))
        urls = str(link)[str(link).find('https'):str(link).find('ping') - 2]
        print()
        print('urls:', urls)
       # time.sleep(5)
        #webbrowser.open(urls)
if __name__ == "__main__":
    searchFunc()​
실행결과

설명

 

2)       해당 속성을 가진 모든 태그를 리스트 형태로 반환

        find_all(name, attrs, recursive, text, limit, **kwargs)

        findAll(name, attrs, recursive, text, limit, **kwargs)

i.     name

a.       특정 이름을 가진 태그 추출

b.      문자열, 정규 표현식, 리스트, 함수, True/False

ii.    attrs

a.       태그명, 해당 태그 추출

b.      여러 개의 태그를 입력하여 복수 개 추출 가능

iii.   kwargs

a.       형식1: 속성= “속성값

b.      형식2: {속성명:속성값}

c.       속성값: 문자열, 정규 표현식, 리스트, 함수, True/False

iv.   text

a.       태그 대신 문자열을 탐색

b.      문자열, 정규 표현식, 리스트, 함수, True/False

v.    limit: 정수형, 반환 받을 결과의 개수

vi.   recursive

a.       True: 디폴트 값. 모든 자손 추출

b.      False: 직계 자손만 추출

from bs4 import BeautifulSoup

html_page3="""
        <html><body>
        <h1 id = 'title'>제목태그</h1>
        <p>웹 문서 읽기</p>
        <p id = 'my'>원하는 자료 선택</p>
        <div>
        <a href = "https://www.naver.com">naver</a>
        <a href = "https://www.daum.net">daum</a>
        </div>
        </body></html>
        """        
soup3 = BeautifulSoup(html_page3, 'html.parser')
 
print(soup3.find_all(['a','p'])) 
print()  
print(soup3.findAll(['a']))
print()        
print(soup3.find_all(['a']))        
print()                 
links = soup3.find_all('a')
print(links)
print()
for i in links:
    href = i.attrs['href']  
    text = i.string
    print(href, ' ', text)
실행결과

 

3)       find_all()이 아무것도 찾을 수 없다면, 빈 리스트를 돌려준다. find()가 아무것도 찾을 수 없다면, None을 돌려준다

from bs4 import BeautifulSoup

html_page3="""
        <html><body>
        <h1 id = 'title'>제목태그</h1>
        <p>웹 문서 읽기</p>
        <p id = 'my'>원하는 자료 선택</p>
        <div>
        <a href = "https://www.naver.com">naver</a>
        <a href = "https://www.daum.net">daum</a>
        </div>
        </body></html>
        """        
soup3 = BeautifulSoup(html_page3, 'html.parser')

print("soup3.findAll(['h2']):", soup3.findAll(['h2']))
print()
print("soup3.find_all(['h2']):", soup3.find_all(['h2']))
print()
print("soup3.find(['h2']):", soup3.find(['h2']))​
실행결과

4)       css selector로 추출: select(), select_one()

        다른 태그 아래의 태그를 찾을 수 있다

soup.select("body a")

 

        다른 태그 바로 아래에 있는 태그를 찾을 수 있다(항상 왼쪽이 부모)

i.     부모 > 직계자손
ex1) soup.select("head > title")
ex2) "div#hello > a":
직계 자손 a만 선택
ex3) "div#hello a":
자손 a 선택 = id hello div안에 모든 a태그
ex4) "div#hello ul.world": id
hello div안에  class world인 자손 ul
ex5) "div#hello ul.world > li" : id
hello div안에  class world인 자손 ul의 직계 자손

from bs4 import BeautifulSoup

html_page4="""
        <html><body>
        <div id='hello'>
        <a href = "https://www.naver.com">naver</a><br/>
        <span>
            <a href = "https://www.daum.net">daum</a>
        </span>
        <ul class="world">
            <li>안녕</li>
            <li>반가워</li>
        </div>
        <div id="hi">
            second div
        </div>
        </body></html>
        """        
soup4 = BeautifulSoup(html_page4, 'lxml')
aa = soup4.select_one("div#hello > a").string  # 단수선택
# select_one: 한개만 나옴, 그러니깐 여기서는 굳이 >안해도 되지만 정확하게 하려고, 
# "div#hello > a": 왼쪽이 부모, 직계자손 a만 선택
# "div#hello a": 왼쪽이 부모, 자손 a 선택 = id가 hello인 div안에 모든 a태그


 
print('aa: ', aa)
print()
bb = soup4.select("div#hello ul.world > li") # 복수선택
# "div#hello ul.world": id가 hello인 div안에  class가 world인 자손 ul
# "div#hello ul.world > li" : id가 hello인 div안에  class가 world인 자손 ul의 직계자손
print('bb: ', bb)
print()
# for문으로 태그없이 내용만 추출
for i in bb:
    print("li: ", i.string)​
실행결과

ii.    태그 + 태그/태그 태그: 아래방향 형제는 + 로 연결, 아래에서 위로 형제는 - 로 연결
ex) location > province + city + data > tmef: location
의 자식 중 province의 동생 city의 동생 data의 자식 tmdf

        CSS 클래스로 태그를 찾는다

i.     class로 태그를 찾는다
ex1) soup.select(".sister"): class = sister
인 태그 추출
ex2) soup.select("[class~=sister]"): class = sister
가 아닌 태그 추출
ex3) soup.select('div.tit3')

ii.    id로 태그를 찾는다
ex) soup.select("#link1")

        속성이 존재하는지 테스트한다
ex) soup.select('a[href]'): href
가 있는 a태그 추출

        속성 값으로 태그를 찾는다
ex1) soup.select('a[href="http://example.com/elsie"]')
ex2) soup.select('div[class=tit3]')

        select().get(속성)/select_one().get(속성): 속성 값을 문자열로 얻음
ex) soup.select('a[href="http://example.com/elsie"]').get(‘href) = http://example.com/elsie

5)       변경

        특정 태그에 문자열 추가

i.     soup.태그.append(문자열)

ii.    soup.태그.new_string(문자열)

        태그 추가: soup.태그.new_tag(태그명, 속성)

        태그 내용 제거: soup.태그.clear()

        태그삭제: soup.태그.extract()

        태그나 문자열을 제거하고 그것을 지정한 태그나 문자열(A)로 교체: replace_with("A")

6)       출력

        HTML/XML 태그마다 따로따로 한 줄에 표시: soup.태그.prettify()

        문자열로만 표시: str(soup.태그)

        출력 인코딩: soup.태그.encode(인코딩방법)

from bs4 import BeautifulSoup

html_page3="""
        <html><body>
        <h1 id = 'title'>제목태그</h1>
        <p>웹 문서 읽기</p>
        <p id = 'my'>원하는 자료 선택</p>
        <div>
        <a href = "https://www.naver.com">naver</a>
        <a href = "https://www.daum.net">daum</a>
        </div>
        </body></html>
        """        
soup3 = BeautifulSoup(html_page3, 'html.parser')

print(soup3.prettify())

실행결과

 

'Python' 카테고리의 다른 글

19. schedule 모듈  (0) 2021.05.07
18. json 모듈  (0) 2021.05.07
16. pandas - (2) DataFrame  (0) 2021.05.02
16. pandas - (1) 개요, Series  (0) 2021.05.01
15. 멀티스레드, 멀티프로세스  (0) 2021.05.01

관련글 더보기