top of page

1.2 정적 크롤링(request, Beautiful Soup 등)

키워드 : 크롤링, 정적크롤링, beautifulsoup, request, urlib, 동적크롤링, find(), find_all(), find_parent() , find_parents(), find_next_sibling(), find_previous_sibling(), select(), .text, .attrs, 파싱, scrapy, 머신러닝, 딥러닝, AI, 파이썬, 데이터 분석, 데이터 시각화, EDA, 데이터 전처리, 데이터 수집, 데이터 모델링

크롤링(Crawling)이란 웹페이지를 가져와서 데이터를 추출해내는 방법을 말합니다. 데이터 수집하는데 크롤링이 왜 필요할까요? 크롤링의 경우 방대한 데이터를 수집하는 것을 자동으로 해결해주므로 사람이 수동으로 하는 것보다 훨씬 더 빨리 일을 처리할 수 있습니다.

크롤링을 하면서 여러분이 만나게 될 페이지는 크게 두 가지로 나뉩니다. 바로 정적 페이지동적 페이지입니다.

💡 정적 페이지: 특정 웹페이지의 url 주소를 주소창에 입력했을 때 웹 브라우저로 HTML 정보를 마음대로 가져다 쓸 수 있는 페이지입니다.

💡 동적 페이지: 동적 페이지는 사용자 요청에 따라 서버가 동적으로 콘텐츠를 생성하고 보여주는 페이지를 말합니다. URL에 추가된 매개변수를 기반으로 서버 사이드 스크립트가 실행되어 데이터를 처리하고, 그 결과로 동적으로 생성된 내용이 웹페이지에 표시됩니다.

즉, 정적 페이지는 서버에 미리 생성된 내용이 사용자에게 전달되는 반면, 동적 페이지는 사용자 요청에 따라 서버에서 콘텐츠가 동적으로 생성되어 제공됩니다.

01. 정적크롤링이란?

정적 크롤링은 말그대로 정적인 데이터를 수집하는 방법을 말합니다. 이 때 정적 데이터란 변하지 않는 데이터를 의미합니다. 즉 한 페이지 안에서 원하는 정보가 모두 드러날 때 정적 데이터라고 할 수 있습니다.

Untitled (2).png

우리가 교보문고 웹사이트에서 드러난 책 두 권의 책 제목과 가격을 크롤링하고 싶은 경우 보이는 화면에 원하는 데이터가 다 드러나 있습니다. 이와 같이 한 페이지 내에서 원하는 정보가 모두 드러난 경우, 정적 크롤링을 할 수 있습니다.

반면에 책 리뷰까지 수집하고 싶은 경우는 상세페이지를 클릭해야 하므로 동적 크롤링에 해당하겠죠. 그러나 이는 앞서 말씀드렸다시피 정적 페이지는 아닙니다. 스크롤을 내리는 사용자의 행위에 의해 다른 책 목록이 나오기 때문에 이는 동적 페이지라는 점 기억해두세요.

1.1 정적 크롤링의 주요 단계

정적 크롤링의 주요 단계는 다음과 같습니다.

  1. 크롤링 허용 여부 확인하기: 원하는 웹페이지를 크롤링하기 전, ‘주소/robots.txt’를 입력하여 크롤링 여부를 확인합니다.

  2. HTTP 요청: 웹 크롤러는 웹 사이트로 HTTP 요청을 보냅니다. 이를 위해 Python의 requests 라이브러리나 urllib 모듈 등을 사용할 수 있습니다.

  3. HTML 문서 다운로드: 웹 사이트는 HTTP 요청에 응답으로 HTML 문서를 반환합니다. 이 문서는 웹 페이지의 모든 정보를 포함하고 있습니다.

  4. HTML 파싱: 웹 크롤러는 다운로드한 HTML 문서를 파싱하여 정보를 추출합니다. Python에서는 BeautifulSoup 라이브러리를 사용하여 HTML 문서를 구문 분석하고 원하는 데이터를 선택할 수 있습니다.

  5. 데이터 추출: 파싱된 HTML 문서에서 원하는 데이터를 찾아냅니다. 이 데이터는 텍스트, 이미지, 링크 또는 테이블 등 다양한 형태일 수 있습니다.

  6. 데이터 저장: 추출한 데이터를 필요에 따라 저장합니다. 이는 파일 형식으로 저장하거나 데이터베이스에 저장하는 등의 방식으로 이루어질 수 있습니다.

02. urllib

