새소식

프로그래밍/Python

보안뉴스 크롤러 제작 - 단순 출력

  • -

예전부터 보안뉴스 크롤러를 만들면 어떨까 생각했었는데 이번 참에 만들어보기로 했다.

이번 글에서는 단순히 실행하면 최신 뉴스 5개에 대한 제목, 일시, 내용을 띄워주는 스크립트를 만들 것이다.

 

 

1. 사이트 분석

 

robots.txt

크롤러 제작에 앞서 보안뉴스 사이트의 robots.txt를 확인했다. 다행히 /secu_admin/을 제외한 모든 페이지는 크롤링이 가능하다.

 

 

시큐리티 카테고리 

시큐리티 카테고리의 주소는 https://www.boannews.com/media/list.asp?mkind=1이다.

 

 

첫번째 기사

기사 제목과 내용을 긁어오려면 첫 번째 기사의 주소를 알아야 한다. 확인을 해본 결과 첫 번째 기사의 주소는https://www.boannews.com/media/view.asp?idx=89847&page=1&mkind=1&kind=이다.

 

기사의 주소는 중간에 idx=5자리숫자로 이루어져 있다.

https://www.boannews.com/media/view.asp?idx=5자리숫자&page=1&mkind=1&kind=

 

즉, 맨 처음 기사의 주소에서 -1을 하면 이전의 기사 주소가 되는 것이다. 

이 정보를 토대로 크롤러를 제작해보자.

 

 


2. 크롤러 제작

2.1) 기본적인 함수

import urllib3
import requests
from time import sleep
from bs4 import BeautifulSoup
from urllib.parse import urljoin

사용한 모듈은 위와 같다. 

 

class BoanCralwer:
    def __init__(self):
        self.host = 'https://www.boannews.com'
        self.header = {
            'Accept-Language' : 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7', 
            'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
        }

        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)

BoanCralwer 클래스를 만들고 requests 요청을 할 때 같이 넣을 헤더와 에러 출력을 막기 위해서 urllib3.disable_warnings()함수를 사용했다.

 

    def absurl(self, path):
        return urljoin(self.host, path)

    def beautiful_soup(self, response):
        return BeautifulSoup(response.text, 'html.parser')

    def url_request(self, url):
        return requests.get(url, headers=self.header, verify=False)

지속적으로 사용하는 함수들은 각각의 기능마다 한 개씩 구현했다.

 

    def url_parse(self, url):
        url = self.absurl(url)
        response = self.url_request(url)
        soup = self.beautiful_soup(response)
        return soup

url_parse()는 위에서 구현한 함수들을 사용하여 soup을 리턴하는 함수이다. 크롤링을 위한 기본적인 기능을 가진 함수를 구현했으니 이제 본격적으로 기사 제목을 추출할 차례이다.

 

 

2.2) 첫 번째 기사 링크 추출

html 구조

위 그림에서 원하는 정보는 <a href="~">에서 기사의 주소가 적혀있는 ~부분이다.

 

 

    def find_title_link(self, soup):
        title_link = soup.find('div',class_ = 'news_main_title').find('a')['href']

        return title_link

soup.find('div',class_ = 'news_main_title')을 통해서 <a href="~">를 얻고 find('a')['href']를 통해 원하는 정보인 기사의 주소를 얻는다.

 

 

print(title_link)를 입력하면 성공적으로 주소를 얻은 것을 알 수 있다.

 

 

2.3) 기사 제목, 본문, 입력시간 추출

이제 앞에서 구한 주소에 접속해서 기사의 제목, 본문, 입력시간을 추출할 것이다.

 

기사의 제목은 <div id="news_title02">안에 있다.

 

 

    def get_title(self, soup):
        title = soup.find('div',id = 'news_title02').get_text()

        return title

find를 이용해 id가 news_title02인 문장을 찾고 get_text()를 통해 문자열만 추출한다.

 

 

 

기사 본문

기사의 본문은 <div id="news_content">안에 존재한다.

 

 

    def get_content(self, url):
        soup = self.url_parse(url)
        content = soup.find('div',id = 'news_content')

 

soup.find('div',id = 'news_content')를 통해 본문을 읽어온 결과는 위 그림과 같다. 여기서 한 가지 문제점이 발생하는데 <div id="news_content">, <p>, <br>과 같은 태그가 남아있다는 점이다. 따라서, 편하게 읽을 수 있게 태그들을 전부 지워야 한다.

 

 

    def get_content(self, url):
        soup = self.url_parse(url)
        content = soup.find('div',id = 'news_content')

        # 지워야할 태그 선택
        tag = content.find_all(['br','div','p','img'])

        # extract 함수 : content 객체에서 해당 태그를 제거한다.
        for script in tag:
            script.extract()

        # 텍스트 단위 결합을 '\n'(줄바꿈)으로 한다.
        # 각 텍스트 단위의 시작과 끝에서 공백을 제거한다.
        contents = content.get_text('\n',strip=True)

