Post

[KT Aivle 3기 AI] 12일차. 웹크롤링 (Web Crawling) (1)

0. 개요

KT Aivle School 3기 AI 12일차

  • 강사 : 박두진 강사님
  • 주제 : 웹크롤링(Web Crawling), 파이썬 Requests 및 API를 활용한 동적 페이지 크롤링
  • 내용 :
    • 웹크롤링을 하는 방법을 배움
    • 웹크롤링을 하기 위한 웹 관련 지식을 알려주시고,
    • Python으로 웹크롤링을 알려주심
    • 웹크롤링을 아무렇게나 하면 법적인 문제가 발생할 수도?
    • 따라서, API 사용을 권장함!
    • 12일차에는 동적페이지, 13일차에는 정적 페이지 진행
    • 페이지의 내용이 바뀔 때 URL이 바뀌면 정적, 안 바뀌면 동적

1. WEB

먼저 WEB에 대한 간단한 지식 정리!

1) Server & Client Architecture

  • Client : 브라우저를 통해 서버에 데이터를 요청
  • Server : Client가 데이터를 요청하면 요청에 따라 데이터를 전송

2) URL

  • Uniform Resource Locator
  • https://news.naver.com:80/main/read.nhn?mode=LSD&mid=shm&sid1=105&oid=001&aid=0009847211#da_727145
    • https:// : Protocol
    • news : Sub Domain
    • naver.com : Domain
      • → 여기까지 건물을 찾아 가는 것!
    • :80 : port (80번 포트 등 특정 포트 번호는 생략이 가능해서 평소에 안보임)
      • 웹 서비스를 사용할 지, 데이터 베이스를 사용할지 결정
      • → 건물에서 어떤 곳을 찾아갈지 정한다.
    • /main/ : path (directory)
    • read.nhn : page (file)
      • → : 파일까지 접근
    • ?mode=LSD&mid=shm&sid1=105&oid=001&aid=0009847211 : query
    • #da_727145 : fragment
      • 페이지에서 특정 위치로 이동

3) Get & Post

  • Request : 데이터 요청
  • Response : 데이터 받기

Request 방법으로 Get 방식과 Post 방식이 있음

  • Get
    • Url에 데이터가 포함된다 → 데이터가 노출 됨
    • 길이 제한이 있음
  • Post
    • Body에 데이터가 포함된다 → 데이터가 숨겨짐

4) Internet

  • 인터넷이란?
    • 인터넷은 컴퓨터로 연결하여 TCP/IP (Transmission Control Protocol/Internet Protocol)라는 통신 프로토콜을 이용해 정보를 주고받는 컴퓨터 네트워크
  • 해저케이블을 이용하여 전세계 서버에 접속하여 인터넷을 사용하고 있다.
  • 무선 인터넷
    • 선이 아니라 주파수를 매체로 사용한다.
    • 우리가 사용하는 단말기만 무선이다.

5) OSI 7 Layer

  • Open Systems Interconnection Reference Model
  • 국제표준화기구(ISO)에서 개발한 모델로, 컴퓨터 네트워크 프로토콜 디자인과 통신을 계층으로 나누어 설명한 것이다.
  • 하위 계층으로 갈수록 페이로드가 증가
  • Cookie : 하드디스크에 저장하는 데이터
    • Client에 저장하는 문자열 데이터로 도메인 별도 따로 저장
    • 로그인 정보, 내가 봤던 상품 정보, 팝업 다시 보지 않음
    • 하나의 클라이언트에 300개, 도메인당 20개, 쿠키 하나당 4Kbyte
  • Session : 접속 정보
    • Server에 저장하는 객체 데이터, 브라우저와 연결시 Session ID 생성
    • Session ID를 Cookie에 저장함으로 로그인 연결 유지
    • 같은 브라우저로 같은 서버에 접속하면 Session ID가 같음
    • 로그인 연결 정보, 원하는 객체 데이터
  • Cache : 메모리에 저장하는 데이터
    • Client나 Server의 메모리에 저장하여 빠르게 데이터를 가져오는 목적의 저장소

