🔗 영상 링크 : https://youtu.be/yQ20jZwDjTE
안녕하세요. 민몽입니다:>
오늘은 저번에 추천드렸던 '나도코딩'님의 유튜브 강의 중 웹스크래핑에 대한 포스팅을 써보려고 합니다.
영상 하나로 직접 원하는 데이터를 스크래핑 해올 수 있을 정도로 잘 정리된 영상이어서 혹시 프로젝트에 웹 스크래핑으로 데이터를 직접 수집하셔야 한다면 추천하는 강의에요.
학부 수업에서 웹스크래핑 파트 접해본 적은 있지만 자세한 설명은 안 해주셔서 왜 이렇게 하는거지 ..? 얜 무슨 기능이지 ..? 하다가 다음에 제대로 공부해봐야지 했었는데 이번 기회에 제대로 배울 수 있었습니다! 특히 정규표현식 외우는 게 어렵고 귀찮기도 했는데 직접 실습하면서 해보니까 더 잘 이해되서 좋았어요.
저도 공모전에서 직접 데이터를 수집해야 할 일이 있었는데 크롤링이나 스크래핑을 한번도 해보지 않은 팀원과 이 영상으로 스터디를 진행했었고 직접 코드를 짜서 스크래핑을 할 수 있을 정도로 배울 수 있었어요 :)
저도 처음엔 웹 크롤링 ? 웹 스크래핑 ? 뭐가 다른거지 .. 많이 헷갈렸어요. 보통 웹 크롤링이라는 단어를 많이 접하실텐데요
먼저 웹 스크래핑과 웹 크롤링의 차이점에 대해 알아봤습니다 !
- 웹 스크래핑 : 웹 페이지에서 원하는 부분만 가져오는 것
- 웹 크롤링 : 웹 페이지가 주어졌을 때, 그 페이지 내부의 링크를 따라가면서 모든 내용을 가져오는 것
웹 페이지 내에서 전체 내용을 가져오느냐, 원하는 특정부분만 가져오느냐가 둘의 차이점이라고 할 수 있겠네요.
다음은 웹의 구성요소입니다.
웹의 구성요소 3가지는 아래와 같아요.
- HTML : 전체적인 구조 (뼈대)
- CSS : 인테리어 ( 예쁘게 )
- JS (JavaScript) : 웹을 살아있게 만드는 것 ( 동적 )
그 중 웹 스크래핑 시 필요한 HTML에 대해 알아보도록 하겠습니다.
HTML(Hyper Text Markup Language)
<html/> <!--하나의 element-->
<html></html> <!--여는태그 & 닫는태그 (/로 시작)-->
<html>
< > <!--홈페이지 제목, HTML 문서 선행작업-->
<meta charset="utf-8">
<title>나도코딩 홈페이지</title>
</head>
<body> <!--웹페이지 본문-->
<input type="text" value = "아이디를 입력하세요"> <!--input 태그 : /없이 닫아도 괜찮음-->
<input type="password"> <!--*로 표시-->
<input type="button" value ="로그인"> <!--버튼형식의 input element 생성-->
<a href = "http://google.com">구글로 이동하기</a> <!--링크-->
</body>
</html>
하나의 element는 태그 + 속성 으로 이루어져 있어요. 웹 스크래핑을 위해서는 이정도 구조만 이해하면 되고
더 자세한 내용은 w3school 에서 HTML 공부 할 수 있습니다 !
X-Path는 HTML에서 어떤 엘리먼트를 지칭하는지 명확하게 하기 위해서 사용해요.
/학교/학년/반/학생[2]
//*[@학번="1-1-5"]
/html/body/div/span/a...
//*[@id="login"]
<학교 이름="나도 고등학교">
<학년 value = "1학년">
<반 value = "1반">
<학생 value="1번" 학번="1-1-1">이지현</학생>
<학생 value="2번" 학번="1-1-2">최다솔</학생>
<학생 value="3번" 학번="1-1-3">강채영</학생>
<학생 value="4번" 학번="1-1-4">지정민</학생>
<학생 value="5번" 학번="1-1-5">이지현</학생>
</반>
<반 value = "2반"/>
<반 value = "3반"/>
<반 value = "4반"/>
</학년>
<학년 value = "2학년"/> ... 3반 유재석 <...>
<학년 value = "3학년"/>
</학교>
Requests 함수는 HTML 문서 정보 가져오는데 사용하는 함수입니다.
아래와 같이 활용할 수 있어요
import requests
# res = requests.get("http://naver.com")
res = requests.get("http://nadocoding.tistory.com")
print("응답코드 : ", res.status_code) # 200 : 정상 403 : 접근 권한 없음
if res.status_code == requests.codes.ok:
print("정상입니다.")
else:
print("문제가 생겼습니다. [에러코드 ", res.status_code, "]")
# if문 대신 사용 가능
res.raise_for_status() # HTML 문서를 가져오지 못한 경우 에러 발생 & 프로그램 종료
print("웹 스크래핑을 진행합니다")
- status_code를 통해서 정상인지 아닌지 확인 할 수 있고 기본적으로 아래처럼 쌍으로 사용합니다.
res = requests.get("http://nadocoding.tistory.com")
res.raise_for_status()
print(len(res.text))
print(res.text)
가져온 HTML 정보를 파일로 만들고 싶다면 아래의 코드를 활용할 수 있어요.
with open("mygoogle.html","w", encoding = "utf8") as f:
f.write(res.text)
HTML 정보 내에서 원하는 부분만 가져오고 싶다면 정규식을 활용할 수 있어요 !
import re
#ca?e 검색 using 정규식
p = re.compile("ca.e")
# . : 하나의 문자 의미
# ^ : 문자열의 시작
# $ : 문자열의 끝
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("careless")
# match : 주어진 문자열의 처음부터 일치하는지 확인
# group : 매치된 경우 매치된 문자열 출력 & 매치되지 않으면 에러 발생
m = p.search("good care")
# search : 주어진 문자열 중에 일치하는게 있는지 확인
lst = p.findall("good care cafe") #findall : 일치하는 모든 것을 리스트 형태로 반환
- p = re.compile("원하는 형태")
- m = p.match("비교할 문자열") : 주어진 문자열의 처음부터 일치하는지 확인
- m = p.search("비교할 문자열") : 주어진 문자열 중에 일치하는게 있는지 확인
- lst = p.findall("비교할 문자열") : 일치하는 모든 것을 "리스트" 형태로 반환
- 원하는 형태 : 정규식
- . : 하나의 문자 의미
- ^ : 문자열의 시작
- $ : 문자열의 끝
정규식에 대한 추가 공부 자료는 w3schools 파이썬 RegEx Or python re docs 문서에서 확인하실 수 있습니다.
네이버같은 포털 사이트는 사용자의 헤더 정보를 통해 pc버전을 줄 것인지, 모바일 버전을 줄 것인지를 결정해요. 하지만 웹 스크래핑의 경우 사람이 접속하지 않기 때문에 사이트의 부하를 가져올 수 있어 접속이 차단되는 경우가 발생해요. 이러한 문제점을 해결하기 해서는 아래 코드를 사용해 User agent 값을 넘겨주면 해결할 수 있습니다.
import requests
# 실제 웹 브라우저에서 접속했을때와 동일한 결과 얻을 수 있음.
headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36"}
url = "http://nadocoding.tistory.com"
res = requests.get(url, headers = headers)
res.raise_for_status()
with open("nadocoding.html","w",encoding = "utf8") as f :
f.write(res.text)
- user_agent 정보 확인 사이트 : https://www.whatismybrowser.com/detect/what-is-my-user-agent
드디어 직접 웹 페이지에서 실습해 볼 시간입니다 !
Beautifulsoup은 스크래핑 패키지로 웹 스크래핑 시 가장 보편적으로 사용하는 패키지에요.
아래 코드를 통해 네이버 웹툰 사이트에서 여러가지 정보를 가져올 수 있어요.
# lxml : 구문 분석 phaser
import requests
from bs4 import BeautifulSoup
url = "https://comic.naver.com/webtoon/weekday"
res = requests.get(url)
res.raise_for_status()
soup = BeautifulSoup(res.text, "lxml") # 가져온 html 문서 -> lmxl 파서를 통해서 beautiful soup 객체로 만듦.
print(soup.title)
print(soup.title.get_text())
print(soup.a) # soup 객체에서 첫번째로 발견되는 a태그 정보 출력
print(soup.a.attrs) # a element의 속성 , 딕셔너리로 반환
print(soup.a["href"]) # a element의 href 속성 '값' 정보 출력
# 대상 페이지에 대해 잘 모르는 경우
print(soup.find("a", attrs = {"class":"Nbtn_upload"} )) # class = "Nbtn_upload" 인 a element 를 찾아줘
# 인기급상승 1~10위 가져오기
rank1 = soup.find("li", attrs = {"class":"rank01"})
print(rank1.a.get_text())
rank2 = rank1.next_sibling.next_sibling # next_sibling : 다음 형제 태그로 이동
rank3 = rank2.next_sibling.next_sibling
print(rank3.a.get_text())
rank2 = rank3.previous_sibling.previous_sibling # previous_sibling : 이전 형제 태그로 이동
print(rank2.a.get_text())
print(rank1.parent) # 부모 태그로 이동
rank2 = rank1.find_next_sibling("li") # 해당 기준을 만족하는 다음 태그 찾기, 개행 상관 X
print(rank2.a.get_text())
rank3 = rank2.find_next_sibling("li")
rank2 = rank3.find_previous_sibling("li")
print(rank2.a.get_text())
rank1.find_next_siblings("li") # 해당 기준 만족하는 모든 형제 태그 찾기
webtoon = soup.find("a", text = "독립일기-시즌2 47화 결막염") # 텍스트에 해당하는 a 태그 찾기
print(webtoon)
import requests
from bs4 import BeautifulSoup
url = "https://comic.naver.com/webtoon/weekday"
res = requests.get(url)
res.raise_for_status()
soup = BeautifulSoup(res.text, "lxml")
# 네이버 웹툰 전체 목록 가져오기
cartoons = soup.find_all("a", attrs = {"class":"title"})
# 조건(class 속성이 title인 모든 a element)에 해당하는 모든 element 찾기
for cartoon in cartoons:
print(cartoon.get_text())
가우스전자 제목 , 링크 , 평점 가져오기
import requests
from bs4 import BeautifulSoup
url = "https://comic.naver.com/webtoon/list?titleId=675554"
res = requests.get(url)
res.raise_for_status()
soup = BeautifulSoup(res.text, "lxml")
cartoons = soup.find_all("td", attrs ={"class":"title"})
title = cartoons[0].a.get_text()
link = cartoons[0].a["href"]
print(title)
print("https://comic.naver.com"+link)
# 만화 제목 + 링크 가져오기
for cartoon in cartoons:
title = cartoon.a.get_text()
link = "https://comic.naver.com"+ cartoon.a["href"]
print(title, link)
# 평점 구하기
total_rates = 0
cartoons = soup.find_all("div", attrs={"class":"rating_type"})
for cartoon in cartoons:
rate = cartoon.find("strong").get_text()
print(rate)
total_rates += float(rate)
print("전체 점수 : ", total_rates)
print("평균 점수 : ", total_rates / len(cartoons))
#참고 문서 | beautifulsoup Documentation
# 쿠팡 노트북 검색 결과 가져오기
import requests
import re
from bs4 import BeautifulSoup
headers = {"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/98.0.4758.82 Safari/537.36"}
for i in range(1,6):
#print("페이지 : ", i)
url = "https://www.coupang.com/np/search?q=%EB%85%B8%ED%8A%B8%EB%B6%81&channel=user&component=&eventCategory=SRP&trcid=&traid=&sorter=scoreDesc&minPrice=&maxPrice=&priceRange=&filterType=&listSize=36&filter=&isPriceRange=false&brand=&offerCondition=&rating=0&page={}&rocketAll=false&searchIndexingToken=1=5&backgroundColor=".format(i)
res = requests.get(url, headers = headers)
res.raise_for_status()
soup = BeautifulSoup(res.text, "lxml")
items = soup.find_all("li", attrs={"class":re.compile("^search-product")})
#print(items[0].find("div", attrs = {"class":"name"}).get_text())
for item in items:
# 광고 제품은 제외
ad_badge = item.find("span", attrs = {"class":"ad-badge-text"})
if ad_badge:
#print(" < 광고 상품 제외힙니다 >")
continue
# 제품명
name = item.find("div", attrs = {"class":"name"}).get_text()
# 레노버 제품 제외
if "레노버" in name:
#print(" < 레노버 상품 제외합니다 >")
continue
# 가격
price = item.find("strong", attrs = {"class":"price-value"}).get_text()
# 리뷰 100개 이상, 평점 4.5 이상 되는 것만 조회
# 평점
rate = item.find("em", attrs = {"class":"rating"})
if rate:
rate = rate.get_text()
else:
#print(" < 평점 없는 상품 제외합니다. >")
continue
# 평점 수
rate_cnt = item.find("span", attrs = {"class":"rating-total-count"})
if rate_cnt:
rate_cnt = rate_cnt.get_text()[1:-1]
else:
#print(" < 평점 수 없는 상품 제외합니다.>")
continue
link = item.find("a", attrs={"class":"search-product-link"})["href"]
if float(rate) >= 4.5 and float(rate_cnt) >= 100:
print(f"제품명 : {name}")
print(f"가격 : {price}")
print(f"평점 : {rate}점 ({rate_cnt}개)")
print("바로가기 : {}".format("https://www.coupang.com" + link))
print("-"*100) # 줄긋기
# 다음 영화 이미지 다운로드
import requests
from bs4 import BeautifulSoup
for year in range(2015,2020):
url = "https://search.daum.net/search?w=tot&q={}%EB%85%84%EC%98%81%ED%99%94%EC%88%9C%EC%9C%84&DA=MOR&rtmaxcoll=MOR".format(year)
res = requests.get(url)
res.raise_for_status()
soup = BeautifulSoup(res.text, "lxml")
images = soup.find_all("img", attrs = {"class":"thumb_img"})
for idx, image in enumerate(images):
#print(image["src"])
image_url = image["src"]
if image_url.startswith("//"):
image_url = "https:" + image_url
print(image_url)
image_res = requests.get(image_url)
image_res.raise_for_status()
with open("movie_{}_{}.jpg".format(year,idx+1), "wb") as f:
f.write(image_res.content)
if idx >= 4 : # 상위 5개 이미지까지만 다운로드
break
여기까지 실습을 마치셨다면 위 코드들을 참고해서 원하는 정보를 직접 스크래핑 해보시는 걸 추천해요.
10번 코드를 따라 치는 것보다 직접 처음부터 끝까지 시도해보는 것이 오래 기억에 남고 중간중간 에러를 해결하는 과정에서도 배우게 되는 점들이 많았어요 !
다음에 프로젝트 했던 글을 정리하면서 직접 티켓 예매처에서 스크래핑 해봤던 얘기도 써보도록 할게요 ㅎ-ㅎ
'데이터 공부 > Python' 카테고리의 다른 글
[백준] 1874. 스택수열 (0) | 2024.08.08 |
---|---|
[Git] git push 에러 / git pull (0) | 2023.01.12 |
[나도코딩] 파이썬 기본편 6-11 (0) | 2023.01.10 |
[나도코딩] 파이썬 기본편 1-5 (0) | 2023.01.10 |
[인프런] 예제로 공부하는 Python 100 문제풀이 (0) | 2023.01.08 |