안녕하세요 뜨거운 여름동안 잘 쉬다가 오랜만에 포스팅을 하게 되었습니다.

이번에는 제가 개인적으로 진행하고 있는 프로젝트의 소스와 내용을 포스팅하게 되었습니다.

그 내용은 제목에 있습니다. 자세히 표현하자면 네이버 뉴스를 1시간마다 크롤링하여

크롤링한 뉴스의 내용을 엘라스틱 서치라는 검색엔진 API로 뿌려주는 겁니다.

일단 사전에 환경이 준비되어 있어야 합니다. 그 환경은 아래에 기재하였습니다.

  1. 엘라스틱서치를 설치하고 실행까지 해주셔야 합니다.
  2. Beautifulsoup와 pandas와 같은 파이썬 패키지를 설치해 주셔야 합니다.
  3. REST API로 api를 구현하되 원하는 프레임워크 및 라이브러리로 구현을 해주시면 됩니다.(저는 nodeJS의 Express로 구현 하였습니다.)
  4. 위의 내용들은 저의 환경이니 각자 원하시는 환경으로 커스터마이징 하셔도 됩니다.

일단 뉴스 내용을 수집해야 하니 크롤러부터 들어갑니다.

바로 소스코드 공개합니다.


realtime.py

#-*- coding: utf-8 -*-
import os
import sys
from bs4 import BeautifulSoup
from urllib.request import urlopen
from datetime import datetime, timedelta
from traceback import format_exc

dir = os.path.dirname(__file__)
sys.path.insert(0, os.path.join(dir, '../save'))

import pandas_csv
import to_es

base_url = "http://news.naver.com/#"

def collecting(base_url):

    data = urlopen(base_url).read()
    soup = BeautifulSoup(data, "html.parser")
    total_data = soup.find_all(attrs={'class': 'main_component droppable'})

    colect_time = str(datetime.utcnow().replace(microsecond=0) + timedelta(hours=9))[:16]

    for each_data in total_data:

        category = ""

        try:
            category = str(each_data.find_all(attrs={'class': 'tit_sec'})).split('>')[2][:-3]
        except:
            pass

        data = str(each_data.find_all(attrs={'class': 'mlist2 no_bg'}))

        news_list = data.split('<li>')

        for each_news in news_list[1:]:

            news_block = each_news.split('href="')[1]
            # print(news_block)

            title = news_block.split('<strong>')[1].split('</strong>')[0]
            # print(title)
            news_url = news_block.split('"')[0].replace("amp;", "")
            # print(news_url)
            soup2 = BeautifulSoup(urlopen(news_url).read(), "html.parser")
            # print(soup2)

            # article_info = soup2.find_all(attrs={'class': 'article_info'})
            # print(article_info)

            article_body = str(soup2.find_all(attrs={'id': 'articleBodyContents'}))
            insert_data = {"source": "naver_news",
                           "category": category,
                           "title": title,
                           "article_body": article_body,
                           "colect_time": colect_time}

            pandas_csv.to_csv(insert_data)
            to_es.to_elastic(insert_data)


collecting(base_url)

위의 realtime.py는 한번 실행시키면 네이버 뉴스의 여러 카테고리의 뉴스들을 좌악~ 크롤링해 옵니다.

그리고 가져온 내용들은 for문을 돌며 각 뉴스 기사 단위로 insert_data라는 딕셔너리에 저장이 됩니다.

저는 이 딕셔너리를 csv와 elastic search 에 저장을 하게 코드를 짜 놓았습니다.

엘라스틱서치에 저장하는 소스코드는


pandas_csv.py

import os
import pandas as pd
from datetime import datetime, timedelta
import configparser
import glob

config = configparser.ConfigParser()
config.read('/home/yutw/project/searchnews/crawler/crawler.conf')