7) HTTP Status Code

  • 서버와 클라이언트가 데이터를 주고 받으면 주고 받은 결과로 상태 코드를 확인할 수 있다.
  • 2xx : success
  • 3xx : redirection (browser cache), 저장되어 있는 것을 가져옴
  • 4xx : request error
  • 5xx : server error

8) Web Language & Framework

  • Client
    • HTML
    • CSS - less, sass
    • Javascript - vue.js, react.js, angelar.js, backborn.js
  • Server
    • Python - Django, Flask
    • Java - Spring
    • Ruby - Rails
    • Javascript - Nodejs (Express)
    • Scala - Play

2. Web Crawling (requests)

  • 웹 페이지에서 데이터를 수집하는 방법을 알아보자.

1) Web Crawling 방법

(1) 웹페이지 종류

  • 정적인 페이지 : 웹 브라우저에 화면이 한 번 뜨면 이벤트에 의한 화면의 변경이 없는 페이지
  • 동적인 페이지 : 웹 프라우저에 화면이 뜨고 이벤트가 발생하면 서버에서 데이터를 가져와 화면을 변경하는 페이지

→ 원하는 버튼을 누르고 url의 변화가 있는지 확인. 변화가 있으면 정적, 없으면 동적

(2) requests 이용

  • 받아오는 문자열에 따라 두가지 방법으로 구분
    1. json 문자열로 받아서 파싱하는 방법 : 주로 동적 페이지 크롤링 할 때 사용
    2. html 문자열로 받아서 파싱하는 방법 : 주로 정적 페이지 크롤링 할 때 사용

(3) selenium 이용

  • 브라우저를 직접 열어서 데이터를 얻는 방법
  • request를 막는 경우가 있기 때문(Abuse로 판단해서 막아버림)

(4) Crawling 방법에 따른 속도

  • requests json > requests html > selenium

2) Web Crawling 실습

(1) Naver Stock Data

  • 네이버 증권 사이트에서 주가 데이터 수집
  • 수집할 데이터 : 일별 kospi, kosdaq 주가, 일별 환율(exchange rate) 데이터
  • 데이터 수집 절차
    • 웹서비스 분석 : url
    • 서버에 데이터 요청 : request(url) > response : json(str)
    • 서버에서 받은 데이터 파싱(데이터 형태를 변경) : json(str) > list, dict > DataFrame
1
2
3
4
5
import warnings
warnings.filterwarnings('ignore') # 경고문구 없애기

import pandas as pd
import requests

[1] 웹서비스 분석 : url

  • pc 웹페이지가 복잡하면 mobile 웹페이지에서 수집
    1. chrome에서 개발자 도구 열기
      • 방법 1) chrome 우측상단 점 세개 → 도구 더보기 → 개발자 도구
      • 방법 2) Crtl + Shift + I
      • 방법 3) F12
    2. 개발자 도구 좌측 상단 toggle device toolbar 클릭

      1

    3. 다시 사이트 검색하여 접속하면 모바일 화면으로 보임
  • 코스피 일별 종가정보를 얻어오기 위한 Url을 알아내보자.
    1. 코스피 일별 시세 정보로 페이지로 들어간다.
    2. 개발자 도구를 켜고 Network를 클릭한다.
    3. Fetch/XHR 탭을 눌러 해당 내용만 확인하도록 한다.
    4. Clear를 눌러 이전 내역을 지운다.
    5. 더보기를 누른다.
    6. 새로 생기는 정보를 확인한다.
    7. 해당 정보를 클릭해 Request URL을 확인한다.
    8. Payload, Preview 등을 확인해보자. 2 3

[2] 서버에 데이터 요청 : request(url) → response : json(str)

  • response의 status code가 200이 나오는지 확인
  • 403이나 500이 나오면 request가 잘못되거나 web server에서 수집이 안되도록 설정이 된것임
    • header 설정 또는 selenium 사용
  • 200이 나오더라도 response 안에 있는 내용을 확인 → 확인하는 방법 : response.text

[3] 서버에서 받은 데이터 파싱(데이터 형태를 변경) : json(str) → list, dict → DataFrame

