3주차 | 웹 크롤링(Web crawling)(1)
KT AIVLE SCHOOL 5기 3주차에 진행한 웹 크롤링(Web crawling) 강의 내용 정리 글입니다.
Python
cpu, ram, ssd(hdd) 활용
- 변수 선언: ram 사용 -> 메모리에 데이터 저장 : 식별자
- 데이터 타입: ram 효율적 사용: 동적 타이핑
- 연산자: cpu 사용 : 산술, 비교, 논리, 할당, 멤버
- 조건문, 반복문 : 코드 작성의 효율을 높이는 문법
- 함수 : 반복되는 코드를 묶어서 코드 작성 실행
- 클래스 : 여러 개의 변수, 함수를 묶어서 코드 작성 실행 문법
- 입출력 : ssd(hdd) 사용 : pickle
클래스
- 객체 지향 구현 문법 : 실제 세계를 모델링하여 개발하는 방법론 : 협업을 용이하게
- 함수 사용법 : 함수 선언 (코드 작성) > 함수 호출 (코드 실행)
- 클래스 사용법
- 클래스 선언(코드 작성) > 객체 생성(메모리 사용) > 메서드(함수) 호출(코드 실행)
- 클래스 식별자 : PascalCase, UpperCamelCase - 클래스
- snake_case - 함수
- (PEP8 -> 맞춰서 사용하는게 좋음)
- 메서도 종류 : instance(90 ~ 95% 이상), class, static
- special method : 앞뒤로 __ 사용 : 특별한 기능을 하는 메서드
- 생성자 메서드 :
__init__()
: 객체가 생성될 때 실행되는 메서드- 메서드에서 사용되는 변수의 초기값을 설정하거나 검사할 때 사용
- 생성자 메서드 :
# 클래스 선언 : 코드 작성
# 은행 계좌 : Account : balance, deposit(), withdraw()
class Account:
def __init__(self, balance): # 생성자
self.balance = balance
def deposit(self, amount): # 입금
self.balance += amount
def withdraw(self, amount): # 출금
self.balance -= amount
# 객체 생성 : 메모리 사용
acc1 = Account(10000)
acc2 = Account(10000)
# dir() : 객체에 들어있는 변수(함수) 출력
dir(acc1)[-3:]
# 변수 값 변경
acc2.balance = 6000
# 메서드 호출 : 코드 실행
acc1.deposit(2000)
acc2.withdraw(3000)
# help : docstring(함수설명글) 출력
# help(df.drop)
- 클래스는 사용자정의 데이터타입이다.
# acc 객체 클래스 : Account
acc = Account(1000)
# acc 객체의 데이터 타입 : Account
type(acc)
# > 클래스는 데이터 타입이다
# Account 객체 만든 사람 : 우리가 직접 : 커스터 마이즈 : 사용자 정의
- 데이터 타입(=클래스)
- 데이터 타입(클래스)가 다르다는 의미 : 사용 가능한 변수(함수)가 다름
Web
Server & Client Architecture
- Client (요청하는 사람)
- Request: Browser를 사용하여 Server에 데이터를 요청
- Server (요청에 응답하는 사람)
- Response: Client의 Browser에서 데이터를 요청하면 요청에 따라 데이터를 Client로 전송
URL
- Uniform Resource Locator
- ex) http://news.naver.com:80/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=001&aid=0009847211#da_727145
http://
: Protocol (https://
: 약간 보안이 강화된 형태 (요즘 많이 사용))news
: Sub Domainnaver.com
: Primary Domain- Domain은 ip주소와 매핑 (서버 컴퓨터)
80
: Port → 서버 컴퓨터의 app 접속에 사용/main/
: Pathread.nhn
: Page (File)? 앞은 file, 뒤는 Data
mode=LSD
: Query- client가 server에 요청하는 data 값
#da_727145
- Fragment (페이지의 위치값)- 브라우저 컨트롤에 사용
- 웹크롤링에서는 잘 사용 안함
HTTP Request Methods
- Get
- URL에 Query 포함
- Query(데이터) 노출, 전송 가능 데이터 작음
- ex) 검색어 입력
- Post
- Body에 Query 포함
- Query(데이터) 비노출, 전송 가능 데이터 많음
- ex) ID, PW 입력하는 경우
HTTP Status Code
- Client와 Server가 데이터를 주고 받은 결과 정보
- 2xx - Success
- 3xx - Redirect
- 이미 받은 정보를 다시 보여줌
- 네트워크 트래픽 줄이고 페이지 빠르게 불러오기 위해
- 4xx - Request Error
- 5xx - Server Error
- HTTP Status Code 관련 링크
cookie & session & cache
- Cookie
- Client의 Browser에 저장하는 문자열 데이터
- ssd에 저장했다가 불러오는 형식
- 데이터 불러오는 게 느림, 가격 경쟁력은 높음(가격이 쌈)
- 사용 예시 : 로그인 정보, 내가 봤던 상품 정보, 팝업 다시 보지 않음 등
- Session
- Client의 Browser와 Server의 연결 정보
- 사용 예시 : 자동 로그인
- Cache
- Client, Server의 RAM(메모리)에 저장하는 데이터
- RAM에 데이터를 저장하면 데이터 입출력이 빠름
- 가격 경쟁력은 낮음 (비쌈)
Web Language & Framework
- Framework
- 빈칸 채우기와 비슷함
- 기능적으로 존재하는 코드들이 이미 존재해서 남은 부분만 채워 넣으면 된다
- Client (Frontend)
- HTML
- CSS - Bootstrap, Semantic UI, Materialize, Material Design Lite
- Javascript - react.js, vue.js, angular, jQuery
- Server (Backend)
- Python - Django, Flask, FastAPI
- Java - Spring
- Ruby - Rails
- Scala - Play
- Javascript - Express(node.js)
Scraping & Crawling
- Scraping
- 특정 데이터를 수집하는 작업
- Crawling
- 웹서비스의 여러 페이지를 이동하며 데이터를 수집하는 작업
- spider, web crawler, bot 용어 사용
- ex) google bot
Internet
- 컴퓨터로 연결하여 TCP/IP 프로토콜을 이용하여 정보를 주고 받는 컴퓨터 네트워크
- 해저 케이블을 사용하여 전세계 컴퓨터에 접속
- 무선 인터넷은 매체(media)를 주파수 사용
Web Crawling
- 웹 페이지에서 데이터를 수집하는 방법
웹크롤링 방법
웹페이지의 종류
- 정적인 페이지 : 웹 브라우져에 화면이 한번 뜨면 이벤트에 의한 화면의 변경이 없는 페이지
- 동적인 페이지 : 웹 브라우져에 화면이 뜨고 이벤트가 발생하면 서버에서 데이터를 가져와 화면을 변경하는 페이지
requests 이용
- 받아오는 문자열에 따라 두 가지 방법으로 구분
json
문자열로 받아서 파싱하는 방법 : 주로 동적 페이지 크롤링할 때 사용html
문자열로 받아서 파싱하는 방법 : 주로 정적 페이지 크롤링할 때 사용
selenium 이용
- 브라우져를 직접 열어서 데이터를 받는 방법
크롤링 방법에 따른 속도
- requests json > requests html » selenium (브라우저가 메모리 많이 잡아먹는다)
동적 페이지 크롤링
Crawling Naver Stock Data
- 네이버 증권 사이트에서 주가 데이터 수집
- 수집할 데이터 : 일별 kospi, kosdaq 주가, 일별 환율(exchange rate) 데이터
과정
def stock_price(code = 'KOSPI', page_size = 60, page = 1): # 주가 데이터 크롤링 # 1. 웹서비스 분석 > URL url = f'https://m.stock.naver.com/api/index/{code}/price?pageSize={page_Size}&page={page}' # 2. request(URL) > response(JSON) : JSON(str) response = requests.get(url) # 3. JSON(str) > list, dict > DataFrame : Data return pd.DataFrame(response.json())[['localTradedAt', 'closePrice']] def exchange_rate(code = 'FX_USDKRW', page_size = 60, page = 1): # 환율 데이터 크롤링 # 1. 웹서비스 분석 > URL url = f'https://m.stock.naver.com/front-api/v1/marketIndex/prices?category=exchange&reutersCode={code}&page={page}&pageSize={page_Size}' # 2. request(URL) > response(JSON) : JSON(str) response = requests.get(url) # 3. JSON(str) > list, dict > DataFrame : Data return pd.DataFrame(response.json()['result'])[['localTradedAt', 'closePrice']]
- 웹서비스 분석 : url
- pc 웹페이지가 복잡하면 mobile 웹페이지에서 수집
- 서버에 데이터 요청 : request(url) > response : json(str)
- response의 status code가 200이 나오는지 확인
- 403이나 500이 나오면 request가 잘못되거나 web server에서 수집이 안되도록 설정이 된것임
- header 설정 또는 selenium 사용
- 200이 나오더라도 response 안에 있는 내용을 확인 > 확인하는 방법 :
response.text
- 서버에서 받은 데이터 파싱(데이터 형태를 변경) : json(str) > list, dict > DataFrame
- 웹서비스 분석 : url
- 추가 작업
page_size
= 60 으로 설정하여 KOSPI, KOSDAQ, USD 데이터 수집- 수집한 데이터
min_max_scaling
하여 데이터 시각화하여 비교 - 상관 관계 분석 진행
- kospi, kosdaq은 아주 강한 양의 상관 관계를 갖는다
- kospi, usd는 강한 음의 상관 관계를 갖는다
Daum Exchange
- 어뷰징 방지를 위해 User-Agent가 python이면 request 방지하기도 함
- header 설정하여 이를 회피
def daum_exchange():
# 1. URL
url = 'https://finance.daum.net/api/exchanges/summaries'
# header 설정 -> request 방지를 회피할 수 있다
headers = {
'User-Agent' : 'User-Agent 값' # 개발자 도구에서 확인 가능
'Referer' : 'Referer 값', # 추가적으로 설정 필요한 값
}
# 2. request > response : json(str)
response = requests.get(url, headers = headers)
# 3. json(str) > list, dict : DataFrame
return pd.DataFrame(response.json()['data'])[['symbolCode', 'currencyCode' ,'basePrice']]
REST API
- API 받아오는 것도 동적 페이지와 비슷 → json을 받아오기 때문
- 과정
- 어플리케이션 등록 : app_key
- api 문서 확인 : url, params, headers
- request(url, params, headers(app_key)) > response(data) : data(json(str))
- data(json(str)) > list, dict > DataFrame
Kakao API
- KoGPT
다음 문장 만들기
def kogpt_api(prompt, command = '', max_tokens = 128, temperature = 1, n = 1): # 1. document : URL url = 'https://api.kakaobrain.com/v1/inference/kogpt/generation' # 2. request(URL : headers, params) > response : json(str) headers = {'Authorization' : f'KakaoAK {REST_API_KEY}', 'Content-Type' : 'application/json'} params = {'prompt' : prompt + command, 'max_tokens' : max_tokens, 'temperature' : temperature, 'n' : n} response = requests.post(url, json.dumps(params), headers = headers) # json.dumps : 문자열 인코딩 : 한글은 인터넷상에서 사용 X : 한글 > 영문, 특수문자로 인코딩 # 3. json(str) > parsing : text results = response.json()['generations'] return [result['text'] for result in results]
추가 작업
- 문장 분류, 한줄 요약, excel 데이터 내용들 요약하여 새로운 열 생성
Naver API
- 통합 검색어 트렌드 API
- 위의 Kakao API 받아오는 방식과 거의 동일
- sdk : API를 통해서 얻어온 정보를 데이터프레임으로 만들어 주는 패키지를 제공하는 서비스
- 구글은 sdk 제공
keywordGroups = [{'groupName' : '트위터', 'keywords' : ['트위터', '트윗']},
{'groupName' : '페이스북', 'keywords' : ['페이스북', '페북']},
{'groupName' : '인스타그램', 'keywords' : ['인스타그램', '인스타']},
]
def naver_api(startDate = '2018-01-01', endDate = '2024-03-01', timeUnit = 'month', keywordGroups = keywordGroups):
url = 'https://openapi.naver.com/v1/datalab/search'
params = {
'startDate' : startDate,
'endDate' : endDate,
'timeUnit' : timeUnit,
'keywordGroups' : keywordGroups,
}
headers = {
'X-Naver-Client-Id' : CLIENT_ID, # App key
'X-Naver-Client-Secret': CLIENT_SECRET, # App key
'Content-Type' : 'application/json'
}
response = requests.post(url, json.dumps(params), headers = headers)
data = response.json()['results']
dfs = []
for row in data:
df = pd.DataFrame(row['data'])
df['title'] = row['title']
dfs.append(df)
return pd.concat(dfs, ignore_index = True)
직방 매물 정보 가져오기
과정
addr = '망원동' # 1. 동이름으로 위도 경도 구하기 url = f'https://apis.zigbang.com/v2/search?leaseYn=N&q={addr}&serviceType=원룸' response = requests.get(url) data = response.json()['items'][0] lat, lng = data['lat'], data['lng'] # 2. 위도 경도로 geohash 알아내기 geohash = geohash2.encode(lat, lng, precision = 5) # 3. geohash로 매물 아이디 가져오기 url = f'https://apis.zigbang.com/v2/items/oneroom?\ geohash={geohash}&depositMin=0&rentMin=0&salesTypes[0]=전세&salesTypes[1]=월세\ &domain=zigbang&checkAnyItemWithoutFilter=true' response = requests.get(url) items = response.json()['items'] items = [item['itemId'] for item in items] # 4. 매물 아이디로 매물 정보 가져오기 url = 'https://apis.zigbang.com/v2/items/list' params = {'domain' : 'zigbang', 'item_ids' : items} response = requests.post(url, params) columns = ['item_id', 'sales_title', 'deposit', 'rent', 'size_m2', 'title', 'address1'] df = pd.DataFrame(response.json()['items'])[columns] df = df.loc[df['address1'].str.contains(addr)].reset_index(drop = True) df.tail(2)