티스토리 뷰
1-1] 크롤링(데이터 긁어오기 = 다운로드)
1. urllib.request 이용한 다운로드 예제
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | import urllib.request import urllib.parse API = "http://www.kma.go.kr/weather/forecast/mid-term-rss3.jsp" # 매개변수를 URL 인코딩합니다. --- (※1) values = { 'stnId': '108' } params = urllib.parse.urlencode(values) # URL 요청시 필요한 param 생성. --- (※2) url = API + "?" + params # param 을 주소뒤에 ? 와 붙여 get방식으로 요청한다. print("url=", url) # 다운로드합니다. --- (※3) data = urllib.request.urlopen(url).read() text = data.decode("utf-8") print(text) | cs |
request.urlopen(url경로) : url을 오픈 한다.
read() 오픈한 url을 메모리에 올린다.
1-2] 스크레이핑(BeautifulSoup)
1. beautifulsoup 라이브러리 설치
pip3 install beautifulsoup4
2. bs 이용한 스크레이핑(원하는 데이터 추출) 예제
1)
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 50 | # 라이브러리 읽어 들이기 --- (※1) from bs4 import BeautifulSoup # 분석하고 싶은 HTML 지정 --- (※2) html = """ <html><body> <h1>스크레이핑이란?</h1> <p>웹 페이지를 분석하는 것</p> <p>원하는 부분을 추출하는 것</p> </body></html> """ # HTML 분석위하여 bs 인스턴스 생성 --- (※3) soup = BeautifulSoup(html, 'html.parser') # 원하는 부분 추출하기 --- (※4) h1 = soup.html.body.h1 p1 = soup.html.body.p p2 = p1.next_sibling.next_sibling #next_sibling을 1번 쓰면 줄바꿈 문자가 출력되므로 2번 써서 2번째 p태그를 추출한다. # 요소의 글자 출력하기 --- (※5) print("h1 = " + h1.string) print("p = " + p1.string) print("p = " + p2.string) # find() 메서드로 원하는 id 부분 추출하기 --- html = """ <html><body> <h1 id="title">스크레이핑이란?</h1> <p id="body">웹 페이지를 분석하는 것</p> <p>원하는 부분을 추출하는 것</p> </body></html> """ title = soup.find(id="title") body = soup.find(id="body") # 텍스트 부분 출력하기 print("#title=" + title.string) print("#body=" + body.string) # find_all() 메서드로 여러개의 요소 추출하기 --- html = """ <html><body> <ul> <li><a href="http://www.naver.com">naver</a></li> <li><a href="http://www.daum.net">daum</a></li> </ul> </body></html> """ links = soup.find_all("a") # 모든 a태그 추출 # 링크 목록 출력하기 --- for a in links: href = a.attrs['href'] # a태그의 href 속성에서 추출 text = a.string print(text, ">", href) # 결과 : naver > http://www.naver.com | cs |
2) select_one 및 심화 추출
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | from bs4 import BeautifulSoup text = ''' <html> <body> <h1 align="center" id="boxofficeTypeId" class="boxofficeType">일별 박스오피스</h1> <center> <h1 id="showRangeId" class="showRange">20180220~20180220</h1> </center> <table border="1" align="center"> <tr> <td class="rank">순위 (rank)</td> <td class="rankOldAndNew">신규진입여부 (rankOldAndNew)</td> <td class="movieCd">영화코드 (movieCd)</td> <td class="movieNm">영화명 (movieNm)</td> <td class="salesAmt">매출액 (salesAmt)</td> <td class="audiCnt">관객수 (audiCnt)</td> </tr> <tr> <td class="rank">1</td> <td class="rankOldAndNew">OLD</td> <td class="movieCd">20170561</td> <td class="movieNm">블랙 팬서</td> <td class="salesAmt">1339822000</td> <td class="audiCnt">171158</td> </tr> </table> </body> </html> ''' soup = BeautifulSoup(text, 'html.parser') #HTML 파서 ################## #태그 h1 = soup.select_one('h1') print(h1) #<h1 align="center" class="boxofficeType" id="boxofficeTypeId">일별 박스오피스</h1> print(h1.text) #일별 박스오피스 print(type(h1)) #<class 'bs4.element.Tag'> print(h1['align']) #center ################## #태그[속성="속성값"] #속성값에 공백이 없으면 속성값 사이에 " 않써도 됨 #CSS Selector h1 = soup.select_one('h1[align=center]') print(h1) #<h1 align="center" class="boxofficeType" id="boxofficeTypeId">일별 박스오피스</h1> print(h1.text) #일별 박스오피스 #*= 부분 문자열 포함 h1 = soup.select_one('h1[align*=ent]') print(h1) #<h1 align="center" class="boxofficeType" id="boxofficeTypeId">일별 박스오피스</h1> print(h1.text) #일별 박스오피스 #속성이 id 일 경우 태그#속성값 사용 가능 h1 = soup.select_one('h1#showRangeId') #h1 = soup.select_one('h1[id=showRangeId]') print(h1) #<h1 class="showRange" id="showRangeId">20180220~20180220</h1> print(h1.text) #20180220~20180220 #속성이 class 일 경우 태그.속성값 사용 가능 h1 = soup.select_one('h1.showRange') #h1 = soup.select_one('h1[class=showRange]') print(h1) #<h1 class="showRange" id="showRangeId">20180220~20180220</h1> print(h1.text) #20180220~20180220 ################## #바로 상위에 center 태그가 있음 h1 = soup.select_one('center > h1') print(h1) #<h1 class="showRange" id="showRangeId">20180220~20180220</h1> print(h1.text) #20180220~20180220 #상위 어디엔가에 center 태그가 있음 h1 = soup.select_one('center h1') print(h1) #<h1 class="showRange" id="showRangeId">20180220~20180220</h1> print(h1.text) #20180220~20180220 | cs |
1 2 3 4 5 6 7 8 9 10 11 | from bs4 import BeautifulSoup import urllib.request as req # HTML 가져오기 url = "http://info.finance.naver.com/marketindex/" res = req.urlopen(url) # HTML 분석하기 soup = BeautifulSoup(res, "html.parser") # 원하는 데이터 추출하기 --- (※1) price = soup.select_one("div.head_info > span.value").string #div 태그의 head_info 클래스에 속한 span 태그의 value 클래스의 텍스트를 가져와라. print("usd/krw =", price) | cs |
3. CSS 선택자 정리
1) 정리
서식 | 설명 |
* | · 모든 요소 |
태그명 | · 태그명이 일치하는 요소(예) |
.클래스명 | · 클래스 속성 값이 일치하는 요소 |
#id명 | · id 속성의 값이 일치하는 요소 |
서식 | 설명 |
선택자, 선택자 | · 열거된 복수의 선택자 (예) h1, h2 |
선택자 선택자 | · 하위 계층의 후손 요소 (예) div > h1 |
선택자 > 선택자 | · 바로 아래 계층의 자식 요소 (예) div > h1 |
선택자A + 선택자B | · 같은 계층에 선택자A 바로 뒤에 있는 선택자B 한 개 (예) h1 + h2 |
선택자A ~ 선택자B | · 같은 계층에 선택자A 바로 뒤에 있는 선택자B 모두 (예) p ~ ul |
서식 | 설명 |
요소[att] | · 특정 속성을 가지는 요소를 선택 |
요소[att = 'val'] | · att 속성의 값이 val인 요소, val값이 전체 일치해야 함 |
요소[att ~ 'val'] | · att 속성의 값에 val을 단어로(스페이스로 구분) 포함하는 요소 |
요소[att |= 'val'] | · att 속성의 값이 val이거나 val로 시작하고 뒤에 하이픈(-)이 있는 모든 요소 |
요소[att ^= 'val'] | · att 속성의 값이 val로 시작하는 요소 |
요소[att $= 'val'] | · att 속성의 값이 val로 끝나는 요소 |
요소[att *= 'val'] | · att 속성의 값에 val을 포함하는 요소 |
서식 | 설명 |
:root | · Document의 루트 요소 |
:nth-child(n) | · 동위 요소 중 n 번째 위치한 요소 |
:nth-last-child(n) | · 동위 요소 중 뒤에서 n 번째 위치한 요소 |
태그:nth-of-type(n) | · 동위 요소 중 지정한 태그 중 n번째 요소 |
:first-child | · 동위 요소 중 첫번째 요소 |
:lat-child | · 동위 요소 중 마지막 요소 |
태그:first-of-type | · 동위 요소 중 지정한 태그 중 첫번째 요소 |
태그:last-of-type | · 동위 요소 중 지정한 태그 중 마지막 요소 |
:only-child | · 동위 요소 없이 한 개의 요소만 있을 때 해당 요소 |
:only-child | · 동위 요소 중 지정한 태그 중 마지막 요소 |
태그:only-of-type | · 동위 요소 중 지정한 태그가 하나만 있을 때 해당 요소 |
:empty | · 내용이 빈 요소 |
:lang(code) | · 특정 언어 code로 된 요소 |
:not(s) | · s 이외의 요소 |
:enabled | · 활성화된 UI 요소 |
:disbled | · 비활성화된 UI 요소 |
:checked | · 체크된 UI 요소 |
2) 예제
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | <html> <body> <div id="main-goods" role="page"> <h1>과일과 야채</h1> <ul id="fr-list"> <li class="red green" data-lo="ko">사과</li> <li class="purple" data-lo="us">포도</li> <li class="yellow" data-lo="us">레몬</li> <li class="yellow" data-lo="ko">오렌지</li> </ul> <ul id="ve-list"> <li class="white green" data-lo="ko">무</li> <li class="red green" data-lo="us">파프리카</li> <li class="black" data-lo="ko">가지</li> <li class="black" data-lo="us">아보카도</li> <li class="white" data-lo="cn">연근</li> </ul> </div> <body> </html> | cs |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | from bs4 import BeautifulSoup fp = open("fruits-vegetables.html", encoding="utf-8") soup = BeautifulSoup(fp, "html.parser") # CSS 선택자로 추출하기 print(soup.select_one("li:nth-of-type(8)").string) #(※1) print(soup.select_one("#ve-list > li:nth-of-type(4)").string) #(※2) print(soup.select("#ve-list > li[data-lo='us']")[1].string) #(※3) print(soup.select("#ve-list > li.black")[1].string) #(※4) # find 메서드로 추출하기 ---- (※5) cond = {"data-lo":"us", "class":"black"} print(soup.find("li", cond).string) # find 메서드를 연속적으로 사용하기 --- (※6) print(soup.find(id="ve-list") .find("li", cond).string) | cs |
4. 링크에 있는 것을 한꺼번에 받기
1) 상대경로가 포함되어 있는 링크에서 다운받기
*base를 지정해 놓으면 상대주소가 있더라도 urljoin 메소드로 쉽게 변환가능*
1 2 3 4 5 6 7 | from urllib.parse import urljoin base = "http://example.com/html/a.html" print( urljoin(base, "b.html") ) print( urljoin(base, "sub/c.html") ) print( urljoin(base, "../index.html") ) print( urljoin(base, "../img/hoge.png") ) print( urljoin(base, "../css/hoge.css") ) | cs |
http://example.com/html/b.html
http://example.com/html/sub/c.html
http://example.com/index.html
http://example.com/img/hoge.png
http://example.com/css/hoge.css
2) 재귀적으로 다운받기(모든 페이지 한꺼번에 다운받는 프로그램)
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 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 | # 파이썬 매뉴얼을 재귀적으로 다운받는 프로그램 # 모듈 읽어 들이기 --- (※1) from bs4 import BeautifulSoup from urllib.request import * from urllib.parse import * from os import makedirs import os.path, time, re # 이미 처리한 파일인지 확인하기 위한 변수 --- (※2) proc_files = {} # HTML 내부에 있는 링크를 추출하는 함수 --- (※3) def enum_links(html, base): soup = BeautifulSoup(html, "html.parser") links = soup.select("link[rel='stylesheet']") # CSS links += soup.select("a[href]") # 링크 result = [] # href 속성을 추출하고, 링크를 절대 경로로 변환 --- (※4) for a in links: href = a.attrs['href'] url = urljoin(base, href) result.append(url) return result # 파일을 다운받고 저장하는 함수 --- (※5) def download_file(url): o = urlparse(url) savepath = "./" + o.netloc + o.path if re.search(r"/$", savepath): # 폴더라면 index.html savepath += "index.html" savedir = os.path.dirname(savepath) # 모두 다운됐는지 확인 if os.path.exists(savepath): return savepath # 다운받을 폴더 생성 if not os.path.exists(savedir): print("mkdir=", savedir) makedirs(savedir) # 파일 다운받기 --- (※6) try: print("download=", url) urlretrieve(url, savepath) time.sleep(1) # 1초 휴식 --- (※7) return savepath except: print("다운 실패: ", url) return None # HTML을 분석하고 다운받는 함수 --- (※8) def analyze_html(url, root_url): savepath = download_file(url) if savepath is None: return if savepath in proc_files: return # 이미 처리됐다면 실행하지 않음 --- (※9) proc_files[savepath] = True print("analyze_html=", url) # 링크 추출 --- (※10) html = open(savepath, "r", encoding="utf-8").read() links = enum_links(html, url) for link_url in links: # 링크가 루트 이외의 경로를 나타낸다면 무시 --- (※11) if link_url.find(root_url) != 0: if not re.search(r".css$", link_url): continue # HTML이라면 if re.search(r".(html|htm)$", link_url): # 재귀적으로 HTML 파일 분석하기 analyze_html(link_url, root_url) continue # 기타 파일 download_file(link_url) if __name__ == "__main__": # URL에 있는 모든 것 다운받기 --- (※12) url = "https://docs.python.org/3.5/library/" analyze_html(url, url) | cs |