[4] 전체 과정 함수로 만들기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
def stock_price(code='KOSPI', page=1, page_size=20):
    '''This function is crawling stock price from naver.
    params:
        code : str : KOSPI, KOSDAQ
        page : int
        page_size : int
    return:
        type : DataFrame
    '''
    # 1. URL
    url = f'https://m.stock.naver.com/api/index/{code}/price?pageSize={page_size}&page={page}'
    
    # 2. request(URL) > response : json(str)
    response = requests.get(url)
    
    # 3. json(str) > list, dict > DataFrame
    data = response.json()
    return pd.DataFrame(data)[['localTradedAt', 'closePrice']]
1
stock_price()
localTradedAtclosePrice
02023-02-162,475.48
12023-02-152,427.90
22023-02-142,465.64
32023-02-132,452.70
42023-02-102,469.73
52023-02-092,481.52
62023-02-082,483.64
72023-02-072,451.71
82023-02-062,438.19
92023-02-032,480.40
102023-02-022,468.88
112023-02-012,449.80
122023-01-312,425.08
132023-01-302,450.47
142023-01-272,484.02
152023-01-262,468.65
162023-01-252,428.57
172023-01-202,395.26
182023-01-192,380.34
192023-01-182,368.32

(2) Daum Exchange

  • https://finance.daum.net
  • 서버에서 어뷰징을 막는 경우 해결 방법
  • headers의 ‘user-agent’와 ‘referer’ 값을 변경해준다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# 1. 웹서비스분석 : URL
url = 'https://finance.daum.net/api/exchanges/summaries'
headers = {
    'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/109.0.0.0 Safari/537.36',
    'referer' : 'https://finance.daum.net/exchanges'
}
# 2. request(URL) > response(json(str))
response = requests.get(url, headers=headers)
response


# 3. json(str) > list, dict > DataFrame
df = pd.DataFrame(response.json()['data'])
df.tail(2)
symbolCodedatecurrencyCodecurrencyNamecurrencyUnitcountryregionnamerecurrenceCountbasePrice...changeRatecashBuyingPricecashSellingPricettBuyingPricettSellingPricetcBuyingPricefcSellingPriceexchangeCommissionusDollarRatechartImageUrl
40FRX.KRWQAR2023-02-16 18:12:10QAR리얄1카타르{'korName': '중동', 'engName': 'Middle East'}카타르 (KRW/QAR)567351.67...0.0032880.00.00.000.0NoneNone3.3550.2737{'day': 'https://t1.daumcdn.net/finance/chart/...
41FRX.KRWTRY2023-02-16 18:12:10TRY리라1터키{'korName': '중동', 'engName': 'Middle East'}터키 (KRW/TRY)56768.16...0.0016110.00.067.4268.9NoneNone26.4000.0530{'day': 'https://t1.daumcdn.net/finance/chart/...

2 rows × 22 columns

3. Web Crawling (API)

  • 크롤링 정책 : robots.txt
    • 웹페이지의 크롤링 정책 설명한 페이지
    • ex) www.naver.com/robots.txt, ted.com/robots.txt
  • 크롤링법은 없지만.. 지적재산권, 서비스과부화, 데이터 사용 표준 등 법적문제가 발생할 수 있음
  • 따라서, API를 사용하면 이러한 문제를 해결 가능
  • API 사용 : 데이터를 가지고 있는 업체에서 데이터를 가져갈 수 있도록 하는 서비스

1) 사용 방법

위의 웹크롤링 방법과 동일하나, access token과 URL을 document에서 찾는 것만 다름.

  1. Client 에서 app 등록 - Server에서 access token(request token) 전달
  2. URL 찾기 (document에 있음)
  3. request(key 포함) → response
  4. data → DataFrame

2) 실습

(1) Naver Rest API

  • 파파고 번역 api
  • 통합검색어 트렌드 api

0. request token 얻기

  • https://developers.naver.com
    1. Request Token 얻기 : 애플리케이션등록 → app_key 획득
    1. app_key를 이용해서 데이터 가져오기

1. 파파고 번역 api

  • https://developers.naver.com/docs/papago/
  • 사용법
    • https://developers.naver.com/docs/papago/papago-nmt-api-reference.md
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import pandas as pd
import requests, json

