개발자/파이썬

파이썬 웹스크래핑(web scraping)

june__kim 2020. 12. 30. 20:21

파이썬 웹스크래핑(web scraping)

 

내가 정말 좋아하는 코딩유튜버 중에 한 분이신 "나도코딩"님이 좋은 영상을 올려주셔서

이를 보고 공부하고 응용하는 연습도 하고 있다.

(나도코딩님 영상: www.youtube.com/watch?v=yQ20jZwDjTE&t=9077s)

 

주제는 파이썬을 이용한 웹 스크래핑이다.

 

지금 잘 정리해두고 나중에 까먹게 되면 이 글 보면서, 다시 써먹어야지~~

 

먼저 용어부터 좀 다뤄보자.

 

아래의 세 단어는 꽤나 혼용된다.

내 친구들 중에서도 그냥 웹에서 정보를 긁어오는걸 통째로 웹 크롤링이라고 부르는 친구도 있었고 사람들마다 부르는 용어가 조금씩 다르다는 걸 느꼈다.

 

엄밀히 말하면 조금 다르니까, 최대한 명확히 나눠보자.

 

※ 웹 크롤링(web crawing) vs 웹 스크래핑(web scraping) vs 웹 파싱(web parsing)

"웹 크롤링"

: 웹 페이지에서 주어진 링크들을 쭉 따라가며 허용된 범위내에서 모든 내용을 긁어오는 것.

"웹 스크래핑"

: 웹 페이지에서 내가 원하는 부분만을 긁어오는 것.

"웹 파싱"

: 웹 페이지의 정보를 가져와서 가공하는 것

 

 

명확히 말하면, 지금 내가 하고 있는 것은 웹 스크래핑이라고 할 수 있을 것 같다.

 

주로 웹 스크래핑(or 웹 크롤링)에는

주로 BeautifulSoup, Selenium, Scrapy 이 세가지를 쓰는 것 같다.

 

"나도코딩"님 영상에선 "BeautifulSoup""Selenium"을 다루고 있다.

"Scrapy"에 대해서는 나중에 한번 혼자 공부해보고 포스팅해야겠다.

 

이번 글에선 BeautifulSoup에 대해서 적어보려고 한다.

 

BeautifulSoup은 HTML, XML의 정보를 뽑아내는 라이브러리로,

주로 requests 또는 urllib이라는 내장모듈을 통해 HTML을 가져와 BeautfulSoup으로 데이터를 뽑아낸다.

 

나의 경우엔, requests를 사용했다.

내가 BeautifulSoup을 이용한 웹 스크래핑에서 사용했던 모듈들에 대해서 한번 쭉 알아보자.

1. requests

# 웹 스크래핑을 하는 경우에 웹사이트에서 막는 경우 userAgency를 이용하여 해결 할 수 있다.

import requests

url = "https://june98.tistory.com"
headers = {"User-Agent":"각자 자신의 User-Agent"}

res = requests.get(url, headers = headers) 
res.raise_for_status() #status_code가 정상인지 체크

with open("nadocoding.html","w",encoding="utf8") as f:
    f.write(res.text)

 

일단은 당연히 터미널창에서 "pip install requests" 를 통해 모듈을 설치해야겠지(?)

 

가장 기본적인 모듈인 requests를 이용해 웹사이트의 데이터를 받아올 수 있다.

이때 주의해야할 점이 어떤 경우엔 웹 사이트에서 웹 스크래핑을 막는 경우가 있는데, 이때 get 메소드의 인자에 headers를 넣어줘야한다.

 

headers에는 User Agent값이 들어가고 자신의 User-Agent는 아래의 웹사이트에서 구할 수 있다.

