iT邦幫忙

2025 iThome 鐵人賽

DAY 15
0
生成式 AI

一起來打造 PTT 文章智慧問答系統!系列 第 15

【Day 15】實作 Django REST Framework API - 建立文章 API:單篇內容與統計資料

  • 分享至 

  • xImage
  •  

Hi大家好,
這是我參加 iT 邦幫忙鐵人賽的第 1 次挑戰,這次的主題聚焦在結合 Python 爬蟲、RAG(檢索增強生成)與 AI,打造一套 PTT 文章智慧問答系統。在過程中,我會依照每天進度上傳程式碼到 GitHub ,方便大家參考學習。也歡迎留言或來信討論,我的信箱是 gerryearth@gmail.com


今天我們將擴充 API 功能,讓系統不只能列出文章清單,還能查詢單篇文章內容,並提供基本統計資訊,為後續智慧問答奠定資料基礎。


今日目標

  • 實作單篇文章查詢 API(透過文章 ID)
  • 建立簡易統計 API:文章總數、熱門看板、熱門作者
  • 建立對應的 View 與 URL 配置

延續前一天的基礎

在前一天,我們已建立 /api/posts/ 的列表查詢 API。今天我們要擴充:

  1. /api/posts/<id>/:查詢特定文章內容
  2. /api/statistics/:回傳整體統計資訊

單篇文章查詢 API

首先請在 article/urls.py 增加:

urlpatterns = [
    ...
    path('posts/<int:pk>/', views.ArticleDetailView.as_view(), name='article-detail'),
]

接下來就要寫 article/views.pyArticleDetailView 的內容:

class ArticleDetailView(APIView):
    @extend_schema(
        description="根據文章 ID 取得特定文章的詳細內容。",
        responses={200: ArticleSerializer(),
                   404: OpenApiResponse(response={"type": "object", "properties": {"error": {"type": "string"}}})}
    )
    def get(self, request, pk):
        if pk <= 0:
            Log.objects.create(level='ERROR', category='user-posts_id', message='文章ID須為正數', )
            return Response({"error": "文章ID須為正數"}, status=status.HTTP_404_NOT_FOUND)
        try:
            articles = Article.objects.get(id=pk)
        except Article.DoesNotExist:
            Log.objects.create(level='ERROR', category='user-posts_id', message='找不到文章,請輸入正確文章ID', traceback=traceback.format_exc())
            return Response({"error": "找不到文章,請輸入正確文章ID"}, status=status.HTTP_404_NOT_FOUND)
        return Response(ArticleSerializer(articles).data, status=status.HTTP_200_OK)

有了前面建立 API 的經驗後,這裡應該就簡單很多了!
比較不一樣的是 get(self, request, pk) 可以接收 URL 中的文章 ID(pk),我們就可以用這個 pk 取得對應文章。


統計 API:文章數量、熱門看板與作者

我們再來新增一個新的 API 端點:/api/statistics/

urlpatterns = [
    ...
    path('statistics/', views.ArticleStatisticsView.as_view(), name='article-statistics'),
]

比較敏銳的人可能發現這個 API 跟昨天的 API 一樣都要進行篩選,因此我們可以把篩選的部分寫成一個 function:

def articles_filter(article_list_request_serializer):
    articles = Article.objects.all()
    author_name = article_list_request_serializer.validated_data.get("author_name")
    board_name = article_list_request_serializer.validated_data.get("board_name")
    start_date = article_list_request_serializer.validated_data.get("start_date")
    end_date = article_list_request_serializer.validated_data.get("end_date")
    if author_name:
        articles = articles.filter(author__name=author_name)
    if board_name:
        articles = articles.filter(board__name=board_name)
    start_datetime = datetime.combine(start_date, time.min) if start_date else None
    end_datetime = datetime.combine(end_date, time.max) if end_date else None
    if start_datetime and end_datetime:
        articles = articles.filter(post_time__range=[start_datetime, end_datetime])
    elif start_datetime:
        articles = articles.filter(post_time__gte=start_datetime)
    elif end_datetime:
        articles = articles.filter(post_time__lte=end_datetime)
    return articles

昨天的 API 就可以寫得更精簡了:

class ArticleListView(APIView):
    ...
    def get(self, request):
        article_list_request_serializer = ArticleListRequestSerializer(data=request.query_params)
        if not article_list_request_serializer.is_valid():
            Log.objects.create(level='ERROR', category='user-posts', message='查詢參數不合法',
                               traceback=traceback.format_exc())
            return Response(article_list_request_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        articles = articles_filter(article_list_request_serializer)
        paginator = LimitOffsetPagination()
        paginator.default_limit = 50
        paginated_queryset = paginator.paginate_queryset(articles.order_by('id'), request)
        serializer = ArticleSerializer(paginated_queryset, many=True)
        return paginator.get_paginated_response(serializer.data)

views.py 新增 ArticleStatisticsView

我們一樣使用 articles_filter 進行篩選,並統計數量就可以完成了!

class ArticleStatisticsView(APIView):
    @extend_schema(
        description="取得文章統計資訊,支援時間範圍、作者名稱和版面過濾",
        parameters=[
            OpenApiParameter("author_name", str, OpenApiParameter.QUERY, description="篩選特定發文者的文章"),
            OpenApiParameter("board_name", str, OpenApiParameter.QUERY, description="篩選特定版面的文章"),
            OpenApiParameter("start_date", str, OpenApiParameter.QUERY, description="篩選起始日期 (YYYY-MM-DD)", ),
            OpenApiParameter("end_date", str, OpenApiParameter.QUERY, description="篩選結束日期 (YYYY-MM-DD)", ),
        ],
        responses={
            200: OpenApiResponse(response={"type": "object", "properties": {"total_articles": {"type": "integer"}}}),
            400: OpenApiResponse(response={"type": "object", "properties": {"error": {"type": "string"}}}),
        }
    )
    def get(self, request):
        article_list_request_serializer = ArticleListRequestSerializer(data=request.query_params)
        if not article_list_request_serializer.is_valid():
            Log.objects.create(level='ERROR', category='user-posts', message='查詢參數不合法', )
            return Response(article_list_request_serializer.errors, status=status.HTTP_400_BAD_REQUEST)
        articles = articles_filter(article_list_request_serializer)
        total_articles = articles.count()
        return Response({"total_articles": total_articles})

測試 API

全都準備好了就來測試 API 吧!
可以發現多了兩個我們新建立的 API :
https://ithelp.ithome.com.tw/upload/images/20250624/20172834B5kEQTg9SO.png
https://ithelp.ithome.com.tw/upload/images/20250624/20172834YGXCarmYU9.png

請大家自由測試。


明天【Day16】認識 RAG 與 向量資料庫 - 整合 RAG 與 Pinecone 的運作流程
我們將從概念出發,帶你了解什麼是 RAG、向量資料庫,以及 Pinecone 在本專案中的角色!


上一篇
【Day 14】實作 Django REST Framework API - 建立文章 API:列表、篩選、查詢
下一篇
【Day 16】認識 RAG 與 向量資料庫 - 整合 RAG 與 Pinecone 的運作流程
系列文
一起來打造 PTT 文章智慧問答系統!17
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言