본문 바로가기
Python , Pip/Python 자동화, Macro

Python 이용한 Web Scraping 방법 # requests beautifulSoup

by Developer88 2022. 5. 7.
반응형

오늘은 Python의 request 와 BeautifulSoup 모듈을 이용해서,

WebScrapping을 하는 방법에 대해서 정리해 보도록 하겠습니다.

 

1. Web Scraping과 작업순서

1-1. Web Scraping

Web Scraping이라고도 하구요. web data extraction이라고도 합니다.

웹사이트들로부터 데이터를 추출해내는 것을 의미하는데요.

요즘은 사이트를 검색하면 사이트의 프리뷰도 미리 검색사이트에서 볼 수 있습니다.

이것도 다 Web Scraping해서 얻은 데이터를 기반으로 하는 것 이지요.

 

1-2. 작업순서

WebScraping은 모듈사용방법을 읽히는 것도 중요하겠지만,
그보다도 Scraping할 페이지를 분석하고 어떻게 읽어올지를 파악하는 것이 중요합니다.

 

만약 특정 게시판이나 상품리스트를 읽어온다고 가정해 보겠습니다.

이것을 읽어올 때, 페이지별로 넘겨서 볼 수 있도록 구성되어 있는 것과, 스크롤하는 것이 있을 텐데요.

 

페이지별로 넘기는 pagenation이 적용된 페이지라면,

각 페이지별로 링크주소가 다를 것이고, 그것의 패턴을 알아내는 것부터 해야하겠지요.

이렇게, 스크래이핑하는 페이지에 대해서어떻게 query해야 하는지를 패턴등을 알아내고 나서,

필요한 모듈들을 설치해서 활용하는 방식으로 작업하게 됩니다.

 

2. WebScraping에 필요한 모듈 설치

WebScraping을 파이썬으로 구현하기 위해서는 request와 beautifulSoup가 필요합니다.

이들의 설치 방법에 대해서 알아보겠습니다.

2-1. request 

request는 http호출을 하기위한 파이썬 모듈입니다.

request 모듈을 설치하기 위해서 아래 명령어를 터미널에서 입력해면 됩니다.

 

python -m pip install requests

 

2-2. beautifulSoup 

beautifulSoup라는 모듈은 HTML의 특정한 element를 가져오기 위해 필요한 모듈입니다.

scraping하면 떠오르게 되는 핵심 모듈이지요.

 

아래 명령어로 beautifulSoup패키지를 설치해 주면 됩니다.

 

python -m pip install beautifulsoup4

 

아래에서는 본격적으로 Scraping을 해보기 전에,

설치했던 request와 BeautifulSoup의 기본적인 사용법에 대해서 알아보겠습니다.

 

3. Request로 웹페이지 받아오기

3-1. 기본 사용법

먼저 request 모듈을 사용하기 위해서 아래와 같이 import 해 주어야 합니다.

 

import requests

 

웹페이지에 접속하기 위해서,

아래와 같이 requests.get()함수를 사용하기만 하면되는데요.

인자로 url을 넣어주면 됩니다.

text로 해당 데이터들의 text들만 접근할 수 있습니다.

 

 

 

3-2. 최종 url과 status 코드 확인

url이 원하는데로 잘 만들어졌는지 확인하기 위해서는 아래 코드를 추가해서 print 해 보면 됩니다.

 

print(r.url)

 

요청이 정상적으로 들어가기 위해서, response code를 보고싶을수도 있는데요.

아래와 같이 'status_code'로 접근해서 볼 수 있습니다.

200을 받는다면 정상적으로 요청이 들어갔다는 것을 알 수 있겠지요. 

 

r.status_code

 

3-3. post request

get request만 보았는데요. 

사이트에 따라서는 POST request를 해 주어야 할 때도 있습니다.

이럴 경우는 아래와 같이 함수 명령어만 바꾸어 주면 됩니다.

 

