Python – 웹 크롤링 (Web Crawling, Web Scraping)

Pyhton 웹 크롤링(Web Crawling)

웹 크롤러란?

위키 백과 – https://ko.wikipedia.org/wiki/%EC%9B%B9_%ED%81%AC%EB%A1%A4%EB%9F%AC

웹 크롤러(web crawler)는 조직적, 자동화된 방법으로 월드 와이드 웹을 탐색하는 컴퓨터 프로그램이다.

웹 크롤러가 하는 작업을 웹 크롤링(web crawling) 혹은 스파이더링(spidering)이라 부른다. 검색 엔진과 같은 여러 사이트에서는 데이터의 최신 상태 유지를 위해 웹 크롤링한다. 웹 크롤러는 대체로 방문한 사이트의 모든 페이지의 복사본을 생성하는 데 사용되며, 검색 엔진은 이렇게 생성된 페이지를 보다 빠른 검색을 위해 인덱싱한다. 또한 크롤러는 링크 체크나 HTML 코드 검증과 같은 웹 사이트의 자동 유지 관리 작업을 위해 사용되기도 하며, 자동 이메일 수집과 같은 웹 페이지의 특정 형태의 정보를 수집하는 데도 사용된다.

크롤링을 위한 Python 모듈

HTTP 요청 모듈 : urllib, requests

urllib 표준 모듈

기본 사용법
  • HTTP 를 통해 웹 서버에 데이터를 얻은데 사용
  • Python3 에서는 urllib.request 까지 import 해야 오류 안남
import urllib
request = urllib.request.Request(URL) 
AttributeError: module 'urllib' has no attribute 'request'
  1. urllib.parse.quote () : GET 요청시 한글 URL 엔코딩
  2. urllib.request.urlopen() : urllib.request.Request 객체를 받아 HTTP 요청
  3. response.getcode() : 응답코드 200 이면 OK, 404 면 File Not Found 등등
  4. response.read() : html 을 읽어 옴
import urllib.request 

search_addr = "http://book.daum.net/search/bookSearch.do?query="
search_word = "파이썬"    
search_word_enc = urllib.parse.quote(search_word) 
URL = search_addr + search_word_enc
request = urllib.request.Request(URL) 
response = urllib.request.urlopen(req)
status_code = response.getcode() 
html_data = response.read()
header 값 추가
  • 크롬등의 브라우저에서 header 값 확인
# 모바일 아이폰 접속시 header 일부 정보
user-agent:Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25
  • header User-Agent를 추가해서 모바일 페이지 호출 하기
import urllib.request
URL = "http://www.daum.net"

