1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

tomowarkar ひとりAdvent Calendar 2019

Day 9

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

Last updated at Posted at 2019-12-07

この記事はtomowarkar ひとりAdvent Calendar 2019の9日目の記事です。

はじめに

この記事ではPythonを使った記事の自動生成について書いていきます。

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

また今回のコードはおひとり様カレンダーランキングを作る際に書いたコードの一部で、

Qiita API で投稿を自動化するの続きの記事なのでよかったら一緒にお読みください。

ファイルツリー

.
├── article
│   └── item.md
├── create_article.py
└── output.csv
article/

article/Qiita API で投稿を自動化するで扱っている内容となりますが、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)

テンプレート内から変数を抽出する関数

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)

変数名と対応するデータペアを作成

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)

ランキング作成

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

テンプレート内の変数の中から辞書で対応表で定義されている時に置換。


    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)

より詳細な出力結果はこちら: おひとり様カレンダーランキング

まとめ

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

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

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

以上明日も頑張ります!!
tomowarkar ひとりAdvent Calendar Advent Calendar 2019

1
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?