r = requests.post('https://httpbin.org/post', data={'key': 'value'})

 

3-4. HTTP Header 추가

어떤 경우에는 특정한 HTTP header를 추가해 주어야 하는데요.

이럴 경우는 아래와 같이 추가해주면 됩니다.

 

url = 'https://api.github.com/some/endpoint'
headers = {'user-agent': 'my-app/0.0.1'}

r = requests.get(url, headers=headers)

 

3-5. Cookies

어떤 웹사이트는 특정한 cookie값을 요구하기도 합니다.

아래와 같이 cookies 값을 dictionary형태로 넣어주시면 됩니다.

 

url = 'https://httpbin.org/cookies'
cookies = dict(cookies_are='working')

r = requests.get(url, cookies=cookies)

 

어떤 경우 서버에서 내려주는 Cookie값을 받아와야 할 때도 있는데요.

아래와 같이 cookies값에 접근할 수 있습니다.

 

url = 'http://example.com/some/cookie/setting/url'
r = requests.get(url)

r.cookies['example_cookie_name']
'example_cookie_value'

 

이상으로 request를 사용하는 방법에 대해서 간단히 알아보았는데요.

 

웹스크레이핑이라는 이런 HTTP요청을 통해서 받은 많은 HTML 엘리먼트중,

원하는 데이터가 들어간 element만 가져오는 작업을 하는 것 입니다.

이 때 필요한 것이 beautifulSoup라는 모듈인데요.

아래에서 사용법을 보도록 하겠습니다.

 

4. BeautifulSoup 기본 사용법

4-1. 기본 사용법

위에서 본것같이 request모듈을 이용해서 html데이터를 가져왔다면,

이것을 soup객체로 변환해서 원하는 element를 찾을 수 있습니다.

 

soup = BeautifulSoup(r.text, 'html.parser')

 

아래는 beautifulSoup공식사이트에 나와있는 예제인데요.

soup객체에서 어떻게 해당태그나, 해당태그에 해당하는 요소들을 가져오는지 볼 수 있습니다.

title태그나 name 및 string등을 아래와 같이 쉽게 접근해서 추출할 수 있습니다.

 

 

4-2. attribute 가져오기

위에서도 잠깐 나왔는데요. 태그안의 attribute도 쉽게가져올 수 있습니다.

예를 들어,a태그의 href attribute만 가져올 경우도 많은데요.

 

 

이럴 때는 아래와 같이 '[ ]' 키워드로 attribute에 접근해주면 됩니다.

 

a['href']
img['src']

 

 

 

 

참고로 아래와 같이 특정한 attribute 을 검색하는 방법도 있는데요.

참고해두면 좋습니다.

 

 

4-3. find_all

많이 사용하게 되는 find_all()함수의 경우 아래와 같이 사용할 수 있는데요.

해당하는 tag를 모두 찾아줄 수 있습니다.

 

 

tag안의 attribute에 대해서 아래와 같이 keyword와 value를 넣어서 검색해서 찾을수도 있습니다.

 

 

보통은 class나 id로 해당요소를 찾는 경우가 많습니다.

class보다는 중복이 되지 않는 id가 찾는데 유리하기는 하겠지요.

 

soup.find_all('div', class_ = "news")
soup.find_all('div', id = "bluer")

 

 

css를 사용해서 검색할 때, 아래와 같이 함수를 인자로 넣어서 검색할 수 있습니다.

 

 

 

아래와 같이, 해당 attribute이 있는 tag만 검색할 수도 있습니다.

 

 

4-4. find_all() 과 recursive옵션

영어로는 nested라고 하는데요.

아래와 같이 html요소안에 같은 태그가 들어가 있는 경우가 있는데요.

find_all로 하면 자손태그까지 모두 가져와 버릴 것 입니다. 

아래 녹색으로 되어있는 직계자손들만 가져오고 싶을 때는 어떻게 해야할까요?

 


<div>
<p>이름(첫번째 직계자손)
     <p>성씨 (첫번째 자손의 자손)</p>
