본문 바로가기
🐍Python

[20210806] requests, urllib, BeatifulSoup, Selenium 라이브러리를 이용한 웹크롤링

by 캔 2021. 8. 6.

웹크롤링

웹크롤링(web crawling)은 웹사이트로 이뤄진 웹 상에서 주기적으로 데이터를 추출하는 것을 말한다. 참고로, 웹에서 데이터를 추출하는 행위 자체는 '웹 스크레이핑'(web scraping)이라고 해야 하지만, 어차피 웹 스크레이핑을 주기적으로 수행하는 것이 웹크롤링이므로, 용어를 웹크롤링으로 통일하여 사용하겠다. 여기서는 파이썬의 기본 내장 라이브러리인 urllib의 하위 모듈들(request, parse, error, robotparse)과, requests 라이브러리, BeautifulSoup 라이브러리, Selenium을 사용한다.

requests

requests는 URL에 요청을 전달하고 응답을 받아오는 역할을 하는 라이브러리이다. 명령 프롬프트나 터미널에 'pip install requests'를 입력하여 설치한다.

# requests
# Naver 메인 페이지 정보 가져오기: get() 메서드로 Naver 서버에서 정보를 가져와서 text 속성으로 html 출력
import requests

url = "https://www.naver.com"
response = requests.get(url)
print(response.status_code)
print(response.text)

# Naver 검색 결과 정보 가져오기
import requests

url = "https://search.naver.com/search.naver"
params = {'query': '여자배구'}
response = requests.get(url, params=params)
print(response.status_code)
print(response.text)

# Naver 검색 결과 정보 가져오기(2)
import requests

url = "https://comic.naver.com/webtoon/detail.nhn"
params = {'titleId': 751168, 'no': 54}
response = requests.get(url, params=params)
print(response.text)

# JSON 형식의 데이터 가져오기: get() 메서드로 정보를 가져와서 json() 메서드로 JSON 출력
import requests

response = requests.get("https://raw.githubusercontent.com/naver/naver-openapi-guide/"
                        "draft/naver-openapi-swagger.json")
result = response.json()
print(type(result))
print(result.items())

 

urllib

urllib은 requests와 유사하게 URL에 요청을 보내고 응답을 받아오는 역할을 하는 request 모듈과 그 외 URL 활용을 도와주는 parse 모듈 등 여러 하부 모듈로 이뤄진 라이브러리이다. 따로 설치할 필요 없이 import만 하면 사용 가능하다.

# urllib
# request, error, parse, robotparser 모듈로 구성
# urllib.request
# get(): URL에 요청을 전달하여 응답을 받아옴
import urllib.request
from urllib import request

url = "https://www.naver.com"
request = request.urlopen(url)  # 해당 URL을 열기, 응답 데이터는 바이트 형식의 HTTPResponse 객체
print(request)
response1 = urllib.request.urlopen(url)
print(response1)
byte_data = response1.read(500)  # urlopen으로 연 객체를 읽고, 인자로 전달하는 숫자만큼 데이터를 읽음
print(byte_data)
text_data = byte_data.decode()  # 바이트 형식 데이터를 원하는 형식으로 변환, 기본값은 UTF-8.
print(text_data)
img_src = "data:image/jpeg;base64,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 "
new_name = 'img.jpg'
urllib.request.urlretrieve(img_src, new_name)  # 웹상의 이미지를 다운로드해주는 역할

# urllib.parse: URL을 파싱하여 분석하기 위한 모듈 (HTTP, FTP, SSH, IMAP 등도 포함)
# urlparse(): URL을 6개의 요소로 분리하여 반환
# ParseResult(scheme='http', netloc='blog.naver.com', path='/koreatech91',
# params='a=1', query='b=2', fragment='b')
import urllib.parse as par

parse = par.urlparse("http://blog.naver.com/koreatech91;a=1?b=2#b")
print(parse)
print(parse[0])  # print(parse.scheme) == http
print(parse[1])  # print(parse.netloc) == blog.naver.com
print(parse[2])  # print(parse.path) == /koreatech91

# urlsplit(): url을 5개로 분리하여 반환
# SplitResult(scheme='http', netloc='blog.naver.com', path='/koreatech91;a=1',
# query='b=2', fragment='b')
import urllib.parse as par

