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())
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 |