urllib은 파이썬에서 웹페이지 데이터를 다루기 위한 내장 패키지 중 하나입니다. urllib 패키지는 URL을 통해 데이터 수집, 요청, 응답을 처리하는 기능을 제공합니다. 이를 통해 웹 페이지에 접속하여 데이터를 수집할 수 있습니다.

 

그렇다면 urllib.request와 requests의 차이는 무엇이며, 왜 urllib보다 requests를 더 사용하는 것일까요? 기본적으로는 큰 차이가 없어도 requests에서 같은 기능을 더 쉽게 사용할 수 있고 직관적으로 이해하는 것이 더 쉽기 때문에 urlib.request보다는 requests를 더 많이 사용합니다.

보다 자세히 설명하자면 requests는 urllib에 비해 사용하기 훨씬 간결하고 직관적인 API를 제공하기 때문에 코드를 더 쉽게 작성하고 이해할 수 있습니다. 또, requests는 URL의 파라미터를 더 쉽게 전달할 수 있도록 딕셔너리 형태로 매개변수를 전달할 수 있으나 urllib에서는 별도로 파라미터를 인코딩해야 합니다. 마지막으로, requests는 HTTP 요청이 실패했을 때 예외 처리를 쉽게 할 수 있도록 설계되어 있습니다. urllib의 경우 에러 처리가 조금 더 복잡합니다.

03. requests

requests는 HTTP 요청을 보내고 응답을 처리하는데 사용되는 패키지로, 웹 페이지의 HTML 내용을 가져오거나 데이터를 전송하기 위해 많이 활용됩니다. requests 패키지는 내부적으로 urllib를 기반으로 구현되어 있지만, 더 간결하고 직관적인 API를 제공합니다.

도서사이트를 통해서 requests 활용 예시를 보여드리겠습니다. 가져오는 데이터가 HTML 형태이므로, 크롤링하기 위해서는 먼저 html 구조와 CSS selector에 대한 정보가 필요합니다.

3.1 requests를 활용한 yes24 크롤링

import requests

url = 'https://www.yes24.com/24/Category/BestSeller'

headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}

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

raw.status_code # 실제로 잘 받아왔는지 코드를 통해서 확인할 수 있습니다! '200'이면 정상입니다!

  1. import requests: requests 모듈을 가져옵니다.

  2. url = 'https://www.yes24.com/24/Category/BestSeller': 요청을 보낼 웹사이트의 URL을 변수 url에 저장합니다. URL이 길기 때문에 변수에 저장하는 것이 앞으로 쓰기 편리합니다. 이 URL은 yes24의 베스트셀러 페이지를 나타냅니다.

  3. headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3' }: HTTP 요청 시 전달할 헤더 정보를 딕셔너리 형태로 저장하는 코드입니다. 여기서는 User-Agent 헤더를 설정하여 웹사이트에서 브라우저로 인식하도록 설정합니다. 이렇게 하면 브라우저에서 온 것으로 인식하여 크롤링을 더 쉽게 할 수 있습니다. 이 헤더를 설정하지 않는 경우 몇몇 웹사이트들은 HTTP 요청을 차단하거나, 다른 응답을 반환할 수 있기 때문에 헤더를 설정하는 것이 좋습니다.

  4. raw = requests.get(url, headers=headers): requests 모듈의 get() 함수를 사용하여 HTTP GET 요청을 보냅니다. 이때, 앞서 정의한 url과 headers를 인자로 넘겨줍니다. 서버로부터의 응답을 raw 변수에 저장합니다.

이 코드를 실행하면, 해당 URL로 HTTP GET 요청이 보내지고 웹사이트로부터의 응답이 raw 변수에 저장됩니다. 이 응답을 이후 작업을 위해 활용할 수 있습니다. 예를 들어, raw.text를 이용하여 웹 페이지의 HTML 내용을 추출하거나, raw.json()을 이용하여 JSON 형식의 데이터를 파싱할 수 있습니다.

raw.text

웹페이지의 HTML 내용이 제대로 추출된 것을 알 수 있습니다. 다만 이 상태로는 우리가 원하는 정보를 구분하기 어려우므로 beautifulsoup 패키지를 추가로 사용하여야 합니다. beautifulsoup이란 아래와 같습니다.

04. beautifulsoup

BeautifulSoup은 파이썬에서 HTML 및 XML 문서를 파싱하고, 데이터를 추출하는 데 사용되는 외부 라이브러리입니다. 웹 페이지에서 특정 요소의 텍스트, 링크, 이미지 URL 등 원하는 데이터를 추출하여 활용할 수 있습니다.