</p>
<p>나이 (두번째 직계자손)</p>
</div>

 

 

이럴 때는 아래와 같이, 'recursive = false' 를 인자로 넣어 주기만 하면 됩니다.

그럼, 녹색글자 부분만 검색해서 가져오게 됩니다.

 

first_name, age = soup.find_all('p', recursive = False)

 

4-5. 해당 요소 내 모든 text가져오기(get_text)

parsing을 하다보면, HTML태그안에 텍스트와 마크업태그등이 섞여서 들어가 있는 경우들이 존재하는데요.

이럴 때 유용한 것이 'get_text()'함수입니다.

태그를 제외한 모든 텍스트들을 모두 가져와 줍니다.

아래와 같이, 텍스트와 mark태그가 섞여들어간 경우에도 유용합니다.

 

 

 

아래는 공식문서에 나온 get_text()의 사용법인데요.

soup객체에 get_text()함수를 사용해 주기만 하면 됩니다.

 

 

인자로 아래와 같은 옵션을 줄 수도 있습니다.

특히 추출된 텍스트가 공백등이 존재할 경우 strip=True 옵션은 유용합니다.

 

soup.get_text(strip=True)

 

 

 

4-6. CSS Selector

css selectors를 사용할 수도 있습니다.

jquery를 사용해 보신분이라면 익숙하실 텐데요.

 

아래와 같이 class명이 'test_class'이고  'test_id'라는 id인 태그안에,

자식인 p태그가 있다고 가정해 보겠습니다.

 

<div class = 'test_class' id = 'test_id'>
	<p>여기를 찾아야 함</p>
</div>

 

그럼 아래와 같이 찾아낼 수 있습니다.

클래스는 '.'(dot)을 앞에 붙여야 하구요. ID는 '#'을 앞에 붙여주어야 합니다.

둘 중 어느것으로든 찾을 수 있다면 괜찮습니다.

보통은 id가 유니크하므로, 둘중 골라야 한다면 id가 더 낫다고 생각합니다.

중요한 것은 자식에 접근할 때, 한간을 띄고 해당 자식의 태그요소를 아래와 같이 넣어주면 됩니다.

 

soup.select('.test_class p')
soup.select('#test_id p')

 

이외에도 아래와 같은 방법들로 element를 쉽게 찾을 수 있습니다.

 

 

크롬을 사용하면 selector를 찾기가 정말 쉬운데요.

inspect 차이 띄워져 있는 상태에서,

원하는 태그에서 우측마우스 버튼을 눌러서 컨텍스트메뉴를 열어주구요.

아래와 같이 Copy> Copy selector를 선택해 주면 됩니다.

그럼, selector가 카피가 되는데요. 붙여넣기해서 사용해주면 됩니다.

 

 

 

그럼 아래와 같이 selector가 나오게 되는데요.

이를 활용해서 해당 요소를 찾아낼수도 있습니다.

어떻게 찾아야할지 모를때 도움이 되겠네요.

 

#contentarea_left > div.box_type_m > table.type_1 > tbody > tr:nth-child(7) > td:nth-child(3)

 

이외에도 css selector를 찾아주는 여러가지 chrome extension 프로그램들도 있으므로,

이를 활용하는 것도 좋습니다.

대부분은 find()나 find_all() 로 충분하구요.

복잡한 트리구조를 가지고 있을 때는, selector로 접근하는 것도 방법이 되겠습니다.

 

Request와 BeautifulSoup의 사용법에 대해서 정리해 보았는데요.

이제 실제로 스크레이핑을 해 보도록 하겠습니다.

 

5. BeautifulSoup 이용해서 실제 스크레이핑 하기

5-1.  가져올 페이지 패턴 분석

먼저 먼저 어떤 페이지를 가져올지 정해야 하는데요.

저는 네이버의 산업분석 리포트를 가져오도록 하려고 하는데요.

