Mac で Stack Exchange API を使うには 2019年版

技術的なQ&Aサイトとして人気のStackOverflowですが、実はStack Exchange という上位のウェブサイト群でAPIも提供されています。
かなり多くのAPIがあり、さまざまな情報を機械的にアクセスできるようになっています。もちろん、OAuth2的な認証認可を行って、その認可ユーザに関する情報を得やすい機能も提供されています。

ここでは、StackOverflowのAPIをMacで使ってみるまでの手順を紹介します。
基本的に、Webブラウザとpythonを使用します。
# Stack Exchange アカウント登録
APIを使用するには[Stack Apps](https://stackapps.com/)というサイトでアプリ登録が必要になりますがまずはそこでStack Exchangeのアカウントを登録します。
必要となるのはユーザー名、メールアドレス、パスワードのみです。facebook, Googleアカウントによる登録もできます。
全て入力し終えるとメールが届くのでメール内にあるリンクをクリックして登録は完了します。

# ローカルサーバー立ち上げ
APIとのOAuthを行うためにmacでローカルサーバーを立ち上げます。
今回はpythonを使って立ち上げを行います。ターミナルでpythonの[バージョン別](https://ja.stackoverflow.com/questions/44454/python%e3%81%a7web%e3%82%b5%e3%83%bc%e3%83%90%e3%83%bc%e3%82%92%e8%b5%b7%e5%8b%95%e3%81%95%e3%81%9b%e3%81%9f%e3%81%84%e3%81%ae%e3%81%a7%e3%81%99%e3%81%8c%e4%b8%8a%e6%89%8b%e3%81%8f%e3%81%84%e3%81%8d%e3%81%be%e3%81%9b%e3%82%93)に以下のコマンドを実行します。

```python:server_launch
python -m http.server#python 3.x の場合
python -m SimpleHTTPServer#python 2.x の場合
```
このコマンドで立ち上がったサーバーのアドレス(一般的にはlocalhost:8000)はこの後のアプリ登録で使用します。

# アプリ登録
続いて[Stack Apps](https://stackapps.com/)でアプリ登録をします。
<img width="1302" alt="Screen Shot 2019-10-08 at 6.25.45 pm.png" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/171432/a65b38ac-38de-7d8a-8d7e-c550172a8d0b.png">
ページ右にある[Register an application](https://stackapps.com/apps/oauth/register)リンクを押すと、アプリ登録のためのフォームが出てきます。
それぞれ必要な値を入力していきます。

- Application Name - アプリの名称です。
- Description - アプリの説明文です。
- OAuth Domain - OAuthの手順で使われるドメイン名を入力します。例えばOAuth2のAuthorization Code GrantやImplicit Grantを使う場合は、`redirect_uri`パラメータで指定するURLのドメイン名を指定します。開発時は`localhost:8000`でも構いません。
- Application Website - 登録するアプリのURLを入れます。これも開発時には`http://localhost:8000`で構いません。
- Application Icon - APIを試すだけなら未入力で大丈夫です。

入力したら、Register Your Applicationボタンを押して、アプリ登録を完了します。次に表示されるページでは、登録されたアプリとしてAPIを利用するための様々な情報が表示されます。

- Client Id
- Client Secret
- Key
- 


# 認証認可画面呼び出し
次に、APIを利用するためのアクセストークンを入手することを目指します。ここでは、OAuth2のAuthorization Code Grantによる手順を紹介します。具体的な手順は、[Authentication](https://api.stackexchange.com/docs/authentication)に書かれています。
まずはURLを組み立てて、認証認可画面をWebブラウザで開きます。URLの構成は以下になります。

```shell:
https://stackexchange.com/oauth?client_id=[CLIENT_ID]&scope=[SCOPE]&redirect_uri=[REDIRECT_URI]&state=[STATE]
```

- CLIENT_ID - 先ほどアプリ登録した結果発行されたアプリを特定するためのIDです。
- SCOPE - ユーザに認可してもらう権限を空白区切りで列挙します。
- REDIRECT_URI - StackOverflowのドメインで行われる認証認可後、アプリに戻ってくる際の戻り先URLを指定します。このURLはアプリ登録時に指定したOAuth Domainがドメイン名として含まれていなければなりません。
- state - CSRF対策の値です。本番環境では必須です。

scopeは以下が提供されています。ここでは、read_indexとprivate_infoを指定します。

- read_inbox - ユーザのグローバルインデックスにアクセスします。
- no_expiry - このscopeを持つアクセストークンには有効期限がありません。
- write_access - ユーザとして書き込み処理を行います。
- private_info - サイト上でのユーザのプライベートな行動全てにアクセスします。

redirect_uriは、アプリ登録時に指定したドメイン名を持つURLであれば、パスやクエリパラメータなどは自由です。ここでは、http://localhost:8000/としましょう。stateは本来指定しないとCSRF脆弱性を発生させてしまうのですが、今回はAPIのテスト目的なので省略します。

まとめると、以下のようになります。

```shell:
https://stackexchange.com/oauth?client_id=[CLIENT_ID]&scope=read_inbox%20private_info&redirect_uri=http://localhost:8000/
```
Webブラウザで上記のURLにアクセスすると、アプリケーションの認証を確認する画面が表示されるため、`Approve`を押します。

# アクセストークン
`Approve`ボタンを押した後、`redirect_uri`パラメータで指定したURLにリダイレクトされます。その際、クエリーパラメータとして`code`という値が渡されてきます。

```shell:
http://localhost:8000/?code=[CODE]
```

このcode値は、認証されたユーザが指定されたscopeに関して認可を行ったことを示しています。これは一時的な値であり、このcode値と他の値を組み合わせて、StackOverflowのサーバからアクセストークンを発行してもらいます。具体的には、https://stackexchange.com/oauth/access_tokenにPOSTメソッドで以下の値を送信します。

- client_id - アプリ登録した結果発行されたアプリを特定するためのIDです。
- client_secret - アプリ登録した際にclient_idと共に発行されたClient Secret値です。
- code - 先ほど入手した`code`値です。
- redirect_uri - 認証認可ページを呼び出した際に指定したredirect_uri値をそのまま指定します。

上記の値は`application/x-www-form-urlencoded`で送信します。pythonを使ってアクセストークンの発行処理をStackOverflowのサーバに依頼します。

```python:request.py
import requests
import urllib
# 任意のurl(エンドポイント)
url = 'https://stackexchange.com/oauth/access_token'
# 送信するパラメータ(例)
params = {'client_id': [CLIENT_ID],'client_secret':[CLIENT_SECRET],'code':[CODE],'redirect_uri':[REDIRECT_URI]} 
# URLをエンコード
params = urllib.parse.urlencode(params)
# headerでコンテンツタイプを指定
headers = {'Content-Type': 'application/x-www-form-urlencoded'}
# authにBasic認証のIDとPASSを設定する
r = requests.post(url=url, data=params, headers=headers)
# 送信結果が知りたい場合には記載
print(r.status_code)
print(r.text)
```
実行後、発行されたアクセストークンとそのアクセストークンの有効期限が返却されます。

```shell:
access_token=[ACCESS_TOKEN]&expires=86400
```
`expires`値の単位は秒です。上記の場合は、24時間有効なアクセストークンを得られたことになります。
これでAPIを利用する準備が整いました。

# APIの利用
StackOverflowが提供するAPIは、基本的には全てRESTful APIです。各APIのEndpoint URLを叩くと、それに応じて処理が行われ、結果がJSON形式で返ってきます。Endpoint URLは、以下です。
```shell:
https://api.stackexchange.com/2.2
```
各APIでは、上記のEndpoint URLに続けて、パスを追加していきます。そして、APIに応じて、HTTP Methodを使い分けていきます。
API呼び出し時に渡したいパラメータは、クエリーパラメータもしくはapplication/x-www-form-urlencoded形式で渡します。その際、基本的に指定が必要となるパラメータがいくつかあります。

- access_token - 先ほど取得したアクセストークン文字列です。
- key - アプリ登録時に発行されたKey値。
- site - APIの対象となる値。stackoverflowを指定します。
上記はAPIに関わらず指定されるパラメータです。それに対して、複数の結果を返却する可能性があるAPIでは、ページングがサポートされています。そのページングを制御するために、以下のパラメータが利用可能です。

- page - 取得したいページの番号。最初のページは1です。
- pagesize - 1ページあたりの件数です。未指定の場合は30が適用されます。0〜100の間の数値を指定可能です。

# API呼び出しの例

#### 2011/1/1から2019/1/1までの投稿を文章も含めて取得。
```shell:
https://api.stackexchange.com/2.2/questions?access_token=[ACCESS_TOKEN]&fromdate=1569888000&todate=1570492800&order=desc&sort=activity&site=stackoverflow&filter=!SnL4eKG_AcYDgViF6n&key=[KEY]
```

### 参考文献:
https://qiita.com/yoichiro6642/items/a951178fbd2d82256d68