BeautifulSoup은 어떤 장점이 있을까요? 우선 쉽고 직관적인 API를 가지고 있기 때문에 빠르게 배우고 사용할 수 있습니다. HTML의 유효성 검사를 엄격하게 하지 않으며, 비록 "잘못된" HTML 문서에서도 유연하게 데이터를 추출할 수 있습니다. BeautifulSoup는 파이썬 내장 파서(html.parser)와 외부 라이브러리인 lxml, html5lib 등 다양한 파서를 지원하기 때문에 이를 선택하여 사용할 수 있습니다.

4.1 BeautifulSoup 모듈

실습에 앞서 BeautifulSoup에서 많이 사용되는 모듈과 활용 용도에 대해서 먼저 설명드리겠습니다.

1. find() / find_all():

  • find(name, attrs, recursive, text, kwargs): 주어진 조건에 맞는 첫 번째 요소를 반환합니다.

  • find_all(name, attrs, recursive, text, limit, kwargs): 주어진 조건에 맞는 모든 요소를 리스트로 반환합니다.

2. find_parent() / find_parents():

  • find_parent(name, attrs, recursive, text, kwargs): 조건에 맞는 첫 번째 부모 요소를 반환합니다.

  • find_parents(name, attrs, recursive, text, kwargs): 조건에 맞는 모든 부모 요소를 리스트로 반환합니다.

3. find_next_sibling() / find_previous_sibling():

  • find_next_sibling(name, attrs, text, kwargs): 조건에 맞는 다음 형제 요소를 반환합니다.

  • find_previous_sibling(name, attrs, text, kwargs): 조건에 맞는 이전 형제 요소를 반환합니다.

4. select():

  • select(selector): CSS 선택자를 사용하여 요소를 선택합니다.

5. .text:

  • 요소의 텍스트 내용을 가져옵니다.비슷한 용도로 get_text() 모듈이 있습니다. get_text()는 해당 요소의 모든 text를 추출한다는 차이가 있으나 대부분 코드 작성 후 출력했을 때 .text와 같은 출력값이 나옵니다.

6. .attrs:

  • 요소의 속성을 딕셔너리 형태로 가져옵니다. 특정 요소의 속성 값을 가져올 때 사용됩니다. 예를 들어, element['href']는 해당 요소의 href 속성 값을 반환합니다.

4.2 Beautifulsoup를 활용한 yes24 크롤링

앞서 requests를 활용하여 yes24 웹페이지의 HTML 내용을 추출한 것까지 진행했습니다. 하지만 HTML 내용 전체를 추출했기 때문에 원하는 정보가 제대로 보이지 않네요. beautifulsoup이 이를 정리해줄 것입니다.

part 01. Parsing

import bs4

html = bs4.BeautifulSoup(raw.text, 'html.parser')

html

  1. bs4.BeautifulSoup: bs4 패키지에서 BeautifulSoup 클래스를 가져옵니다.

  2. raw.text: 이전에 requests에서 받아온 HTTP 응답 객체로 가정합니다. raw.text는 해당 응답 객체에서 HTML 문자열을 가져옵니다. 즉, HTTP 응답 객체에서 웹 페이지의 HTML 내용을 추출합니다.

  3. 'html.parser': 이 부분은 BeautifulSoup의 파서를 선택하는 부분입니다. **'html.parser'**는 파이썬의 내장 파서를 사용하여 HTML 문서를 파싱합니다. 이 외에도 'lxml', 'html5lib' 등의 다른 파서를 사용할 수 있습니다. 하지만 보통은 내장 파서가 대부분의 경우에 충분하고 빠르므로 **'html.parser'**를 주로 사용합니다.

자 이제 웹페이지의 HTML을 파싱해서 추출했습니다. 하지만 여기서 끝난 것이 아닙니다. 보통 크롤링을 할 때 저희는 웹페이지 전체보다는 필요한 요소들만 추출하죠. 많은 것을 한꺼번에 하면 힘들기 때문에 이 페이지에서는 베스트셀러 1위 도서의 제목만 크롤링 해보도록 하겠습니다. 만약 제목 이외의 저자, 가격까지 크롤링을 하고 싶으시다면 같은 방식으로 html 구조를 따라 코드를 작성하시면 됩니다.

웹페이지에서 추출하고자 하는 요소는 명확합니다. 제목을 추출할 것이기 때문에 웹페이지의 개발자 도구를 통해서 원하는 요소를 찾은 후 코드를 짜는 것이 더 편합니다.

