6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

Qiita APIでの投稿記事取得のページネーション実装2種(Python)

Posted at

はじめに

Qiita APIを利用した投稿記事取得の際、取得結果が複数ページになる場合にページネーションを行う方法のメモです。

Qiita APIでのページネーションに関する仕様

Qiita APIのドキュメントには、ページネーションに関連して、以下の2つの記載があります。

  • ページを指定できるAPIでは、Linkヘッダ を含んだレスポンスを返す
  • ページを指定できるAPIでは、要素の合計数が Total-Count レスポンスヘッダに含まれる

Curlコマンドで確認

上記を実際に、curlコマンドで確認してみます。

$ curl -D - -s  -o /dev/null -H 'Authorization: Bearer [アクセストークン]' 'https://qiita.com/api/v2/items' | grep -e 'link' -e 'total-count'
link: <https://qiita.com/api/v2/items?page=1>; rel="first", <https://qiita.com/api/v2/items?page=2>; rel="next", <https://qiita.com/api/v2/items?page=34025>; rel="last"
total-count: 680484

実装

APIのレスポンス仕様を参考にして、以下の2種類の方法でページネーションを実装します。

  1. Total-Countヘッダを利用した実装
  2. Linkヘッダを利用した実装

(その1)Total-Countヘッダを利用した実装

def pagenation_by_total_count(token = None, query = None):
    df_ret = pd.DataFrame()
    # クエリパラメータの準備
    params = prepare_parameter(query)
    # アクセストークンが指定された場合に付与
    req_headers = prepare_headers(token)

    for page_num in range(1, 101):
        params['page'] = str(page_num)
        url = "https://qiita.com/api/v2/items?" + urllib.parse.urlencode(params)

        req = urllib.request.Request(url, headers=req_headers)
        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'])
            if page_num >= (total_count + 99) // 100:
                print('# of articles: ', total_count)
                break

    return df_ret

(その2)Linkヘッダを利用した実装

def pagenation_by_link_header(token = None, query = None):
    df_ret = pd.DataFrame()
    # クエリパラメータの準備
    params = prepare_parameter(query)
    # アクセストークンが指定された場合に付与
    req_headers = prepare_headers(token)
    
    # 初期化
    page_num = 0
    next_url = "https://qiita.com/api/v2/items?" + urllib.parse.urlencode(params)
    
    while page_num < 100 and next_url is not None:
        page_num += 1

        req = urllib.request.Request(next_url, headers=req_headers)
        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))
            # Linkヘッダに次ページのURLがある場合は「next_url」にセットする
            next_url = None
            link = res.headers['link']
            if link is not None:
                m = re.match(r'.*<(https://qiita.com/api/v2/.*?)>;\s*rel="next"', link)
                if m is not None:
                    next_url = m.group(1)

    return df_ret
  • その1と同様に、PandasのDataFrameに取得した記事情報を格納します。
  • 正規表現を用いて、Linkヘッダに次ページのURLがある場合は取得します。
  • next_urlという変数に次に取得するページがある場合は、そのURLを格納します。

コード全体

最後にコード全体を掲載します。

pagenation.py
import urllib.request
import json
import re
import csv
import pandas as pd

ACCESS_TOKEN = "YOUR ACCESS TOKEN"

# クエリパラメータを組み立てる(1ページ100件固定)
def prepare_parameter(query):
    params = {
        'per_page': '100'
    }
    if (query is not None):
        params['query'] = query
    
    return params

# 認証トークンが指定された場合にヘッダに付与する
def prepare_headers(token):
    req_headers = {}
    if (token is not None):
        req_headers = {
            'Authorization': 'Bearer ' + token
        }
    
    return req_headers

def pagenation_by_total_count(token = None, query = None):
    df_ret = pd.DataFrame()
    # クエリパラメータの準備
    params = prepare_parameter(query)
    # アクセストークンが指定された場合に付与
    req_headers = prepare_headers(token)

    for page_num in range(1, 101):
        params['page'] = str(page_num)
        url = "https://qiita.com/api/v2/items?" + urllib.parse.urlencode(params)

        req = urllib.request.Request(url, headers=req_headers)
        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'])
            if page_num >= (total_count + 99) // 100:
                print('# of articles: ', total_count)
                break

    return df_ret

def pagenation_by_link_header(token = None, query = None):
    df_ret = pd.DataFrame()
    # クエリパラメータの準備
    params = prepare_parameter(query)
    # アクセストークンが指定された場合に付与
    req_headers = prepare_headers(token)
    
    # 初期化
    page_num = 0
    next_url = "https://qiita.com/api/v2/items?" + urllib.parse.urlencode(params)
    
    while page_num < 100 and next_url is not None:
        page_num += 1

        req = urllib.request.Request(next_url, headers=req_headers)
        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))
            # Linkヘッダに次ページのURLがある場合は「next_url」にセットする
            next_url = None
            link = res.headers['link']
            if link is not None:
                m = re.match(r'.*<(https://qiita.com/api/v2/.*?)>;\s*rel="next"', link)
                if m is not None:
                    next_url = m.group(1)

    return df_ret

def main():
    # Total-Countヘッダを利用して、「QiitaAPI」タグの記事を取得
    df_total_count = pagenation_by_total_count(token = ACCESS_TOKEN, query = 'tag:QiitaAPI')
    df_total_count.to_csv('pagenation_by_total_count.csv', index=False, columns=['title', 'likes_count', 'created_at', 'url'])
    # Linkヘッダを利用して、「Python」タグの2020年に作成された記事を取得
    df_link = pagenation_by_link_header(token = ACCESS_TOKEN, query = 'tag:Python created:2020')
    df_link.to_csv('pagenation_by_link.csv', index=False, columns=['title', 'likes_count', 'created_at', 'url'])

if __name__ == "__main__":
    main()
  • 2種類の実装で、それぞれ記事を取得し、記事の「タイトル」、「LGTMの数」、「作成日」、「URL」をCSVに書き出します。
    • Total-Countヘッダを利用して、「QiitaAPI」タグの記事を取得
    • Linkヘッダを利用して、「Python」タグの2020年に作成された記事を取得

参考

6
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
6
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?