CSVファイルから記事を自動生成する

この記事は[tomowarkar ひとりAdvent Calendar 2019](https://qiita.com/advent-calendar/2019/tomowarkar)の9日目の記事です。
# はじめに
この記事ではPythonを使った記事の自動生成について書いていきます。

正規表現を勉強したかったために、少しトリッキーな自動生成をしてみました!!


また今回のコードは[おひとり様カレンダーランキング](https://qiita.com/tomowarkar/items/c3939c0985c36929b35a)を作る際に書いたコードの一部で、

[Qiita API で投稿を自動化する](https://qiita.com/tomowarkar/items/11488231c6d22d960323)の続きの記事なのでよかったら一緒にお読みください。
### ファイルツリー

```
.
├── article
│   └── item.md
├── create_article.py
└── output.csv
```
##### article/
`article/`は[Qiita API で投稿を自動化する](https://qiita.com/tomowarkar/items/11488231c6d22d960323)で扱っている内容となりますが、Qiitaに投稿する内容を管理するディレクトリです。`item.md`は投稿する記事の内容を保持します。
##### create_article.py
今回のメインの処理を書いていきます。
##### output.csv
以下のようなタイトル名やurlをもつデータテーブルとします。

|title|url|like|mark|latest_title|latest_url|
|---|---|---|---|---|---|
|tomowarkar ひとり Advent Calendar 2019|https://qiita.com/advent-calendar/2019/tomowarkar|1000|1000|タイトル|https://qiita.com/items/c33c1137b15cf5f5dcf1|
|...||||||||

# コード全文
```create_article.py
import re
import pandas as pd

def find_template_vars(template):
    template_vars = re.findall("{{\s[.]+\w+\s}}", template)
    return [re.findall("\w+", r)[0] for r in list(set(template_vars))]

def create_dictionary(df_row, ranking_num):
    return {
        "ranking": str(ranking_num),
        "calender_name": df_row["title"],
        "calender_url": df_row["url"],
        "likes": str(int(df_row["like"])),
        "fans": str(int(df_row["mark"])),
        "article_name": df_row["latest_title"],
        "article_url": df_row["latest_url"],
    }

def create_contents(df, template):
    contents = ""
    targets = find_template_vars(template)
    ranking_num = 1
    for index, row in df.iterrows():
        d = create_dictionary(row, ranking_num)
        tmp = template
        for target in targets:
            if target in d:
                tmp = re.sub("{{ ." + target + " }}", d[target], tmp)
        contents += tmp
        ranking_num += 1
    return contents

def write_to(path, article):
    with open(path, "w") as f:
        f.write(article)

item = r"""
### {{ .ranking }}. [{{ .calender_name }}]({{ .calender_url }})
👍いいね数　👍:  {{ .likes }}

⭐️　購読数　⭐️:  {{ .fans }}

📚最新の記事📚:  [{{ .article_name }}]({{ .article_url }})

"""

if __name__ == "__main__":
    df = pd.read_csv("output.csv", index_col=0)
    subtitle = "# おひとり様カレンダーランキング\n"
    contents = create_contents(df, item)
    write_to(f"article/item.md", subtitle + contents)

```

# コード解説
ただforループを回して演算子で記事を作っていくのもいいのですが、もう少しだけ拡張性、可読性をあげるためにテンプレートアイテムを定義して、テンプレートアイテム内の変数を正規表現で置換していくという方法をとってみました。今回の場合、

1. `{{ .var }}`という形でテンプレートアイテム内の変数を定義、
2. 変数名とデータの対応表を作成
3. 対応表をもとにテンプレートアイテム内の変数を置換

といった感じです。

正規表現はかなり苦手なんですが、今回で3mmだけ距離が近まった気がします。。。💦

### find_template_vars(template)
テンプレート内から変数を抽出する関数

```py
def find_template_vars(template):
    template_vars = re.findall("{{\s[.]+\w+\s}}", template)
    return [re.findall("\w+", r)[0] for r in list(set(template_vars))]

item = r"""
### {{ .ranking }}. [{{ .calender_name }}]({{ .calender_url }})
👍いいね数　👍:  {{ .likes }}

⭐️　購読数　⭐️:  {{ .fans }}

📚最新の記事📚:  [{{ .article_name }}]({{ .article_url }})

"""
targets = find_template_vars(item) 
# > ['calender_url', 'ranking', 'article_name', 'article_url', 'likes', 'fans', 'calender_name']
```
1. `re.findall("{{\s[.]+\w+\s}}", template)`の部分で`{{ .var }}`を全て抽出
2. `list(set(template_vars))`で重複を削除
3. `re.findall("\w+", r)[0]`で変数名のみに

このような形を取ってます。
もっとスマートにできそうな気もしますが、今のぼくではここが限界。

### create_dictionary(df_row, ranking_num)
変数名と対応するデータペアを作成

```py
def create_dictionary(df_row, ranking_num):
    return {
        "ranking": str(ranking_num),
        "calender_name": df_row["title"],
        "calender_url": df_row["url"],
        "likes": str(int(df_row["like"])),
        "fans": str(int(df_row["mark"])),
        "article_name": df_row["latest_title"],
        "article_url": df_row["latest_url"],
    }
```
### create_contents(df, template)
ランキング作成

```py
def create_contents(df, template):
    contents = ""
    targets = find_template_vars(template)
    ranking_num = 1
    for index, row in df.iterrows():
        d = create_dictionary(row, ranking_num)
        tmp = template
        for target in targets:
            if target in d:
                tmp = re.sub("{{ ." + target + " }}", d[target], tmp)
        contents += tmp
        ranking_num += 1
    return contents
```
テンプレート内の変数の中から辞書で対応表で定義されている時に置換。

```py

    for target in targets:
        if target in d:
            tmp = re.sub("{{ ." + target + " }}", d[target], tmp)
```
# 出力結果

```article/item.md

# おひとり様カレンダーランキング
### 1. [tomowarkar ひとり Advent Calendar 2019](https://qiita.com/advent-calendar/2019/tomowarkar)
👍いいね数　👍:  1000

⭐️　購読数　⭐️:  1000

📚最新の記事📚:  [タイトル](https://qiita.com/items/c33c1137b15cf5f5dcf1)
```

より詳細な出力結果はこちら: [おひとり様カレンダーランキング](https://qiita.com/tomowarkar/items/c3939c0985c36929b35a)

# まとめ
今回のような行数の少ない記事であれば演算子で直接記事を作る方がよかったとは思います。

ですが、正規表現を使った変数置換をやってみたかったのでうまく動いてよかったです。

もっと同じ変数がたくさん使われる場合や長文になってくると今回のような正規表現を用いた置換の方がより見通し良くなりそうですね。

以上明日も頑張ります！！
[tomowarkar ひとりAdvent Calendar Advent Calendar 2019](https://qiita.com/advent-calendar/2019/tomowarkar)