(www.whatismybrowser.com/detect/what-is-my-user-agent

 

 

2. re (정규식)

import re

p = re.compile("ca.e") 
# . : 하나의 문자를 의미한다 ex) ca.e -> care, cafe, case etc...
# ^ ; 문자열의 시작을 의미 ex) ^de -> dequeue, delever, depend etc...
# $ : 문자열의 끝을 의미 ex) se$ -> case, base etc...

#m = p.match("case") # Match되는 경우
#n = p.match("cass") # Match되지 않는 경우 이때 에러가 발생

def print_match(m):
    if m:
        print("m.group():",m.group()) # 일치하는 문자열 반환
        print("m.string:",m.string) # 입력받은 문자열
        print("m.start():",m.start()) # 일치하는 문자열의 시작 index
        print("m.end():",m.end()) # 일치하는 문자열의 끝 index
        print("m.span():",m.span()) # 일치하는 문자열의 시작 / 끝 index
    else:
        print("매칭되지 않음")

""" 첫 번째 """
# m = p.match("caselala")
# print_match(m)

""" 두 번째 """
# n = p.match("lesscare care") # match: 주어진 문자열의 처음부터 일치하는지 확인하여 그 부분만 체크
# print_match(n)

""" 세 번째 """
# m = p.search("racale good care") # search : 주어진 문자열 중에 일치하는게 있는지 확인 
# print_match(m)

""" 네 번째 """
# lst = p.findall("careless good cafe") # findall: 일치하는 모든 것을 "리스트" 형태로 반환
# print(lst)

 

 

물론 여기서도 터미널창에 "pip install re"를 통해 re 모듈을 설치해야겠지? 

※ match

첫 번째와 두번째를 비교하여 출력 결과를 확인해보면,

첫 번째 출력결과
두 번째 출력결과

이를 통해, match라는 메소드는 문자열의 처음이 일치하는지 확인한다는 것을 확인 할 수 있다.

※ search

세 번째의 출력 결과를 확인해보면,

세 번째 출력결과

이를 통해, search라는 메소드는 문자열중에 일치하는 것이 있는지 확인하고 그것을 반환한다.

 

여기까지의 결과값을 통해, match와 search가 약간은 다르다는 걸 확인할 수 있다.

(match는 처음부터 일치하는지를, search는 그냥 일치하는 것이 있는지를 Check)

※ findall

네 번째의 출력 결과를 확인해보면,

네 번째 출력결과

이전의 match, search와는 다르게 findall은 일치하는 모든 것들을 리스트형태로 반환하는 것을 확인 할 수 있다.

 

3. BeautifulSoup

import requests
from bs4 import BeautifulSoup

url = "https://comic.naver.com/index.nhn"
res = requests.get(url)
res.raise_for_status()

soup = BeautifulSoup(res.text,"lxml") # res.text를 lxml이라는 parser로 파싱.


print(soup.title.get_text())
print(soup.a) # soup 객체는 전체 엘리먼트를 갖고 있는데 이때 첫 번째 a 엘리먼트를 반환한다.
print(soup.a.attrs) # 해당 엘리먼트의 속성을 가져온다.
print(soup.a["href"]) # 해당 엘리먼트의 href 속성 '값' 정보를 출력할 수 있다. 
print(soup.find("a", attrs={"class": "Nbtn_upload"}))
print(soup.find(attrs={"class": "Nbtn_upload"})) # class="Nbtn_upload"인 엘리먼트를 찾아줘.
print(soup.find("li",attrs={"class": "rank01"}).a["title"])

webtoon = soup.find_all("h6", attrs={"class": "title"}) # 이렇게 text를 통해서 해당 엘리먼트를 가져올 수 있다.

여기서도 당연히 "pip install beautifulsoup4"를 통해 해당 모듈을 설치해야한다.

 

여기서 자주 쓰이는 메소드는 find("태그", attrs={"속성": "속성값"}), find_all("태그", attrs={"속성": "속성값"}), get_text() 이다.

 

위의 코드를 통해 메소드 사용방법을 잘 기억해두자.

rank1 = soup.find("li", attrs={"class": "rank01"})
print(rank1.a.get_text())

# next_sibling. 다음 엘리먼트로 넘기는 메소드 rank1과 rank2 사이에 어떤 개행이 존재하기 때문에 두 번 넘겨야한다.
rank2 = rank1.next_sibling.next_sibling 
print(rank2.a.get_text())

# previous_sibling. 이전 엘리먼트로 넘기는 메소드
rank3 = rank2.previous_sibling.previous_sibling 
print(rank3.a.get_text())

# parent. 한 엘리먼트의 부모를 반환한다.
parent_rank1 = rank1.parent 
print(parent_rank1) 

# 위에서 개행을 처리하는 것을 조금 더 효율적으로 사용하기 위해 해당하는 태그만을 찾아서 반환한다.
rank4 = rank1.find_next_sibling("li") 
print(rank4.a.get_text())
rank5 = rank4.find_previous_sibling("li") # 이건 반대로 이전 것을 반환

# find_next_siblings 라는 method를 통해 li태그인 sibling을 모두 가져올 수 있다.
rank6 = rank1.find_next_siblings("li") 

그리고 알아두면 유용한 메소드중에 next_sibling, previous_sibling, parent, find_next_sibling(s) 등등이 있다.

(구글에 찾아보면 더 많은 메소드들이 있다.)

 

다음 글에서는 이 모듈들을 이용한 여러가지 예제들을 정리해보려고 한다.