python으로 web scraping

yuns_u·2021년 9월 21일
0

💛 web scraping, crawling

웹 크롤링과 웹 스크래핑에 대해 간단하게 정리해보자. 이 둘은 정보를 수집하기 위한 목적을 가진 두 방법론이라고 할 수 있다.

웹 크롤링(Web Crawling): 자동화된 웹 크롤러로 검색 엔진을 통해 웹에서 필요한것을 찾아 자료들을 다운받고 인덱스라는 목차를 만들고 내가 원하는 정보를 메타정보를 기준으로 추출하여 분리하는 작업.

웹 크롤링(web crawling)이란 자동화 봇(웹 크롤러)이 웹을 돌아다니면서 정보를 얻는 것이다. 웹에서 사이트들을 기어다니면서 필요한 정보들을 수집하는 것이다. 크롤러란 조직적, 자동화된 방법으로 월드와이드 웹을 탐색하는 컴퓨터 프로그램이다. 크롤링은 크롤러가 하는 작업을 부르는 말로, 여러 인터넷 사이트의 페이지(문서, html 등)를 수집해서 분류하는 것이다. 대체로 찾아낸 데이터를 저장한 후 쉽게 찾을 수 있게 인덱싱하는데 인터넷 상의 사이트를 인덱싱하는 목적을 가지고 있다.

웹 스크래핑(web scraping): 웹 사이트 상에서 원하는 부분에 위치한 데이터를 추출하여 수집하는 기술.

웹 스크래핑(web scraping)은 원하는 특정한 정보를 웹에서 추출하는 것이다. HTTP를 통해 웹 사이트의 내용을 긁어다 원하는 형태로 가공하는 것으로 크롤링도 스크래핑에 포함된다고 보는 견해도 있다.

🍏 requests 라이브러리

웹 스크래핑의 기초는 웹과 소통하는 것이다.

파이썬에는 웹과의 소통을 편하게 해주는 requests라는 라이브러리가 있다. 이 라이브러리는 파이썬에서 HTTP 요청을 보낼 때 표준으로 사용되는 경우가 많을 정도로 대중적으로 쓰인다. 특히 HTTP 요청을 간단한 메소드를 통해 실행할 수 있도록 짜여졌다는 것이 가장 큰 장점 중 하나이다. 따라서 사용자는 HTTP 요청이나 응답에 대한 고민은 줄이고 실제 서비스나 기능에 더욱 집중할 수 있다.

설치하기

$ pip install requests

요청보내기

requests 라이브러리를 설치 후 불러오기만 한다면 요청을 보낼 준비가 끝났다.

import requests

구글 홈페이지에 HTTP 요청을 보내보자.

requests.get('https://google.com')

사용한 메소드는 get인데 이 HTTP요청 메소드 또한 내용을 가져올 때 사용된다는 점에 유의하자.

정상적으로 연결되었다면 위 코드를 실행했을 때 HTTP 응답 객체가 리턴된다.

<Response [200]>

파이썬의 type으로 어떤 타입인지 확인을 해보면 아래와 같은 결과를 확인할 수 있다.

<class 'requests.models.Response'>

requests 라이브러리의 Response타입이다.

응답받기

requests 라이브러리르 통해 요청을 보냈다면 응답을 받게 된다.
앞서 요청을 보냈을 때 돌려받게 되는 객체는 Response 타입으로 requests 라이브러리에서 사용하는 응답 객체이다.

응답 객체에는 여러 종류들이 있다. 그 종류를 예시를 통해 살펴보자.

import requests

url = 'https://google.com'

resp = requests.get(url)

응답 성공 여부

가장 먼저 해야하는 것은 보낸 요청이 제대로 갔는지 확인하는 것이다. HTTP에서는 응답에 따라 상태 메세지와 번호를 부여한다.

HTTP 상태코드

200번이 나온다면 요청이 성공적이라는 뜻이므로 가장 중요하다고 할 수 있다.
이와 같은 상태 코드를 확인할 수 있는 코드는 아래와 같다.

resp.status_code

상태 코드에 따라 파이썬에서 if 구문으로 확인하는 것보다 raise_for_status메소드를 통해서 응답이 성공적이지 않은 경우에 에러를 일으키는 방법이 더 효율적일 수 있다.

requests 라이브러리에 있는 HTTPError를 통해서 에러가 발생했을 때 HTTP 에러만 따로 빼주는 작업을 한다면 아래처럼 표현할 수 있다.

import requests
from requests.exceptions import HTTPError

url = 'https://google.com'

try:
	resp = requests.get(url)
    resp.raise_for_status()
    