게시판의 3페이지까지 열어서 스크레이핑 해 올려고 합니다.

 

 

분석할 요소를 알아보기 위해서는 크롬브라우저에서 해당 요소에 마우스를 가져간 다음,

우측마우스버튼을 눌러 나오는 컨텍스트 메뉴에서 'inspect'를 선택해주면 됩니다.

저는 게시판 페이지번호가 나온 '1|2|3.......맨뒤' 부분을 inspect 해 보겠습니다.

 

 

inspect 해 보니, 마지막에 페이지번호만 다르게 붙이는 방식으로 url link가 작성되어 있습니다.

'...industry_list.naver?&page=1'

게시판의 페이지가 표시된 html엘리먼트를 추출해서,

이제 게시판 각 페이지들의 url주소를 요청한 다음,

해당 페이지들의 정보를 한곳에 담으면 스크레이핑이 될 것 같습니다.

 

5-2. 모듈 import

이제 필요한 모듈들을 import 해 주어서 작업을 시작해 보겠습니다.

먼저, requests모듈과 beautifulSoup 모듈을 아래와 같이 import해 주어야 합니다.

 

import requests
from bs4 import BeautifulSoup

 

5-3. request 와 파싱

다음으로 아래와 같이 requests의 get()함수를 이용해서 text를 가져옵니다.

 

html = requests.get('url주소').text

 

이제 가져온 html텍스트를 BeautifulSoup를 이용해서 파싱을 해 주어야 하는데요.

BeautifulSoup()안에는 인자를 두개 넣어주어야 합니다.

 

 

첫번째는 파싱할 마크업이고, 두번째는 파싱할 종류인데요.

저희는 html이니 'html.parser'를 넣어줍니다.

 

 

 

print해 보면, 아래와 같이 html요소만 추출되는 것을 볼 수 있는데요.

이제 원하는 엘리먼트만 골라서 추출 해 내는 작업을 해야하겠지요.

 

 

5-4. 원하는 html element 페이지 네비게이션 번호 파싱

원하는 위치인 페이지번호가 적인 element 를 알아내야 하는데요.

크롬에서 원하는 요소를 선택한 후 오른쪽 마우스버튼으로 inspect를 해 주면 쉽게 html element의 구조를 쉽게 볼 수 있습니다.

'Nnavi'라는 클래스를 가진, table엘리먼트를 먼저 가져오도록 하겠습니다.

 

 

이제 아래와 같이 추출된 것을 볼 수 있는데요.

 

 

연습이므로, 이 중 3개의 a태그들만 리스트에 저장하도록 하겠습니다.

 

 

5-5. 해당페이지들 여러번 호출하여 return 하기

이제 'page_list'에 담긴 페이지들을 request로 호출해 준다음,

다시 beautifulSoup로 파싱해주면 되겠습니다.

 

 

 

결과를 출력해보면 다음과 같은데요.

파싱이 잘 된 것을 볼 수 있구요. 잘못 파싱되어서 no strings found로 나오는 것도 없는 것을 확인하였습니다.

 

 

재사용을 위해서 아래와 같이 코드를 함수로 분리해 주었구요.

dictionary타입으로 return되도록 해 주었습니다.

이렇게 return된 값을 array에 넣어주거나 데이터베이스에 넣어주면 한번에 자료들에 접근할 수 있겠지요.

 

 

이제 아래와 같이 여러번 호출해 주면 되겠습니다.

 

 

 

이제 reports에 리스트로 dictionary데이터들이 저장된 것을 볼 수 있습니다.

 

6. 무한스크롤(infinite Scroll) 스크레이핑

위 정도의 지식만으로도 상당히 많은 사이트들의 정보를 크롤링할수 있는데요.

문제는 pinterest와 같은 무한 스크롤 방식의 페이지들입니다.

 

6-1. 무한스크롤에 대한 이해

이 문제를 해결하기 위해서 알아야 할 것은,

