** 웹 요청 시 필요한 데이터
=>url: 주소
=>parameter: 서버에게 넘겨주는 데이터 - dict로 만들어서 부착
=>header: 서버에게 넘겨주는 데이터(보이지 않습니다.) - dict로 만들어서 부착
=>method: 전송방식(GET, POST, DELETE, PUT)
**requests 패키지
=>웹에서 데이터를 가져올 때 많이 사용하는 패키지 - 정적인 데이터만 가져올 수 있습니다.
ajax를 이용하지 않는 데이터나 프레임을 사용하지 않는 데이터만 가져올 수 있습니다.
=>이 패키지는 python의 기본 패키지가 아닙니다.
=>pip install requests 로 설치
1.데이터를 가져오기 위한 구문
requests.전송방식(url)을 호출하면 Response 객체가 리턴됩니다.
2.Response 클래스
status_code 속성: 상태 정보
text: 가져온 데이터를 문자열로 저장
content: 가져온 데이터를 바이트 배열로 저장 - 이미지나 음성 데이터
3.parameter 나 heade가 존재하는 경우
requests.전송방식(url, data={파라미터}, headers={헤더})
4.실습
http://httpbin.org/get 사이트에 get 방식 요청
get을 put 과 delete 방식으로 요청
post는 파라미터를 설정해서 요청
Kakao Open API 데이터를 가져오기
1)requests 설치 - anaconda를 설치했으면 할 필요가 없습니다.
pip install requests
2)get 요청을 위한 코드를 작성
import requests
#requests가 가지고 있는 것들을 현재 커널에 requests 라는 이름으로 가져옴
#get 요청을 해서 텍스트를 확인
resp = requests.get('http://httpbin.org/get')
#help(requests.get) #함수의 도움말 확인
#print(type(resp)) #리턴된 결과의 자료형을 확인
#print(dir(requests.models.Response)) #Response 자료형은 어떤 속성들을 가졌는지 확인
print(resp.text) #가져온 문자열을 확인
3)put 과 delete 요청을 수행
=>서버에서 지원을 해야 가능
#put 과 delete 요청
4)post 방식으로 요청하는 파라미터와 함께 전송
#help(requests.post)
#requests 모듈을 사용하면 파라미터 인코딩을 할 필요가 없습니다.
5)Open API를 사용하다 보면 header 나 cookie를 이용해야 하는 경우가 있습니다.
headers 옵션이나 cookies 옵션에 dict 형태로 만들어서 추가해주면 됩니다.
#Kakao Open API 도서 검색 사용
addr = 'https://dapi.kakao.com/v3/search/book'
params = {'query':'삼국지', 'target':'title'}
headers = {'Authorization': ''}
resp = requests.get(addr, params=params, headers=headers)
print(resp.text)
6)읽어온 텍스트가 현재 기본 인코딩과 맞지 않아서 텍스트가 깨지는 경우
=>받아온 Response 객체의 encoding 속성에 None을 대입해서 인코딩을 유추하게 만들거나 직접 문자열로 인코딩을 설정하면 됩니다.
utf-8: 전 세계 모든 글자를 인코딩하는 방식 - 3byte가 1글자
euc-kr: 예전에 웹에서 한글만을 표현하기 위한 인코딩 방식
cp949(ms949): windows에서 한글만을 표현하기 위한 인코딩 방식
cp949 와 euc-kr은 거의 비슷합니다.
iso-8859-1: iso-latin-1 이라고도 하는데 서유럽 문자 표시를 위한 인코딩 방식 - 한글 안됨
5.이미지 파일 다운로드
=>Response 의 text 속성 대신에 content 속성을 사용
=>파일 객체를 생성해서 write로 content 속성의 값을 기록하면 파일로 생성
#이미지를 다운로드 받아서 파일로 만들기
#이미지를 가져올 URL을 생성
imgurl = 'http://www.onlifezone.com/files/attach/images/962811/376/321/005/2.jpg'
resp = requests.get(imgurl)
#파일 객체를 만들고 이름은 f로 사용 - close를 할 필요가 없습니다.
with open('2.jpg', 'wb') as f:
f.write(resp.content)
#현재 작업디렉토리 확인
import os
print(os.getcwd())
**JSON Parsing
1.JSON
=>속성과 값의 쌍으로 이루어진 데이터 오브젝트를 전달하기 위해 텍스트를 사용하는 개방형 표준 포맷
=>javascript 나 python에서 객체(dictionary) 와 배열(list)의 표현 방법을 가져와서 사용
=>최근의 프로그래밍 언어들은 내장 또는 별도의 라이브러리를 이용해서 이 데이터를 객체로 변환해서 사용합니다.
javascript 와 python은 내장
=>최근의 Open API 그리고 스마트 기기 들의 통신은 대부분 JSON 포맷을 사용
=>[ ]: list, { }: dict
2.Python에서의 JSON 파싱
json.loads(json 문자열)
=>문자열을 python 객체로 변환해 줍니다.
3.실습
=>카카오 카테고리 검색을 이용해서 음식점 이름과 주소를 list로 만들어서 출력
headers = {'Authorization': ''}
#웹에서 정적인 데이터를 가져오기 위한 라이브러리를 import
import requests
#다운로드 받을 URL 만들기
#header 만들기
headers = {'Authorization': ''}
#데이터 가져와서 출력
resp = requests.get(addr, headers=headers)
text = resp.text
#print(text)
#json 파싱 모듈을 가져오기 - 내장 모듈
import json
jsondata = json.loads(text)
#print(type(jsondata)) #맨 처음 시작이 {} 이므로 dict
#print(jsondata['documents']) #documents 키의 데이터 가져오기 - list
li = []
#가져온 배열을 행 단위로 읽어서
for imsi in jsondata['documents']:
#print(imsi['address_name'], ':', imsi['place_name'])
#place_name 과 address_name을 가지고 dict를 생성
d = {'장소':imsi['place_name'], '주소':imsi['address_name']}
#dict를 list에 추가
li.append(d)
#확인
for temp in li:
print(temp)
**HTML Parsing
=>open api가 제공되면 json 이나 xml 형태의 데이터를 받아서 파싱해서 사용하면 됩니다.
=>web site에 보여는 주지만 open api 형태로 제공되지 않는 데이터의 경우 html을 수집해서 파싱을 해서 원하는 데이터를 가져오게 됩니다.
html을 무단 수집하는 것은 법적으로 문제가 되는 경우가 있습니다.
1.사용되는 패키지: beatifulsoup4
=>이 패키지는 python에는 없고 anaconda에는 존재
=>이 패키지의 bs4 라는 모듈을 이용
=>bs4.BeautifulSoup(html 텍스트, 'html.parser') 를 호출하면 BeautifulSoap 객체를 리턴
이 객체는 html의 내용을 편리하게 찾을 수 있도록 메모리 트리 형태로 펼쳐놓은 객체 입니다.
2.태그로 찾는 경우는 find 나 findall 이용
find(tag, attributes, recursive, text, limit, keyword)
=>tag가 찾고자 하는 태그
=>attributes 는 태그 안에 속성이 있는 경우 속성명=값 의 형태로 대입해서 속성의 값까지 일치하는 것을 찾습니다.
<div id='1'></div>
<div id='2' align='center'></div>
<span id='3'></span>
div 다 찾기: find('div')
id가 2번인 div 찾기: find('div', align='center')
=>find_all은 사용법은 같은데 find는 1개만 찾고 find_all 은 전부 찾아옵니다.
3.선택자로 찾는 경우는 select()
=>Tag의 list로 리턴
4.찾은 데이터에서 정보를 추출
1)태그 안의 내용을 가져오기: getText()
2)속성의 값을 가져오기:get('속성명')
5.실습1 : https://ko.wikipedia.org/wiki/%EA%B8%B0%EA%B3%84_%ED%95%99%EC%8A%B5
에서 하이퍼링크 태그의 url과 텍스트를 수집
하이퍼링크가 /wiki/로 시작하는 것만 출력
#위키피디아에서 하이퍼링크(a) 태그의 내용 가져오기
import requests
addr = 'https://ko.wikipedia.org/wiki/%EA%B8%B0%EA%B3%84_%ED%95%99%EC%8A%B5'
resp = requests.get(addr)
#print(resp.text)
#html 파싱을 위한 라이브러리 가져오기
import bs4
#DOM 객체로 만들기
bs = bs4.BeautifulSoup(resp.text, 'html.parser')
#print(type(bs))
#a 태그의 내용 가져오기
li = bs.find_all('a')
#print(li)
#정규식(문자열 패턴을 조회하기 위한 식) 모듈 import
import re
for temp in li:
#print(temp.getText()) #태그 안의 내용 가져오기
#print(temp.attrs['href']) #key 에러 - href가 없는 경우도 있음
if 'href' in temp.attrs: #href 속성이 temp.attrs에 있는 경우에만
#href 속성에 /wiki/로 시작하는 링크의 텍스트와 링크만 출력
href = temp.attrs['href']
#정규식 생성 - /wiki/ 로 시작하는
p = re.compile('^(/wiki/)')
if p.search(href) != None:
print(temp.getText(), ':', href)
6.실습2: https://tv.naver.com/에서 상위 항목의 제목과 링크 가져오기
=>선택자 이용
=>선택자를 찾는 방법은 소스 보기를 해서 직접 찾아도 되고 chrome의 검사 기능에서 필요한 부분을 선택하고 copy selector를 이용해서 찾아도 됩니다.
#네이버 tv 팟에서 하이퍼링크(a) 태그의 내용 가져오기
import requests
addr = 'https://tv.naver.com/'
resp = requests.get(addr)
#print(resp.text)
#html 파싱을 위한 라이브러리 가져오기
import bs4
#DOM 객체로 만들기
bs = bs4.BeautifulSoup(resp.text, 'html.parser')
#선택자를 이용해서 선택
tvlist = bs.select('dl > dt > a > tooltip')
for temp in tvlist:
print(temp.getText())
7.태그 속성
태그(중복 가능 - 웹 브라우저에서의 모양) : 선택자로 사용할 때는 tag이름
클래스(중복 가능 - 태그들을 그룹화하기 위한 속성) : 선택자로 사용할 때는 .class이름
아이디(중복 불가능 - 태그를 다른 것과 구별하기 위한 속성) : 선택자로 사용할 때는 #id
name(중복 가능 - 서버에게 전송할 때 사용할 파라미터 이름)
xpath(중복 불가능 - 브라우저가 태그를 구분하기 위해서 사용하는 경로)
8.실습3
http://www.weather.go.kr/weather/observation/currentweather.jsp 에서 도시이름과 온도, 습도를 가져와서 꺽은선 그래프로 출력
=>찾고자 하는 데이터는 class 이름이 table_develop3
#오늘 날씨를 가져와서 그래프 그리기
import requests
#필요한 html 가져오기
resp = requests.get('http://www.weather.go.kr/weather/observation/currentweather.jsp')
text = resp.text
#print(text)
import bs4
bs = bs4.BeautifulSoup(text, 'html.parser')
#class 속성의 값이 table_develop3 인 데이터 찾아오기
table = bs.select('.table_develop3')
#print(table)
#도시이름, 온도, 습도를 저장할 list를 생성
loc = []
temp = []
hum = []
#가져온 테이블 중에서 첫번째 테이블로부터 줄(tr) 단위로 읽기
for tr in table[0].find_all('tr'):
tds = tr.find_all('td') #tr 태그에서 td를 전부 찾아서 tds에 대입
for td in tds:
if td.find('a'): #a 태그가 있으면 도시이름이 있는 행입니다.
#도시이름을 loc 추가
loc.append(td.find('a').text)
#온도를 temp에 추가
temp.append(tds[5].text)
#습도를 temp에 추가
hum.append(tds[10].text)
#데이터 확인
#인덱싱은 번호1개만 작성해서 하나의 데이터만 추출
print(loc[0:5]) #0-4 번행 까지 확인 - 슬라이싱(범위를 가지고 추출)
print(temp[0:5])
print(hum[0:5])
#필요한 도시의 데이터만 추출
cities = ['서울', '부산', '인천', '대구', '대전', '광주', '울산', '창원', '흑산도']
#위 도시들의 온도와 습도를 저장할 list
temperatures = []
humidities = []
#cities에 있는 데이터만 추출해서 저장
for city in cities:
j = 0
for c in loc:
if c == city:
temperatures.append(temp[j])
humidities.append(hum[j])
break
j = j + 1
print(cities)
print(temperatures)
print(humidities)
#파이썬에서 시각화를 하는 기본 패키지
import matplotlib.pyplot as plt
#한글 폰트 사용을 위한 패키지
from matplotlib import font_manager, rc, rcParams
import platform #운영체제 확인을 위한 패키지
#주피터 노트북에서 그래프를 셀 안에 출력해주는 설정
%matplotlib inline
#음수를 제대로 표현하기 위한 설정
rcParams['axes.unicode_minus'] = False #이 설정을 하지 않으면 음수가 네모로 출력
#한글 폰트 설정
if platform.system() == 'Windows': #윈도우즈라면
font_name = font_manager.FontProperties(fname='c:/windows/Fonts/ahn_b.ttf').get_name()
rc('font', family=font_name)
elif platform.system() == 'Darwin': #매킨토시라면
rc('font', family='AppleGothic')
else:
print('알 수 없는 운영체제')
#그래프 그리기
plt.figure(figsize=(12,4), dpi=300) #그래프 크기 설정 - 가로 12inch 세로 4인치
#꺽은선 그래프 - plot
plt.plot(temperatures, label='온도', lw=3, color='r', linestyle='-', marker='s')
#plt.plot(humidities, label='습도', lw=3, color='g', linestyle='-', marker='s')
#x 축
plt.xticks(range(0,len(cities),1), cities, rotation='vertical')
#범례
plt.legend()
plt.savefig('graph.png', dpi=300)
#출력
plt.show()
**XML 파싱
=>XML: 데이터를 태그 형식으로 표현하는 표준 데이터 포맷
=>예전에 거의 모든 데이터 포맷이 XML
지금은 우리나라 공공기관 Open API 와 실시간 뉴스를 전송해주는 RSS 그리고 프로젝트 설정 파일에 주로 이용
=>BeautifulSoup나 xml 패키지를 이용해서 파싱 가능
=>BeautifulSoup를 사용하는 경우 html.parser 대신에 lxml-xml 을 사용하면 됩니다.
http://www.hani.co.kr/rss/ 에서 item 태그 안의 title 만 전부 조회
#XML 파싱
#http://www.hani.co.kr/rss/ 에서 item 태그의 title만 추출
#데이터 가져오기
import requests
resp = requests.get('http://www.hani.co.kr/rss/')
text = resp.text
#print(text)
import bs4
#트리로 펼쳐내기
bs = bs4.BeautifulSoup(text, 'lxml-xml')
items = bs.find_all('item')
for item in items:
title = item.find('title')
print(title.getText())
**Selenium
=>web에서 동적으로 생성되는 데이터는 requests 모듈을 가지고 가져올 수 없습니다.
requests 모듈은 처음 접속했을 때 출력되는 데이터만 가져옵니다.
ajax 처럼 비동기적으로 데이터를 가져오거나 로그인을 한 후 보여지는 데이터들은 requests 모듈로는 가져올 수 없습니다.
이처럼 동적으로 만들어지는 데이터는 브라우저를 직접 실행시켜서 동작시키는 Selenium 이라는 웹 앱 테스트 프레임워크를 이용해야 합니다.
=>웹 애플리케이션을 테스트하기 위한 프레임워크
=>기본 패키지가 아니므로 설치를 해야 합니다.
pip install selenium
1.브라우저 동작
=>동작시킬 브라우저의 드라이버를 다운로드
=>브라우저를 동작시키지 않고 가져오고자 하는 경우에는 pantom.js를 이용 - Deprecated
=>크롬을 이용할 것이라서 크롬 드라이버를 다운로드
1)chrome 에서 크롬 정보 확인 - chrome 79 버전
2)https://chromedriver.chromium.org/downloads 에서 자신의 버전에 맞는 드라이버를 다운로드
=>압축을 해제하고 exe 파일을 찾기 쉬운곳에 배치 : c:/chromedriver.exe
3)브라우저 실행
selenium.webdriver.Chrome("크롬 드라이버 경로")
4)실습
#크롬 실행하기
from selenium import webdriver
driver = webdriver.Chrome('c:/chromedriver')
2.webdriver 의 메소드
=>implicitly_wait(시간): 초 단위 대기 - 여러 사이트에 데이터를 크롤링 하는 경우 또는 첫 화면에 ajax로 데이터를 호출하는 경우 일정시간 대기시켜서 데이터를 읽어옵니다.
=>get(url): 브라우저가 url에 접속
=>페이지 내의 element에 접근하는 메소드
find_element_by_name(name을 가지고 접근)
find_element_by_id(id를 가지고 접근)
find_element_by_xpath(xpath를 가지고 접근)
find_element_by_css_selector(선택자를 가지고 접근)
find_element_by_class_name(class name을 가지고 접근)
find_element_by_tag_name(tag를 가지고 접근)
=>element를 찾으면 입력도구 인 경우는 send_keys(값) 을 호출하면 값을 입력
=>click()을 호출하면 클릭한 효과
앞의 3개는 1개의 데이터를 리턴하고 뒤의 3개는 list 형태로 리턴
=>quit(): 브라우저 종료
=>execute_script(자바스크립트 코드): 자바스크립트 코드 수행
=>page_source 속성: html 가져오기
3.실습
1)실습1. daum 카페 페이지에 접근
#다음 카페 목록에 접근하기
#크롬 실행하기
from selenium import webdriver
driver = webdriver.Chrome('c:/chromedriver')
#3초대기
driver.implicitly_wait(3)
#다음 카페 목록에 접근 - 로그인 되어 있지 않으면 로그인 페이지로 이동
driver.get('url 주소')
2)다음 웹 사이트에 로그인을 한 후에 카페 목록 페이지로 이동
다음 로그인 페이지: url주소
#다음에 로그인을 하고 카페 목록으로 이동
from selenium import webdriver
driver = webdriver.Chrome('c:/chromedriver')
#3초대기
driver.implicitly_wait(3)
#다음 로그인 페이지로 이동
driver.get('url 주소')
#아이디와 비밀번호 입력받기
username = input('아이디를 입력하세요')
userpw = input('비밀번호를 입력하세요')
#다음 로그인 페이지에 아이디와 비밀번호 입력
#driver.find_element_by_id('id').send_keys(username)
driver.find_element_by_xpath('//*[@id="id"]').send_keys(username)
driver.find_element_by_xpath('//*[@id="inputPwd"]').send_keys(userpw)
#로그인 버튼을 클릭한 후 카페 목록 페이지로 이동
driver.find_element_by_xpath('//*[@id="loginBtn"]').click()
driver.find_element_by_xpath('//*[@id="loginBtn"]').click()
#여러 페이지를 이동할 때는 중간에 잠시 대기 시간을 주어야 할 필요가 있습니다.
import time
time.sleep(3) #3초 대기
#카페 목록으로 이동
driver.get('http://top.cafe.daum.net/_c21_/my_cafe')
#접속한 사이트의 html 가져오기
html = driver.page_source
4.frame
=>frame은 html 페이지 내에서 다른 html 페이지의 내용을 출력하기 위해 사용하는 객체
=>스크래핑을 하다보면 화면에 출력이 되고 페이스 소스에서도 확인이 되는데 접근이 안되는 경우가 있습니다.
이런 경우는 ajax로 나중에 불려졌거나 아니면 프레임을 이용해서 다른 파일에서 출력한 경우입니다.
ajax 의 경우는 딜레이를 주고 데이터를 읽으면 가능합니다.
프레임의 경우는 프레임에 직접 접근해서 데이터를 읽어야 합니다.
1)현재 페이지에서 frame 조회
변수 = driver.find_element_by_css_selector('iframe')
for 임시변수 in 변수:
print(임시변수.get_attribute('name')
=>현재 페이지의 모든 iframe의 이름을 조회
2)프레임 이동
=>driver.switch_to_frame('프레임이름') : 프레임이름에 해당하는 곳으로 이동
=>driver.switch_to_default_content() : 상위 프레임으로 전환
5. 프레임 사용 실습
=>이전 것에 이어서 작성
#카페 목록 중에서 첫번째 페이지로 이동
#driver.find_element_by_xpath('//*[@id="mArticle"]/div/div[1]/div/div[2]/ul/li/a/div[1]/div[2]/div/div/strong').click()
#직접 카페로 이동 - 카페 글쓰기 페이지로 이동
driver.get('http://cafe.11111daum.net/samhak7/_memo')
#특정 프레임으로 이동
driver.switch_to_frame('down')
#글작성 란에 텍스트를 입력
driver.find_element_by_xpath('//*[@id="memoForm"]/div/table/tbody/tr[1]/td[1]/div/textarea').send_keys('파이썬에서의 매크로')
driver.find_element_by_xpath('//*[@id="memoForm"]/div/table/tbody/tr[1]/td[2]/a[1]/span[2]').click()
6.크롬을 화면에서 출력시키지 않고 데이터 가져오기
=>웹 드라이버를 옵션을 추가해서 생성
#크롬을 화면에 출력하지 않고 실행하기
options = webdriver.ChromeOptions()
options.add_argument('headless')
options.add_argument('window-size=1920x1080')
options.add_argument("disable-gpu")
# 혹은 options.add_argument("--disable-gpu")
driver = webdriver.Chrome('c:/chromedriver', chrome_options=options)
7.20번 스크롤 한 데이터 가져오기
#20번 스크롤 하기
from selenium import webdriver
import time
from selenium.webdriver.common.keys import Keys
import bs4
driver = webdriver.Chrome('c:/chromedriver')
driver.get('')
time.sleep(5)
body = driver.find_element_by_tag_name('body')#스크롤하기 위해 소스 추출
num_of_pagedowns = 20
#10번 밑으로 내리는 것
while num_of_pagedowns:
body.send_keys(Keys.PAGE_DOWN)
time.sleep(2)
num_of_pagedowns -= 1
html = bs4.BeautifulSoup(driver.page_source,'html.parser')
print(html)