def to_csv(data):

    pathlink ="/home/yutw/data/searchnews"

    # db create
    if not os.path.isdir(pathlink):
        os.mkdir(pathlink)

    present_date = str(datetime.utcnow() + timedelta(hours=9))[:10]

    # col = ["source", "category", "title", "article_body", "colect_time"]

    if len(glob.glob(pathlink + "/" + present_date + ".csv")) == 1:
        cnt = len(pd.read_csv(pathlink + "/" + present_date + ".csv", index_col=0).index)
        time_pd = pd.DataFrame(data, index=[cnt])
        time_pd.to_csv(pathlink + "/" + present_date + ".csv", mode='a', header=False)
    else:
        cnt = 0
        time_pd = pd.DataFrame(data, index=[cnt])
        time_pd.to_csv(pathlink + "/" + present_date + ".csv", mode='a')



요 위까지 csv로 내용을 저장해 줍니다.

 

그리고 엘라스틱서치에도 insert를 해줍니다.

to_es.py

from elasticsearch import Elasticsearch
es = Elasticsearch([{'host': 'localhost', 'port': 9200}])
import json
import configparser
from datetime import datetime, timedelta
import pandas as pd

config = configparser.ConfigParser()
config.read('/home/yutw/project/searchnews/crawler/crawler.conf')

def to_elastic(data):

    pathlink = "/home/yutw/data/searchnews"

    present_date = str(datetime.utcnow() + timedelta(hours=9))[:10]
    del_date = str(datetime.utcnow() - timedelta(hours=39))[:10]

    cnt = len(pd.read_csv(pathlink + "/" + present_date + ".csv", index_col=0).index)
    es.index(index="searchnews", doc_type='naver_news', id=present_date + "-" + str(cnt), body=json.dumps(data))

    if cnt == 1:
        days = [x for x in range(1, int(del_date[-2:]))]
        for day in days:
            if len(str(day)) == 1:
                for each in range(1, 1500):
                    try:
                        es.delete(index="searchnews", doc_type="naver_news", id=del_date[:8] + '0' + str(day) + "-" + str(each))
                    except:
                        pass
            else:
                for each in range(1, 1500):
                    try:
                        es.delete(index="searchnews", doc_type="naver_news", id=del_date[:8] + str(day) + "-" + str(each))
                    except:
                        pass

 

이 후에 저장한 내용을 API로 뿌려주면 되는데요.

 

여기서 저는 Express 의 api를 사용해서 간단하게 API서버를 구현하였습니다.

우선 Express 기본 샘플 프로젝트를 만들어 줍니다.

https://github.com/gothinkster/node-express-realworld-example-app.git

이 곳을 통해 내려받거나 원하는 형태의 소스코드를 받으시고

api쪽(저는 routes의 디렉터리 안에 구성하였습니다.)


index.js

var express = require('express');
var fs = require('fs');
var router = express.Router();

var elasticsearch = require('elasticsearch');
var client = new elasticsearch.Client({
    host: 'localhost:9200',
    log: 'trace'
});


/* GET home page. */
router.get('/', function(req, res, next) {
  res.render('index', { title: 'Express' });
});

router.get('/newsSearch/:keyword', function (req, res) {
    client.search({
        q: req.params.keyword
    }).then(function (body) {
        var hits = body.hits.hits;
        res.json(hits);
    }, function (error) {
        console.trace(error.message);
    });
});

router.get('/user', function (req, res) {
    var value = {"test": "user"};
    console.log(value);
    res.json(value);
});


module.exports = router;

 

API 또한 원하는 방향으로 해주시면 됩니다. 위의 소스코드는 keyword에 따라 모두 검색할 수 있게 코드를 작성하였습니다.

이제 API서버도 동작시켜 줍니다.

그러면 모두 완료되었습니다. python realtime.py 를 실행하면 크롤링이 되고 자동으로 엘라스틱서치에도 insert가 되어 api서버에서 keyword를 검색하면 키워드에 맞는 뉴스들이 검색이 됩니다.

이런식으로 request를 보내면

이렇게 json형식으로 response가 떨어집니다. 여기까지 제 방식대로 코딩했는데요. 원하시는 내용을 코드를 편집해서 쓰셔도 됩니다.

여기까지 포스팅을 마칩니다.