Djangoを使ってみる#4

#概要
OSSの更新情報をスクレイピングで集めて、DBに登録する部分を作ります。

#環境
- Django==2.0
- beautifulsoup4==4.6.0
- requests==2.18.4
- lxml==4.1.0

#参考
下記サイトを参考にしました。
https://tonari-it.com/python-html-get-text-attr/
https://qiita.com/itkr/items/513318a9b5b92bd56185
https://qiita.com/matsu0228/items/edf7dbba9b0b0246ef8f

#手順
##スクレイピング
requrstsを用いてスクレイピングしたいページをGETします。レスポンスがのステータスコードが20x系以外の場合は例外を起こし、soupにエラーページを返します。20x系の場合はGETしたページがパースされてsoupに返されます。
今回は各OSSのトップページのnews欄から、リリース情報を抜き出し、バージョンとリリース日を取得するようにしました。

```python:scraping.py
# coding: utf-8

import requests #httpリクエストを実行
import bs4 #BeutifulSoupをインポート
import re #正規表現を利用
from datetime import *

url_apachehttpd = 'https://httpd.apache.org/'
url_apachetomcat = 'http://tomcat.apache.org/'
url_openssl = "https://www.openssl.org/"


def getsoup(url):
	res = requests.get(url)
	try:
		res.raise_for_status() #20x系以外の場合例外を起こす
	except requests.exceptions.HTTPError:
		html = """<html><head><title>Error</title></head></html>"""
		soup = bs4.BeautifulSoup(html,'lxml')
	else:
		soup = bs4.BeautifulSoup(res.text,'lxml')
	finally:
		return soup


def apachehttpd_scraping():
	soup = getsoup(url_apachehttpd)
	elems = soup.select('h1') #タグすべてをリストに取得
	#バージョン取得
	list = []
	for elem in elems:
		text = elem.getText()
		if text.find("Released") == -1:
			pass
		elif text.find("End") != -1:
			pass
		else:
			retext = re.findall('[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}' , text)
			strRawdate = elem.select('span')[0].getText()
			dateDatetime = datetime.strptime(strRawdate, '%Y-%m-%d')
			dateDate = date(dateDatetime.year, dateDatetime.month, dateDatetime.day)
			#print(dateDate)#リリース日
			list.append([retext[0],dateDate])
			
	return list


def apachetomcat_scraping():
	soup = getsoup(url_apachetomcat)
	elems = soup.select('h3') #タグすべてをリストに取得
	
	list = []
	for elem in elems:
		text = elem.get("id")
		pattern = "Tomcat_[0-9]{1}"
		
		if re.match(pattern , text) != None:
			retext = re.findall('[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}' , text)
			strRawdate = elem.select("span")[0].getText()
			dateDatetime = datetime.strptime(strRawdate, '%Y-%m-%d')
			dateDate = date(dateDatetime.year, dateDatetime.month, dateDatetime.day)
			list.append([retext[0],dateDate])
			
	return list


def openssl_scraping():
	soup = getsoup(url_openssl)
	table = soup.findAll("table",{"class":"newsflash"})
	rows = table[0].findAll("tr")
	list = []
	for row in rows:
		text = row.select("td.t")[0].getText()
		pattern = "OpenSSL [0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}[a-z] is now available"
		
		if re.match(pattern , text) != None:
			strRawdate = row.select("td.d")[0].getText()
			dateDatetime = datetime.strptime(strRawdate, '%d-%b-%Y')
			dateDate = date(dateDatetime.year, dateDatetime.month, dateDatetime.day)
			retext = re.findall('[0-9]{1,}\.[0-9]{1,}\.[0-9]{1,}[a-z]' , row.select("td.t")[0].getText())#リリース日
			list.append([retext[0], dateDate])
			
	return list


if __name__ == '__main__':
    print(apachehttpd_scraping())
    print(apachetomcat_scraping())
    print(openssl_scraping())

```

##DB登録
DBへのデータ登録はSQLで行ってもいいのですが、せっかくなのでDjangoの機能を使用しました。createがSQLのinsertに対応しています。Djangoの機能を使用するためには次のおまじないが必要になります。

```python:おまじない
import django

sys.path.append("C:\oss_project")
os.environ.setdefault("DJANGO_SETTINGS_MODULE","oss_project.settings")

django.setup()

```
setting.pyファイルがどこにあるかを教えてあげているようです。他のバージョンのDjangoではどうなのか気になるところです。

scraping.pyで取得したデータをDBに登録します。登録日時も生成して登録しています。今回はバージョンとリリース日をキーにしてあります。データが重複した場合は例外が発生しますが、今回は正常処理ということでpass文で特に何もしないようにしました。

```python:dbimport.py
# coding: utf-8

from datetime import *
from dateutil import tz
import scraping as sc
from django.db import IntegrityError

import sys
import os
import django

sys.path.append("C:\oss_project")
os.environ.setdefault("DJANGO_SETTINGS_MODULE","oss_project.settings")

django.setup()
from info.models import *

for list in sc.apachehttpd_scraping():
	oss_id = 1
	version = list[0]
	released = list[1]
	now = datetime.now().replace(tzinfo=tz.tzlocal())
	registrated = datetime(now.year, now.month, now.day, now.hour, now.minute, now.second)
	try:
		Info.objects.create(oss_id=oss_id, version=version, released=released, registrated=registrated)
	except IntegrityError:
		pass
	
for list in sc.apachetomcat_scraping():
	oss_id = 2
	version = list[0]
	released = list[1]
	now = datetime.now().replace(tzinfo=tz.tzlocal())
	registrated = datetime(now.year, now.month, now.day, now.hour, now.minute, now.second)
	try:
		Info.objects.create(oss_id=oss_id, version=version, released=released, registrated=registrated)
	except IntegrityError:
		pass
	
	
for list in sc.openssl_scraping():
	oss_id = 3
	version = list[0]
	released = list[1]
	now = datetime.now().replace(tzinfo=tz.tzlocal())
	registrated = datetime(now.year, now.month, now.day, now.hour, now.minute, now.second)
	try:
		Info.objects.create(oss_id=oss_id, version=version, released=released, registrated=registrated)
	except IntegrityError:
		pass
```

##バッチ
あとはdbimport.pyを実行するバッチを作成します。今回は実行されたことがわかるようにリダイレクトでバッチログを出力します。エラーレベルに応じてログの出力を変えています。

```*.bat:main_batch.bat
cd /d %~dp0
chcp 932
echo %DATE%%TIME% "バッチ処理を開始します" >> "C:\oss_project\logs\batch_log.txt"
C:\Users\xxxxx\Anaconda3\python.exe "C:\oss_project\dbimport.py" %*

if %ERRORLEVEL% NEQ 0 goto FAILURE

:SUCCESS
echo %DATE%%TIME% "バッチ処理を正常終了します" >> "C:\oss_project\logs\batch_log.txt"
goto END

:FAILURE
echo "バッチ処理を異常終了します" >> "C:\oss_project\logs\batch_log.txt"

:END
exit
```

##さいごに
あとはWindowsのタスクスケジューラでbatを定期的に実行してやればよいです。

少々URL等、実装が異なっていますが、下記がお手本サイトになります。
https://ground0state.com/oss/update_info/

以上でいったん「Djangoを使ってみる」を終わらせたいと思います。ログイン機能やセッション管理は改めて、別のサイト構築をモデルに行いたいと思います。