parse2 = par.urlsplit("http://blog.naver.com/koreatech91;a=1?b=2#b")
print(parse2)
print(parse2[0])  # print(parse.scheme) == http
print(parse2[1])  # print(parse.netloc) == blog.naver.com
print(parse2[2])  # print(parse.path) == /koreatech91;a=1

# urlunparse(), urlunsplit(): 분리된 url을 다시 합침
import urllib.parse as par

parse = par.urlparse("http://blog.naver.com/koreatech91;a=1?b=2#b")  # 리턴 값이 튜플
print(parse)
parse = list(parse)  # 리스트로 바꾼 후 값을 변경할 수 있다.
parse[1] = "blog.daum.net"
unparse = par.urlunparse(parse)  # http://blog.daum.net/koreatech91;a=1?b=2#b
print(unparse)

# 쿼리를 변경하여 요청할 때 활용
import urllib.parse as par

parse = par.urlparse("https://www.naver.com?a=1&b=2&c=3&d=4")
print(parse)
print(parse.query)  # a=1&b=2&c=3&d=4
print(type(parse.query))  # <class 'str'>
qs = par.parse_qs(parse.query)
print(qs)  # {'a': ['1'], 'b': ['2'], 'c': ['3'], 'd': ['4']}
print(type(qs))  # <class 'dict'>
qs1 = par.parse_qsl(parse.query)
print(qs1)  # [('a', '1'), ('b', '2'), ('c', '3'), ('d', '4')]
print(type(qs1))  # <class 'list'>

# urljoin(a, b): a와 b의 url을 합쳐주는 기능, '/' 유무에 따라 URL 주소가 달라지는 것에 주의
import urllib.parse as par

url = "https://naver.com/a/b"
print(par.urljoin(url, 'c'))
print(par.urljoin(url, '/c'))

# quote(), unquote(): 아스키 코드가 아닌 문자들을 퍼센트 인코딩으로 교환해줌
# url에 한글이 섞이면 오류가 발생한다.
import urllib.request as req
import urllib.parse as pas

# url = 'http://search.naver.com/search.naver?query=파이썬' -> 한글 오류
url = "http://search.naver.com/search.naver?query=python"
response_urllib = req.urlopen(url)
byte_data = response_urllib.read()
text_data = byte_data.decode()
print(pas.quote('파이썬'))  # %ED%8C%8C%EC%9D%B4%EC%8D%AC
# print(text_data)

# urllib 라이브러리 활용
# 1. 홈페이지를 로컬에 파일로 저장
import urllib.request as req

request = req.Request("http://www.naver.com")
data = req.urlopen(request).read()
f = open("pc.html", "wb")
f.write(data)
f.close()

# 2. 헤더(header)를 추가해서(접속기기를 모바일로 인식하도록 설정) 모바일 페이지를 파일로 저장
import urllib.request as req

header = {'User-Agent': 'Mozilla/5.0 (iPhone)'}
request = req.Request("https://www.naver.com", headers=header)
data = req.urlopen(request).read()
f = open("mobile.html", "wb")
f.write(data)
f.close()

# 3. 파라미터를 변경해 여러 가지 네이버 검색 결과 가져오기
import urllib.request as req
import urllib.parse as par

query_list = ['파이썬', '웹 크롤링', '빅데이터', 'python']
url = 'https://search.naver.com/search.naver?query='
for i in query_list:
    new_url = url + par.quote(i)  # 한글, 특수문자 처리 % 문자로
    response_urllib = req.urlopen(new_url)
    byte_data = response_urllib.read()
    text_data = byte_data.decode()
    print(text_data)

# 4. 파이썬 문법을 활용하여 연관 검색어 리스트 출력(현재는 서비스 중지)
# 정규 표현식 및 문자열 함수 이용
import urllib.request as req
import urllib.parse as par
import re

query_list = ['파이썬', '웹 크롤링', '빅데이터', 'python']
url = 'https://search.naver.com/search.naver?query='
for i in query_list:
    new_url = url + par.quote(i)  # 한글, 특수문자 처리 %문자로
    response_urllib = req.urlopen(new_url)
    byte_data = response_urllib.read()
    text_data = byte_data.decode()
    # text_data = text_data.split('연관검색어')[1].split('도움말')[1].split('닫기 후 1주일간')[0]
    text_data = re.sub('<.+?>', '', text_data, 0, re.I | re.S)
    text_data = text_data.replace('  ', ' ').strip()
    print(i, ": ", text_data)

 

BeautifulSoup

