pythonを利用した、qiitaApiで記事一覧の取得方法(windows)

laravelの事を聴いたら大体の事を答えてくれる
larvel先生のAiを作りたくなったので、
強化学習用のデータセットを作る為にqiitaを利用させて貰う。

https://qiita.com/koki_develop/items/57f86a1abc332ed2185d

https://qiita.com/nishikyon/items/bf27c4e6ce7170931ec3

こちらの記事を参考にさせていただきました。
ありがとうございます。


## Apiのアクセストークンを作る

[アクセストークンの発行ページ](https://qiita.com/settings/tokens/new)に移動する
記事一覧を取得したいだけなので、

* アクセストークンの説明
* スコープをread_qiita

にして、
「発行する」ボタンをクリックする

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/42d9fa20-24b9-8659-5079-f973da9cf849.png)

APIが発行できた。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/34260b5f-30b4-ac95-a874-c7fe7b585ca6.png)
赤く囲んでる部分がアクセストークンになる。

## 次にpythonでApiを叩く

pythonのrequestsライブラリを利用するので、インストールする

インストールが完了したので、以下のようなコードでとりあえず1ページで100件のデータを取得する。

queryへの検索条件の入れ方は[ここ](https://help.qiita.com/ja/articles/qiita-search-options)で見た。

```
pip install requests
```

```py
import requests


url = "https://qiita.com/api/v2/items"
params = {'query': 'tag:laravel', 'page': '1','per_page':'100'}
headers = {'Authorization': 'Bearer アクセストークン'}

response = requests.get(url, params=params, headers=headers)

if response.status_code == 200:
    data = response.json()
    print(data)
    # データを加工するなどの処理を行う
else:
    print("Error: ", response.status_code)
```

実行すると、こんな感じでばさ～と記事が取れた！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/34d5d06a-800c-ae6a-9eaf-f2e5bf7af7a7.png)

これだと意味が解らないので
sqliteにでも入れようか・・・
たぶん正しくはpandasとかでCSVに吐き出すのが正解だけど、
今後データ加工とかをする事になるので、sqliteの方が使いやすい気がする。
mysqlでも良いけど、気軽に使えるのがsqliteジャン？

## Sqliteを利用する。

### WindowsでsqlliteのDBを作成する

https://sqlite.org/download.html

で下の画像のヤツをダウンロードしてインストールした。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/0da8042f-1cbe-75dd-f7e8-a2537bd66224.png)

ダウンロードした`sqlite-tools-win-x64-3440000.zip`は解凍しておく

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/b4ae985f-2f01-21a3-2e7c-820c7c6d15a1.png)

shiftを押しながら右クリックで`パスをコピー`をクリックして
![2023-11-22_22h44_43.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/645029ba-b583-266a-cab5-a9319fbd719a.png)

PowerShellに張り付け
前後に"が入っちゃうから消してEnterで実行
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/f699cb2b-e5cd-7f81-a68e-9733ff50b657.png)

PowerShellが起動した。

今回は`E:\git\getQiitaPost`に`laravelposts.db`を作りたいので、
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/c60741c2-677e-b08f-0294-18058e3fff3a.png)

出来た。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/c6fccecc-0771-ee2e-89bd-f34e08bb814b.png)

### DBeaverでテーブルを作る

DBeaverでさっき作ったlaravelposts.dbに接続する

接続方法はここが解りやすかった。

https://netha.hatenablog.com/entry/2019/04/07/135055

Dbeaverのテーブルから、「あたらしく作る 表」をクリックする
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/04ed6dee-0076-be7a-1791-7b749b5344f2.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/a585a13b-4e2a-c6d6-2e0b-bc9d22042cd6.png)

こんな感じに表示されるのでカラムを作って行く。

ちなみにDB構造はこんな感じで考えている。

| カラム名 | 型   | 制約                      |
| :------- | :--- | :------------------------ |
| id       | int  | PRIMARY KEY AUTOINCREMENT |
| title    | text | NOT NULL                  |
| content  | text | NOT NULL                  |
| url      | text | NOT NULL                  |

テーブル名を入れる
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/fe171531-2611-5f8a-6556-bdf4202a614a.png)

カラムの作り方は下の図の当たりを右クリックから、  
「新しくつくる カラム」をクリックすると、カラム作成画面が表示される
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/2ec6d766-d1a7-fdf8-5608-b64ed97c97c8.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/5767e9aa-86ce-d19c-f339-21621fb72e6c.png)


こんな感じ
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/9ed14bac-c819-f3a8-08bf-38b36ddd9f7c.png)
画面右下の「Save」ボタンをクリックで保存

SQLプレビューが表示されるので、「持続する」をクリック
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/4044de27-d45d-6640-2e12-e2f2c5a51310.png)


出来た
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/3c203ec0-0445-3b29-fb32-d4d5fb7db45d.png)

## Pythonで記事をDBに保存

後はpythonで取得した記事情報をこのDBにぶち込めばOK
pythonのコードはこんな感じにした。

```python
import requests
import sqlite3

con = sqlite3.connect('./laravelposts.db')
cur = con.cursor()

url = "https://qiita.com/api/v2/items"
params = {'query': 'tag:laravel', 'page': '1','per_page':'100'}
headers = {'Authorization': 'Bearer アクセストークン'}

response = requests.get(url, params=params, headers=headers)

if response.status_code == 200:
    data = response.json()
    for post in data:
            body = post["rendered_body"].replace("'", "''").replace('"', '""')
            body = body.replace('\n', '').replace('\r', '').replace('\t', '')
            body = body.replace('\b', '').replace('\f', '')
            body = body.replace('(', "'<").replace(')', "'>")
            title = post["title"].replace("'", "''").replace('"', '""')
            title = title.replace('\n', '').replace('\r', '').replace('\t', '')
            title = title.replace('\b', '').replace('\f', '')
            title = title.replace('(', "'(").replace(')', "')")
            sql = F' ("{title}", "{body}","{post["url"]}") '
            print(sql)
            cur.execute(F'INSERT INTO posts (title,content,url) values {sql}')
else:
    print("Error: ", response.status_code)
    
con.commit()

print('処理が完了しました。')
```