💡개발자 도구를 통해서 원하는 부분의 요소를 추출하기 위한 절차를 보여주는 이미지입니다. 코드를 통해서 html 요소를 추출하는 것도 좋지만, 원하는 요소를 빠르게 찾기 위해서는 개발자 도구를 이용하는 것이 더 빠르고 간편합니다.

part 02. 요소 추출을 위한 모듈(제목 추출)

위 이미지와 같이 Parsing된 데이터에서 ‘세이노의 가르침’이라고 보이는 책 제목을 추출하는 방법은 여러가지가 있습니다. 한가지씩 실습해보겠습니다. a에서 c까지 3방법의 결과물은 모두 ‘세이노의 가르침’으로 같습니다.

a. find()

html.find('p', class_='image').img['alt']

  1. soup.find('p', class_='image'): find() 메서드를 사용하여 HTML 문서에서 <p> 태그를 찾습니다. 이때, 클래스가 image인 요소를 선택합니다. 즉, class="image"**를 가지는 <p> 태그를 선택하는 것입니다.

  2. .img: 선택된 <p> 태그 내부의 <img> 태그를 추출합니다. 이 코드를 통해 <p> 태그 내부의 이미지 태그를 가져옵니다.

  3. ['alt']: 추출된 이미지 태그의 alt 속성 값을 가져옵니다. 대괄호([]) 내에 속성 이름인 'alt'를 사용하여 해당 속성의 값을 가져옵니다.

따라서 soup.find('p', class_='image').img['alt'] 코드는 다음과 같은 과정을 거쳐 해당 HTML 문서에서 클래스가 image인 <p> 태그 내부의 이미지 태그의 alt 속성 값을 추출하는 것을 의미합니다. 이 경우에는 이미지 태그의 alt 속성 값인 "세이노의 가르침"을 추출하게 됩니다.

b. find_all()

  1. soup.find_all('p', class_='image'): HTML 문서에서 클래스가 image인 모든 <p> 태그를 선택합니다. 이 결과는 리스트 형태로 반환됩니다.

  2. [0]: 위의 리스트에서 첫 번째 요소를 선택합니다. **[0]**을 사용하므로써 첫 번째 매치되는 <p> 태그 요소를 선택하게 됩니다.

  3. .img: 선택된 <p> 태그 내부의 <img> 태그를 추출합니다. 이 코드를 통해 <p> 태그 내부의 이미지 태그를 가져옵니다.

  4. ['alt']: 추출된 이미지 태그의 alt 속성 값을 가져옵니다. 대괄호([]) 내에 속성 이름인 **'alt'**를 사용하여 해당 속성의 값을 가져옵니다.   

 

따라서  soup.find_all('p', class_='image')[0].img['alt'] 코드는 다음과 같은 과정을 거쳐 해당 HTML 문서에서 클래스가 image인 첫 번째 <p> 태그 내부의 이미지 태그의 alt 속성 값을 추출하는 것을 의미합니다.                     

                                                                                 

이 코드에서는 클래스가 image인 요소가 하나뿐이므로, find_all()과 find()의 차이가 크게 나타나지는 않습니다. 하지만 여러 개의 요소가 있는 경우에는 find_all()을 사용하여 모든 요소를 가져올 수 있습니다.

c.select():

title = html.select('#location_0 img')[0]['alt']

title

  1. html.select('#location_0 img'): 이 부분은 CSS 선택자 #location_0 img를 사용하여 HTML 문서에서 id가 location_0인 요소 내부에 있는 <img> 태그를 선택합니다. 즉, id가 location_0인 요소 내부의 <img> 태그를 선택하는 것입니다. 이 결과는 리스트 형태로 반환됩니다.

  2. [0]: 리스트에서 첫 번째 요소를 선택합니다. 위의 코드에서 [0]을 사용하므로써 첫 번째 매치되는 <img> 태그 요소를 선택하게 됩니다.

  3. ['alt']: 선택된 <img> 태그의 alt 속성 값을 추출합니다. 대괄호([]) 내에 속성 이름인 'alt'을 사용하여 해당 속성의 값을 가져옵니다.

 

따라서 html.select('#location_0 img')[0]['alt'] 코드는 다음과 같은 과정을 거쳐 해당 HTML 문서에서 id가 location_0인 요소 내부의 첫 번째 <img> 태그의 alt 속성 값을 추출하는 것을 의미합니다. 이 경우에는 <img> 태그의 alt 속성 값인 "세이노의 가르침"을 추출하게 됩니다.