BeautifulSoup은 홈페이지 내 데이터를 쉽게 추출할 수 있도록 도와주는 라이브러리이다. 웹 문서 내 수많은 HTML 태그들을 parser 모듈을 활용해 사용하기 편한 파이썬 객체로 만들어 제공한다. 웹 문서 구조를 알고 나서 사용하면 편하게 원하는 데이터를 뽑아 활용할 수 있다. 명령 프롬프트나 터미널에 'pip install bs4'를 입력하여 설치한다.

# BeautifulSoup
import requests
from bs4 import BeautifulSoup

req = requests.get("https://www.naver.com")
html = req.text
soup = BeautifulSoup(html, 'html.parser')
print(soup.prettify())

# 태그를 통해 가져오기
# 태그['속성']: HTML 해당 태그의 속성 중 첫 번째 값을 가져옴
import requests
from bs4 import BeautifulSoup

req = requests.get("https://www.naver.com")
html = req.text
soup = BeautifulSoup(html, 'html.parser')
print(soup.title)
print(soup.title.name)
print(soup.title.string)
print(soup.img)
print(soup.img['alt'])
print(soup.img['width'])
print(soup.img['height'])

# find(속성='값'): 해당 속성과 첫 번째로 일치하는 값을 가져옴
print(soup.find('a'))
print(soup.find(id='search'))

# find_all(): HTML의 해당 태그와 일치하는 모든 결과를 리스트 형식으로 가져옴
# limit 옵션으로 개수 지정 가능
print(soup.find_all('a', limit=2))
print(soup.find_all('a')[0])

# css 속성으로 필터링(class_로 클래스를 직접 사용 혹은 attrs에서 '속성=값'으로 필터링)
print(soup.find_all('span', class_="blind"))
print(soup.find_all("span", attrs={"class": "blind"}))

# string으로 검색(해당 값이 있는지 없는지 검사할 때 활용, 정규 표현식과 함께 활용)
print(soup.find_all(string='자동완성 끄기'))
import re

print(soup.find_all(string=re.compile("네이버")))

# select_one(), select(): css 선택자를 활용하여 원하는 정보를 가져옴 (find(), find_all()하고 비슷)
print(soup.select_one('a'))
print(soup.select('a'))
print(soup.select("body a"))
print(soup.select('div> ul'))

# Beautifulsoup을 이용한 데이터 가공
# get_text(): 검색 결과에서 태그를 제외한 텍스트만 출력
# get('속성'): 해당 속성의 값을 출력
text = soup.find("span", attrs={"class": "blind"})
print(text)  # 태그 및 내용 전부 출력
print(text.get_text())  # 태그 제외, 내용만 출력
print(text.get('class'))  # 클래스 속성 값 출력

# string: 검색 결과에서 태그 안에 또 다른 태그가 없는 경우 해당 내용을 출력
text = '''<li class="an_item">
<a class="an_a  mn_checkout data-clk="svc.pay" href="https://order.pay.naver.com/home">
<span  class="an_icon"></span><span class="an_txt">네이버페이</span>
</a>
</li>'''
soup = BeautifulSoup(text, 'html.parser')
print(soup.string)  # 태그안에 태그가 있기 때문에 None 반환
result = soup.find('span', class_='an_txt')
print(result)  # <span class="an_txt">네이버페이</span>
print(result.string)  # 네이버페이

# BeautifulSoup 응용
# 네이버 영화 랭킹 가져오기
# https://movie.naver.com/movie/sdb/rank/rmovie.nhn
# Beautifulsoup로 파싱
import requests
from bs4 import BeautifulSoup

req = requests.get("https://movie.naver.com/movie/sdb/rank/rmovie.nhn")
html = req.text
soup = BeautifulSoup(html, 'html.parser')
print(soup)

# 1. 텍스트에서 영화 랭킹 찾기
# 2. 영화 랭킹에 해당하는 부분의 태그 찾기
# td 태그 class=title, div 태그, class는 tit3, a 태그, title 및 내용이 영화 제목
# Beautifulsoup 객체의 함수로 데이터 가공
movie_ranking_list = soup.find_all('div', class_="tit3")
for i in range(len(movie_ranking_list)):
    print("{:2} 위: {}".format(i + 1, movie_ranking_list[i].get_text().strip()))

 

Selenium