except HTTPError as Err:
	print('HTTP 에러가 발생했습니다.')
    
except Exception as Err:
	print('다른 에러가 발생했습니다.')

else:
	print('성공')

이런 방식으로 서버와 통신하여 웹 페이지를 일단 받아올 수 있어야한다.
만약 에러가 발생하거나 문제가 발생한다면 어떤 문제인지 확인하고 제대로 응답이 성공적일 때까지 확인하는 것이 크롤링의 기본이다.

응답 내용

웹 브라우저를 통해 웹 페이지에 접속하게 되면 보이는 HTML은 브라우저에서 뒷작업을 통해 보여지게 되는 결과물이다. 본질적으로 HTML, CSS 등의 문서 파일인 것이다.

따라서 requests 라이브러리를 활용해서 웹 브라우저가 받는 동일한 HTML 문서를 받을 수도 있다. 뿐만 아니라 서버에서 보내주는 데이터도 받을 수 있다.

일단 Response 객체에는 text라는 속성이 존재한다. 이 속성은 서버에서 받은 응답을 텍스트 형식으로 보여주게 된다. 원래 서버에서 받게 되는 응답의 데이터는 사실 바이트(bytes)로 받되는데 이를 사람이 알아보기 좋은 방식으로 텍스트로 변환해서 보여주는 것이다. 따라서 해당 데이터를 텍스트로 인지하기 위해서는 알맞게 디코딩 작업을 거쳐야 한다. 이 때 특정 인코딩 방식을 정하고 싶다면 encoding 속성을 설정해주면 된다.

이 디코딩 작업을 text 속성이 알아서 해주기 때문에 일반적으로는 큰 문제가 없을 것이다. resp.text를 통해 어떤 정보가 담겨져있는지 확인할 수 있다.

resp.text에 어떤 정보가 담겼는지 살펴보면 매우 긴 문자열이 나온다. 이는 하나의 html 파일이다.(자세히 보면 <!doctype html>로 시작해서 </html>로 끝난다.)

💛 파싱(parsing)

파싱(parsing)이란 문자열로 구성된 어떤 문서들(xml, html 등)에서 내가 원하는 데이터를 특정 패턴이나 순서로 추출하여 파이썬 등에서 쉽게 사용할 수 있도록 정보를 가공하는 것이다.

좀 더 컴퓨터 과학적인 개념으로 접근해보면 파싱이란 일련의 문자열을 의미있는 토큰(token)으로 분해하고 이들로 이루어진 파스 트리(parse tree)를 만드는 과정을 말한다.

인터프리터나 컴파일러의 구성 요소 가운데 하나로, 입력 토큰에 내제된 자료 구조를 빌드하고 문법을 검사하는 역할을 한다.

(컴파일러, 인터프리터는 둘 다 C나 자바같은 고레벨언어로 작성된 프로그래밍 언어를 기계어로 변환한다는 공통점을 가지고 있는 통역사의 역할을 하는 것이라고 일단 간단하게 이해하자.)

🍏 BeautifulSoup 라이브러리

웹 스크레이핑에서는 서버에 요청을 보내고 응답을 받는것이 시작이다.
이제 서버에서 돌려받은 응답내용을 파싱하고 정보를 얻어낼 때 자주 쓰이는 BeautifulSoup이라는 라이브러리를 사용하는 방법을 정리하고자 한다.

BeautifulSoup은 받아온 HTML 파일을 파싱하여 원하는 정보를 손쉽게 찾을 수 있도록 한다.

설치

$ pip install beautifulsoup4

기본 파싱

beautifulsoup4(이하 bs4) 라이브러리를 사용하는 방법을 정리해보자.

먼저 파싱할 문자열과 어떻게 파싱할 것인지 정한다.
기본적으로 사용할 수 있는 파서는 html.parser이다.

다음과 같은 방식으로 문자열로 된 html 파일을 넘길 수 있다.

import requests
from bs4 import BeautifulSoup

url = 'https://google.com'
page = requests.get(url)

soup = BeautifulSoup(page.content, 'html.parser')

위의 예처럼 requests라이브러리로 먼저 파싱할 페이지를 받아온 뒤에 내용물을 문자열로 변환한 page.content를 인수로 넘기고 어떻게 파싱할 것인지 정해야 한다. 현재 사용하고 있는 html.parser는 파이썬 기본 라이브러리에 포함되어 있기 때문에 별도로 설치하지 않아도 된다. 만약 XML 문서나 다른 html 파서(html5lib 등)을 사용하려면 따로 설치를 진행해야한다.

요소 찾기