05. 그 외 Beautifulsoup 함수 소개

원하는 정보를 추출하기 위한 방법은 앞서 소개한 part 01과 part 02를 차례로 수행하면 모두 완료됩니다. 다만 앞에서 실습한 내용에서 다루지 않은 그 외 여러가지 함수를 참고 차 소개해드리겠습니다.

5.1 find_parent() / find_parents():

find_parent()/find_parents() 함수의 경우 부모를 찾는 find()함수입니다. 앞서 저희는 find() 함수를 통해 soup.find('p',class_='image' 라는 코드로 제목을 추출했습니다.

soup.find('p',class_='image')

이 코드를 변경하여 find_parent 함수를 실습해보면 다음과 같습니다.

soup.find('p',class_='image').find_parent('body')

Untitled (16).png

soup.find('p',class_='image').find_parent('html')

즉, 위의 코드와 같이 find_parent()는 class="image"를 가지는 <p> 태그에서 부모 태그(상위 태그)인 <body>와 <html>을 찾아주는 코드입니다. 이는 조건에 맞는 부모 요소를 찾을 때 보통 쓰이는 것을 알 수 있습니다. find_parents()의 경우 자신이 포함하고 있는 모든 부모를 찾아서 리스트로 돌려줍니다.

5.2 find_next_sibling() / find_previous_sibling():

이는 현재 요소의 다음 형제 요소들을 찾는 데 사용됩니다. 따라서 특정 요소와 직접적인 형제 관계에 있는 경우에는 이를 활용하여 정보를 추출할 수 있습니다.

soup.find('p',class_='image').find_next_siblings('p')

위의 코드는 선택된 <p> 태그의 다음 형제 요소들 중에서 <p> 태그를 가진 요소들을 모두 찾아 리스트로 반환하는 코드입니다.

5.3 .text:

.text의 경우는 간단합니다. 제목까지 추출했으면 이제 .text를 이해하는 것은 쉽습니다! 이해하기 쉽게 이번엔 제목이 아닌 저자를 가져와보겠습니다

soup.find('p', class_='aupu').a

저희는 여기서 저자인 세이노(SayNo)만 가져오고 싶습니다. 그러기 위해서는 요소의 텍스트만 가져오면 되기 때문에 뒤에 .text만 붙여주면 됩니다. 아주 간단하죠?

soup.find('p', class_='aupu').a.text

Untitled (17).png

5.4 .attrs:

.attrs는 요소의 속성을 딕셔너리의 형태로 반환합니다. 그럼 저희가 추출한 제목의 속성을 알아볼 수 있겠죠.

soup.find('p',class_='image').attrs

Untitled (18).png

06. Scrapy

6.1 Scrapy란?

Scrapy는 파이썬으로 작성된 웹 크롤링 및 웹 스크레이핑 프레임워크입니다. 이 프레임워크는 웹사이트에서 데이터를 추출하거나 웹 페이지를 크롤링하여 필요한 정보를 수집하는데 사용됩니다. requests와 scrapy는 모두 웹 데이터를 가져오는 도구라는 점에 있어서 공통점이 있지만 requests는 단일 HTTP 요청을 보내고 응답을 받는 라이브러리로, 간단한 스크레이핑 작업에 활용됩니다.

반면 Scrapy는 웹 크롤링 및 스크레이핑에 특화된 프레임워크로, 복잡한 작업과 대규모 데이터 수집을 위해 사용됩니다. 스파이더라는 규칙 기반의 크롤링 단위를 제공하며, 자동화와 스케줄링 기능을 갖추고 있습니다

Scrapy는 복잡한 웹 크롤링 작업을 간편하게 자동화하고 효율적으로 처리할 수 있는 프레임워크를 제공한다는 장점이 있어 많이 사용됩니다. 스파이더를 활용하여 웹 페이지 내 데이터를 추출하며, 병렬 처리와 자동화된 기능을 통해 대량의 데이터를 수집하고 관리할 수 있다는 점이 있습니다.

6.2 Scrapy 사용법

Scrapy에 대한 자세한 내용과 실습 코드가 궁금하신 분들은 아래 내용을 참고하시기를 바랍니다.

07. 참고자료

​이번 게시물에서 쓰이는 패키지나 함수의 경우 Da,ta 팀에서 자체적으로 진행했던 도서사이트 프로젝트를 참고하였습니다. 

bottom of page