비정형데이터 - HTTP
1. HTTP
- Hyper Text Transfer Protocol
- HTTP로 가져오면 기본적으로 document가 HTML이다.
- Request를 보내면 반드시 Response를 받아야 한다.
Request
- Header와 Body(Optional)로 이루어져 있다.
Header
: Get, Host, Accept, Accept-Language, Accept-Charset, User-agent, Connection 등으로 구성됨
-
Header 예시
Get /index.html HTTP/1.1 Host www.example.com Accept text/html, */* Accept-Language en-us Accept-Charset ISO-8859-1,utf-8 User-agent Mozilla/5.0 Connection Keep-alive
HTTP Method
- GET
- HEAD
- PUT
- POST
- PATCH
- TRACE
- OPTIONS
- DELETE
REST
- REST : Representational State Transfer
- CRUD : Create, Read, Update, Delete
- Restful
Response
- status가 200이면 정삭적으로 되었다는 뜻
2. 법적 문제
Opt-in vs. Opt-out
- Opt-in: 정보수집을 명시적으로 동의할 때에만 정보수집 가능
- Opt-out : 정보수집을 명시적으로 거부할 때에만 정보수집 중단
- 웹에서는 이 방식을 사용하므로 Opt-out으로 명시된 사항들이 뭐가 있는지를 확인해야 함
합법 or 불법?
합법
- 검색엔진(bot, spider, crawler etc.), 가격비교 등은 합법
- Opt-out
- 공공의 목적이 있기 때문에
불법
- 봇이 개인정보, 지적재산권이 포함된 DB에서 정보를 가져오게 되면 불법
- 과도한 트래픽을 유발하면 다른 이용자들이 이용하기 힘들어지기 때문에 불법
- 텀을 길게 주는 등 과도한 트래픽을 발생시키지 않는 범위 내에서 크롤링되어야 한다.
robots.txt
- Crawler와 같은 bot 접근을 제어하기 위한 규약
- 대상 봇, 수집 여부, 수집 범위 등을 기술
- 권고안
- 사이트이름/robots.txt로 찾을 수 있다.
모든 User-agent에 대해 허용
'''
User-agent:*
Allow:/
'''
모든 User-agent에 대해 불허
'''
User-agent:*
Disallow:/
'''
Name | User-Agent |
---|---|
Googlebot | |
Google image | Googlebot-image |
Msn | MSNBot |
Naver | Yeti |
Daum | Daumoa |
유의하면 좋을 것들
- Robots.txt : 접근 제약 규칙 준수
- Crawl delay : 사이트에 최대한 부담 지양
- Term of use : 사이트 이용방침(약관) 준수
- Public Content : 지저재산권 침해 여부
- Authentication-based site : 민감한 정보 수집 주의
3. URL Handling Modules (urllib 패키지)
- urllib.request : Opening and reading URLs
- urllib.error : Contaning the exceptions raised by urllib.request
- urllib.parse : Parsing URLs
- urllib.robotparser : Parsing robots.txt files
- urllib.response : Used internally by the urllib request module
(1) RobotParser
from urllib import robotparser
robot = robotparser.RobotFileParser()
robot.set_url('https://news.naver.com/robots.txt')
robot.read()
robot.can_fetch('Yeti', '/main/imagemontage')
# True: Yeti라는 user-agent는 접근가능
(2) Request
urlopen
은 url을 string이나 Request 객체의 형태로 받아 열어준다.
from urllib.request import urlopen, Request
resp = urlopen('http://www.google.com/robots.txt')
type(resp) # http.client.HTTPResponse
resp.getheaders() # ...
resp.code, resp.reason # (200, 'OK')
resp.info() # <http.client.HTTPMessage at 0x196bb36ce08>
read는 한 번 실행하면 사라지기 때문에 일단 담아두는 것이 좋다.
body = resp.read()
# 정리해서 보기
[_.split(':') for _ in body.decode('UTF-8').split('\n')]
구글의 경우, Request 객체가 필요하다.
req = Request(url='https://www.google.com/search?q=%EC%9C%A0%ED%8A%9C%EB%B8%8C&oq=%EC%9C%A0%ED%8A%9C%EB%B8%8C&aqs=chrome..69i57j69i59l3j0j69i61l3.1961j0j7&sourceid=chrome&ie=UTF-8')
# 아직 header는 없는 상태.
req.headers() # {}
header를 추가해보자. 필요한 user-agent를 찾아오기 위해서는 Chrome 개발자도구 -> Network -> 새로고침 -> 제일 위의 search?q=...
선택 -> Headers -> Request Headers -> user-agent 확인의 단계를 거치면 된다.
req.add_header('user-agent','Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36')
Request 객체를 만들 때 바로 header를 추가할 수도 있다.
- ex.
req = Request(url, headers={'user-agent': 'Mozilla~~})
resp = urlopen(req)
resp.getheaders()
resp.read().decode('utf-8')
Request 객체를 사용할 필요가 없는 경우, 다음과 같이 할 수 있다.
resp = urlopen('https://search.naver.com/search.naver?sm=top_hty&fbm=1&ie=utf8&query=%EC%9B%A8%EC%9D%BC')
resp.code
resp.read().decode('utf8')
(3) URL Parse
from urllib.parse import urlparse, urljoin, urlencode, quote, quote_plus, unquote, unquote_plus
urlparse
: URL을 6개의 구성요소로 parsing 한다.
urlparse(url)
# ParseResult(scheme='https', netloc='search.naver.com', path='/search.naver', params='', query='sm=top_sly.hst&fbm=1&ie=utf8&query=%ED%8C%8C%EC%9D%B4%EC%8D%AC', fragment='')
urlparse('http://domain.com/service/service/asd/asdasda/asdasdasdfasf/?')
# ParseResult(scheme='http', netloc='domain.com', path='/service/service/asd/asdasda/asdasdasdfasf/', params='', query='', fragment='')
urljoin
: 기본 URL을 다른 URL과 결합해 돌려준다.
url = 'https://search.naver.com/search.naver?sm=top_sly.hst&fbm=1&ie=utf8&query=%ED%8C%8C%EC%9D%B4%EC%8D%AC'
urljoin(url, '/get?asf/asdfasdfasdfasdfsaf')
# 'https://search.naver.com/get?asf/asdfasdfasdfasdfsaf'
urlencode
: str이나 bytes 객체를 포함할 수 있는 매핑 객체나 두 요소 튜플의 시퀀스를 퍼센트 인코딩된 ASCII 텍스트 문자열로 변환한다.
urlencode({'key': '파이썬'})
# 'key=%ED%8C%8C%EC%9D%B4%EC%8D%AC'
quote
, unquote
, quote_plus
, unquote_plus
: 비 ASCII 텍스트를 인코딩하여 URL 구성요소로 안전하게 사용할 수 있게 해주거나 반대로 디코딩한다. quote와 quote_plus의 차이는 띄어쓰기를 %20으로 하느냐 +로 하느냐의 차이이다.
quote('파 이 썬')
# '%ED%8C%8C%20%EC%9D%B4%20%EC%8D%AC'
quote_plus('파 이 썬')
# '%ED%8C%8C+%EC%9D%B4+%EC%8D%AC'
unquote('%ED%8C%8C%20%EC%9D%B4%20%EC%8D%AC')
# '파 이 썬'
unquote_plus('%ED%8C%8C+%EC%9D%B4+%EC%8D%AC')
# '파 이 썬'
(4) Error
from urllib.error import HTTPError
try:
urlopen(url + '?' + urlencode(params))
except HTTPError as e:
# HTTP Status Code
print('code:',e.code)
# 에러의 이유
print('\nreason:',e.reason)
# HTTPError를 일으킨 HTTP request에 대한 header
print('\nheader:', e.headers)
# 403 Error
HTTP Status Codes
Code | Meaning | 설명 |
---|---|---|
2xx | Success | 성공적으로 동작함 |
4xx | Client error | 정상적인 사용자와 동일한 권한이 아님 |
5xx | Server error | 서버 내부에서 코드가 꼬이거나 일시적으로 사용자가 폭주해서 정상적으로 응답을 못함 |
4. URL Handling Modules (requests 패키지)
get 방식 사용해보기
import requests
resp = requests.get('http://httpbin.org/get?key=한글')
resp.headers
print(resp.text) # 위에서 body에 해당했던 내용
resp.request.headers
post 방식 사용해보기
resp = requests.post('http://httpbin.org/post?ie=utf8', data={'key':'value'})
print(resp.text)
5. urllib, request 사용하여 함수 만들기
from urllib.robotparser import RobotFileParser
from requests.compat import urlparse, urljoin
from requests.exceptions import HTTPError
import time
import requests
# 접근 가능한지 확인하는 함수
def canfetch(url, agent='*', path='/'):
robot = RobotFileParser(urljoin(url, '/robots.txt'))
robot.read()
return robot.can_fetch(agent, urlparse(url)[2])
# download 함수
def download(url, params={}, headers={}, method='GET', limit=3):
if canfetch(url) == False:
print('[Error] ' + url)
try:
resp = requests.request(method, url,
params=params if method=='GET' else {},
data=params if method=='POST' else {},
headers=headers)
resp.raise_for_status()
except HTTPError as e:
if limit > 0 and e.response.status_code >= 500:
print(limit)
time.sleep(1)
resp = download(url, params, headers, method, limit-1)
else:
print('[{}]'.format(e.response.status_code) + url)
print(e.response.status_code)
print(e.response.reason)
print(e.response.headers)
return resp
함수 적용해보기
download('http://httpbin.org/status/500', {'k':'v'}, method='GET')
url = 'https://www.google.com/search'
params = {
'q':'',
'oq':'',
'aqs':'chrome.0.69i59j35i39j0l3j69i61j69i60j69i61.1283j0j7',
'sourceid':'chrome',
'ie':'UTF-8'
}
params['q'] = params['oq'] = '파 이 썬'
headers = {
'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
}
resp = download(url, params, headers, 'GET')
resp.text
Leave a comment