1. 기본 웹 크롤러
[ 기본 웹 크롤러 ]
- Requests로 웹 페이지를 추출하고, lxml로 웹 페이지 스크래핑 및 sqlite3 DB에 데이터를 저장
- 크롤링 대상 = 한빛 미디어 사이트의 "새로나온 책"목록
- 전형적인 목록/상세 패턴을 가진 웹사이트를 기반으로 도서 정보 추출 크롤러 제작
(목록 페이지 : 제목, 저자 정보 / 상세 페이지 : 제목, 가격, 목차 정보 추출 )
[ 목록 페이지에서 퍼머 링크 목록 추출 ]
크롤링 대상 페이지 : https://www.hanbit.co.kr/store/books/new_book_list.html
- 목록 페이지에서 상세 페이지로의 링크 목록을 추출
import requests
import lxml.html
response = requests.get('http://www.hanbit.co.kr/store/books/new_book_list.html')
root = lxml.html.fromstring(response.content)
for a in root.cssselect('.view_box a'):
url = a.get('href')
print(url)
제목(a)에서 하이퍼링크(href) 추출
- "javascript" 로 시작하는 목록 제거 & 퍼머 링크 목록 추출
import requests
import lxml.html
response = requests.get('http://www.hanbit.co.kr/store/books/new_book_list.html')
root = lxml.html.fromstring(response.content)
# 모든 링크를 절대 URL로 변환
root.make_links_absolute(response.url)
# 목록에서 javascript 제거
for a in root.cssselect('.view_box .book_tit a'):
url = a.get('href')
print(url)
make_links_absolute 로 response.url을 절대 URL 로 변환함
.book_tit에서 href를 다시 추출 -> javascript 제거
- scrape_llist_page() 함수의 반환값은 llist처럼 반복 가능한 제너레이터로 구현
def main():
'''
크롤러의 메인 처리
'''
# 여러 페이지에서 크롤링을 위해 Session 사용
session = request.Session()
#scrpae_list_page() 함수를 호출해 제너레이터를 추출
response = session.get('http://www.hanbit.co.kr/store/books/new_book_list.html')
urls = scrape_list_page(response)
for url in urls :
print (url)
print ('-'*70)
main 함수에서 scrape_list_page 함수를 호출해 url 추출
def scrape_list_page(response):
root = lxml.html.fromstring(response.content)
root.make_links_absolute(response.url)
for a in root.cssselector('.view_box .box_tit a'):
url = a.get('href')
#yield 구문으로 제너레이터 요소 반환
yield url
scrape_list_page 함수 정의 (href 추출 함수)
[ 상세 페이지 스크래핑 ]
- 개발자 도구로 CSSSelector 확인
타이틀 : .store_product_info_box h3
가격 : .pbr strong
목차 : #tabs_3.hanbit_edit_view 내부의 p 태그들
- response를 매개변수로 scrape_detail_page()를 호출해서 책의 상세 정보를 추출
- scrape_detail_page() 함수에서는 CSS Selector를 사용해 스크래핑
- 제목과 가격은 root.cssselector() 함수로 추출한 리스트의 첫번째 요소에서 문자열을 추출
- 목차는 List Comprehension을 사용해 목차를 리스트로 추출
- 목차에 포함되어 있는 공백을 제거할 수 있는 normalize_space() 함수 정의
List Comprehension 구문에 조건을 추가해 빈 문자열을 제거
import requests
import lxml.html
import re
def scrape_detail_page(response):
"""
상세 페이지의 Response에서 책 정보를 dict로 추출
"""
root = lxml.html.fromstring(response.content)
ebook = {
'url': response.url,
'title': root.cssselect('.store_product_info_box h3')[0].text_content(),
'price': root.cssselect('.pbr strong')[0].text_content(),
'content': [normalize_spaces(p.text_content())
for p in root.cssselect('#tabs_3.hanbit_edit_view p')
if normalize_spaces(p.text_content()) != '']
}
return ebook
url, title, price, content 추출
content는 normalize_spaces 함수로 공백 제거
def normalize_spaces(s):
"""
연결된 공백을 하나의 공백으로 변경
"""
return re.sub(r'\s+', ' ', s).strip()
공백 제거 함수 정의
- 전체 페이지 크롤링을 위해 main() 함수의 for 구문에서 break를 제거
대상 서버에 부하를 주지 않은 상태에서 크롤링을 위해 time 모듈 import 및 time.sleep(1)을 넣어 1초 동안 대기
import time
import requests
import lxml.html
import re
def main():
# 여러 페이지에서 크롤링을 위해 Session 사용
session = requests.Session()
# scrape_list_page() 함수를 호출해서 제너레이터를 추출
response = session.get('http://www.hanbit.co.kr/store/books/new_book_list.html')
urls = scrape_list_page(response)
for url in urls:
time.sleep(1) # 1초간 대기
response = session.get(url) # Session을 사용해 상세 페이지를 추출
ebook = scrape_detail_page(response) # 상세 페이지에서 상세 정보를 추출
print(ebook) # 상세 정보 출력
break
2. 고급 웹 크롤러
[고급 웹 크롤러]
- daum IT 뉴스 크롤러
https://news.daum.net/breakingnews/digital
1. 뉴스 목록 첫페이지 -> 2. 뉴스 상세페이지 -> 3. 다음 페이지 이동 -> 1. 뉴스 목록 첫페이지
반복
- 다음 뉴스 목록 첫 페이지
import requests
import lxml.html
import pandas as pd
import sqlite3
from pandas.io import sql
import os
REG_DATE = '20200902'
response = requests.get('https://news.daum.net/breakingnews/digital?regDate={}'.format(REG_DATE))
root = lxml.html.fromstring(response.content)
for li in root.xpath('//*[@id="mArticle"]/div[3]/ul/li'):
a = li.xpath('div/strong/a')[0]
url = a.get('href')
print(url, a.text)
for문을 이용해 한 기사씩 추출 (for li in root.xpath)
a 에 기사 제목 추출 (li 상대 경로)
url에 하이퍼링크 추출 (a 태그에서 href 추출)
-> url, 제목 출력
* XPath 찾기
1. 개발자 도구 실행 후 Select 도구로 추출할 기사 선택
2. 해당 태그에서 오른쪽 마우스 - copy - copy XPath
- 다음 뉴스 목록 상세 페이지
import requests
import lxml.html
import pandas as pd
import sqlite3
from pandas.io import sql
import os
import re
import string
def get_detail(url):
body = []
punc = '[!"#$%&\'()*+,-./:;<=>?[\]^_`{|}~“”·]'
response = requests.get(url)
root = lxml.html.fromstring(response.content)
for p in root.xpath('//*[@id="harmonyContainer"]/section/p'):
if p.text: # 체크
body.append(re.sub(punc, '', p.text)) # 특수문자 제거
full_body = ' '.join(body)
return full_body
get_detail('https://news.v.daum.net/v/20200902130715304')
상세 내용을 가져오는 get_detaill 함수 생성
특수문자 정규식 만듬
harmonyContainer div의 /section/p 전체 검사
p를 체크해 특수문자를 제고하고 body에 추가
full body 변수에 p간 공백을 주며 조인 (반복)
full body 변수 출력
- 다음 뉴스 첫 페이지 목록과 상세 페이지
page = 1
max_page = 0
REG_DATE = '20200819'
response = requests.get('http://news.daum.net/breakingnews/digital?page={}®Date={}'\
.format(page, REG_DATE))
root = lxml.html.fromstring(response.content)
for li in root.xpath('//*[@id="mArticle"]/div[3]/ul/li'):
a = li.xpath('div/strong/a')[0]
url = a.get('href')
article = get_detail(url)
print(f'URL : {url}')
print(f'TITLE : {a.text}')
print(f'ARTICLE : {article}')
print('-' * 100)
response 의 url 각 {}에 page 번호, 날짜(REG_DATE) 를 대입
for문으로 기사를 한개씩 읽어옴 (mArticle div의 li 태그)
a 변수에 제목
url 변수에 a 태그 내 href 추출
article에 get_detail 함수를 사용해 본문 내용 저장
url, title, article을 출력한 후 대시(-)줄로 구분함
- 다음 뉴스 다음 페이지 이동과 마지막 페이지
import requests
import lxml.html
import pandas as pd
import sqlite3
from pandas.io import sql
import os
import time
page = 58
max_page = 0
REG_DATE = '20200819'
while(True):
df_list = []
response = requests.get('http://news.daum.net/breakingnews/digital?page={}®Date={}'\
.format(page, REG_DATE))
root = lxml.html.fromstring(response.content)
for li in root.xpath('//*[@id="mArticle"]/div[3]/ul/li'):
a = li.xpath('div/strong/a')[0]
url = a.get('href')
article = get_detail(url)
df = pd.DataFrame({'URL' : [url],'TITLE':[a.text],'ARTICLE' : [article]})
df_list.append(df)
if df_list:
df_10 = pd.concat(df_list)
db_save(df_10)
# 페이지 번호 중에서 max 페이지 가져오기
for a in root.xpath('//*[@id="mArticle"]/div[3]/div/span/a'):
try:
num = int(a.text)
if max_page < num:
max_page = num
except:
pass
# 마지막 페이지 여부 확인
span = root.xpath('//*[@id="mArticle"]/div[3]/div/span/a[@class="btn_page btn_next"]')
if (len(span) <= 0) & (page > max_page):
break
else:
page = page + 1
time.sleep(1)
for문으로 기사의 제목, 링크, 본문을 읽어와 데이터 프레임에 넣음
데이터 프레임 리스트에 여러 기사 정보를 넣음
- 크롤링 정보 저장 및 조회
def db_save(NEWS_LIST):
with sqlite3.connect(os.path.join('.','sqliteDB')) as con:
try:
NEWS_LIST.to_sql(name = 'NEWS_LIST', con = con, index = False, if_exists='append')
#if_exists : {'fail', 'replace', 'append'} default : fail
except Exception as e:
print(str(e))
print(len(NEWS_LIST), '건 저장완료..')
NEWS_LIST 테이블에 저장
def db_delete():
with sqlite3.connect(os.path.join('.','sqliteDB')) as con:
try:
cur = con.cursor()
sql = 'DELETE FROM NEWS_LIST'
cur.execute(sql)
except Exception as e:
print(str(e))
NEWS_LIST 테이블에서 삭제
def db_select():
with sqlite3.connect(os.path.join('.','sqliteDB')) as con:
try:
query = 'SELECT * FROM NEWS_LIST'
NEWS_LIST = pd.read_sql(query, con = con)
except Exception as e:
print(str(e))
return NEWS_LIST
NEWS_LIST 테이블에서 조회
3. 셀레늄을 이용한 크롤러
셀레늄이란 ?
다양한 프로그래밍 언어로 웹드라이버를 통해 다양한 브러우저 상에서 웹 자동화 테스트 혹은 웹 자동화 프로그램을 구현하기 위한 라이브러리
* 지원 브라우저 - Chrome, FireFox, Safari, Opera, Internet Explor
* 지원 언어 - Python, R, JavaScript, Ruby, PHP, C#, Objective-C 등
Selenium 설치
pip install selenium (윈도우)
pip3 install selenium (맥, 리눅스)
Selenium Standalone Server 다운로드(크롬 브라우저로 접속, ipynb파일 위치에 다운로드)
https://www.selenium.dev/downloads/
Webdriver 설치 (크롬 브라우저 버전 확인 필요)
https://chromedriver.chromium.org/downloads
- Webdriver 로드 및 크롬 브라우저 가동
from selenium.webdriver import Chrome
import time
import sqlite3
from pandas.io import sql
import os
import pandas as pd
from selenium import webdriver
path = '/Users/jione/Downloads/chromedriver' # 크롬 드라이버 경로 변수
options = webdriver.ChromeOptions()
options.add_argument("--start-maximized");
browser = webdriver.Chrome(path, options=options) # 크롬 드라이버
- 가동된 브라우저를 통한 URL 접속
browser.get('https://www.data.go.kr/')
browser.implicitly_wait(5)
- 가동된 브라우저를 통한 URL 접속
browser.find_element_by_xpath('//*[@id="header"]/div/div/div/div[2]/div/a[1]').click()
# 로그인 버튼 XPath
browser.implicitly_wait(5)
- ID/Password 입력 및
browser.find_element_by_xpath('//*[@id="mberId"]').send_keys('사용자 ID')
browser.find_element_by_xpath('//*[@id="pswrd"]').send_keys('사용자 Password')
browser.find_element_by_xpath('//*[@id="loginVo"]/div[2]/div[2]/div[2]/div/div[1]/button').click()
browser.implicitly_wait(5)
- 정보공유 링크 클릭
browser.find_element_by_xpath('//*[@id="M000400_pc"]/a').click()
browser.find_element_by_xpath('//*[@id="M000402_pc"]/a').click()
- 자료실 데이터 추출 및 저장
def db_save(ARTICLE_LIST):
with sqlite3.connect(os.path.join('.','sqliteDB')) as con: # sqlite DB 파일이 존재하지 않는 경우 파일생성
try:
ARTICLE_LIST.to_sql(name = 'ARTICLE_LIST', con = con, index = False, if_exists='append')
#if_exists : {'fail', 'replace', 'append'} default : fail
except Exception as e:
print(str(e))
print(len(ARTICLE_LIST), '건 저장완료..')
trs = browser.find_elements_by_xpath('//*[@id="searchVO"]/div[5]/table/tbody/tr')
df_list = []
for tr in trs:
df = pd.DataFrame({
'NO': [tr.find_element_by_xpath('td[1]').text],
'TITLE': [tr.find_element_by_xpath('td[2]').text],
'IQRY': [tr.find_element_by_xpath('td[3]').text],
'REGDT': [tr.find_element_by_xpath('td[4]').text],
'CHGDT': [tr.find_element_by_xpath('td[5]').text],
})
df_list.append(df)
ARTICLE_LIST = pd.concat(df_list)
db_save(ARTICLE_LIST)
10 건 저장완료..
No에 No.
TITlE에 제목
IQRY에 조회수
REGDT에 등록일
CHGDT에 수정일
- 자료실 글목록 상세보기 클릭
browser.find_element_by_xpath('//*[@id="searchVO"]/div[5]/table/tbody/tr[1]/td[2]/a').click()
browser.implicitly_wait(3)
- 상세보기 첨부파일 다운로드 및 브라우저 종료
browser.find_element_by_xpath('//*[@id="recsroomDetail"]/div[2]/div[4]/div/a').click()
time.sleep(10)
browser.quit()
'Database & Bigdata > 공공 빅데이터 청년 인턴십' 카테고리의 다른 글
[ DAY 16 ] 표준분석모델실습 - 전기차 충전 인프라 설치 입지 선정(천안) (0) | 2020.09.14 |
---|---|
[ DAY 15 ] 빅데이터 분석Tool을 이용한 분석 ( BigZAMi, QGIS 분석 실습 ) (2) | 2020.09.11 |
[ DAY 7 ] 데이터 저장/스크래핑 프로세스 (0) | 2020.09.01 |
[ DAY 7 ] 웹 크롤링/스크래핑 (0) | 2020.09.01 |
[DAY 6] Python기초 (0) | 2020.08.31 |
댓글