header_info = {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25'} 
request = urllib.request.Request(URL, headers=header_info) 

response = urllib.request.urlopen(request)
print(response.geturl()) # https://m.daum.net/?nil_top=mobile
print(response.info()) # 응답해더 값
예외 오류 처리
  • urllib.error 객체 사용
import urllib.request
import urllib.error 
search_addr = "http://book.daum.net/search/bookSearch.do?query="
search_word = "파이썬"    
search_word_enc = urllib.parse.quote(search_word) 
URL = search_addr + search_word_enc
request = urllib.request.Request(URL) 

try:
    response = urllib.request.urlopen(request)
    status_code = response.getcode() 
    if status_code == 200:
        print('Status Code :' + str(status_code))
        html_data = response.read()
        #print(response.info())
    else:
        print('Status Code :' + str(status_code))
except urllib.error.HTTPError as e:
    print(e.code)
    print(e.reason)
    print(e.headers) 

requests 모듈

pip install requests
기본 사용법
  1. requests.get(URL) : URL HTTP 요청
  2. response.status_code : 응답코드 200 이면 OK, 404 면 File Not Found 등등
  3. response.text : 응답내용을 읽어 옴
import requests 

search_addr = "http://book.daum.net/search/bookSearch.do?query="
search_word = "파이썬"    
URL = search_addr + search_word_enc
response = requests.get(URL) 
status_code = response.status_code 
html_data = response.text
print(status_code)
GET 요청
  • requests.get() 에서 parmas 옵션 사용
import requests 

URL = "http://book.daum.net/search/bookSearch.do"
params = {'query' : '파이썬'}
response = requests.get(URL, params=params) 
status_code = response.status_code 
html_data = response.text
print(response.url)
print(status_code)
POST 요청
  • requests.get() 에서 data 옵션 사용
import requests 

URL = "http://book.daum.net/search/bookSearch.do"
data = {'query' : '파이썬'}
response = requests.get(URL, data=data) 
status_code = response.status_code 
html_data = response.text
print(response.url)
print(status_code)
header 값 추가
  • requests.get() 에서 headers 옵션 사용
import requests 

URL = "http://book.daum.net/search/bookSearch.do"
params = {'query' : '파이썬'}
headers = {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25'}

response = requests.get(URL, headers=headers, params=params) 
status_code = response.status_code 
html_data = response.text
print(response.url) # m.book.daum.net 모바일 페이지가 호출 됨
print(status_code)
예외 오류 처리
import requests 

URL = "http://book.daum.net/search/bookSearch.do1"
params = {'query' : '파이썬'}
headers = {'User-Agent': 'Mozilla/5.0 (iPhone; CPU iPhone OS 6_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/6.0 Mobile/10A5376e Safari/8536.25'}

response = requests.get(URL, headers=headers, params=params) 
print(dir(response))

status_code = response.status_code 
if status_code == 200:
    html_data = response.text
    print(response.url)
    print(status_code)
else:
    print('Error code : ' + str(status_code))
    print('Error reason : ' + str(response.reason))

HTML, XML 파싱 모듈 : BeautifulSoup

pip install beautifulsoup4

기본 사용법

BeautifulSoup()

  • requests.get(URL)요청의 응답 데이터 파싱
import requests
from bs4 import BeautifulSoup

params = {'query' : '파이썬' }
url = 'http://book.daum.net/search/bookSearch.do'
res = requests.get(url, params=params)

html = BeautifulSoup(res.content, 'html.parser')
print(html.prettify())

태그 검색 방법

방법 1 : BeautifulSoup객체.태그명
html_body = html.title
print(html_body.prettify())

html_body_ul = html.body.ul
print(html_body_ul.prettify())
방법 2 : BeautifulSoup객체.(‘태그명’)
html_body = html('title')
print(html_body.prettify())

html_body_ul = html.body('ul')
print(html_body_ul.prettify())
방법 3 : BeautifulSoup객체.find(‘태그명’)
html_body = html.find('title')
print(html_body.prettify())

html_ul = html.find('ul')
print(html_body_ul.prettify())

일치하는 태그 숫자 제한

하나만 검색 : find(‘태그명’)
html_ul = html.find('ul')
print(html_body_ul.prettify())
모두 검색 : find_all(‘태그명’)
html_meta = html.find_all('meta')
for i in html_meta:
    print(i)
숫자 제한 해서 검색 : find_all(‘태그명’, limit=숫자)
html_meta = html.find_all('meta', limit=3)
for i in html_meta:
    print(i)

클래스(class) 아이디(id) 로 검색

  • find_all(class_ 옵션 ) 사용
html_class = html.find_all(class_='cate_list')
for i in html_class:
    print(i)

아이디(id) 로 검색

  • find_all(id 옵션 ) 사용
html_id = html.find_all(id='query')
for i in html_id:
    print(i)

속성으로 검색

  • find_all(attrs={‘속성’ : ‘값’ ) 사용
# class
html_attr = html.find_all(attrs={'class':'cate_list'})
for i in html_attr:
    print(i)

# id
html_attr = html.find_all(attrs={'id':'query'})
for i in html_attr:
    print(i)

# type
html_attr = html.find_all(attrs={'type': True})
for i in html_attr:
    print(i)

태그의 텍스트(내용) 만 검색

  • text 속성 사용
html_ul = html.find('ul')
print(html_body_ul.text)

다음(daum) 에서 ‘파이썬’ 책 검색을 해서 책제목, 가격, 지은이, 출판일 검색

  • 첫 번째 책 만 가져오기
  • html에서 <form class="" method="post" name="libaryForm"> 부분 이 하에서 책 리스트 검색됨
  • 하위 트리에서 ul.li 가 책 리트스
  • text 등 속성을 이용해서 원하는 값 가져 오기
import requests
from bs4 import BeautifulSoup

params = {'query' : '파이썬' }
url = 'http://book.daum.net/search/bookSearch.do'
res = requests.get(url, params=params)

html = BeautifulSoup(res.content, 'html.parser')
html_step1 = html.find(attrs={'name':'libaryForm'})
html_step2 = html_step1.ul.li
book_info = {}
book_title = html_step2.find(attrs={'class' : 'title'}).text
book_title = book_title.replace('\n','')
book_writer = html_step2.find(attrs={'class' : 'view'}).a.text
book_price =  html_step2.find(attrs={'class' : 'prc'}).text
book_date =  html_step2.find(attrs={'class' : 'date'}).text
book_price = book_price.replace('\n','')
book_price = book_price.replace('\t','')
#print(html_step2)

book_info['title'] = book_title
book_info['writer'] = book_writer
book_info['price'] = book_price
book_info['date'] = book_date
print(book_info)
  • 페이지 전체 책 가져오기
import requests
from bs4 import BeautifulSoup

params = {'query' : '파이썬' }
url = 'http://book.daum.net/search/bookSearch.do'
res = requests.get(url, params=params)

html = BeautifulSoup(res.content, 'html.parser')
html_step1 = html.find(attrs={'name':'libaryForm'})
html_step2s = html_step1.ul.find_all('li')
book_infos = []
for html_step2 in html_step2s:

    book_info = {}
    book_title = html_step2.find(attrs={'class' : 'title'}).text
    book_title = book_title.replace('\n','')
    book_writer = html_step2.find(attrs={'class' : 'view'}).a.text
    book_price =  html_step2.find(attrs={'class' : 'prc'}).text
    book_date =  html_step2.find(attrs={'class' : 'date'}).text
    book_price = book_price.replace('\n','')
    book_price = book_price.replace('\t','')

    book_info['title'] = book_title
    book_info['writer'] = book_writer
    book_info['price'] = book_price
    book_info['date'] = book_date

    book_infos.append(book_info)

for book_info in book_infos:
    print(book_info)