Post

[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) 웹크롤링 절차

  1. 웹서비스 분석(개발자도구) : URL
  2. request(URL) > response(data) : data(json(str), html(str))
  3. 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
        • 웹페이지의 제목 정보를 넣습니다.
    • body
      • 화면을 구성하는 엘리먼트가 옵니다.
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에서 문자를 나타내는 태그들
  1. head :
    • title을 나타낼때 사용
    • Head는 총 6가지 종류의 태그가 있습니다.(h1~h6)
    • 숫자가 커질수록 문자의 크기가 줄어듭니다.
  2. p : 한 줄의 문자열을 출력하기 위한 태그

  3. span : 한 블럭의 문자열을 표현하기 위한 태그

  4. pre : 줄바꿈이나 띄어쓰기가 적용되는 태그

  5. code :
    • 코드를 작성하는 태그
    • 들여쓰기나 두칸 이상의 공백은 적용이 안됩니다.

5) 문자 이와의 HTML 태그

  1. div : 레이아웃을 나타내는 태그
  2. table : 로우와 컬럼이 있는 테이블 모양을 나타낼때 사용하는 태그
  3. ul, li : 리스트를 나타내는 태그
  4. a :
    • 링크를 나타내는 태그
    • href 속성에 url을 넣습니다.
      • url과 상대경로를 모두 사용 가능
      • target="_blank"는 링크를 열 때 새탭에서 열도록 하는 기능
  5. image : 이미지를 나타내는 태그
  6. iframe :
    • 외부 url 링크 페이지를 보여주기 위한 엘리먼트
    • 모든 웹 페이지를 보여줄 수 있는 건 아니고, iframe으로만 출력이 되던가 안되거나 하는 등의 설정을 할 수 있다.
  7. input
    1. input : 문자열을 입력받을 때 사용하는 input 타입
    2. password : 비밀번호를 입력받을 때 사용하는 input 타입
    3. radio : 비밀번호를 입력받을 때 사용하는 input 타입
    4. checkbox : 여러개의 버튼이 체크되는 버튼
    5. select, option : 옵션 선택을 할 수 있는 드랍다운 태그
    6. textarea : 여러줄 입력이 가능한 태그
    7. button : 마우스 클릭을 입력받는 버튼 태그

3. 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-childdiv.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) 네이버 연관 검색어 추출

방법

  1. 웹페이지 분석 : URL
  2. request(URL) > response : str(html)
  3. str(html) > bs object
  4. bs object > .select(css-selector), .select_one(css-selector) > str(text)
  5. 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
keywordquery
0삼성전자주가삼성전자
1삼성전자 배당금삼성전자
2삼성전자 성과급삼성전자
3삼성전자 주식삼성전자
4삼성전자서비스삼성전자
5오늘 삼성전자 주가삼성전자
6삼성전자 세일 페스타삼성전자
7삼성전자 배당금 지급일삼성전자
8삼성 전자레인지삼성전자
9삼성전자 감자빵삼성전자

(2) 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)
titlelinkimgo_prices_price
198[비달사순]비달사순 2000W 전문가용 헤어드라이기 VSD5129Khttp://item.gmarket.co.kr/Item?goodscode=18079...http://gdimg.gmarket.co.kr/1807976048/still/30...정가25,330원할인가22,800원
199농협우리쌀20kghttp://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)
titlelinkimgo_prices_price
198[비달사순]비달사순 2000W 전문가용 헤어드라이기 VSD5129Khttp://item.gmarket.co.kr/Item?goodscode=18079...http://gdimg.gmarket.co.kr/1807976048/still/30...25,33022,800
199농협우리쌀20kghttp://item.gmarket.co.kr/Item?goodscode=14867...http://gdimg.gmarket.co.kr/1486733140/still/30...49,80044,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.

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

[KT Aivle 3기 AI] 14일차. 머신러닝 (1) 머신러닝 기초