[Python]Qiitaの記事情報をmongoDBに突っ込んだ

# やりたいこと
Qiitaの記事情報をQiitaAPIで取得し、どの記事、どのタグがよく見られているのか？などを確認したい。
その第一歩として、APIで取得した情報をmongoDBに登録してみる

# Qiitaの記事情報を取得する
今回の内容はPythonで書いています。
記事情報の取得は以下の記事を参考にさせていただきました。

[Qiita APIをPythonから使ってみる](https://qiita.com/sotoiwa/items/e2254893f5b0b92bb06e)

~~~ get_qiita_info.py
import requests
import logging
import json

formatter = '%(asctime)s %(name)-12s %(levelname)-8s %(message)s'
logging.basicConfig(level=logging.WARNING, format=formatter)
logger = logging.getLogger(__name__)

class GetQiitaInfo(object):

    def __init__(self):
        self.token = 'your token'        

    def get_next_url(self, response):
        """次のページがある場合は'rel="next"'としてurlが含まれるので、urlを抽出して返す。
        ない場合はNoneを返す。

        link: <https://qiita.com/api/v2/authenticated_user/items?page=1>;
        rel="first", <https://qiita.com/api/v2/authenticated_user/items?page=2>;
        rel="next", <https://qiita.com/api/v2/authenticated_user/items?page=4>;
        rel="last"

        :param response:
        :return: 次のurl
        """
        link = response.headers['link']
        if link is None:
            return None

        links = link.split(',')

        for link in links:

            if 'rel="next"' in link:
                return link[link.find('<') + 1:link.find('>')]
        return None
      
    def get_items(self):
        
        """ページネーションして全ての記事を取得し、
        ストック数とビュー数は一覧に含まれないので、それらの情報も追加して返す。

        :param token:
        :return: 記事のリスト
        """
        
        url = 'https://qiita.com/api/v2/authenticated_user/items'
        headers = {'Authorization': 'Bearer {}'.format(self.token)}

        items = []
        while True:
            response = requests.get(url, headers=headers)
            response.raise_for_status()
            items.extend(json.loads(response.text))
            logger.info('GET {}'.format(url))
            # 次のurlがあるかを確認する
            url = self.get_next_url(response)
            if url is None:
                break

        # 各記事についてビュー数とストック数の情報を取得して追加する
        # page_views_countは一覧APIにもフィールドはあるがnullが返ってくる
        for item in items:

            # ビュー数
            url = 'https://qiita.com/api/v2/items/{}'.format(item['id'])
            logger.info('GET {}'.format(url))
            response = requests.get(url, headers=headers)
            response.raise_for_status()
            itemJson = json.loads(response.text)
            item['page_views_count'] = itemJson['page_views_count']
            item['tag1'] = itemJson['tags'][0]['name']
            item['tag2'] = itemJson['tags'][1]['name'] if len(itemJson['tags']) >= 2 else ''
            item['tag3'] = itemJson['tags'][2]['name'] if len(itemJson['tags']) >= 3 else ''
            item['tag4'] = itemJson['tags'][3]['name'] if len(itemJson['tags']) >= 4 else ''
            item['tag5'] = itemJson['tags'][4]['name'] if len(itemJson['tags']) >= 5 else ''

            tag_list = []
            for i in range(len(itemJson['tags'])):
                tag_list.append(itemJson['tags'][i]['name'])
            item['tag_list'] = tag_list

            # ストック数
            url = 'https://qiita.com/api/v2/items/{}/stockers'.format(item['id'])
            logger.info('GET {}'.format(url))
            response = requests.get(url, headers=headers)
            response.raise_for_status()
            users = json.loads(response.text)
            for user in users:
                logger.info({
                    'id': user['id'],
                    'name': user['name']
                    })
            item['stocks_count'] = len(users)

        return items
~~~

自身の勉強のため、参考にさせていただいた記事から２点変更を加えました。
・クラスにした
・tag1～tag5、tag_listを追加

# mongoDB操作用クラス
以前にmongoDB操作の記事を書きましたが、その内容のままです。

参考
[Pythonでmongodbを操作する～その４：insert編～](https://qiita.com/bc_yuuuuuki/items/07aab6cd5615e79bdf58)

~~~ mongo_sample.py
from pymongo import MongoClient

class MongoSample(object):

    def __init__(self, dbName, collectionName):
        self.client = MongoClient()
        self.db = self.client[dbName] #DB名を設定
        self.collection = self.db.get_collection(collectionName)

    def find_one(self, projection=None,filter=None, sort=None):
        return self.collection.find_one(projection=projection,filter=filter,sort=sort)

    def find(self, projection=None,filter=None, sort=None):
        return self.collection.find(projection=projection,filter=filter,sort=sort)

    def insert_one(self, document):
        return self.collection.insert_one(document)

    def insert_many(self, documents):
        return self.collection.insert_many(documents)

~~~

# 記事情報を取得しmongoDBに登録する

~~~ sample.py
from get_qiita_info import GetQiitaInfo
from mongo_sample import MongoSample

# Qiitaの記事情報を取得する
qiita = GetQiitaInfo()
items = qiita.get_items()

# arg1:DB Name
# arg2:Collection Name
mongo = MongoSample("db", "qiita")

# 不要なキー値を削除しないのであれば
# mongo.insert_many(items)
# で一括登録

for item in items:
    # rendered_body/body は不要なので削除
    item.pop("rendered_body")
    item.pop("body")
    # 一件ずつ登録
    mongo.insert_one(item)

result = mongo.find_one()
print(result)
~~~

上記のコードを実行後のmongoDBを見てみます。
※上記のコードの実行結果として１件データを表示していますが、１行に表示されて見辛いのでmongoDBで確認。。

~~~
> db.qiita.findOne()
{
        "_id" : ObjectId("5e38ff43c92e7c532aeffb47"),
        "coediting" : false,
        "comments_count" : 0,
        "created_at" : "2020-02-04T13:37:44+09:00",
        "group" : null,
        "id" : "331ae2289a95f5a9b901",
        "likes_count" : 0,
        "private" : false,
        "reactions_count" : 0,
        "tags" : [
                {
                        "name" : "Python",
                        "versions" : [ ]
                },
                {
                        "name" : "Python3",
                        "versions" : [ ]
                }
        ],
        "title" : "[Python]No value for argument 'self' in unbound method callが出た",
        "updated_at" : "2020-02-04T13:37:44+09:00",
        "url" : "https://qiita.com/bc_yuuuuuki/items/331ae2289a95f5a9b901",
        "user" : {
                "description" : "ブロックチェーン／ＡＩ／Python／Golang／MongoDBなどを学習中です。\r\nこのサイトにおける掲載内容はあくまで私自身の見解であり、必ずしも私の所属団体・企業における立場、戦略、意見を代表するものではありません。",
                "facebook_id" : "",
                "followees_count" : 0,
                "followers_count" : 2,
                "github_login_name" : null,
                "id" : "bc_yuuuuuki",
                "items_count" : 28,
                "linkedin_id" : "",
                "location" : "",
                "name" : "",
                "organization" : "",
                "permanent_id" : 476876,
                "profile_image_url" : "https://pbs.twimg.com/profile_images/1157834557783072768/ktpc9kGV_bigger.jpg",
                "team_only" : false,
                "twitter_screen_name" : "bc_yuuuuuki",
                "website_url" : ""
        },
        "page_views_count" : 54,
        "tag1" : "Python",
        "tag2" : "Python3",
        "tag_list" : [
                "Python",
                "Python3"
        ],
        "stocks_count" : 0
}
~~~

登録されていることが確認出来ました。

# 感想
今回のコードではAPIの取得結果に多少手を加えていますが、APIを叩いて取得したJSONを何も考えずに突っ込んで検索や集計が出来るようになるのは便利ですね。