위의 코드를 추가하니 성공적으로 태그들이 지워졌다.

 

 

 

기사의 입력 시간은 <div id="news_util01">안에 있다.

 

 

    def get_time(self, soup):
        time = soup.find('div',id = 'news_util01').get_text()

        return time

find를 이용해 id가 news_util01인 문장을 찾고 get_text()를 통해 문자열만 추출한다.

 

 

2.4) 크롤링

    def act(self, link):
    	# 링크에서 5자리 숫자부분만 추출한다. (ex 88739)
        index = int(link[20:25])

        # for문을 통해 index를 1씩 감소해 이전 기사들에 대한 정보를 얻는다.
        for i in range(5):
            soup = self.url_parse('/media/view.asp?idx='+str(index-i)+'&page=1&mkind=1&kind=')
            title = self.get_title(soup)
            content = self.get_content(soup)
            time = self.get_time(soup)

            print('['+title+']')
            print(time)
            print(content,end='\n\n')

            sleep(2)

목표는 최신 기사 5개이므로 for문의 범위는 5로 지정하고 위에서 구현한 함수들을 이용해 정보를 얻는다. 

 

 

스크립트 실행 결과 성공적으로 출력되었다.

 

 


3. 전체 코드

#!/usr/bin/env python3
#-*- coding: utf-8 -*-

import urllib3
import requests
from time import sleep
from bs4 import BeautifulSoup
from urllib.parse import urljoin

class BoanCralwer:
    def __init__(self):
        self.host = 'https://www.boannews.com'
        self.header = {
            'Accept-Language' : 'ko-KR,ko;q=0.9,en-US;q=0.8,en;q=0.7', 
            'User-Agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.116 Safari/537.36'
        }

        urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
        
    def absurl(self, path):
        return urljoin(self.host, path)

    def beautiful_soup(self, response):
        return BeautifulSoup(response.text, 'html.parser')

    def url_request(self, url):
        return requests.get(url, headers=self.header, verify=False)

    def url_parse(self, url):
        url = self.absurl(url)
        response = self.url_request(url)
        soup = self.beautiful_soup(response)
        
        return soup

    def find_title_link(self, soup):
        title_link = soup.find('div',class_ = 'news_main_title').find('a')['href']

        return title_link

    def get_title(self, soup):
        title = soup.find('div',id = 'news_title02').get_text()

        return title

    def get_content(self, soup):
        content = soup.find('div',id = 'news_content')

        tag = content.find_all(['br','div','p','img'])

        for script in tag:
            script.extract()

        contents = content.get_text('\n',strip=True)
        
        return contents

    def get_time(self, soup):
        time = soup.find('div',id = 'news_util01').get_text()

        return time

    def act(self, link):
        index = int(link[20:25])

        for i in range(5):
            soup = self.url_parse('/media/view.asp?idx='+str(index-i)+'&page=1&mkind=1&kind=')
            title = self.get_title(soup)
            content = self.get_content(soup)
            time = self.get_time(soup)

            print('['+title+']')
            print(time)
            print(content,end='\n\n')

            sleep(2)


def main():
    boan = BoanCralwer()
    soup = boan.url_parse('/media/list.asp?Page=1&mkind=1&kind=')
    first_link = boan.find_title_link(soup)
    boan.act(first_link)
    sleep(10)

if __name__ == '__main__':
    main()

프로그램의 전체 코드는 위와 같다.

 

이렇게 보안 뉴스의 최신 기사 5개의 제목, 본문, 입력 시간을 출력하는 스크립트를 만들었다. 이번 글에서는 단순히 프롬프트 창에 기사 내용을 출력하는 정도이지만 다음 글에서는 텔레그램의 API를 활용해 기사의 제목, 주소를 나한테 보내주는 스크립트를 만드는 방법을 설명할 것이다.

 

 

 

 

 

'프로그래밍 > Python' 카테고리의 다른 글

Python 명명 규칙  (0) 2022.07.04
Python 잡동사니  (0) 2022.07.03
파이썬 코딩 스타일  (0) 2020.07.16
모듈 설치 하는 방법  (0) 2020.07.16
문자열 포맷팅  (0) 2020.07.08
Contents

포스팅 주소를 복사했습니다

이 글이 도움이 되었다면 공감 부탁드립니다.