自分がある期間に投稿した記事の一覧をQiita APIを使って取得する(python)

自分がある期間に投稿した記事の一覧を取得する必要があったので、その際のメモです。
2024/01/16: 認証方法も記載して、ページビューの数を取得できるようにしました。

# 1. Qiita API 
Qiita API v2はQiitaが提供しているQiitaのデータの取得・記事の投稿などができるREST APIです。
https://qiita.com/api/v2/docs


[利用制限](https://qiita.com/api/v2/docs#%E5%88%A9%E7%94%A8%E5%88%B6%E9%99%90)があって、認証している状態ではユーザごとに1時間に1000回まで、認証していない状態ではIPアドレスごとに1時間に60回までリクエストを受け付けます。

1時間に60回もあれば別に認証いらない気がしたので、認証なしでやってみます。

ちなみにQiitaの他の記事ではちゃんと認証の解説もしてるようなので、1時間に60回以上リクエストしそうだったらだったら、[他の記事](https://qiita.com/search?q=qiita+api)を参考にしてみてください（説明が長くなるだけで特に難しくはないです）。

作成中エラーが出て何回もやってみたりはしたのですが、1時間に60回を超えることはなかったです。

**2024/01/16追記:**
認証してないことでの制限は1時間あたりのリクエスト数だけかと思っていたのですが、実は自分(ID所有者)しかみれない情報、ページビューの数が取得できないことに気がつきました。
これら情報が欲しい場合は認証してください。
認証ってアクセストークンを取得してRequest Headerに入れるだけでした！
アクセストークンに有効期限はないので、一度取得してしまって、どこかに保存しておけばずっと使えます。

## 1.1 (オプション)アクセストークンの取得
自分(ID所有者)しかみれない情報、ページビューの数を取得したい場合で、Qiitaのアクセストークンを持っていない場合は、以下の方法で取得してください。
1) Qiitaにログイン後、https://qiita.com/settings/applications　にアクセス
2) 「個人用アクセストークン」の右の「新しくトークンを発行する」をクリック
3） 「アクセストークンの説明」を入力し、必要なスコープにチェックを入れ(今回の用途では「read_qiita」にチェックが入っていればよい）、「発行する」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/87385e52-3d9d-b3be-ec35-38b178393f73.png)
4）　発行されたアクセストークンが表示されるので、表示された文字列をどこかにコピーしてわかるように保存しておく。この文字列は再表示できないです。
ただ何個でも作れるのでなくしたら再発行すればよいです。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/294516/a6962365-58e1-a148-6591-df2042e50e5f.png)

以上でアクセストークンの取得は完了です。




