解決したい問題とか
にゃんぱす〜
自分の研究室とかプロジェクトではTrelloっていうタスク管理サービス使ってその日に何やったのかとかメモってるわけなんですが、ある日大学から「一日ごとに何時間活動して、何を活動したかとか学内ネットワークからログインして記録とってね」みたいなこといわれるですが、まあ面倒。
なぜわざわざ、linuxからログインすることさえできない学内ネットワーク使ってそんなことやらにゃいかんのかと。
こんな無駄なことに毎年何百人という学生の時間が割かれているのはクソクソなので、なにかスマートに解決する方法を考えました
どうやって解決するのん?っていう概要
Trelloに記入した情報をスクレイピングで取得して、それをなんかかんか編集して、学内ネットに記入するやつをつくる、っていう方針でいこう
じゃあどんな技術とかライブラリとか使うんかね?っていうのを部分ごとに考えてみる。
- Trelloに記入した情報をスクレイピングで取得
Beautifulsoup4使う。
- 学内ネットに記入する
Selenium使う。
- なんかかんか編集する
正規表現でうまいこと日付とか活動時間とか取得して、TrelloのMarkdownテキストをそれっぽい形にする。
例えばとかの絵文字消したりとかね
え、なんかできそうじゃね?
手法の選定、考察(selenium&beautifulsoup vs API)
Trelloにログインして情報取ってくるっていうのは、selenium&beautifulsoupでやる方法もあるけど、実はTrelloAPIっていうのがあって、そっちでやる方法もある。
そこでメリットデメリットを整理してみる
-
selenium&beautifulsoup
- メリット
画面が実際に自動で制御されているところをユーザに見せることができるので、人に使ってもらったときに満足感が得られやすい
- デメリット
Trelloのレイアウト変更で即使えなくなる。直すのは簡単だけど、そのたびにgit pull
してもらう必要がある
- メリット
画面が実際に自動で制御されているところをユーザに見せることができるので、人に使ってもらったときに満足感が得られやすい
-
TrelloAPI
- メリット
APIの仕様はTrelloのページレイアウトよりも変わりにくく、まあ2年ぐらいは正常に動作するだろうってことでメンテナンスがあまり必要ないかも
API使ったほうが、コメントを誰が書いたのかとか、誰がいつ返信したのかっていう情報が扱いやすい
CUIなので動作軽い
- デメリット
APIのキー?だったかの有効期限が30日ごととかで、30日ごとにseleniumとbeautifulsoup使ってAPIキーだったかを更新するコードを実装する必要が出てくるので、それなら最初から全部seleniumとbeautifulsoupでよくね?ってなる。
- メリット
それでどっちを使うの?っていう
悩んだけども、結局selenium&beautifulsoupでいくことにした。
Why
ユーザは主にCUI慣れしていない大学生なので、実際にブラウザが自動的に制御されて記入されていく感じを見れると満足度高くなるし、「すげぇ、こんなの自分も作ってみたい!」ってなふうにソフトウェアへの関心が高まればいいなぁっていうことで、こっちにしました。
メンテナンスが必要になるっていうデメリットは確かに大きいですよね
まあしばらくは自分が大学にいるんで、その間の数年は大丈夫でしょうという感じ
それまでにスクレイピングについて分かる人が来てくれたらその後も安心なんだけどなぁ(勧誘)
作ってみた
正規表現部分
こんな感じで日付とか時刻とか曜日とかを取ってきてる
クラス変数としてコンパイルしてるのは、何回も正規表現を使い倒すからですね。
あとなんか頭良くみえる
class Comment:
"""News information class"""
date_patterns = [\
re.compile(r'([0-1]?[0-9])(?:/|-|\.)([0-3]?[0-9])'),\
re.compile(r'([0-1]?[0-9])月([0-3]?[0-9])日?'),\
]
# 4/01 ???
time_patterns = [\
re.compile(r'([0-2]?[0-9]):([0-5]?[0-9]) ?(?:~|-) ?([0-2]?[0-9]):([0-5]?[0-9])'),\
]
address_patterns = [\
re.compile(r'(?:to|To|TO):([a-zA-Z]+)'),\
]
university_pattern = [\
re.compile(r'(U|u)niv?')
]
private_pattern = [\
re.compile(r'(P|p)rivate')
]
day_of_the_week_pattern = [\
re.compile(r' [月火水木金土日]'),\
re.compile(r'\([月火水木金土日]\)')
]
def __init__(self, _html_string):
''' 変数初期化して、引数のテキストから整形とか情報の抽出とかする関数の呼び出す '''
self.plain_html_text = _html_string
self.address = 'university' # デフォルトで大学行き
self.timestamp = None
self.body_text = None
self.activity_time = datetime.timedelta()
self.ExtractInfosFromPlainText()
self.MarkdownTextToPlainText()
def ExtractInfosFromPlainText(self):
'''
引数のhtmlテキストからタイムスタンプ等の情報を抽出する
separate to body_text,timestamp,address
'''
text = self.plain_html_text # <str>
# TIME STAMP
for pattern in self.date_patterns: # re pattern
obj = pattern.search(text)
if obj == None:
pass
#return -1 # or, raise ERR
else:
#print(obj.groups())
month,day = obj.groups() # taple
# set timestamp
year = datetime.date.today().year # int
self.timestamp = datetime.date(year=year,month=int(month),day=int(day))
print("time stamp : ",self.timestamp)
# ACTIVITY TIME
for pattern in self.time_patterns: # re pattern
obj_list = pattern.findall(text)
for obj in obj_list:
s_h,s_m,e_h,e_m = obj
s_time = datetime.timedelta(hours=int(s_h),minutes=int(s_m))
e_time = datetime.timedelta(hours=int(e_h),minutes=int(e_m))
self.activity_time += e_time - s_time
print("activity time : ",self.activity_time)
# ADDRESS
for pattern in self.address_patterns: # re pattern
obj = pattern.search(text)
if obj == None:
pass
else:
# obj.group example "to:univ" -> ('univ',)
#print(obj.groups())
string, = obj.groups() # for catch univ # taple string to plane string
for p in self.university_pattern + self.private_pattern: # re pattern
if p.search(string) != None:
self.address = 'university'
elif p.search(string) != None:
self.address = 'private'
#elif :
print("address : ",self.address)
def MarkdownTextToPlainText(self):
''' Markdownで書かれたものを活動記録に記入する文に変える '''
text = self.plain_html_text # <str>
#modify = re.sub(r"(@[a-zA-Z0-9_]*:)+","",modify)
# 正規表現のパターンにマッチングする文字列を取り除く
pattern_markdown = [\
# 強調 \w \^wを使うか?
#re.compile(r'\*+(.| )+\*+'),\
# 顔文字消し
re.compile(r':(.)+:'),\
# 見出し消し
re.compile(r'#+ '),\
]
pattern_sum = \
self.date_patterns + \
self.time_patterns + \
self.address_patterns + \
self.university_pattern + \
self.private_pattern + \
self.day_of_the_week_pattern
for p in pattern_sum:
text = re.sub(p,'',text)
for p in pattern_markdown:
# 複数のグループに対してそれぞれ本文だけを残して置換する?
# text = text.replace("*", "") でよくね?
text = re.sub(p,'', text)
# 非正規表現のパターンを消してく
text = text.replace("<br/>", "\n")
text = text.replace("*", "")
self.body_text = text
print(self.body_text)
やってみての使用感とかそんなかんじの
- ヴァージョンの話
新しくpython使ってなにか作ろうとするなら、python3.5以降を使うことをおすすめしますね。
pythonの正規表現を扱うreライブラリですが、3.5から固定長なんたらとかいうものも使えるようになるらしく、3.5がちょっとした節目になっているように感じました。
- vim
html読むときとか最強
筆者は
=
コマンドをこのサービス作ってる時に初めて知ったんですが、やっぱvimは素晴らしい - スクレイピング
github見ればわかるけど、動けばいいやの精神で作ったスクレイピング部分の実装がなかなかひどい。
Trelloのレイアウト変わると即死するのはやっぱな〜、自分がメンテすればいい話だけどつらいよね〜