무한스크롤시에 구현방법에 대해서 알아두는 것이 좋은데요.

JS로 직접구현하던, 라이브러리를 이용하던,

브라우저상에서 스크롤이 아래부분에 도착했다는 이벤트를 감지하면 호출하는 Callback함수를 구현합니다.

 

이벤트가 발생해서 Callback이 되면, 화면에 더욱 많은 아이템들을 서버에 요청하는데요.

정리하면, 아래와 같은 순서로 구현한다는 것 인데요.

  1. 사용자가 스크롤 아래부분에 도착했다는 이벤트 감지
  2. Callback함수가 호출되어서 
  3. 다음 페이지에 해당하는 컨텐츠를 DB에서 로딩해서 현재 페이지에 붙이기

 

이 때 핵심이 되는 것은, 페이지를 바꾸지 않고, 현재 화면상에서 서버에 요청해서 데이터를 받아온다는 것 입니다

이렇게 하기위해서 사용하는 것이 Ajax 인데요.

추가적으로 스크롤하는 데이터에 대하여 Ajax요청을 해야한다는 것 입니다.

 

스크래이핑에서 중요한 것은,

Callback함수가 호출된다음,

다음 페이지에 컨텐츠를 호출해서 붙이는 방법인데요.

다음과 같은 방법들이 있을 것 같습니다.

  • page:1 (&page=1과 같이 Query String으로 붙여주는 경우가 많음)
  • has_next:true같은 repsponse를 주어서 다음에 로딩이 가능한지를 알려주는 경우도 있습니다.

 

 

6-2. 무한스크롤 스크래이핑 하는 방법

무한스크롤이 어떻게 구현되는지는 알았는데요.

그럼, 이것을 어떻게 스크래이핑해서 가져올것인가도 생각해 보아야 겠습니다.

 

두가지 방법을 생각해 볼 수 있는데요.

다만 후자는 Selenium을 이용해야 하므로, 여기서는 다루지 않겠습니다.

  • 스크롤 할 때마다 서버에 어떻게 요청하는지 보고 요청해서 추가되는 데이터를 스크래이핑 한다.
  • 스크롤 할 수 있는 최대 높이까지 스크롤해서 전부다 로딩시킨다음 스크래이핑 한다.

 

A. 스크롤시 네트워크요청 흉내내기

첫번째 방법은 서버에 어떻게 요청을 해서 가져오는지를 보고,

해당하는 url로 우리도 서버에 요청을 하는 것 입니다.

 

Javascript를 보고 찾아낼수도 있겠지만, 대부분 난독화되어있거나, minify되어있어서 쉽게 찾아보기는 어렵구요.

크롬브라우저를 이용하는 것이 훨씬 효율적입니다.

 

먼저 해당 페이지에서 우측마우스버튼을 눌러서 컨텍스트 메뉴를 만든다음, 메뉴에서 'inspect'를 눌러줍니다.

나오는 화면에서 Network 탭을 선택해 줍니다.

 

 Network탭이 나온 상태에서,

새롭게 추가된 아이템들을 찾아서 선택해 줍니다.

그 다음, Headers를 보면 Request URL이 나오는 것을 볼 수 있는데요.

추가된 아이템을 찾기위해서, 왼쪽에 Search창을 활용하는 것도 좋은 방법입니다.

 

 

여기서 스크롤을할 때 어떤 호출이 이루어지는지 보고 그것을 우리도 호출해주면 됩니다.

 

이런식으로 여러번 내려보면서 얻어내는 URL을 가지고 패턴을 찾아내면 되겟지요.

왜냐하면 해당페이지에서도 웹을 만들때,

유저가 아래로 스크롤해서 Callback이 이루어지면,

몇번째에서부터 몇번째까지의 아이템들을 호출하라는 식으로 코딩할 것이기 때문입니다.

 

호출할 때마다 어떤 url을 규칙적으로 사용하고, 다음 다음 호출을 할 때, url내의 숫자등이 어떤 규칙등으로 바뀌는지를 보고,