# Make Function
def translate(txt, source='ko', target='en'):
    # 0. request token 얻기
    CLIENT_ID, CLIENT_SECRET = 'DGJwjFI3owlSfbdd88dE', '*******'
    
    # 1. API 문서에서 URL 찾기
    url = "https://openapi.naver.com/v1/papago/n2mt"
    headers = {
        'Content-Type' : 'application/json',
        'X-Naver-Client-Id' : CLIENT_ID,
        'X-Naver-Client-Secret' : CLIENT_SECRET,
    }
    
    # 2. request(URL) > response : json(str)
    params = {'source' : source, 'target' : target, 'text' : txt}
    
    response = requests.post(url, json.dumps(params), headers=headers)
    
    # 3. json(str) : en_txt
    return response.json()['message']['result']['translatedText']
1
translate('웹크롤링은 재밌습니다.')
1
'Web crawling is fun.'

2. 활용 예시

1
2
3
4
5
6
df = pd.DataFrame({
    'id' : list('AB'),
    'title' : ['인공지능 스쿨 화이팅', '데이터 크롤링'],
})

df
idtitle
0A인공지능 스쿨 화이팅
1B데이터 크롤링
1
2
df['en_title'] = df['title'].apply(translate)
df
idtitleen_title
0A인공지능 스쿨 화이팅AI school, fighting!
1B데이터 크롤링Crawling data

(2) 통합검색어 트렌드 api

  • 서비스 : https://datalab.naver.com/keyword/trendSearch.naver
  • 내 애플리케이션 > dss 애플리케이션 > API 설정 > 데이터랩(검색어 트렌드) 추가
  • 사용법 : https://developers.naver.com/docs/serviceapi/datalab/search/search.md#통합-검색어-트렌드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# 1. API 문서 : URL
url = 'https://openapi.naver.com/v1/datalab/search'
params = {
    'startDate' : '2018-01-01',
    'endDate' : '2023-02-16',
    'timeUnit' : 'month',
    'keywordGroups' : [
        {'groupName' : '트위터', 'keywords' : ['트위터', '트윗']},
        {'groupName' : '페이스북', 'keywords' : ['페이스북', '페북']},
        {'groupName' : '인스타그램', 'keywords' : ['트위터', '인스타']},
    ]
}

headers = {
    'Content-Type' : 'application/json',
    'X-Naver-Client-Id' : CLIENT_ID,
    'X-Naver-Client-Secret' : CLIENT_SECRET,
}

# 2. request(URL) > response : json(str)
response = requests.post(url, json.dumps(params), headers=headers)

# 3. json(str) > list, dict > DataFrame
results = response.json()['results']
1
2
3
4
5
6
7
8
9
# columns : period, ratio, title
dfs = []
for result in results:
    df = pd.DataFrame(result['data'])
    df['title'] = result['title']
    dfs.append(df)

result_df = pd.concat(dfs, ignore_index=True)
result_df
periodratiotitle
02018-01-0114.59502트위터
12018-02-0113.02380트위터
22018-03-0112.97953트위터
32018-04-0112.82991트위터
42018-05-0113.05391트위터
............
1812022-10-0132.86715인스타그램
1822022-11-0130.52275인스타그램
1832022-12-0130.16556인스타그램
1842023-01-0131.27293인스타그램
1852023-02-0116.00833인스타그램

186 rows × 3 columns

1
2
3
4
5
6
7
8
9
10
11
12
# column : title(twitter, facebook, instagram) 
# index : period
# value : ratio

pivot_df = result_df.pivot('period', 'title', 'ratio')
pivot_df.columns = ['instagram', 'twitter', 'facebook']

import matplotlib.pyplot as plt
%config InlineBackend.figure_formats = {'png', 'retina'} # plot 선명하게

pivot_df[:-1].plot(figsize=(20, 5))
plt.show()

output_26_0

This post is licensed under CC BY 4.0 by the author.

[KT Aivle 3기 AI] 11일차. 미니프로젝트 2

[KT Aivle 3기 AI] 13일차. 웹크롤링 (Web Crawling) (2)