使い捨てスクリプトの寿命を長くしよう

この記事は、[スタートトゥデイ工務店 Advent Calendar](http://qiita.com/advent-calendar/2016/stk) 1日目の記事です。

# はじめに
プログラムを組む仕事をやっていると、ちょこちょこ差込で「csvの集計やってくれない？」とか「データ集めてくれない？」とか言われますよね？
その他にも、「株のデータ集めたいな・・・」とか、個人でもちょっと気になったことを解決しようとするときに、プログラミングで解決しようとしたことあるんじゃないでしょうか。
1回で済むかなーと思って、雑にスクリプト書いて解決してみたものの、意外と追加で要求が発生したりするもんです。
使い捨てのスクリプトは使い捨てじゃないことが多いんです。
個人的にこんな経験を何度もしてるうちに、使い捨てで作ったスクリプトになんとなく共通点があるんじゃないかと思い始めました。
6、7割がた以下のような感じになるんじゃないかなーと。1はなかったりすることもありますが。
1. ファイル読み込み(元になるデータのインプット)
2. スクレイピング、DBから値引っ張る（外部データ取得）
3. 出力用に加工
4. 出力する(アウトプット)
これみてると、なんかバッチ処理に似てますよね。起動用パラメータもらって、DBとかから値引っ張って、集計して、出力して、結果コード返すみたいな。

# 設計テンプレート
ってことで、バッチ処理から名前をもらって以下のような設計を考えてみました。
実行パラメータをもらって処理を始める、Jobクラス。
WebやDBから値を引っ張ってくる、Taskクラス。
引っ張ってきた値を出力用に変換する、Coverterクラス。

![シーケンス図.png](https://qiita-image-store.s3.amazonaws.com/0/65020/5d1970ee-727e-cce9-bc8d-c011c8ba949d.png)


# サンプルコード概要
例として、[ZOZOTOWN](http://zozo.jp/)の商品の在庫をとってくるスクリプトを作ってみましょう。
監視する商品のURLが書いてあるitems.txtを読み込んで、在庫状態をstocks.txtへ出力するスクリプトです。
items.txtは以下のようなURLのリストです。

```
http://zozo.jp/shop/wego/goods-sale/14138391/?did=30898886
http://zozo.jp/shop/vanquish/goods/12997112/?did=29119523
```

stocks.txtは商品名と、色、サイズ、それらの在庫情報からなるテキストです。

# コード例
コードは[github](https://github.com/nittyan/batch/tree/master/example/stock)にあります。
githubのコードは、バッチのテンプレート用の親クラス郡を継承してるので、ここで説明するのより若干省略されてます。
メインの処理は以下のような感じです。

```py3:main.py
def main():
    reader = UrlReader('items.txt')                     # 今回の話とは関係ないファイル読み込みようクラスの初期化
    urls = reader.load()                                # ファイルから在庫確認する商品のURL一覧を取得
    batch = ItemStockConfirmJob(urls, ZozoItemFetchTask(), HtmlToStockConverter())  # 商品在庫を確認するクラスに、商品をスクレイピングしてくるクラスと、スクレイピングをしてとってきたHTMLを出力用に変換するためのコンバータクラスを渡します
    stocks = batch.run()                                # 処理を実行
    writer = StockWriter('stocks.txt')                  # ファイル出力用のクラスの初期化
    writer.write(stocks)                                # 今回の話とは関係ないファイル出力処理

if __name__ == '__main__':
    main()
```

ZozoFetchTaskは、与えられたURLのHTMLを取得して返すだけでシンプルです。

```py3:ZozoFetchTask
class ZozoItemFetchTask:

    def execute(self, param: str) -> str:                 # 引数paramにURLがわたってきます
        response = requests.get(param)
        response.encoding = response.apparent_encoding
        return response.text
```

ItemStockConfirmJobの処理は以下のような感じです。
与えられたパラメータに、それぞれTaskを実行して、得られたHTMLを引数にConverterを実行するだけです。

```py3:ItemStockConfirmJob
class ItemStockConfirmJob:
    
   def __init__(self, parameters: List[str], task: ZozoItemFetchTask, converter: HtmlToStockConverter):
       self._parameters = parameters
       self._task = task
       self._converter = converter

   def run() -> List[Stock]
       return [self._converter.convert(self._task.execute(p)) for p in self._parameters]
```

HtmlToStockConverterはHTMLのパースが煩雑なだけなので、気になるようであれば[github](https://github.com/nittyan/batch/tree/master/example/stock)で確認してください。

# まとめ
大体の場合、Converterの処理が複雑だったり、単調になったりすると思いますが、その他のところはかなり単純にいけるかなと。
今回は、商品のURLをあらかじめ用意していましたが、ランキングの情報を取得して、それの在庫情報を確認する場合には、あらたにJob、Task、Converterのセットを追加して、その結果を今回のItemStockConfirmクラスのパラメータに渡すようにすれば対応できます。
Job、Task、Converterのセットの機能をなるべくシンプルにすれば、追加仕様にも、新たなJob、Task、Converterのセットの追加で対応しやすくなります。

まぁ、在庫の確認は[ZOZOTOWNアプリ](https://play.google.com/store/apps/details?id=jp.zozo.android.town&hl=ja)とかだとリアルタイムに教えてくれる機能があるので、そっち使ってくださいね・・・