Qiita APIを使って記事を検索・自動収集しようとした際、「クエリを指定したのに意図した結果が取得できない」「検索結果が0件になってしまう(空振りする)」というトラブルに直面していませんか?
Qiita API v2における検索クエリは、Qiita本体のWeb検索と同等の強力な演算子が利用できる一方で、構文ルールやURLエンコード、API特有の仕様を正確に理解していないと簡単に検索条件が破綻します。
- Qiita API v2で記事を検索する際は
GET /api/v2/itemsエンドポイントのqueryパラメーターに検索演算子を組み合わせて指定する - 演算子の構文ミス、スペース処理の不備、URLエンコード漏れ、Qiita Team専用機能との混同が「検索の空振り」を引き起こす最大の原因である
- レート制限(認証時1000回/時、未認証時60回/時)やスコープ設定、ページネーション(1?100)の仕様を把握し、エラーコード(401 / 403 / 429)を適切にハンドリングすることが安定したAPI運用の鍵となる
Qiita API v2における記事検索の基本概念と仕様
Qiita API v2を利用して記事(アイテム)を検索する場合、基本となるエンドポイントは GET /api/v2/items です。このエンドポイントに対してURIクエリパラメーターを付与することで、様々な条件に基づいた記事の検索と取得が可能になります。
記事検索で利用する主なリクエストパラメーター
GET /api/v2/items では、以下のパラメーターを指定してデータを絞り込みます。
-
query: 検索条件を指定する文字列(検索クエリ)。本記事のメインテーマです。 -
page: 取得するページ番号。指定可能な範囲は 1から100(デフォルトは1)。 -
per_page: 1ページあたりの取得件数。指定可能な範囲は 1から100(デフォルトは20)。
ページネーションとヘッダー情報
検索結果が多数存在する場合、1回のリクエストですべてのデータを取得することはできません。APIレスポンスのヘッダーには、ページネーションを制御するための重要な情報が含まれています。
-
Total-Count: 検索条件にマッチした全体の合計件数 -
Link: 最初のページ(first)、前のページ(prev)、次のページ(next)、最後のページ(last)へのURL関係を示すHTTPヘッダー
Link: <https://qiita.com/api/v2/items?page=1&per_page=20&query=tag%3APython>; rel="first",
<https://qiita.com/api/v2/items?page=2&per_page=20&query=tag%3APython>; rel="next",
<https://qiita.com/api/v2/items?page=5&per_page=20&query=tag%3APython>; rel="last"
Total-Count: 100
認証とアクセストークンの役割
Qiita API v2は未認証でも利用可能ですが、アクセストークンをHTTPリクエストの Authorization ヘッダーに設定することで、利用制限の大幅な緩和や限定リソースへのアクセスが可能になります。
ヘッダーの指定形式:
Authorization: Bearer <YOUR_ACCESS_TOKEN>
アクセストークンに紐付けられるスコープ(Scope)は以下の4種類です。用途に応じて適切なスコープを選択する必要があります。
-
read_qiita: Qiita上の認証ユーザーに関連するデータの読み取りを許可 -
read_qiita_team: Qiita Team上のデータの読み取りを許可(Qiita Team専用) -
write_qiita: Qiita上のデータの書き込み・更新を許可 -
write_qiita_team: Qiita Team上のデータの書き込み・更新を許可(Qiita Team専用)
Qiita APIで使える検索演算子一覧・早見表
Qiita APIの query パラメーターに渡すクエリでは、特定のキーと記号を用いた演算子が使用できます。これらを適切に組み合わせることで、ピンポイントで目的の記事を検索できます。
主要検索演算子の一覧
| 演算子 | 記述例 | 説明・検索対象 |
|---|---|---|
tag: |
tag:Python |
指定したタグが設定されている記事を検索 |
title: |
title:Docker |
記事のタイトル内に指定したキーワードが含まれる記事を検索 |
body: |
body:GraphQL |
記事の本文(Markdown)内に指定したキーワードが含まれる記事を検索 |
code: |
code:import |
記事内のコードブロック中に指定した文字列が含まれる記事を検索 |
user: |
user:qiita |
指定したユーザーID(アカウント名)が投稿した記事を検索 |
stocks: |
stocks:>=50 |
指定したストック数以上の記事を検索(<, >, <=, >= が利用可能) |
likes: |
likes:>=100 |
指定した「いいね」数以上の記事を検索(公開Qiita専用) |
created: |
created:>=2023-01-01 |
投稿作成日時を指定して検索(日付形式: YYYY-MM-DD) |
updated: |
updated:>=2023-06-01 |
最終更新日時を指定して検索(日付形式: YYYY-MM-DD) |
user_stock: |
user_stock:qiita |
指定したユーザーがストックしている記事を検索 |
論理演算子と特殊記号の扱い
複数条件を組み合わせる場合は、論理演算子を利用します。
| 演算子 / 条件 | 記述例 | 動作 |
|---|---|---|
| AND(積集合) | tag:Python title:FastAPI |
半角スペース区切りで複数条件を指定(両方を満たす) |
| OR(和集合) | tag:Python OR tag:Ruby |
OR(大文字)を挟んで指定(いずれかを満たす) |
| NOT / 除外 |
-tag:Java または NOT tag:Java
|
条件の直前に -(ハイフン)または NOT を付与して除外 |
| フレーズ検索 | title:"Web API" |
熟語やスペースを含む文字列をダブルクォーテーションで囲む |
検索クエリで「空振り(0件)」が発生する主な原因と対策
Qiita APIで検索を行った際に、存在するはずの記事が見つからず検索が「空振り」してしまう場合、以下の原因が考えられます。
原因1: 全角スペースや構文ルールの不備
最も多い失敗が、演算子と値の間のコロン(:)や区切り文字に全角文字を混ぜてしまうケースです。
-
誤り(空振りする例):
tag:Python stocks:>=10(全角コロンや全角スペース、全角不等号が混入) -
正しい例:
tag:Python stocks:>=10(すべて半角)
また、OR や NOT は小文字(or, not)で書くと単なる検索文字列として解釈され、論理演算子として機能しません。必ず大文字で記述してください。
原因2: URLエンコード(パーセントエンコーディング)の漏れ
APIに対して直接HTTPリクエストを送信する際、query パラメーターに含まれるスペースや不等号(>、<)、ダブルクォーテーションなどは、正しくURLエンコードされる必要があります。
例えば、query=tag:Python stocks:>=50 をそのまま未エンコードで送信すると、Webサーバー側でスペースがパラメーターの切れ目と誤認されたり、不正なリクエスト(400 Bad Request)となったり、検索クエリが途中で切れて空振りする原因になります。
-
エンコード前:
tag:Python stocks:>=50 -
エンコード後:
tag%3APython+stocks%3A%3E%3D50
Pythonの requests ライブラリを使用する場合は、辞書型オブジェクトとして params 引数に渡せば自動的に適切なURLエンコードが行われます。
原因3: Qiita(公開)とQiita Team専用属性の混同
Qiita(qiita.com)とQiita Team(*.qiita.com)では、利用できる機能やレスポンスに含まれるデータ構造が一部異なります。
-
「いいね」と「ストック」: 公開版Qiitaでは「いいね(
likes_count)」と「ストック(stocks_count)」が存在します。検索クエリでもlikes:>=10やstocks:>=10が機能します。 - 「絵文字リアクション(Emoji reaction)」: Qiita Team専用の機能です。Qiita Teamでは記事に対する反応として絵文字リアクションが用いられます。
公開版Qiitaに対してTeam専用の条件を指定したり、逆にTeam環境でサポートされていない評価軸を指定すると、期待通りのデータが取得できなくなります。
Python requestsによる実践:基本の検索リクエスト実装
ここからは、Pythonの requests ライブラリを用いて、Qiita API v2から安全かつ確実に記事を検索・取得するスクリプトの実装例を解説します。
セキュリティ上のベストプラクティスに従い、アクセストークンはコード内に直接記述(ハードコード)せず、環境変数 QIITA_TOKEN から読み込みます。
スクリプト1: 単一・複合条件による記事検索とエラーハンドリング
以下のコードは、指定したタグとストック数を超える記事を検索し、ステータスコードに応じた例外処理を行う最小限の実装です。
import os
import requests
def search_qiita_articles():
# 環境変数からアクセストークンを取得
token = os.environ.get("QIITA_TOKEN")
# 公式のエンドポイントURL
url = "https://qiita.com/api/v2/items"
# リクエストヘッダーの設定
headers = {}
if token:
headers["Authorization"] = f"Bearer {token}"
# 検索パラメーター(requestsが自動でURLエンコードを実施)
params = {
"page": 1,
"per_page": 20,
"query": "tag:Python stocks:>=50"
}
try:
response = requests.get(url, headers=headers, params=params, timeout=10)
# ステータスコードの確認と処理分岐
if response.status_code == 200:
items = response.json()
total_count = response.headers.get("Total-Count", "不明")
print(f"検索成功: 該当件数 {total_count} 件(取得数: {len(items)}件)\n")
for item in items:
title = item.get("title")
item_url = item.get("url")
likes = item.get("likes_count")
stocks = item.get("stocks_count")
print(f"- [{title}]({item_url}) (Likes: {likes}, Stocks: {stocks})")
elif response.status_code == 401:
print("エラー 401: 認証に失敗しました。QIITA_TOKEN が正しく設定されているか確認してください。")
elif response.status_code == 403:
print("エラー 403: アクセスが拒否されました。スコープの権限が不足している可能性があります。")
elif response.status_code == 429:
print("エラー 429: レート制限(Rate Limit)を超過しました。時間をおいて再試行してください。")
else:
print(f"エラー {response.status_code}: 予期せぬエラーが発生しました。")
print(response.text)
except requests.exceptions.RequestException as e:
print(f"通信エラーが発生しました: {e}")
if __name__ == "__main__":
search_qiita_articles()
Python requestsによる実践:複数条件検索とページネーション
次に、OR条件や否定(除外)条件を含むより複雑な検索クエリを発行し、ページネーションを利用して複数ページにわたる検索結果を一括取得する実践的なコード例を紹介します。
スクリプト2: 複数ページの自動巡回とレート制限情報の確認
import os
import requests
def fetch_multi_page_results(query_string, max_pages=2):
token = os.environ.get("QIITA_TOKEN")
endpoint = "https://qiita.com/api/v2/items"
headers = {"Accept": "application/json"}
if token:
headers["Authorization"] = f"Bearer {token}"
all_articles = []
for current_page in range(1, max_pages + 1):
params = {
"page": current_page,
"per_page": 50, # 1?100の範囲で指定可能
"query": query_string
}
response = requests.get(endpoint, headers=headers, params=params, timeout=10)
# レート制限残数の確認(レスポンスヘッダーから取得)
rate_limit = response.headers.get("X-RateLimit-Limit")
rate_remaining = response.headers.get("X-RateLimit-Remaining")
print(f"[Page {current_page}] Status: {response.status_code} | レート制限残数: {rate_remaining}/{rate_limit}")
if response.status_code == 200:
articles = response.json()
if not articles:
print("該当する記事がこれ以上見つかりません。")
break
all_articles.extend(articles)
elif response.status_code in (401, 403, 429):
print(f"クライアントエラーが発生しました (Status: {response.status_code})。処理を中断します。")
break
else:
print(f"サーバーエラーまたは不明な応答 (Status: {response.status_code})")
break
return all_articles
if __name__ == "__main__":
# OR条件とNOT条件(除外)を組み合わせた高度な検索クエリ
complex_query = "(title:FastAPI OR title:Flask) -tag:Django created:>=2023-01-01"
results = fetch_multi_page_results(complex_query, max_pages=2)
print(f"\n合計取得結果: {len(results)} 件の記事を取得しました。")
API利用時の認証・レート制限・エラー処理と廃止APIの注意点
Qiita API v2を安定してシステムに組み込むためには、レート制限(Rate limit)の仕組みとエラーコードの定義を正確に理解しておくことが不可欠です。
レート制限(Rate Limit)の仕様
Qiita API v2では、サービスの安定性を維持するためにリクエスト回数制限が設けられています。
- 認証リクエスト(アクセストークンあり): 1時間あたり 1,000 回 まで
- 未認証リクエスト(アクセストークンなし): 1時間あたり 60 回 まで(IPアドレス単位でカウント)
レート制限を超過した場合、APIはステータスコード 429 Too Many Requests を返します。制限の解除時刻や残リクエスト数は、レスポンスのHTTPヘッダー(X-RateLimit-Reset や X-RateLimit-Remaining)で確認できます。
レスポンスステータスコード早見表
API利用時に返される主要なHTTPステータスコードと、その意味の一覧です。
| ステータスコード | 意味・種別 | 概要・対処方法 |
|---|---|---|
200 OK |
成功 | GETやPATCHリクエストが正常に処理された |
201 Created |
成功 | POSTリクエストによりリソースが正常に作成された |
204 No Content |
成功 | PUTやDELETEリクエストが成功(レスポンスボティなし) |
400 Bad Request |
クライアントエラー | リクエストパラメーターやJSONフォーマットの不正 |
401 Unauthorized |
認証エラー | アクセストークンが無効、期限切れ、または未指定 |
402 Payment Required |
権限エラー | 対象機能の利用に有料プラン契約が必要 |
403 Forbidden |
権限エラー | アクセス権限が不足、またはスコープ(Scope)の設定不備 |
404 Not Found |
リソース不存在 | 指定したIDのアイテムやエンドポイントが存在しない |
429 Too Many Requests |
レート制限超過 | 1時間あたりのリクエスト上限に到達。時間を置いて再試行 |
500 Internal Server Error |
サーバーエラー | Qiita側の障害。時間を置いて再リクエストを試みる |
廃止済みAPI・機能に関する重要な注意点
APIのアップデートに伴い、過去に提供されていた一部のエンドポイントや機能は廃止されています。古い技術ブログ記事やサンプルコードを参照する際は、現役機能として紹介されている廃止APIを使わないよう注意してください。
-
Project(プロジェクト)関連機能の廃止
-
GET /api/v2/projectsなどのプロジェクト関連エンドポイントは 2022年10月1日 をもって全廃されました。現在は利用できません。
-
-
Thank(感謝)機能・Team Like機能の廃止
-
PUT /api/v2/comments/:comment_id/thankなどのThank関連エンドポイントは 2020年11月4日 に廃止されました。
-
これら存在しないエンドポイントにアクセスすると 404 Not Found が返されます。公式ドキュメントで定義されている最新のエンドポイントのみを使用するように実装してください。
Team専用機能の判別
以下の機能は Qiita Team(組織向けナレッジ共有サービス)専用の機能であり、通常の公開版Qiitaのアクセストークンでは利用できません。
-
グループ(Group)管理:
GET /api/v2/groups等 -
テンプレート(Template)展開:
POST /api/v2/expanded_templates等 -
絵文字リアクション(Emoji reaction):
POST /api/v2/items/:item_id/reactions等 -
アイテムのアクション取得:
GET /api/v2/items/reactions(閲覧数や反応の取得) -
チームアクセストークン(Team access token):
POST /api/v2/team_access_tokens -
SSO(シングルサインオン)環境でのTeam認証: 専用の
*.qiita.comホストとチームトークンが必要
まとめ
Qiita API v2を使った記事検索において、意図通りにデータが取得できない「空振り」を防ぐための要点をまとめます。
-
構文と記号の正確な指定: 検索演算子(
tag:,title:,stocks:など)はすべて半角で記述し、ORやNOTなどの論理演算子は大文字で指定する。 -
URLエンコードの徹底: リクエスト時のクエリ文字列は適切にエンコードする。Pythonの
requestsを利用する場合はparams辞書を活用して自動処理させる。 -
適切な認証とエラーハンドリング: 環境変数
QIITA_TOKENでトークンを安全に管理し、1000回/時のレート制限やステータスコード(401 / 403 / 429)を考慮した実装を行う。
正しい仕様と演算子のルールをマスターし、Qiita APIを活用した効率的な情報収集・連携ツールを構築しましょう。