# 2. 投稿の取得方法
投稿　https://qiita.com/api/v2/docs#%E6%8A%95%E7%A8%BF
を参考にGETで取得すればよいです。
しかし、[ページネーション](https://qiita.com/api/v2/docs#%E3%83%9A%E3%83%BC%E3%82%B8%E3%83%8D%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)という考え方があり、量が多い場合は、ページに分けて取得する必要があります。そんなに投稿してるわけではないので、あまり考えずに1ページ分だけの取得でもいいのですが、綺麗に説明しているQiita投稿がありましたので、そちらをほぼそのまま使用させていただきました（@mochi_yu2さんに感謝!）。

[Qiita APIでの投稿記事取得のページネーション実装2種(Python)](https://qiita.com/mochi_yu2/items/556b91e9f25b2eec83de)

そのまま使うなら上記の投稿を見ればよいかと思うかもしません。
ではこの投稿のポイントはというと、
**Queryの条件複数指定方法**
です。

url encodingすべき文字としてはいけない文字があり、そこを切り分けています。

# 3. Queryの組み立て

[こちら](https://qiita.com/api/v2/docs#get-apiv2items)にあるように、`/api/v2/items`の後に、パラメータとして、
pageとper_pageの値を渡し、そして、queryパラメータの値を渡せばよいとなっています。

API Docに載っているサンプル:
```
GET /api/v2/items?page=1&per_page=20&query=qiita+user%3AQiita HTTP/1.1
Host: api.example.com
```

`page`はページ番号、`per_page`は1ページあたりに含まれる要素数 (1から100まで)です。詳細は[ページネーション](https://qiita.com/api/v2/docs#%E3%83%9A%E3%83%BC%E3%82%B8%E3%83%8D%E3%83%BC%E3%82%B7%E3%83%A7%E3%83%B3)参照お願いします。

そして重要な`query`は、
- 検索クエリ
- Example: "qiita user:Qiita"
となっていました。

検索クエリには[検索時に利用できるオプション](https://help.qiita.com/ja/articles/qiita-search-options)を基本は使えるようです。

では今回の目的「自分がある期間に投稿した記事の一覧」を出すQueryを考えてみます。

- ユーザーは自分のID
  - `user:nishikyon` 

- ある期間 --- 仮に　2022/07/01から2022/09/30とする
  - `created:>=2022-07-01`
  - `created:>=2022-09-30`
    - ちなみに2022-09-31とか実在しない日付を設定してしまうと、Internal Serverという仰々しいエラーにが出て気づかないとかなり悩みます。

この3つの条件を指定する必要があります。
さて複数条件の指定方法は???

[ここ](https://qiita.com/api/v2/docs#get-apiv2itemsreactions)のGETサンプルをみると `+`で繋ぐようです。**そして`+`はurl encodeしてはいけないようです**（url encodeすると正しい検索条件になりませんでした）。まあGoogleの検索と一緒ですね・・・
```sh
GET /api/v2/items/reactions?page=1&per_page=20&query=created%3A%3E%3D2020-01+created%3A%3C%3D2020-12 HTTP/1.1
Host: api.example.com
```

よってこの3つをパラメーターとして渡す際は以下のような感じになります。
```shell
query=user%3Anishikyon+created%3A%3E%3D2022-07-01+created%3A%3C%3D2022-09-30`
```

コードを使わせていただいた[Qiita APIでの投稿記事取得のページネーション実装2種(Python)](https://qiita.com/mochi_yu2/items/556b91e9f25b2eec83de)だとqueryに渡す値を全てurl encodeしてしまうので、その部分の変更と認証の部分を不要にして、Jupyter Notebookで動かしてみます。

# 4. Jupyter Noteebookでのコード

2024/01/16: アクセストークンをセットする方法も記載して、いいねやページビューの数を取得できるようにしました。
ページビューの数を取得したい場合は、`access_token`にアクセストークンの文字列をセットしてください。アクセストークン取得方法は1.1を参照してください。セットしない場合は、そのままでも動きます。

```python
#必要なライブラリのインポート、定数の設定
import urllib.request
import json
import pandas as pd

# 1ページ100
per_page=100

# access tokenをセットする場合は以下に入れる
access_token="XXXXXXXXXX"
```
```python
# [Qiita APIでの投稿記事取得のページネーション実装2種(Python)](https://qiita.com/mochi_yu2/items/556b91e9f25b2eec83de)を
# ちょっと変更
def pagenation_by_total_count(query = None):
    df_ret = pd.DataFrame()
   
    for page_num in range(1, 101): 
        url = "https://qiita.com/api/v2/items?per_page={0}&page={1}&{2}".format(str(per_page), str(page_num), query)
        print(url)

        req = urllib.request.Request(url)
        with urllib.request.urlopen(req) as res:
            body = json.load(res)
            # DataFrameに記事情報を格納
            df = pd.json_normalize(body)
            df_ret = pd.concat([df_ret, df])
            print("Page: " + str(page_num))
            # Total-Countヘッダの値から最後のページまで取得したかを判断
            total_count = int(res.headers['Total-Count'])
            print(total_count)
            if page_num >= (total_count + 99) // 100:
                print('# of articles: ', total_count)
                break

    return df_ret
```

以下の`qiita_user`,`date_from`, `date_to`, `file_name`は自分の値に変更します。
```python
# dateは実在する日にする
qiita_user = "nishikyon"     #QiitaユーザーID
date_from = ">=2022-07-01"　　#取得したい作成日のFROM
date_to = "<=2022-09-30"　　　　#取得したい作成日のTO
file_name = 'Qiita_Title_List.csv'　#データ保存先
 
query_list = []

# user quer
q_qtr="user:"+qiita_user
query_list.append(urllib.parse.quote(q_qtr))　　 #url encoding

# date_from
q_qtr="created:"+date_from
query_list.append(urllib.parse.quote(q_qtr)) #url encoding


# date_to
q_qtr="created:"+date_to
query_list.append(urllib.parse.quote(q_qtr)) #url encoding


query_str = "query=" + '+'.join(query_list) #+で繋ぐ
print(query_str) #検索パラメータ


```

`q_qtr="created:"+date_to`
`query_list.append(urllib.parse.quote(q_qtr)) #url encoding`
この2行を変更すればタグなど他の検索オプションも使えます。
単なる文字列で検索は他の検索オプションなしで追加してください。
例:
`q_qtr="検索したい文字列"`
`query_list.append(urllib.parse.quote(q_qtr)) #url encoding`



<br>

↓columnsには保存したい属性を指定してください。属性は[ここ](https://qiita.com/api/v2/docs#%E6%8A%95%E7%A8%BF)参照。
尚、ページビューの数は、`page_views_count`です。以下の例には入っていないので、必要に応じて入れてください。

```python
# [Qiita APIでの投稿記事取得のページネーション実装2種(Python)](https://qiita.com/mochi_yu2/items/556b91e9f25b2eec83de)を
# ちょっと変更
df_total_count = pagenation_by_total_count(query = query_str)

# columnsには保存したい属性をセット　　https://qiita.com/api/v2/docs#%E6%8A%95%E7%A8%BF
# 1レコード以上ある場合のみファイルに保存
if df_total_count.size > 0: 
    df_total_count.to_csv(file_name, index=False, columns=['title',  'url', 'created_at'])
else:
    print("The total count is 0")
```

ついでにMarkdownでプリント
```python
for i, row in df_total_count.iterrows():
    print("[{}]({})".format(row['title'], row['url']))
```

出力例:
```markdown
[Db2地理空間分析: SHAPEファイルのインポート](https://qiita.com/nishikyon/items/fd6d166fdbf6523930d4)
[Db2地理空間分析: JGD2011座標系の設定](https://qiita.com/nishikyon/items/fc97391f02b4e9e4bb9c)
[Db2地理空間分析: 地理空間分析機能を有効にする](https://qiita.com/nishikyon/items/4cd0430e4ebb7d541a82)
[DBeaverからDb2 on Cloudにアクセスする](https://qiita.com/nishikyon/items/d0b0e57b72e0996c331c)
[Db2地理空間分析: Db2の地理空間分析機能](https://qiita.com/nishikyon/items/2f8bc4858e2d54bf9b1a)
[公共交通オープンデータセンターにあるドコモ・バイクシェアのstation_information情報をCSVファイルにする](https://qiita.com/nishikyon/items/bfb328bca59c64228474)
[RHEL8でユーザー作成、そのユーザーのみでrootにsuできるようにしてrootのsshログイン禁止](https://qiita.com/nishikyon/items/be8efab76dd32b80921a)
[Db2 on Cloudにデータをコマンドでロードする3つの方法　](https://qiita.com/nishikyon/items/a5ab65cbb48ec177ed5f)
[Cognos Analytics on CloudからWatsonQueryにアクセスする](https://qiita.com/nishikyon/items/5284365fc3f4fb06fe43)
[Db2 CLIからWatsonQueryにアクセスする](https://qiita.com/nishikyon/items/a61283f88dc89a600743)
```

無事自分がある期間に投稿した記事の一覧を取得できました！

# 参考Link
- @mochi_yu2さん [Qiita APIでの投稿記事取得のページネーション実装2種(Python)](https://qiita.com/mochi_yu2/items/556b91e9f25b2eec83de)
- [Qiita API v2](https://qiita.com/api/v2/docs)
- [検索時に利用できるオプション](https://help.qiita.com/ja/articles/qiita-search-options)