티스토리 뷰

PL/Python

1장 크롤링과 스크레이핑

Jinhyy 2018. 10. 16. 17:11

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
3) urllib에서 사용된 메소드

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



<결과 : 아보카도가 6개 출력됨>




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




공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함