파싱을 완료했다면 원하는 요소들을 찾아내야 한다. 찾고자 하는 요소가 어떤 특징을 가지고 있는지에 따라 그 요소를 찾아내는 방법은 달라진다. 이 포스트에서는 기본적인 id, class, tag 등의 특징들과 findfind_all 메소드를 통해 찾아내는 방법들을 살펴보고자 한다.

findfind_all

bs4에서 한 개의 요소를 찾을 때에는 find를, 여러 개의 요소들을 찾을 때에는 find_all을 사용하게 된다.

find와 같은 경우에는 조건에 일치하는 첫 번째 결과를 리턴하고 find_all은 조건에 일치하는 모든 결과를 리스트에 담아 리턴한다. 여기에서 얻게 되는 결과들은 기존에 bs4에서부터 파싱된 html문서이기 때문에 결과를 가지고 다시 find 혹은 find_all과 같은 메소드를 실행할 수 있다.

id 활용

id는 주로 한 번만 이용되기 때문에 주로 find를 사용한다.

예를 들어 id가 'dog'인 요소를 찾고 싶을 때에는 아래처럼 코드를 입력하여 찾을 수 있다.

soup.find(id='dog')

class 활용

class를 이용해 요소들을 찾을 때에는 class가 아닌, 뒤에 _가 추가된 class_를 사용한다. _가 추가되지 않으면 파이썬의 class로 인식되기 때문에 꼭 class_`를 입력해주도록 하자.

예를 들어 class가 'animal'인 요소를 찾고 싶을 때에는 아래처럼 코드를 입력하여 찾을 수 있다.

soup.find_all(class='animal')

이 코드에서 찾은 결과물들을 이용해서 다시 세부적으로 검색을 할 수 있다. 예를 들어 class에 animal이 있는 결과들을 가지고 다음과 같은 코드를 작성할 수 있다.

animal_elements = soup.find_all(class_ = 'animal')

for animal_el in animal_elements:
	animal_el.find(class_='fish')

tag 활용

class를 사용해서 찾을 수 있기도 하지만 상세하게 찾고자할 때에는 태그와 함께 조합하여 사용할 수 있다.

예를 들어 'animal'이라는 클래스가 'div' 태그에도 있고 'p' 태그에도 있다고 하자. 이 때 'div' 태그를 사용하는 요소를 가지고 올 때 아래와 같은 코드로 원하는 태그를 정해서 사용할 수 있다.

animal_div_elements = soup.find_all('div', class_='animal')

string 활용

특정한 문자열이 포함되어 있는 것을 정하고 싶을 때의 사용할 수 있다. 만약 'awesome'이라는 문자열이 포함된 요소를 찾고 싶다면 string 파라미터를 이용해 다음과 같은 코드를 사용할 수 있다.

soup.find_all(string='awesome')

이 방법의 장점이자 단점은 명시한 문자열을 그대로 찾는다는 것이다. 만약 대소문자나 띄어쓰기까지 완전히 동일한 문자열을 포함한 요소를 찾는 것이다. 즉, 엄격한 기준으로 요소를 찾는다고 할 수 있다.

이보다 느슨한 기준으로 찾는다면 파이썬의 익명 함수를 활용할 수 있다. 'awesome'이 대소문자 구분없이 들어가 있는 것을 찾고 싶다면 다음처럼 함수를 넘길 수 있다.

soup.find_all(string= lamda text: 'awesome' in text.lower())

string을 사용하면 사용자가 구체적으로 원하는 정보를 찾을 수는 있다. 하지만 각 요소의 .string 속성을 불러오는 것이기 때문에 요소가 아닌 문자열로 리턴이 된다. 따라서 하나의 요소로 받기 위해서는 태그도 같이 추가해주어야 한다. 아래의 예는 h3이라는 태그를 가지면서 awesome이라는 문자열을 가지고 있는 요소를 리턴하는 코드이다.

soup.find_all('h3', string='awesome')

정보 얻기

원하는 요소들을 선택했다면 이제부터는 정보를 얻어낼 수 있어야 한다.
기본적으로 text 속성을 이용해서 내부 문자를 얻어낼 수 있다.

예를 들어 html에서 'p' 태그 내부 글을 얻으려면 text 속성을 사용할 수 있다.

<p class='animal'> This is p-cat </p>
animal_el = soup.find('p', class_='animal')
animal_el.txt

이 결과는 'This is p-cat'이 나온다.

이렇게 추출된 결과에서 띄어쓰기를 없애고 싶다면 파이썬의 strip 메소드를 사용해서 정리해주면 된다.

profile
💛 공부 블로그 💛

0개의 댓글