スクレイピングしたデータから、pythonで正規表現使って日付時刻とか取ってきた話

# 解決したい問題とか:octocat:
にゃんぱす〜  
自分の研究室とかプロジェクトではTrelloっていうタスク管理サービス使ってその日に何やったのかとかメモってるわけなんですが、ある日大学から「一日ごとに何時間活動して、何を活動したかとか学内ネットワークからログインして記録とってね」みたいなこといわれるですが、まあ面倒。  
なぜわざわざ、linuxからログインすることさえできない学内ネットワーク使ってそんなことやらにゃいかんのかと。  
こんな無駄なことに毎年何百人という学生の時間が割かれているのはクソクソなので、なにかスマートに解決する方法を考えました

## どうやって解決するのん？っていう概要:octocat:
**Trelloに記入した情報をスクレイピングで取得して、それをなんかかんか編集して、学内ネットに記入するやつをつくる**、っていう方針でいこう

じゃあどんな技術とかライブラリとか使うんかね？っていうのを部分ごとに考えてみる。

- Trelloに記入した情報をスクレイピングで取得  
Beautifulsoup4使う。  
- 学内ネットに記入する  
Selenium使う。  
- なんかかんか編集する  
正規表現でうまいこと日付とか活動時間とか取得して、TrelloのMarkdownテキストをそれっぽい形にする。  
例えば:v:とかの絵文字消したりとかね:wink:

え、なんかできそうじゃね？:octocat::octocat::octocat:


## 手法の選定、考察(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でよくね？ってなる。  

## それでどっちを使うの？っていう:octocat:
悩んだけども、結局**selenium＆beautifulsoup**:clap:でいくことにした。  
### Why
ユーザは主にCUI慣れしていない大学生なので、実際にブラウザが自動的に制御されて記入されていく感じを見れると満足度高くなるし、「すげぇ、こんなの自分も作ってみたい！」ってなふうにソフトウェアへの関心が高まればいいなぁっていうことで、こっちにしました。  
メンテナンスが必要になるっていうデメリットは確かに大きいですよね
まあしばらくは自分が大学にいるんで、その間の数年は大丈夫でしょうという感じ
それまでにスクレイピングについて分かる人が来てくれたらその後も安心なんだけどなぁ(勧誘)  

## 作ってみた
:octocat::octocat::octocat:[ここにソースコードおいとく](https://github.com/OkanoShogo0903/fill_in_activities):octocat::octocat::octocat:  

## 正規表現部分
こんな感じで日付とか時刻とか曜日とかを取ってきてる
クラス変数としてコンパイルしてるのは、何回も正規表現を使い倒すからですね。
あとなんか頭良くみえる:octocat:

~~~python
 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のレイアウト変わると即死するのはやっぱな〜、自分がメンテすればいい話だけどつらいよね〜