[KT Aivle 3기 AI] 13일차. 웹크롤링 (Web Crawling) (2)
0. 개요
KT Aivle School 3기 AI 13일차
- 강사 : 박두진 강사님
- 주제 : 웹크롤링(Web Crawling), BeautifulSoup과 Selenium을 활용한 정적 페이지 크롤링
- 내용 :
- 12일차에 이어 Web Crawling에 대해 강의해주셨다.
- 직방 예제로 12일차에 배운 동적 페이지 Crawling 실습 진행
- HTML에 대한 기본 지식 및 CSS Selector에 대해 알려주시고,
- BeautifulSoup과 Selenium을 활용한 정적 페이지 Crawling에 대한 실습 진행
1. Web Crawling Summary
1) Summary
- Web : Server-Client 구조 : url
- requests :
- 동적 페이지 : URL 변화 없이 페이지의 데이터 수정 : json(str) > response.json() > DataFrame
- 정적 페이지 : URL 변화 있이 페이지의 데이터 수정 : html(str) > BeautifulSoup > css selector > DataFrame
- selenium : 웹브라우저를 python 코드로 컨트롤해서 데이터 수집
- 실행 속도
- requests(동적페이지, API) > requests(정적페이지) > selenium
2) 웹크롤링 절차
- 웹서비스 분석(개발자도구) : URL
- request(URL) > response(data) : data(json(str), html(str))
- data(json(str), html(str)) > response.json(), BeautifulSoup(css-selector) > DataFrame
3) request 할 때 에러 발생
- request 할 때 401, 403, 500 등등의 에러가 발생하는 경우 > headers 수정해서 데이터 요청(user-agent, referer)
4) API 이용
- request token 수집 후 크롤링
2. HTML
1) HTML 이란?
- HTML은 Hyper Text Markup Language의 약자로 웹 문서를 작성하는 마크업 언어 입니다.
2) HTML 구성 요소
- Document : 한페이지를 나타내는 단위
- Element : 하나의 레이아웃을 나타내는 단위 : 시작태그, 끝태그, 텍스트로 구성
- Tag : 엘리먼트의 종류를 정의 : 시작태그(속성값), 끝태그
- Attribute : 시작태그에서 태그의 특정 기능을 하는 값
- id : 웹 페이지에서 유일한 값
- class : 동일한 여러개의 값 사용 가능 : element를 그룹핑 할때 사용
- attr : id와 class를 제외한 나머지 속성들s
- Text : 시작태그와 끝태그 사이에 있는 문자열
- 엘리먼트는 서로 계층적 구조를 가질수 있습니다.
웹페이지의 구성 요소 : HTML(레이아웃, 문자열), JS(이벤트), CSS(스타일)
3) HTML 구조
- DOCTYPE
- 문서의 종류를 선언하는 태그 입니다.
- html
- head
- meta
- 웹페이지에 대한 정보를 넣습니다.
- title
- 웹페이지의 제목 정보를 넣습니다.
- meta
- body
- 화면을 구성하는 엘리먼트가 옵니다.
- head
1
2
3
4
5
6
7
8
9
10
11
<!-- HTML 웹문서의 기본적인 구조 -->
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title></title>
</head>
<body>
</body>
</html>
4) HTML 태그
- html에서 문자를 나타내는 태그들
- head :
- title을 나타낼때 사용
- Head는 총 6가지 종류의 태그가 있습니다.(h1~h6)
- 숫자가 커질수록 문자의 크기가 줄어듭니다.
p : 한 줄의 문자열을 출력하기 위한 태그
span : 한 블럭의 문자열을 표현하기 위한 태그
pre : 줄바꿈이나 띄어쓰기가 적용되는 태그
- code :
- 코드를 작성하는 태그
- 들여쓰기나 두칸 이상의 공백은 적용이 안됩니다.
5) 문자 이와의 HTML 태그
- div : 레이아웃을 나타내는 태그
- table : 로우와 컬럼이 있는 테이블 모양을 나타낼때 사용하는 태그
- ul, li : 리스트를 나타내는 태그
- a :
- 링크를 나타내는 태그
- href 속성에 url을 넣습니다.
- url과 상대경로를 모두 사용 가능
target="_blank"
는 링크를 열 때 새탭에서 열도록 하는 기능
- image : 이미지를 나타내는 태그
- iframe :
- 외부 url 링크 페이지를 보여주기 위한 엘리먼트
- 모든 웹 페이지를 보여줄 수 있는 건 아니고, iframe으로만 출력이 되던가 안되거나 하는 등의 설정을 할 수 있다.
- input
- input : 문자열을 입력받을 때 사용하는 input 타입
- password : 비밀번호를 입력받을 때 사용하는 input 타입
- radio : 비밀번호를 입력받을 때 사용하는 input 타입
- checkbox : 여러개의 버튼이 체크되는 버튼
- select, option : 옵션 선택을 할 수 있는 드랍다운 태그
- textarea : 여러줄 입력이 가능한 태그
- button : 마우스 클릭을 입력받는 버튼 태그
3. CSS Selector
- CSS Selector는 CSS 스타일을 적용시킬 HTML 엘리먼트를 찾기 위한 방법입니다.
- CSS Selector 실습코드
1) Element Selector
- 엘리먼트를 이용하여 선택할때 사용
- css selector로
div
를 사용하면 가장 위에 있는 dss1이 선택
1
2
3
<div>dss1</div>
<p>dss2</p>
<span>dss3</span>
2) ID Selector
- 아이디를 이용하여 선택할때 사용
- 아이디를 셀렉할때는
#(아이디 이름)
으로 선택 - css selector로
#ds2
를 사용하면 dss2가 선택 - 여러개를 셀렉할때는 ,로 구분
- css selector로
#ds2, #ds3
를 사용하면 dss2와 dss3가 선택
1
2
3
<p id="ds1">dss1</p>
<p id="ds2">dss2</p>
<p id="ds3">dss3</p>
3) Class Selector
- 클래스를 이용하여 선택할때 사용
- 클래스를 셀렉할때는
.(클래스 이름)
으로 선택 - 엘리멘트를 그룹핑하여 스타일을 적용할때 사용
- css selector로
.ds2
를 사용하면 dss2, dss3가 선택
1
2
3
<p class="ds1">dss1</p>
<p class="ds2">dss2</p>
<p class="ds2">dss3</p>
4) not Selector
- 셀렉터로 엘리먼트를 하나만 제거하고 싶을때 사용
- not을 사용하여 셀렉트 할때에는
:not(선택에서 제거하고 싶은 셀렉터)
으로 선택 - 아래의 HTML에서
.ds:not(.ds2)
으로 셀렉트 하면 class가 ds2인 클래스를 제외 하고 나머지 ds1, ds3, ds4, ds5가 선택
1
2
3
4
5
<p class="ds ds1">ds1</p>
<p class="ds ds2">ds2</p>
<p class="ds ds3">ds3</p>
<p class="ds ds4">ds4</p>
<p class="ds ds5">ds5</p>
5) first-child Selector
- 엘리먼트로 감싸져있는 가장 처음 엘리먼트가 설정한 셀렉터와 일치하면 선택
.ds:first-child
로 설정하면 ds1과 ds3가 선택
1
2
3
4
5
6
7
8
9
10
11
12
<body>
<p class="ds" id="ds1">ds1</p>
<p class="sc" id="ds2">ds2</p>
<div class="ds">
<p class="ds ds1">ds3</p>
<p class="ds ds2">ds4</p>
<p class="ds ds3">ds5</p>
<p class="ds ds4">ds6</p>
<p class="ds ds5">ds7</p>
</div>
</body>
div.ds
엘리먼트의 가장 처음.ds
를 선택하고 싶으면div.ds > .ds:first-child
로 셀렉터를 작성
6) last-child Selector
- 엘리먼트로 감싸져있는 가장 마지막 엘리먼트가 설정한 셀렉터와 일치하면 선택
.ds:last-child
로div.ds
가 선택되어 ds3~ds7이 선택
1
2
3
4
5
6
7
8
9
10
11
12
<body>
<p class="ds" id="ds1">ds1</p>
<p class="sc" id="ds2">ds2</p>
<div class="ds">
<p class="ds ds1">ds3</p>
<p class="ds ds2">ds4</p>
<p class="ds ds3">ds5</p>
<p class="ds ds4">ds6</p>
<p class="ds ds5">ds7</p>
</div>
</body>
7) nth-child Selector
- 엘리먼트로 감싸져있는 n번째 엘리먼트가 설정한 셀렉터와 일치하면 선택
.ds:nth-child(3), .ds:nth-child(4)
로 설정하면 ds4, ds5가 선택nth-child
의 ()안의 숫자는 가장 첫번째가 0이 아니라 1로 시작
1
2
3
4
5
6
7
8
<div class="wrap">
<span class="ds">ds2</span>
<p class="ds ds1">ds3</p>
<p class="ds ds2">ds4</p>
<p class="ds ds3">ds5</p>
<p class="ds ds4">ds6</p>
<p class="ds ds5">ds7</p>
</div>
8) 모든 하위 depth(공백) Selector
- 공백문자로 하위 엘리먼트를 셀렉트 했을때, 모든 하위 엘리먼트를 선택
.contants h1
를 선택하면 inner_1, inner_2가 선택
1
2
3
4
5
6
<div class="contants">
<h1>inner_1</h1>
<div class="txt">
<h1>inner_2</h1>
</div>
</div>
9) 바로 아래 depth(>) Selector
>
문자로 하위 엘리먼트를 셀렉트 했을때, 바로 아래 엘리먼트를 선택.contants > h1
를 선택하면 inner_1이 선택
1
2
3
4
5
6
<div class="contants">
<h1>inner_1</h1>
<div class="txt">
<h1>inner_2</h1>
</div>
</div>
4. 실습
1) BeautifulSoup 활용
- 정적(static) 웹페이지 데이터 수집
- BeautifulSoup을 이용하여 HTML 문자열 데이터 parsing
(1) 네이버 연관 검색어 추출
- 네이버에서 검색했을 때 나오는 네이버 연관 검색어를 추출
- Naver 연관 검색어 추출 실습코드
방법
- 웹페이지 분석 : URL
- request(URL) > response : str(html)
- str(html) > bs object
- bs object > .select(css-selector), .select_one(css-selector) > str(text)
- str(text) > DataFrame
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import pandas as pd
import requests
from bs4 import BeautifulSoup
# 1. 웹페이지 분석 : URL
query = '삼성전자'
url = f'https://search.naver.com/search.naver?query={query}'
# 2. request(URL) > response : str(html)
response = requests.get(url)
# 3. str(html) > bs object
dom = BeautifulSoup(response.text, 'html.parser')
# 4. bs object > .select(css-selector), .select_one(css-selector) > str(text)
elements = dom.select('#nx_right_related_keywords > div > div.related_srch > ul > li')
# 5. str(text) > DataFrame
keywords = [element.select_one('.tit').text for element in elements]
df = pd.DataFrame({'keyword' : keywords})
df['query'] = query
df
keyword | query | |
---|---|---|
0 | 삼성전자주가 | 삼성전자 |
1 | 삼성전자 배당금 | 삼성전자 |
2 | 삼성전자 성과급 | 삼성전자 |
3 | 삼성전자 주식 | 삼성전자 |
4 | 삼성전자서비스 | 삼성전자 |
5 | 오늘 삼성전자 주가 | 삼성전자 |
6 | 삼성전자 세일 페스타 | 삼성전자 |
7 | 삼성전자 배당금 지급일 | 삼성전자 |
8 | 삼성 전자레인지 | 삼성전자 |
9 | 삼성전자 감자빵 | 삼성전자 |
(2) Gmarket
- 베스트 상품 200개 데이터 수집
- 상품의 이미지 200개 다운로드
- Gmarket 실습코드
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
import pandas as pd
import requests
from bs4 import BeautifulSoup
# 1. URL 찾기
url = 'https://corners.gmarket.co.kr/bestsellers'
# 2. request > response : str(html)
response = requests.get(url)
# 3. bs > DataFrame
dom = BeautifulSoup(response.text, 'html.parser')
elements = dom.select('#gBestWrap > div > div:nth-child(5) > div > ul > li')
data = []
for element in elements:
data.append({
'title' : element.select_one('.itemname').text,
'link' : element.select_one('.itemname').get('href'),
'img' : 'http:' + element.select_one('img').get('data-original'),
'o_price' : element.select_one('.o-price').text,
's_price' : element.select_one('.s-price').text.strip().split('\n')[0],
})
df = pd.DataFrame(data)
df.tail(2)
title | link | img | o_price | s_price | |
---|---|---|---|---|---|
198 | [비달사순]비달사순 2000W 전문가용 헤어드라이기 VSD5129K | http://item.gmarket.co.kr/Item?goodscode=18079... | http://gdimg.gmarket.co.kr/1807976048/still/30... | 정가25,330원 | 할인가22,800원 |
199 | 농협우리쌀20kg | http://item.gmarket.co.kr/Item?goodscode=14867... | http://gdimg.gmarket.co.kr/1486733140/still/30... | 정가49,800원 | 할인가44,820원 |
1
2
3
4
5
6
7
8
9
10
11
# 데이터 전처리
# o_price 비어 있는 곳 채우기 (할인을 안하는 경우)
idx = df[df['o_price'] == ''].index
df['o_price'].loc[idx] = df['s_price'].loc[idx]
# 정규표현식을 통해 가격 정보만 남기기
# regex : 정규표현식, 문자열 데이터를 특정 패턴으로 처리할 때 사용하는 문법
import re
df['o_price'] = df['o_price'].apply(lambda price: re.findall('[0-9,]+', price)[0])
df['s_price'] = df['s_price'].apply(lambda price: re.findall('[0-9,]+', price)[0])
df.tail(2)
title | link | img | o_price | s_price | |
---|---|---|---|---|---|
198 | [비달사순]비달사순 2000W 전문가용 헤어드라이기 VSD5129K | http://item.gmarket.co.kr/Item?goodscode=18079... | http://gdimg.gmarket.co.kr/1807976048/still/30... | 25,330 | 22,800 |
199 | 농협우리쌀20kg | http://item.gmarket.co.kr/Item?goodscode=14867... | http://gdimg.gmarket.co.kr/1486733140/still/30... | 49,800 | 44,820 |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
# download image
# 이미지 저장할 디렉토리 생성
import os
dir_name = 'data'
if not os.path.exists(dir_name):
os.makedirs(dir_name)
for idx, data in df[:20].iterrows():
name = '0' * (3 - len(str(idx))) + str(idx)
filename = f'data/{name}.png'
print(idx, data['img'], filename)
response = requests.get(data['img'])
with open(filename, 'wb') as file:
file.write(response.content)
2) Selenium
https://www.selenium.dev
- 자동화를 목적으로 만들어진 다양한 브라우져와 언어를 지원하는 라이브러리
- 크롬 브라우저 설치
- 크롬 브라우저 드라이버 다운로드 (크롬 브라우져와 같은 버전)
- 다운로드한 드라이버 압축 해제
- chromedriver, chromedriver.exe 생성
- windows : 주피터 노트북 파일과 동일한 디렉토리에 chromedriver.exe 파일 업로드 (모든 디렉토리에서 사용하려면, 환경변수 설정해야함)
- mac : sudo cp ~/Download/chromedirver /usr/local/bin
- 드라이버로 열린 브라우저는 웬만하면 건들지 않기 (에러 발생 가능성 높음)
(1) Daum 연관 검색어 추출
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
import pandas as pd
from selenium import webdriver
from selenium.webdriver.common.by import By
# 드라이버 열기
driver = webdriver.Chrome()
# url 접속
driver.get('https://daum.net')
# 입력창에 '파이썬' 입력
driver.find_element(By.CSS_SELECTOR, '#q').send_keys('파이썬')
# 검색 버튼 클릭
selector = '.inner_search > .ico_pctop.btn_search'
driver.find_element(By.CSS_SELECTOR, selector).click()
# 연관검색어 펼치기 버튼 클릭 (Selenium은 보이는 것에서만 작업이 가능하기 때문에 안보이면 못불러들임)
selector = '#netizen_more_btn_top'
driver.find_element(By.CSS_SELECTOR, selector).click()
# 연관검색어 가져오기
selector = '#netizen_lists_top > .wsn'
elements = driver.find_elements(By.CSS_SELECTOR, selector)
# 불러온 연관검색어 확인
keywords = [element.text for element in elements]
# 자바스크립트 코드 실행 예시 (스크롤)
driver.execute_script('window.scrollTo(200, 300);')
# 드라이버 종료
driver.quit()
(2) TED 텍스트 데이터 가져오기
- TED 사이트 :
https://www.ted.com
1
2
3
4
5
6
7
# 드라이버 열기
driver = webdriver.Chrome()
# url 접속
driver.get('https://www.ted.com/talks')
# text 가져오기
selector = '.talks-header__title'
driver.find_element(By.CSS_SELECTOR, selector).text
- xpath를 이용한 엘리먼트 선택
//*[@id="shoji"]/div[2]/div/div[2]/header/div/div[1]
//
: 최상위 엘리먼트*
: 모든 하위 엘리먼트[@id="shoji"]
: 속성값으로 엘리먼트 선택/
: 한단계 하위 엘리먼트div[2]
: 태그[n번째]
1
2
3
# xpath 이용 text 가져오기
selector = '//*[@id="shoji"]/div[2]/div/div[2]/header/div/div[1]'
driver.find_element(By.XPATH, selector).text
1
2
# driver 종료
driver.quit()
(3) Headless
- 브라우져를 화면에 띄우지 않고 메모리상에서만 올려서 크롤링하는 방법
- window가 지원되지 않는 환경에서 사용이 가능
- chrome version 60.0.0.0 이상부터 지원 합니다.
1
2
3
4
5
6
options = webdriver.ChromeOptions()
options.add_argument('headless')
driver = webdriver.Chrome(options=options)
driver.get('https://www.ted.com/talks')
selector = '.talks-header__title'
driver.find_element(By.CSS_SELECTOR, selector).text
1
driver.quit()
This post is licensed under CC BY 4.0 by the author.