호출을 해 주어야 합니다.

 

7. HTML로 표시되지 않는 데이터를 스크래이핑하기

데이터들중에서는 데이터를 받아서 그래픽으로 표시하거나 하여서 HTML로 나오지 않는 경우들이 있습니다.

차트데이터들중에 image로 출력되지 않는경우, 서버에서 받은 데이터를 직접 그리기 때문인데요.

이런 경우는 어떻게 해야하는지 보도록 하겠습니다.

 

 

이럴 때도 위에서 보았던 크롬의 Network 탭을 활용해서 위에서 사용하는 데이터를 찾아내야 하는데요.

이러한 데이터를 찾는 방법은 여러가지가 있습니다.

위 차트는 주봉에서 월봉으로 탭을 수정하면 다른 데이터를 서버에서 보내주기 때문에 이를 활용해주었습니다.

Filter는 'Fetch/XHR' 을 선택해 주었구요.

 

 

 

중요한 점은, 불러온 Network데이터중에서 필요한 정보만 잘 캐치해내야한다는 것 입니다.

다행히 수많은 파일중에서 원하는 파일들을 가져올 수 있도록, Filter를 할 수 있는데요.

좌측의 'Filter'창에 직접 검색을 할 수도 있구요.

우측에 보면, Fetch/XHR 과 같이 정해진 Filter중에서 선택할 수도 있습니다.

 

 

아래와 같이 데이터 결과가 나오는 화면에는 Preview 탭이 있는데요.

Preview에서 해당하는 데이터가 파싱해서 가져오려고 하는 값인지 확인해 보아야 하겠습니다.

이렇게 해서 찾기 어렵다면, Filter에서 검색어를 이용해서 데이터를 가져오는 것이 빠릅니다.

 

 

받아온 response는 json형태인 경우가 많은데요.

Python의 Standard Library에 포함되어 있는 json 모듈을 이용해서,

Dictionary로 파싱해서 사용해 주기만 하면 됩니다.

아래와 같이 json모듈의 loads()를 이용하면 쉽게 파싱해서 사용할 수 있습니다.

 

import json

json_data = '{"title": "tesla", "product":["modelX", "modelS", "modelY"]}'
decoded_json = json.loads(json_data)
print(type(decoded_json))
print(decoded_json['product'][0])

 

 

8. 팁 

8-1. 특정한 문자제거

파싱한 결과에서 특정한 문자를 제거하고 싶을때는,

Python StandardLibrary에 존재하는 strip함수들을 사용하면 도움이 됩니다.

  • strip([chars]) - 인자로 전달된 문자를 제거(인자 없으면 공백제거)
  • rstrip([chars]) - 인자로 전달된 문자를 String의 오른쪽에서 제거(인자 없을경우 공백제거)
  • lstrip([chars]) - 인자로 전달된 문자를 String의 왼쪽에서 제거(인자없으면 공백제거)

 

개행을 제거할 때는 다음 중 하나를 제거해주면 됩니다.

  • \n
  • \r
  • \r\n

 

참고로 '\n'과 '\r'의 차이는 개행후에 커서를 해당하는 행의 맨 앞으로 이동시키느냐의 차이인데요.

'\n'을 사용하면 커서가 이동하지 않지만, '\r'을 사용하면 커서가 맨 앞으로 이동합니다.

개행을 하고 나서 다음줄에 공백이 있지 않도록 하려면, '\r'을 사용해야 합니다.

'\r\n'과 같이 두개 다 사용하는 경우도 있구요.

 

9. BeautifulSoup 공식사이트

문제가 풀리지 않거나 특별한 기능의 함수가 필요할 때는,

최신화된 공식사이트 메뉴얼에서 찾을수 있는 경우도 있는데요.

아래의 주소를 참조하시면 됩니다.

>> https://www.crummy.com/software/BeautifulSoup/bs4/doc

 

728x90

댓글