웹 스크래핑-준비
파이썬을 공부하다 보면 웹 크롤링과 웹 스크래핑에 대해서 이야기 한다.
이 차이점는 무엇일까?
웹 크롤링
인터넷에는 수많은 웹사이트와 수많은 페이지들이 존재하고 이를 대상으로 필요한 정보를 직접 찾아 모으기란 번거롭고 시간이 오래걸리는 작업이다.
웹 크롤링은 이런 작업들을 자동으로 해주는 기술이다.
Crowling은 영어 뜻대로 기어다닌다는 의미로 모든 정보를 긁어 오는 것을 의미한다.
웹 스크래핑
웹 스크래핑은 이렇게 글을 전부를 뽑아오는게 아니라 원하는 정보만 골라서 추출해주는 것이다.
웹 스크래핑은 HTML을 가져와서 그 안에 원하는 정보들을 골라 내서 사용한다.
xpath
xpath(XML Path Language)는 HTML이나 XML 문서에서 원하는 요소를 정확하게 찾아내기 위한 경로 지정 언어이다.
웹 페이지의 구조가 나뭇 가지 처럼 여기저기 뻗어 나가 있을 때 원하는 나무의 가지를 하나 찾고 싶을때 이 위치를 정확하게 지정해주는 주소라고 보면 된다.
xpath에서 사용되는 문법에는 단순하게 아래와 같은 요소들이 존재한다.
- / : 루트부터 따라간다(절대경로)
- // : 어디든 상관없이 태그를 검색한다(상대경로)
- [@attr="값"] : 특정 속성값을 가진 태그를 찾는다
- [n] : n번째 요소 선택(1부터 시작)
- text() : 태그 안의 텍스트만 추출
- contains(@class, "abc") : class에 "abc"가 포함된 요소 찾기
예시를 보자면
<html>
<body>
<div>
<h1>제목</h1>
<p class="info">이것은 설명입니다</p>
</div>
</body>
</html>
- /html/body/div/h1 : <h1>태그를 root 부터 경로를 찾아간다
- //h1 : 문서의 전체에서 <h1>태그를 찾는다
- //p[@class="info"] : class가 info인 <p> 태그를 찾는다
- //div/p : <div>안의 <p>태그를 찾는다
이걸 우리가 쳐서 사용하는 경우는 없는것 같고 크롬내부에서 개발자도구를 열었을때
원하는 태그를 우클릭해서 Copy > Copy XPath 로 가져오면
//*[@id="container"]/div[8]/a
이렇게 붙여넣을 수 있도록 설정되어 있다.
여기서 Full XPath를 선택하면 축약되어 있는 형태가 아니라 그냥 절대 경로로 데이터를 가져온다.
/html/body/ntp-app//div/cr-most-visited//div/div[8]/a
이 XPath가 나중에 스크래핑을 할때 중요한 정보이기 때문에 이렇게 사용한다는 것을 알고 넘어가자.
requests
웹 스크래핑에서는 가장 먼저 웹 페이지의 HTML을 가져와야하기 때문에 이를 받아오기 위해서는 파이썬의 requests 라이브러리를 필수적으로 사용해야만 한다.
requests 라이브러리는 URL에 접속해서 HTML, 이미지, JSON 등 컨텐츠를 가져오는 역할을 한다.
그렇기에 파이썬의 pip를 사용해서 requests를 받아주자.
>> pip install requests
requests를 import 해주고 get메서드를 사용해서 url정보를 전달하면 응답으로 값을 받아온다.
import requests
res = requests.get("https://naver.com")
이렇게 받아온 응답에서 먼저 어떤 응답 코드가 왔는지를 확인해서 정상적으로 데이터를 받아왔는지를 확인할 수 있다.
print(res.status_code)
200이 나온거 보면 정상적인 응답을 받은 것이다.
이렇게 정상적으로 값을 받았을 경우 로직을 수행하기 위해서 아래와 같이 requests의 code중 ok라는 값과 status_code와 비교해주면 정상적으로 값을 받았을때의 로직을 추가할 수 있다.
if res.status_code == requests.codes.ok:
print("정상적으로 값을 받아왔습니다")
else:
print("비정상적인 응답입니다:", res.status_code)
여기서 추가로 res의 raise_for_status()라는 함수가 있는데
res.raise_for_status()
print("정상적으로 실행됩니다")
이 함수를 사용하면 정상적인 응답을 받았을때는 문제없이 아래 코드를 출력하는데 비정상적인 응답이 왔을 경우 해당 라인에서 에러를 출력시켜서 다음 코드를 실행하지 않도록 한다.
그래서 코드를
import requests
res = requests.get("https://naver.com")
res.raise_for_status()
이렇게만 작성해줘도
import requests
res = requests.get("https://naver.com")
if res.status_code == requests.codes.ok:
print("정상적으로 값을 받아왔습니다")
else:
print("비정상적인 응답 : ", res.status_code)
이것과 같은 역할을 하도록 할 수 있다.
이는 보통 표준적으로 사용하는 부분이니 그냥 쌍으로 같이 온다고 생각하면 된다.
이제 본문에 대해서 확인해보자면 먼저 res.text는 해당 페이지에서 받은 응답을 담고 있고 그 양을 len으로 출력해보면
print(len(res.text)) //293795
이렇게 30만에 가까운 글자를 가져온것을 볼 수 있다.
네이버는 메인 페이지 자체가 내용이 많으니까 다시 구글로 변경해서
import requests
res = requests.get("https://google.com")
res.raise_for_status()
with open("google.html", "w", encoding="utf-8") as f :
f.write(res.text)
html파일로 추출해서 보면
이렇게 html파일이 생성되고 이걸 한번 열어보면
이렇게 구글 홈페이지의 형태와 동일하게 값을 가져온다(스타일이나, 이미지는 가져오지 못함)
정규식
파이썬에서 정규식을 사용하기 위해서는 re라는 라이브러리를 import 해서 사용한다
정규식을 전체 다 보지는 않고 간단하게 사용방법이나 필요한 간단한 정보 정도를 확인해보고자 한다.
먼저 re를 임포트하고
import re
이걸 사용하는것은 아래와 같이 사용한다.
p = re.compile("정규식")
여기서 먼저 간단한 정규식 문법에 대해서 보자면
1. abcd, book, desk
2. ca?e
3. care, cafe, case, cave
4. caae, cabe, cace, cade
위와 같은 문자들이 존재할때 .(점)과 ^와 $에 대해서 확인해보자.
1. .(마침표)
.(마침표) : 하나의 문자를 의미
ca.e => ca로 시작하고 그 다음에 아무런 문자가 하나가 존재하며 다음 문자가 e일때
ex> case, cave, cafe, care ..
2. ^
^ : 문자열의 시작이 어떤 것일때
^de => 문자열의 시작이 de로 시작하는 단어
ex) desk, demage, destination..
3.$
$ : 문자열의 끝
se$ => 끝의 문자가 se로 끝나는 문자열
ex) case, base ..
이런 정규식을 아래와 같이 compile 함수의 전달인자로 전달하고
p = re.compile("ca.e")
match라는 함수를 사용해서 비교하려는 값을 전달하고 응답으로 받아온 객체의 group() 함수를 사용해주면
m = p.match("case")
print(m.group())
매칭된 문자열이 들어가 있게 된다.
매칭이 되지 않는다면 아무런 데이터도 들어가지 않게 된다.
여기서 문자열이 만약
import re
p = re.compile("ca.e")
m = p.match("caseasdfgh")
print(m.group())
이렇게 보내지더라도 시작부터 확인했을때 일치하는 부분이 시작점에 있다면 일치하는 문자열은 group에 담아준다.
추가로 search라는 함수의 경우는 모든 문자열 중에 일치하는 문자열이 존재한다면 그 문자열을 group에 저장한다.
import re
p = re.compile("ca.e")
m = p.search("test case")
print(m.group())
만약 match로 변경해서 확인해보면
m = p.match("test case")
m으로 반환하는게 없어서 에러를 발생시키는 것을 볼 수 있다.
- .match("정규식") : 문장이 '이렇게' 시작하는가?
- .search("정규식") : 문장 어딘가에 '이런 게' 있는가?
추가로 메서드에 대해서 알아보자면
1. .group() : 일치하는 문자열 반환
import re
p = re.compile("ca.e")
m = p.search("test case")
print(m.group()) #case
2. .string : 입력받은 문자열
import re
p = re.compile("ca.e")
m = p.search("test case")
print(m.string) # test case
3. .start() : 일치하는 문자열의 시작 index
import re
p = re.compile("ca.e")
m = p.search("test case")
print(m.start()) # 5
4. .end() : 일치하는 문자열의 끝 index
import re
p = re.compile("ca.e")
m = p.search("test case")
print(m.end()) # 9
5. .span() : 일치하는 문자열의 시작 / 끝 index
import re
p = re.compile("ca.e")
m = p.search("test case")
print(m.span()) # (5, 9)
**위 모든 함수들은 결국 매칭이 되지 않으면 사용할 수 없는 함수들이라는 점을 유의하자.
6. .findall("문자열") : 일치하는 모든것을 리스트 형태로 반환
import re
p = re.compile("ca.e")
lst = p.findall("cafe case")
print(lst) # ['cafe', 'case']
추가적인 부분은 필요할때 찾아서 더 공부하도록 하자.
User Agent
원하는 페이지에서 URL로 요청을 보냈을때 정상적이지 않은 응답을 반환하는 경우
import requests
res = requests.get("https://chatgpt.com/")
res.raise_for_status()
이럴때 활용하는 것이 User Agent이다.
https://www.whatismybrowser.com/detect/what-is-my-user-agent/
What is my user agent?
Every request your web browser makes includes your User Agent; find out what your browser is sending and what this identifies your system as.
www.whatismybrowser.com
위 url로 들어가보면 우리가 브라우저에서 접속할때 사용하는 user agent 정보이다.
이는 접속하는 브라우저에 따라서 내용이 다를 수 있다.
서버에서는 이 정보를 가지고 권한을 판단하는 경우가 있다.
그래서 이 정보를 우리가 request의 헤더에 담아 요청이 가능하다.
url과 header를 따로 변수에 저장해주고
url = "https://chatgpt.com/"
headers = {"User-Agent":"Mozilla/5.0 ~~~"}
이걸 get 메서드에 url은 그냥 넣고 headers는 옵션을 지정해서 값을 넣어주자.
res = requests.get(url, headers=headers)
이러면 파이썬으로 우리가 요청을 보낼 때 브라우저에서 요청하는것과 동일하게 요청하게 된다.
#이렇게 해도 요청이 안되는 경우도 있긴하니 잘 알아보고 추가적인 해결 방안을 강구하는 것이 좋다.#