Qiitaの記事をバックアップする（Python）

QiitaではAPIが公開されているので、これを利用して記事をバックアップします。

https://qiita.com/api/v2/docs

## 1. QiitaのAPIを利用する
### Qiitaのアクセストークンを取得する
Qiitaにログインした状態で、[[設定] - [アプリケーション]](https://qiita.com/settings/applications)にアクセスします。
個人用アクセストークンの「新しくトークンを発行する」をクリックします。

![qiita_api_01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/a2f29e72-d648-5a30-cb04-afaa83b387e3.png)


アクセストークンの説明を入力し、read_qiitaにチェックを入れ、「発行する」ボタンをクリックします。

![qiita_api_02.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/18e32c89-c39c-1b21-61bf-d0352124f5a3.png)

アクセストークンが表示されるので、控えておきます。

![qiita_api_03.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/5d3be7cd-aa59-62dd-0aa0-d309f1e442fc.png)

### APIを使用する
投稿した記事一覧を取得するには`GET /api/v2/authenticated_user/items`のリソースを使用します。
PostmanなどのAPIクライアントを使用してQiita APIを叩きます。
ヘッダーに`Authorization: Bearer {qiitaで取得したアクセストークン}`を追加します。
Bearerとアクセストークンの間に半角スペースが必要です。

![qiita_api_04.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/354aad72-fce4-3772-b463-c6828089fb41.png)

下記のようなレスポンスがかえってきます。`body`にマークダウンの本文が設定されています。以下ではこれをPythonで取得するプログラムを作成します。

```jsonc
[
    {
        "rendered_body": "\n<h2>\n<span id=\"apex-pmdとは\" class=\"fragment\"></span>...",
        "body": "## Apex PMDとは\nApex PMDはApexのソースコードを静的解析...",
        "coediting": false,
        "comments_count": 0,
        "created_at": "2021-09-01T00:30:21+09:00",
        "group": null,
        "id": "d85a93009acefceaf10d",
        "likes_count": 2,
        "private": false,
        "reactions_count": 0,
        "tags": [
            {
                "name": "Salesforce",
                "versions": []
            },
            {
                "name": "Apex",
                "versions": []
            },
        ],
        "title": "[Apex PMD] VSCodeでApexの静的解析を行う",
        "updated_at": "2021-09-01T00:30:21+09:00",
        "url": "https://qiita.com/TaaaZyyy/items/d85a93009acefceaf10d",
// 略
```

## 2. Pythonでバックアップツールを実装する
下記の「FILL_ME_IN」をアクセストークンに置換して実行するとバックアップできます。

```py:qiita_backup_tool.py
#!/usr/bin/python
##########################################################################################
# SECURITY_TOKENにQiitaのアクセストークンを設定してください。
SECURITY_TOKEN = 'FILL_ME_IN'
DIRECTORY_NAME = 'output'
##########################################################################################

import urllib.request
import json
import os
import shutil
from datetime import datetime
from io import BytesIO

# download function
def download_qiita():
    print('Qiita記事のバックアップを開始します。')
    dir = DIRECTORY_NAME

    # バックアップフォルダをリフレッシュ
    if os.path.exists(dir):
        shutil.rmtree(dir)
    os.makedirs(dir)

    # 一度に100件までしか取得できないため、ループで処理する
    for i in range(100):
        # Qiita記事を取得
        url = 'https://qiita.com/api/v2/authenticated_user/items'
        url += '?page=' + str(i + 1) + '&per_page=100'
        headers = {'Authorization' : 'Bearer ' + SECURITY_TOKEN}
        req = urllib.request.Request(url, None, headers)
        res = urllib.request.urlopen(req)
        items = json.load(res)
        res.close

        if not items:
            break

        # Qiita記事をマークダウン形式で保存
        for item in items:
            body = item['body']
            private = item['private']
            url = item['url']
            title = item['title']
            tags = item['tags']
            created_at = item['created_at']
            updated_at = item['updated_at']

            # 公開記事と限定公開記事のフォルダを分ける
            subDir = dir + '/private' if private else dir + '/public'
            if not os.path.exists(subDir):
                os.makedirs(subDir)

            # マークダウンで保存
            file = open(subDir + '/' + create_filename(title, created_at), 'w', encoding='utf-8', newline="\n")
            file.write(create_info(title, tags, created_at, updated_at, url) + body)

    print('バックアップが完了しました。')

def create_info(title, tags, created_at, updated_at, url):
    # ファイルの冒頭にメタデータを追加する
    info = '---\n'
    info += 'title: ' + title + '\n'
    info += 'tags: ' 
    for tag in tags:
        info += tag['name'] + ' '
    info += '\n'
    info += 'created_at: ' + created_at + '\n'
    info += 'updated_at: ' + updated_at + '\n'
    info += 'url: ' + url + '\n'
    info += '---\n'
    return info

def create_filename(title, created_at):
    # ファイル名に「作成日_タイトル」を使用する
    filename_date = format(datetime.strptime(created_at, '%Y-%m-%dT%H:%M:%S%z'), '%Y%m%d')
    # ファイル名に使用できない文字列を置換する
    filename_title = title.replace('\\', ' ')
    filename_title = filename_title.replace('/', ' ')
    filename_title = filename_title.replace(':', ' ')
    filename_title = filename_title.replace('*', ' ')
    filename_title = filename_title.replace('?', ' ')
    filename_title = filename_title.replace('"', ' ')
    filename_title = filename_title.replace('<', ' ')
    filename_title = filename_title.replace('>', ' ')
    filename_title = filename_title.replace('|', ' ')
    return filename_date + '_' + filename_title + '.md'

download_qiita()
```

下図のようにバックアップファイルが作成されていきます。

![qiita_api_07.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/fb316078-2f1e-d004-dd8f-c684cede393e.png)


メタデータはファイルの冒頭に追加されます。

![qiita_api_08.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/125621/a9cc4cc4-74b5-a755-1bac-5289a8aeba95.png)