안녕하세요 뜨거운 여름동안 잘 쉬다가 오랜만에 포스팅을 하게 되었습니다.
이번에는 제가 개인적으로 진행하고 있는 프로젝트의 소스와 내용을 포스팅하게 되었습니다.
그 내용은 제목에 있습니다. 자세히 표현하자면 네이버 뉴스를 1시간마다 크롤링하여
크롤링한 뉴스의 내용을 엘라스틱 서치라는 검색엔진 API로 뿌려주는 겁니다.
일단 사전에 환경이 준비되어 있어야 합니다. 그 환경은 아래에 기재하였습니다.
- 엘라스틱서치를 설치하고 실행까지 해주셔야 합니다.
- Beautifulsoup와 pandas와 같은 파이썬 패키지를 설치해 주셔야 합니다.
- REST API로 api를 구현하되 원하는 프레임워크 및 라이브러리로 구현을 해주시면 됩니다.(저는 nodeJS의 Express로 구현 하였습니다.)
- 위의 내용들은 저의 환경이니 각자 원하시는 환경으로 커스터마이징 하셔도 됩니다.
일단 뉴스 내용을 수집해야 하니 크롤러부터 들어갑니다.
바로 소스코드 공개합니다.
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가 떨어집니다. 여기까지 제 방식대로 코딩했는데요. 원하시는 내용을 코드를 편집해서 쓰셔도 됩니다.
여기까지 포스팅을 마칩니다.