Selenium은 웹브라우저를 컨트롤해서 웹 UI를 자동화하는 도구, 라이브러리이다. 명령 프롬프트나 터미널에 'pip install selenium'을 입력하여 설치할 수 있다. 추가적으로, 브라우저를 조종하기 위한 드라이버가 필요하다. 보통 크롬 드라이버를 많이 쓰며, https://sites.google.com/a/chromium.org/chromedriver/downloads에서 다운로드할 수 있다. 크롬 드라이버를 압축을 풀고 같은 디렉터리에 위치시켜야 한다.

# Selenium
# python.org 웹사이트에 방문해서 상단 메인 메뉴 문자열을 출력하고
# 웹사이트의 PyPI 메뉴를 클릭한 후 5초 후에 브라우져를 종료시킨다.
from selenium import webdriver
import time

browser = webdriver.Chrome()
browser.get("http://python.org")
menus = browser.find_elements_by_css_selector('#top ul.menu li')
pypi = None
for m in menus:
    if m.text == "PyPI":
        pypi = m
    print(m.text)
pypi.click()  # 클릭
time.sleep(5)  # 5초 대기
browser.quit()  # 브라우저 종료

 

2021 KBO 프로 야구 관중 수 크롤링 및 시각화

한국야구위원회(https://www.koreabaseball.com/History/Crowd/GraphDaily.aspx)에서는 일간 프로야구 관객 수를 제공하고 있다. 이 데이터를 사용해서 데이터를 수집하고 Seaborn 라이브러리를 이용해서 시각화해보자.

# 2021년 KBO 프로야구 관객 수를 크롤링하여 시각화
# https://www.koreabaseball.com/History/Crowd/GraphDaily.aspx

import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
import requests
from bs4 import BeautifulSoup

plt.rc('font', family='gulim')

req = requests.get('https://www.koreabaseball.com/History/Crowd/GraphDaily.aspx')
soup = BeautifulSoup(req.text, 'html.parser')

# class 이름이 tData인 table을 가져온다.
tdata = soup.find('table', {'class': 'tData'})

# table에서 tbody를 찾는다
series = tdata.find('tbody')

# 객체가 아닌 string 형태로 담아준다.
kbdata_spec = series.text

# tr 태그를 찾는다.
table_rows = tdata.find_all('tr')

# res라는 list에 row 별로 담아준다.
res = []
for tr in table_rows:
    td = tr.find_all('td')
    row = [tr.text.strip() for tr in td if tr.text.strip()]
    if row:
        res.append(row)

# DataFrame으로 만들어준다.
df = pd.DataFrame(res, columns=["date", "day", "team_1", "team_2", 'place', 'number'])

# 첫 줄은 드롭한다.
df = df.drop(0)

# 전 처리
# 콤마 제거
df['number'] = df['number'].str.replace(',', '')

# datetime type으로 변환
df['date'] = pd.to_datetime(df['date'])

# 날짜 칼럼을 분리
df['year'] = df['date'].dt.year
df['month'] = df['date'].dt.month
df['day'] = df['date'].dt.day
df['dayofweek'] = df['date'].dt.dayofweek

# 숫자형 칼럼로 분리
df['number'] = df['number'].astype(int)

# 관객 수대로 정렬
df.sort_values('number', ascending=False)

# 시각화
# 관객 숫자 분포도
sns.distplot(df['number'])
plt.show()

# 월별 관중 수 막대 그래프
plt.figure(figsize=(12, 9))
x = df.groupby('month')['number'].sum().keys()
y = df.groupby('month')['number'].sum()
sns.barplot(x, y)
plt.show()

# 구장별 관중 수 막대 그래프
x = df.groupby('place')['number'].sum().keys()
y = df.groupby('place')['number'].sum()
sns.barplot(x, y)
plt.xlabel('구장')
plt.ylabel('관중수')
plt.show()

# 요일별 관중 수 막대 그래프
xlabel = ['월', '화', '수', '목', '금', '토', '일']
plt.figure(figsize=(12, 9))
x = df.groupby('dayofweek')['number'].sum().keys()
y = df.groupby('dayofweek')['number'].sum()
ax = sns.barplot(x, y)
ax.set(xticklabels=xlabel)
plt.xlabel('요일')
plt.ylabel('관중수')
plt.show()

# 일별 관중 수 막대 그래프
plt.figure(figsize=(12, 9))
x = df.groupby('day')['number'].sum().keys()
y = df.groupby('day')['number'].sum()
ax = sns.barplot(x, y)
plt.xlabel('일별')
plt.ylabel('관중수')
plt.show()