Python의 BeautifulSoup를 사용해 크롤러를 만들어 봅니다.

 

 

 

1. 사전 준비

 

본 연습 프로젝트는 레드마인의 이슈를 크롤링 해오는것을 목표로 합니다.

 

레드마인 준비는 별도로 설명하지 않습니다. 

 

** 언제까지 올라가 있을지는 장담할 수 없으나 본문에 제 레드마인 조회용 계정을 사용하셔도 됩니다.

 

별도의 파이썬 설치방법은 설명하지 않습니다. 다만 원활한 개발을 위해 VirtualEnv만 설명합니다.

 

파이썬을 설치하고 VSCode로 프로젝트 폴더를 엽니다.

 

Ctlr + `를 눌러 터미널 창을 열고 다음 명령어로 VirtualEvn를 설치합니다.

 

> pip install virtualenv

 

 

이제 다음 명령어를 통해 VirtualEnv를 활성화시킵니다.

 

> virtualenv venv

 

 

다음과 같이 프로젝트 폴더에 venv 폴더가 생성된 것을 확인할 수 있습니다.

 

 

다른 폴더의 이름을 원하시면 venv대신 다른 이름을 지정하시면 됩니다.

 

이제 virtualenv를 활성화시킵시다. ".\venv\scripts\activate" 파일을 실행시키면 됩니다.

 

>.\venv\scripts\activate

 

 

이제 터미널 앞에 가상 환경으로 정의한 (venv)가 나타난 것을 확인할 수 있습니다.

 

작업을 종료하고 싶으시면 ".\venv\scripts\deactivate"를 입력하시면 됩니다.

 

 

 

2. BeautifulSoup 이용하기

 

BeautifulSoup는 Python 패키지중 하나로 HTML 태그를 Python 객체로 만들어줍니다.

 

문서는 다음과 같이 설명하고 있습니다.

 

"Beautiful Soup transforms a complex HTML document into a complex tree of Python objects."
"BeautifulSoup는 HTML document안에 있는 수많은 HTML 태그들을 사용하기 편한 Python 객체 형태로 만들어 줍니다."

 

이 외에도 파싱을 돕는 다양한 내장 함수가 있어 편하게 원하는 데이터를 추출해 올 수 있습니다.

 

이제 터미널에서 다음 명령어를 실행해 beautisulsoup를 설치합시다.

 

> pip install beautifulsoup4 

 

 

이 외에도 requests라는 패키지가 필요합니다. 이 패키지는 입력한 URL에 대해 반환받는 HTML을 가져오는 역할을 합니다. 다음 명령어를 실행해 requests를 설치합니다.

 

>pip install requests

 

 

이제 이 패키지들을 사용해 보겠습니다. crawler.py 파일을 만든 뒤 다음과 같이 코딩합니다.

 

import requests
from bs4 import BeautifulSoup

class Crawler:
    def __init__(self):
        self.__url = 'https://src.smoh.kr/projects/crawlerissues/issues'
        return

    def main(self):
        response = requests.get(self.__url)
        if response.status_code == 200:
            obj = BeautifulSoup(response.content, 'html.parser')
            print(obj.html)
        else:
            print('Failed(Code: %d)' %(response.status_code))
        return

crawler = Crawler()
crawler.main()

 

지정한 URL로부터 응답을 받고 코드가 200 성공인 경우 내용을 BeautifulSoup을 이용해 객체로 생성한 뒤 html을 출력하는 코드입니다.

 

HTML결괏값이 제대로 보이시나요?

 

 

 

3. 이슈 크롤링하기

 

이 URL 정보는 CrawlerIssues 프로젝트에 생성된 이슈를 보여주는 URL입니다만 우리가 원하는 이슈 정보를 보려고 하기엔 권한이 부족합니다. HTML 결과를 살펴보면 로그인을 하는 화면임을 확인할 수 있습니다.

 

실제 URL로 들어가서 개발자 도구(F12)를 연 뒤 Elements 탭에서 확인해 보겠습니다.

 

 

이제 우리는 "username"에 유저 ID를, "password"에 암호를 넣고 "login" 액션을 통해 로그인을 진행하는 것을 확인했습니다.

 

저기에 유저 정보를 넣어 로그인을 해봅시다. 크롤러가 로그인 정보를 갖기 위해서 세션을 사용합니다. 다음과 같이 코드를 수정합니다.

 

import requests
from bs4 import BeautifulSoup

class Crawler:
    def __init__(self):
        self.__login_url = 'https://src.smoh.kr/login'
        self.__seach_url = 'https://src.smoh.kr/projects/crawlerissues/issues'
        self.__login_data = {
            'username': 'jd_user',
            'password': 'P@ssword'
        }
        return

    def main(self):
        with requests.Session() as s:
            req = s.post(self.__login_url, self.__login_data)
            if req.status_code == 200:
                html = s.get(self.__seach_url)
                print(html)
            else:
                print('Failed to login(Code: %d)' %(req.status_code))
        return

crawler = Crawler()
crawler.main()

 

** 크롤링할 레드마인이 없으신 분은 저 계정을 사용하시면 됩니다만 사전 공지 없이 변경될 수 있음을 알려드립니다.

 

결과가 어떻게 나오나요? "Failed to login(Code: 422)"와 함께 로그인이 제대로 되지 않을 겁니다. 원인이 무엇인지 postman을 통해 직접 post 요청을 날리고 메시지를 확인해 봅시다.

 

 

"Invalid form authenticity token"라는 에러와 함께 로그인에 실패하네요. 혹시 위의 HTML Elements에서 authenticity token을 보셨나요? 

 

 

해당 값은 보안을 위해 발급된 토큰으로 매 로그인 시마다 다른 값을 요구합니다. 따라서 크롤러에서 이 값을 사용하기 위해 코드를 다음과 같이 수정합시다.

 

import requests
import datetime
from bs4 import BeautifulSoup

class Crawler:
    def __init__(self):
        self.__main_url = 'https://src.smoh.kr/'
        self.__login_url = 'https://src.smoh.kr/login'
        self.__search_url = 'https://src.smoh.kr/projects/crawlerissues/issues?set_filter=1'
        self.__login_data = {
            'username': 'jd_user',
            'password': 'P@ssword'
        }
        self.__issues = {}
        return

    def main(self):
        with requests.Session() as s:
            main_req = s.get(self.__main_url)
            html = main_req.text
            obj = BeautifulSoup(html, 'html.parser')
            auth_token = obj.find('input', {'name': 'authenticity_token'})['value']
            self.__login_data = {**self.__login_data, **{'authenticity_token': auth_token}}

            login_req = s.post(self.__login_url, self.__login_data)
            if login_req.status_code == 200:
                search_req = s.get(self.__search_url)
                search_obj = BeautifulSoup(search_req.text, 'html.parser')
                thead = search_obj.select('div.autoscroll > table > thead > tr > th')
                tbody = search_obj.select('div.autoscroll > table > tbody > tr > td')
                issue_obj = {}
                issue_list = []
                for i in range(1, len(tbody) +1):
                    if(i % len(thead) != 0):
                        issue_obj[thead[i % len(thead)].text] = tbody[i].text
                    else:
                        issue_list.append(issue_obj)
                        issue_obj = {}
                self.__issues = {
                    'execution_dttm': str(datetime.datetime.now().isoformat()),
                    'issues': issue_list
                }
            else:
                print('Failed to login(Code: %d)' %(req.status_code))
            print(self.__issues)
        return

crawler = Crawler()
crawler.main()

 

차근차근 코드를 살펴봅시다.

 

우선 url이 세 개로 늘었습니다. auto_token을 받아오는 메인 페이지, 로그인을 요청하는 페이지, 이슈를 조회하는 페이지가 있습니다. 로그인 시 사용하는 객체는 __login_data에 저장해 두었으며 __issues는 조회한 이슈를 정리하여 담아두기 위한 객체입니다.

 

실제 작업의 워크 플로우를 봅시다.

 

먼저 세션을 생성하고 세션 내에서 auth_token을 구해옵니다. 구해온 토큰은 __login_data에 저장해 두었습니다.

 

이제 로그인시 필요한 정보가 모두 모였으니 로그인을 시도합니다. status_code를 확인해 정상적으로 로그인이 되었다면 조회를 시작합니다. 

 

조회 결과를 search_req에 담아둔 뒤 BeautifulSoup를 이용해 객체로 변환합니다.

 

 

모든 HTML이 아닌 위에 보이는 테이블의 헤더와 row정보만 필요하므로 셀렉터를 사용해서 값을 추출해주었습니다.

 

추출한 값을 루프 문을 통해 각각의 이슈를 객체로 만든 뒤 리스트에 담아두었습니다. 모든 작업이 끝나면 시간과 함께 JSON으로 만들기 위해 __issues에 저장한 후 출력까지 해보았습니다.

 

 

값이 위와 같이 잘 나오시나요? 이로써 가장 간단하고 기초적인 크롤러를 만들어보았습니다. 다음에는 이 크롤러를 좀 더 발전시켜보도록 하겠습니다.

 

 

** 본 포스팅의 소스코드는 여기에서 확인할 수 있습니다.

 

 

 

반응형

+ Recent posts