OK保存できた
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/68818ec1-75ea-a9a2-af0f-f918ea330742.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/0866d2cd-9ba8-f015-7b93-996a86293dfb.png)

これを永遠ループで全部の記事をDBに保存しちゃえば良いんだね！

## と思ったけど・・・

laravelのタグが付いている記事は13885件あるから、
1回100件取れるとして、13885/100=138.85なので、139回回せばいいなね。

でも、apiでpageパラメーターは100が最大値だから、39ページ3900件は保存できない。
作成日時の降順で取得できるから、あわせて、最後のレコードの日付を表示して、
それ以降で2回回さないといけないか・・・

### 単純にとれる最終の記事の公開日時を調べる

という事でとりあえず、100回目の最後の記事の日付を取得する処理を書く。

```python
import requests
import sqlite3

con = sqlite3.connect('./laravelposts.db')
cur = con.cursor()

url = "https://qiita.com/api/v2/items"
params = {'query': 'tag:laravel', 'page': 100,'per_page':'100'}
headers = {'Authorization': 'Bearer アクセストークン'}

response = requests.get(url, params=params, headers=headers)
print(response.url)
if response.status_code == 200:
	data = response.json()
	for post in data:
			print(post['created_at'])
else:
	print("Error: ", response.status_code)
	

print('処理が完了しました。')
```
ダメダメな処理だけど、1回走らせるだけならこれで十分

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/eca2ef58-9692-1493-fc35-db755ef851f6.png)

これで単純に取得できる記事の一番古いのが「2019-06-12T16:55:39+09:00」である事が解ったので、  
それ以前の記事も取得できるように変える。

### ついでに、DBにも記事の作成日時を保存するカラムを追加する

```sql
CREATE TABLE posts (
	id INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
	title TEXT NOT NULL,
	content TEXT,
	url TEXT NOT NULL,
	created TEXT
);
```

## 記事を取得する処理はこんな感じに書き換える。

参考にしないでください。マジで許してください。  
穴があったら入りたいレベルのクソ汚いコードでごめんなさい。

```python
import requests
import sqlite3

con = sqlite3.connect('./laravelposts.db')
cur = con.cursor()

url = "https://qiita.com/api/v2/items"
headers = {'Authorization': 'Bearer アクセストークン'}
params = {'query': 'tag:laravel created:>=2019-06-13', 'page': 0,'per_page':'100'}
#とりあえず100ページまで取得
for i in range(1, 100):
    params['page'] = i
    response = requests.get(url, params=params, headers=headers)
    if response.status_code == 200:
        data = response.json()
        for post in data:
                body = post["rendered_body"].replace("'", "''").replace('"', '""')
                body = body.replace('\n', '').replace('\r', '').replace('\t', '')
                body = body.replace('\b', '').replace('\f', '')
                body = body.replace('(', "'<").replace(')', "'>")
                title = post["title"].replace("'", "''").replace('"', '""')
                title = title.replace('\n', '').replace('\r', '').replace('\t', '')
                title = title.replace('\b', '').replace('\f', '')
                title = title.replace('(', "'(").replace(')', "')")
                sql = F' ("{title}", "{body}","{post["url"]}","{post["created_at"]}") '
                # print(sql)
                cur.execute(F'INSERT INTO posts (title,content,url,created) values {sql}')
    else:
        print("Error: ", response.status_code)
        
    con.commit()
    print(F'{i}/100')

# 100ページ以上は取得できないので、無理やり取得
params['query'] = 'tag:laravel created:<2019-06-13'
for i in range(1, 100):    
    params['page'] = i
    response = requests.get(url, params=params, headers=headers)
    if response.status_code == 200:
        data = response.json()
        for post in data:
                body = post["rendered_body"].replace("'", "''").replace('"', '""')
                body = body.replace('\n', '').replace('\r', '').replace('\t', '')
                body = body.replace('\b', '').replace('\f', '')
                body = body.replace('(', "'<").replace(')', "'>")
                title = post["title"].replace("'", "''").replace('"', '""')
                title = title.replace('\n', '').replace('\r', '').replace('\t', '')
                title = title.replace('\b', '').replace('\f', '')
                title = title.replace('(', "'(").replace(')', "')")
                sql = F' ("{title}", "{body}","{post["url"]}","{post["created_at"]}") '
                cur.execute(F'INSERT INTO posts (title,content,url,created) values {sql}')
    else:
        print("Error: ", response.status_code)
        
    con.commit()
    print(F'{i}/100')
print('処理が完了しました。')
```
超絶汚いソースだけど、許してくれ！  
次回以降使う事があったら、リファクタリングはする。  
ちなみにこの状態で「ソースレビューして」ってコミットされてたらブチぎれる自信がある！

## という事で実行する

```
py index.py
```

後は放置！

こんな感じで10秒に1回ぐらいカウンターが進んでいく
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/612e56b9-8854-e52e-c15e-c4b310c3a7c7.png)

取り合えず記事は13783レコード取得できた！
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1681950/01ef5748-da13-30f0-8255-2948956ccd89.png)

次回に続く