歩いたら休め

If the implementation is easy to explain, it may be a good idea.

【Python2】弊社のコーポレートサイトが更新してたらChatworkに通知するbotを作った

私、ずっと弊社にいたのに、弊社のこと全然分かってあげられてなかった…。

「他の部署が面白いことやってるのに、社長が話してて初めて知った!」ってことが多かったので、 弊社のコーポレートサイトが更新したらChatworkに通知するbotを作りました。

ChatworkAPIに関してはこちらの記事を参考にしました。

qiita.com

本当はclassごとにファイルを分けるべきなんでしょうが、 jupyter notebookを使って作ったので1ファイルで作っちゃいました。

Python2.7とBeautifulSoup4を利用しました。 これをCronで一日おきにチェックすればいいかな。

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

import urllib
import urllib2
import pycurl
from bs4 import BeautifulSoup

class heishaNewsScraper:
    def set_url(self, url):
        self.url = url

    def set_stop_path(self, path):
        self.stop_path = path

    def get_news_list(self):
        soup = self._url_to_soup(self.url)
        news_list = self._soup_to_list(soup)
        return news_list

    def read_stopfile(self):
        try:
            with open(self.stop_path, 'r') as f:
                saved_dates =  f.read()
            return int(saved_dates.rstrip('\n'))
        except:
            return 0 # ストップファイルがない場合0を返すので、初回実行時には全部タレ流してしまうことに注意

    # ストップファイル(現時点の最新記事の日付が20160101の形式で保存されているファイル)を保存する
    def save_stopfile(self, news_list):
        newest_date = self._get_newest_date(news_list)
        with open(self.stop_path, 'w') as f:
            f.write(str(newest_date))

    def _url_to_soup(self, url):
        html = urllib2.urlopen(url)
        return BeautifulSoup(html)
    
    def _soup_to_list(self, soup):
        news_soups = self._get_news_soups(soup)
        return self._get_news_infos(news_soups)

    def _get_news_soups(self, soup):
        return soup.find(class_='***').find(class_='*******').findAll(class_='*****')

    def _get_news_infos(self, news_soups):
        return [self._get_news_info(n) for n in news_soups]

    # いろいろやった後に(日付, カテゴリー, タイトル, URL)を返す関数
    def _get_news_info(self, li_soup):
        ret_list = [self._get_soup_string(li_soup.find(class_=c)) 
                   for c in ('date', 'cat')]
        ret_list[0] = self._rm_non_number(ret_list[0])
        title_soup = li_soup.find(class_='title').find('a')
        title = self._get_soup_string(title_soup)
        url = self._make_url(title_soup.get('href'))
        ret_list.extend([title, url]) # 破壊的操作
        return tuple(ret_list)

    # たまに他のディレクトリを参照しているURLがあったので対策
    def _make_url(self, url):
        if 'http' in url:
            return url
        else:
            return '/'.join(self.url.split('/')[:3]) + url

    def _get_newest_date(self, news_tuple):
        dates = map(lambda x: x[0], news_tuple)
        sorted(dates, reverse=True)
        return dates[0]

    # .find(***)で対象のタグが見つからず、Noneタイプが返ってきている場合の対策
    def _get_soup_string(self, soup):
        try:
            return soup.string
        except:
            return None

    # 比較しやすくするため、日付から数字以外の部分を取り除く
    def _rm_non_number(self, string):
        return int(''.join(c for c in string if c.isdigit()))

class chatWorkBot:
    def set_roomid(self, roomid):
        self.roomid = roomid

    def set_token(self, token):
        self.token = token

    def make_message(self, message_tuple):
        ascii_message = u'弊社のニュースが更新されました\n' +\
                str(message_tuple[0]) + ': ' + message_tuple[2] + '\n' +\
                message_tuple[3]
        return unicode(ascii_message).encode('utf_8') # Python2系だと文字コードの処理が面倒だよね…

    def send_message(self, message):
        option = {u'body': message}
        c = pycurl.Curl()
        c.setopt(pycurl.URL, 'https://api.chatwork.com/v1/rooms/%s/messages'%str(self.roomid))
        c.setopt(pycurl.HTTPHEADER, ['X-ChatWorkToken: %s'%self.token])
        c.setopt(pycurl.POST, 1)
        # http://stackoverflow.com/questions/8332643/pycurl-and-ssl-cert 
        # c.setopt(pycurl.SSL_VERIFYPEER, 0)
        # c.setopt(pycurl.SSL_VERIFYHOST, 0)
        c.setopt(pycurl.POSTFIELDS, urllib.urlencode(option))
        c.perform()

def main():
    scraper = heishaNewsScraper()
    scraper.set_url('http://*************.***/****/')
    scraper.set_stop_path('./STOPFILE')
    news_list = scraper.get_news_list()

    saved_date = scraper.read_stopfile()
    ratest_news = [x for x in news_list if x[0] > saved_date]
    
    # ストップファイルに記録されている日付以降の情報をbotで投稿する 
    bot = chatWorkBot()
    bot.set_roomid(*****) # chatworkの部屋のid
    bot.set_token('*****************') # Chatworkのtoken
    for n in ratest_news:
        message = bot.make_message(n)
        bot.send_message(message)
    
    # ストップファイルを更新する
    scraper.save_stopfile(news_list)

if __name__ == '__main__':
    main()