項目)
3個步驟搞定目前手機銷量排行榜實戰(zhàn)項目
代碼跑不通?別慌。很多初學者卡在環(huán)境配置和報錯堆棧上,其實只要理清數(shù)據(jù)流,問題就解決了一半。今天咱們不聊虛的,直接拆解一個實戰(zhàn)項目:基于真實場景的“目前手機銷量排行榜”系統(tǒng)。
這不僅僅是寫幾行查詢語句,而是從數(shù)據(jù)清洗、聚合計算到前端展示的完整閉環(huán)。哪怕你只是剛接觸后端開發(fā),跟著這個實戰(zhàn)項目走一遍,也能摸清從0到1搭建業(yè)務邏輯的脈絡。
項目目標與場景還原
在做任何代碼之前,先搞清楚我們要解決什么問題。真實的手機銷量數(shù)據(jù)不是整齊劃一的Excel表格,而是散落在各個渠道、格式混亂的原始日志。
我們的核心目標是構建一個輕量級服務,輸入過去30天的銷售流水,輸出實時更新的Top 10銷量排行榜。這里有一個關鍵難點:數(shù)據(jù)清洗?,F(xiàn)實中,同一款手機可能有“iPhone 15 Pro”、“蘋果15Pro”、“IP15P”等多種叫法。如果不去重,排行榜就會失去意義。
這個實戰(zhàn)項目的核心價值在于模擬真實業(yè)務的“臟數(shù)據(jù)”處理能力。很多教程直接給你干凈的數(shù)據(jù)集,導致你一旦接觸真實生產(chǎn)環(huán)境,代碼就崩。我們要做的,就是在一個受控環(huán)境中,復現(xiàn)這種“臟”與“亂”,并找到穩(wěn)健的解決方案。
通過完成這個實戰(zhàn)項目,你不僅學會了排序,更學會了如何處理不確定性數(shù)據(jù)。這是初級工程師和中級工程師的分水嶺。
目錄結構與技術選型
為了保持輕量,我們選擇 Python 作為后端語言,F(xiàn)astAPI 作為框架,SQLite 作為存儲。為什么選這套組合?因為啟動快,部署簡單,非常適合快速驗證業(yè)務邏輯。
項目目錄結構如下:
phone_ranking_project/
├── main.py # FastAPI 入口
├── database.py # 數(shù)據(jù)庫連接與初始化
├── models.py # Pydantic 數(shù)據(jù)模型
├── services.py # 核心業(yè)務邏輯(清洗、聚合)
├── data/
│ └── raw_sales.json # 模擬原始臟數(shù)據(jù)
└── requirements.txt這種結構清晰明了。services.py 是靈魂,所有的臟數(shù)據(jù)清洗邏輯都放在這里,避免在路由層寫復雜邏輯。database.py 負責連接管理,確保資源釋放。
注意,我們特意沒有引入復雜的 ORM 框架,而是使用 SQLAlchemy 的基礎 Core 模塊。因為在處理大量聚合查詢時,直接寫 SQL 往往比 ORM 生成的動態(tài)查詢更高效,也更容易調試。這也是很多資深工程師在性能敏感場景下的偏好。
核心代碼實現(xiàn)與逐行解析
接下來進入硬骨頭。我們將分三步實現(xiàn)核心邏輯:數(shù)據(jù)加載與清洗、聚合計算、API 暴露。
1. 模擬臟數(shù)據(jù)與清洗邏輯
首先,看 data/raw_sales.json 的一部分。這里故意制造了大小寫不一致、空格多余、品牌名稱不統(tǒng)一的問題。
[{id: 1, name: iPhone 15 Pro , qty: 100, date: 2023-10-01},{id: 2, name: apple 15pro, qty: 50, date: 2023-10-01},{id: 3, name: iPhone 15, qty: 200, date: 2023-10-02}
]在 services.py 中,我們實現(xiàn)一個清洗函數(shù)。這里的關鍵是標準化映射。
import re
from collections import defaultdictdef normalize_phone_name(raw_name: str) - str:標準化手機名稱1. 去除首尾空格2. 統(tǒng)一轉小寫3. 替換常見別名name = raw_name.strip().lower()# 簡單規(guī)則:將 'apple' 替換為 'iphone' 的前綴處理邏輯# 實際項目中,這里應該查一個映射表if name.startswith('apple'):name = name.replace('apple', 'iphone')# 去除中間多余空格name = re.sub(r'\s+', ' ', name)return name這段代碼雖然簡單,但體現(xiàn)了處理臟數(shù)據(jù)的通用思路:規(guī)范化(Normalization)。在實際的實戰(zhàn)項目中,這個映射表可能來自數(shù)據(jù)庫,由運營人員維護。
2. 聚合計算與排序
清洗完成后,我們需要按標準化后的名稱聚合銷量。
from datetime import datetime, timedeltadef get_top_rankings(limit: int = 10) - list[dict]:獲取過去30天的銷量排行榜cutoff_date = datetime.now() - timedelta(days=30)# 1. 從數(shù)據(jù)庫獲取原始數(shù)據(jù)# 假設 db_session 已建立raw_records = db_session.query(SaleRecord).filter(SaleRecord.date = cutoff_date).all()# 2. 內存中聚合(數(shù)據(jù)量小適用;數(shù)據(jù)量大需用SQL GROUP BY)sales_dict = defaultdict(int)for record in raw_records:std_name = normalize_phone_name(record.name)sales_dict[std_name] += record.qty# 3. 排序sorted_items = sorted(sales_dict.items(), key=lambda item: item[1], reverse=True)# 4. 截取 Top Nresult = []for i, (name, total) in enumerate(sorted_items[:limit], 1):result.append({rank: i,name: name.title(), # 轉回標題格式展示total_sales: total})return result這里有一個常見的坑:時間過濾放在數(shù)據(jù)庫層還是應用層?
在數(shù)據(jù)量小于10萬條時,拉取到內存處理更靈活,方便做復雜的清洗。但如果數(shù)據(jù)量達到百萬級,必須在 SQL 層完成 GROUP BY 和 SUM,然后再應用清洗邏輯。這是性能優(yōu)化的關鍵轉折點。
3. API 暴露
在 main.py 中,暴露接口非常簡單。
from fastapi import FastAPI, HTTPExceptionapp = FastAPI()@app.get(/api/rankings)
def get_rankings():try:rankings = get_top_rankings(limit=10)return {code: 200, data: rankings}except Exception as e:# 記錄日志,不要直接拋堆棧給前端print(fError fetching rankings: {e})raise HTTPException(status_code=500, detail=Internal Server Error)注意異常處理。在生產(chǎn)環(huán)境中,永遠不要把原始的 Exception 堆棧返回給前端,這會泄露服務器路徑信息,造成安全隱患。
運行與測試:如何驗證代碼沒寫錯
代碼寫完只是開始,跑通才是真本事。很多初學者卡在這里,因為環(huán)境依賴沖突。
1. 環(huán)境準備
創(chuàng)建一個虛擬環(huán)境,避免污染全局 Python 環(huán)境。
python -m venv venv
source venv/bin/activate # Windows 用戶用 venv\Scripts\activate
pip install -r requirements.txtrequirements.txt 內容如下:
fastapi==0.100.0
uvicorn==0.23.1
sqlalchemy==2.0.192. 初始化數(shù)據(jù)庫
運行 database.py 中的初始化腳本,確保表結構存在。
python database.py3. 啟動服務與測試
uvicorn main:app --reload打開瀏覽器訪問 http://127.0.0.1:8000/docs,這是 FastAPI 自帶的 Swagger 文檔。點擊“Try it out”,輸入?yún)?shù),點擊 Execute。
重點檢查:返回的 JSON 格式是否正確?
銷量數(shù)字是否與手動計算一致?
名稱是否被正確標準化?例如 “apple 15pro” 是否變成了 “Iphone 15 Pro”?如果返回 500 錯誤,查看終端日志。90% 的錯誤都是字段名不匹配或數(shù)據(jù)類型轉換失敗。例如,日期字段在數(shù)據(jù)庫中是 String,但在 Python 中是 datetime 對象,比較時會報錯。
4. 單元測試
在 tests/test_services.py 中寫一個簡單的測試,確保清洗邏輯的穩(wěn)定性。
from services import normalize_phone_namedef test_normalize():assert normalize_phone_name( Apple 15 Pro ) == apple 15 proassert normalize_phone_name(apple15pro) == apple 15 pro # 假設你有空格填充邏輯使用 pytest 運行:pytest -v。綠鉤才是安全的。
優(yōu)化擴展:從 Demo 到生產(chǎn)級
目前的實現(xiàn)是同步阻塞的,適合小規(guī)模數(shù)據(jù)。如果要擴展到真實生產(chǎn)環(huán)境,有幾個方向值得探索。
1. 引入緩存機制
排行榜是典型的“讀多寫少”場景。每次請求都查數(shù)據(jù)庫,壓力巨大。引入 Redis 緩存,TTL 設置為 60 秒。
import redisr = redis.Redis(host='localhost', port=6379, db=0)def get_top_rankings_cached():key = rankings:top10cached = r.get(key)if cached:return json.loads(cached)data = get_top_rankings(limit=10)r.setex(key, 60, json.dumps(data))return data2. 異步化處理
FastAPI 支持異步。如果數(shù)據(jù)源是遠程 API 而非本地數(shù)據(jù)庫,應將 get_raw_data 改為 async def,使用 httpx 或 aiohttp 發(fā)起非阻塞請求。
3. 數(shù)據(jù)可視化
后端只返回 JSON 太單調??梢约?ECharts,前端直接渲染柱狀圖。這一步能極大提升項目的展示效果,尤其是在面試展示或 GitHub 倉庫中。
4. 監(jiān)控與告警
在 services.py 中增加 Prometheus 指標埋點。例如,統(tǒng)計清洗失敗的記錄數(shù)。如果失敗率突然飆升,說明上游數(shù)據(jù)源格式發(fā)生了變更,需要立即告警。
這些優(yōu)化點,正是區(qū)分“學生作業(yè)”和“工程代碼”的關鍵。面試官往往不關心你的算法有多復雜,而關心你是否考慮了容錯、性能、可觀測性。
小結與互動
回顧這個目前手機銷量排行榜的實戰(zhàn)項目,我們完成了從臟數(shù)據(jù)處理到 API 暴露的全流程。核心收獲有三點:數(shù)據(jù)清洗是基礎:不要假設輸入數(shù)據(jù)是干凈的,防御性編程是后端的基本功。
分層架構的重要性:業(yè)務邏輯、數(shù)據(jù)訪問、接口展示分離,代碼才能維護。
性能思維:從小數(shù)據(jù)量到大數(shù)據(jù)量的切換點,決定了你的架構選型。這個實戰(zhàn)項目代碼已整理好,結構清晰,注釋詳盡,適合初學者直接復現(xiàn)。你可以在此基礎上,嘗試增加“按品牌分類”、“按月份趨勢”等新功能,進一步鍛煉自己的能力。
技術棧的選擇沒有絕對的對錯,關鍵在于解決實際問題。Python + FastAPI 的組合輕量高效,非常適合快速原型開發(fā);如果你更傾向于類型安全,可以嘗試 TypeScript + NestJS 實現(xiàn)同樣的功能,邏輯是相通的。
你公司項目里是怎么處理這種“臟數(shù)據(jù)”清洗的?是寫在 SQL 里,還是 Python 里?有沒有遇到過因為數(shù)據(jù)格式變更導致線上事故的經(jīng)歷?歡迎在評論區(qū)分享你的踩坑經(jīng)驗,咱們一起避坑。