站設(shè)計靈感庫:SQLite+Playwright+Flask打造可檢索系統(tǒng))
這次我們來看一個 Hacker News 上的 Show HN 項目標(biāo)題是 Show HN: I spent almost 2 years collecting website design inspiration。一眼就能看懂作者用了將近兩年時間持續(xù)收集網(wǎng)站設(shè)計靈感最后做成一個公開項目。Show HN 是 Hacker News 上專門用于曬自創(chuàng)產(chǎn)品的標(biāo)簽說明這不是簡單轉(zhuǎn)載而是作者自己動手做的作品。對設(shè)計資源和前端開發(fā)感興趣的人看到這個標(biāo)題基本就能判斷出它的分量長期維護、真實案例、高篩選價值核心是做設(shè)計參考時不用再漫無目的地去全網(wǎng)翻案例。這類項目的價值其實不在技術(shù)棧而在內(nèi)容沉淀。兩年積累意味著作者見過大量網(wǎng)站后已經(jīng)形成了自己的篩選標(biāo)準(zhǔn)哪些頁面值得收錄、哪些設(shè)計只是炫技、哪些案例能真正給設(shè)計師和前端開發(fā)者參考。每個被收進去的頁面都經(jīng)過了一輪時間篩選。對讀者來說直接訪問一個經(jīng)過長期維護的靈感庫比在社交平臺臨時刷圖高效得多你不需要自己從幾千個網(wǎng)站里挑作者已經(jīng)替你挑過一輪了。普通人的瀏覽器收藏夾里也躺著幾百個網(wǎng)址但沒有分類、沒有截圖、沒有備注時間一長就變成“死書簽”而這類項目把書簽庫升級成了可檢索、可瀏覽、有篩選邏輯的設(shè)計資源系統(tǒng)。這篇文章會做兩件事。第一拆解這類網(wǎng)站設(shè)計靈感收集項目的使用價值、適用人群和使用邊界第二給出一套可以照著做的自建方案——用 SQLite 存案例、用 Playwright 批量截圖、用 Flask 提供檢索接口最后演示怎么備份和部署。技術(shù)上不需要高端配置一臺普通電腦足夠重點是數(shù)據(jù)結(jié)構(gòu)和采集流程的設(shè)計。如果你正打算做一個類似的設(shè)計靈感庫或者想給自己的團隊搭一套可檢索的設(shè)計參考系統(tǒng)這篇文章可以直接按流程走一遍。1. 核心能力速覽能力項說明項目類型Show HN 個人長期維護的網(wǎng)站設(shè)計靈感合集內(nèi)容定位收集真實網(wǎng)站的設(shè)計參考視覺、布局、配色、交互等內(nèi)容形態(tài)常見形態(tài)為網(wǎng)頁畫廊、分類目錄、鏈接集合具體以發(fā)布頁為準(zhǔn)使用門檻低瀏覽器直接訪問即可如果是開源倉庫則需按 README 部署是否開源不確定需看作者發(fā)布形式是否支持 API不確定不建議默認假設(shè)有公開 API主要價值省去找案例的時間沉淀高質(zhì)量參考適用讀者UI/UX 設(shè)計師、前端工程師、獨立開發(fā)者、產(chǎn)品經(jīng)理使用限制只做靈感參考不能直接復(fù)制設(shè)計、素材和代碼為什么要把“是否開源”單獨列出因為在 Hacker News 的 Show HN 帖子里有些作者直接掛一個在線網(wǎng)站有些公開倉庫地址有些兩者都有。在沒有看到完整發(fā)布內(nèi)容之前不建議讀者假設(shè)它有公開 API 或者可以一鍵 clone。后面自建方案的章節(jié)解決的是這類項目本身的工程化思路而不是替作者承諾某一種部署方式。不過有一點可以確定這類項目的核心能力不是“有多少個頁面”而是“每個頁面為什么值得看”。如果作者只是簡單堆鏈接那它和普通書簽沒有本質(zhì)區(qū)別如果每個案例都經(jīng)過了篩選、分類、甚至標(biāo)注那它就是一套完整的網(wǎng)頁設(shè)計參考系統(tǒng)。你在瀏覽時重點也應(yīng)該放在這個維度上。2. 適用場景與使用邊界先說你什么情況下值得收藏這類設(shè)計靈感庫。如果你主要做 UI/UX 設(shè)計日常任務(wù)包括落地頁、官網(wǎng)首頁、SaaS 產(chǎn)品界面這類項目能快速提供真實行業(yè)案例比看抽象概念圖更接近實際交付。如果你在前端開發(fā)寫頁面之前先看一批同類型網(wǎng)站能避免“從零構(gòu)思布局”的盲目也能從案例里反推柵格、斷點、組件排布。如果你是獨立開發(fā)者或產(chǎn)品經(jīng)理可以把靈感庫當(dāng)成競品視覺對標(biāo)庫產(chǎn)品立項時快速確認視覺方向省去和設(shè)計討論時“說不太清想要什么”的尷尬。它解決的核心問題有兩個一是找參考的效率二是沉淀的連續(xù)性。普通人的收藏夾里通常躺著幾百個網(wǎng)址但時間一長就變成“死書簽”——沒有分類、沒有截圖、沒有備注。而這這類項目用兩年時間維護相當(dāng)于把收藏升級成可檢索、可瀏覽、有篩選邏輯的設(shè)計資源系統(tǒng)。這也是它值得細看的根本原因。對照著它你也能發(fā)現(xiàn)自己平時的收集方式到底缺在哪一環(huán)。邊界也很明確不要拿它來“抄設(shè)計”。視覺風(fēng)格可以借鑒但完整頁面布局、文案、插畫、圖片素材都不代表可以免費使用。如果某個設(shè)計案例是真實商業(yè)網(wǎng)站把它的視覺直接搬到自己的項目里可能涉及版權(quán)或商標(biāo)問題。正確用法是抽象思路分析配色為什么舒服、信息層級怎么排、交互反饋怎么做然后應(yīng)用到你自己的內(nèi)容上。另外如果這個靈感庫后續(xù)需要商用必須確認作者的使用條款和授權(quán)范圍涉及個人面部的頁面、未公開數(shù)據(jù)的后臺界面、明顯有盜版風(fēng)險的素材站都不應(yīng)該被收錄進自己的公開項目。3. 這類靈感庫怎么用篩選、記錄、復(fù)盤訪問一個設(shè)計靈感庫時不要漫無目的地刷。最好帶著具體任務(wù)進去常見任務(wù)有三種確認視覺風(fēng)格、確認交互模式、確認信息架構(gòu)。做風(fēng)格參考時重點看首頁首屏、字體搭配、主色與輔助色做交互參考時重點看導(dǎo)航、滾動動效、表單反饋做信息架構(gòu)參考時重點看一個頁面里有多少個模塊、模塊順序如何影響瀏覽動線。同樣是看一個案例關(guān)注點不同得到的結(jié)論完全不同。判斷一個設(shè)計案例是否值得記錄建議看五個維度。一是設(shè)計目的商業(yè)落地頁和概念作品判斷標(biāo)準(zhǔn)完全不同不要拿落地頁的轉(zhuǎn)化邏輯去批判一個實驗性作品二是視覺一致性頁面內(nèi)和站內(nèi)頁面之間是否統(tǒng)一字體、間距、圓角半徑有沒有明顯亂跳三是響應(yīng)式把窗口縮到移動端寬度看布局是否依舊清晰四是加載體驗圖片是否懶加載頁面結(jié)構(gòu)是否穩(wěn)定會不會因為字體加載導(dǎo)致首屏跳動五是可訪問性文字對比度、按鈕尺寸、焦點狀態(tài)是否考慮到了。這樣篩出來的案例才不是“好看”而是“好用”。記錄時不要只存 URL。至少要留三樣?xùn)|西案例亮點、適用場景、截圖。亮點負責(zé)提醒你當(dāng)時為什么看中它場景負責(zé)告訴你它適合用在哪截圖負責(zé)在離線狀態(tài)下也能快速回顧。定好標(biāo)簽體系比如按行業(yè)SaaS、電商、內(nèi)容社區(qū)、按風(fēng)格極簡、玻璃擬態(tài)、深色、新擬態(tài)、按布局類型落地頁、定價頁、博客、模板站來管理后續(xù)檢索時才不會又變成一團亂麻。這個過程其實就是在復(fù)刻作者兩年里做的事。4. 自建設(shè)計靈感收集系統(tǒng)環(huán)境準(zhǔn)備4.1 需要準(zhǔn)備什么自建一套類似的靈感收集系統(tǒng)不需要高性能顯卡也不需要云服務(wù)器一臺能聯(lián)網(wǎng)的普通電腦就夠。推薦的軟件清單是Python 3.10 以上、Node.js 18 以上、SQLitePython 自帶、Git以及瀏覽器截圖庫 Playwright。操作系統(tǒng)不限Windows、macOS、Linux 都可以。磁盤方面截圖會是主要占用建議先預(yù)留 10GB 以上具體大小取決于截圖數(shù)量和分辨率。如果你是團隊使用建議選一臺長期開機的內(nèi)網(wǎng)機器方便統(tǒng)一維護采集腳本和數(shù)據(jù)庫如果只是個人使用放在自己電腦上完全沒問題。整套系統(tǒng)沒有硬性的內(nèi)存門檻批量截圖時內(nèi)存需求會上升建議至少 8GB 內(nèi)存。單頁截圖時 4GB 內(nèi)存也能跑只是并發(fā)要壓得更低。這個項目本身不需要 GPUCPU 也只需要普通水平瓶頸通常在網(wǎng)絡(luò)加載速度而不是計算能力。4.2 驗證基礎(chǔ)環(huán)境先確認本機環(huán)境和版本再開始安裝依賴。以下命令在 Windows PowerShell、macOS 或 Linux 終端里都可以執(zhí)行python --version node -v git --version如果 Python 版本低于 3.10建議先升級否則部分類型注解和異步語法在舊版本上會報錯。接著安裝項目依賴pip install flask playwright playwright install chromiumPlaywright 的 install 命令會下載 Chromium 瀏覽器體積較大需要一點時間。下載慢的時候可以檢查網(wǎng)絡(luò)下載完成后可以用playwright install --with-deps chromium安裝系統(tǒng)依賴Linux 環(huán)境下常用Windows 和 macOS 一般不需要額外系統(tǒng)依賴。安裝完成后可以用playwright --version驗證一下是否成功。安裝過程最常見的問題是網(wǎng)絡(luò)超時或權(quán)限不足。Windows 上建議用管理員權(quán)限打開終端Linux 上如果提示缺少系統(tǒng)庫就執(zhí)行playwright install --with-deps chromium補齊。安裝本身不復(fù)雜但這一步?jīng)]做好后面批量采集時會頻繁報錯所以值得花幾分鐘確認干凈環(huán)境。5. 數(shù)據(jù)層設(shè)計用 SQLite 存案例、標(biāo)簽、截圖路徑5.1 表結(jié)構(gòu)設(shè)計靈感庫的核心數(shù)據(jù)是網(wǎng)站 URL、標(biāo)題、描述、標(biāo)簽和截圖路徑。用一個簡單的 SQLite 數(shù)據(jù)庫就能放下不必上 PostgreSQL。先建三張表websites 存網(wǎng)站案例tags 存標(biāo)簽website_tags 存多對多關(guān)系。這樣設(shè)計的好處是擴展性好以后想按行業(yè)、風(fēng)格、布局篩選時只需要往 tags 表里加數(shù)據(jù)不需要改表結(jié)構(gòu)。SQLite 的建表語句如下CREATE TABLE websites ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT UNIQUE NOT NULL, title TEXT, description TEXT, screenshot_path TEXT, created_at TEXT DEFAULT (datetime(now)) ); CREATE TABLE tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL ); CREATE TABLE website_tags ( website_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY (website_id, tag_id), FOREIGN KEY (website_id) REFERENCES websites(id), FOREIGN KEY (tag_id) REFERENCES tags(id) );URL 字段加 UNIQUE 約束批量采集時重復(fù) URL 會直接報錯方便我們用冪等邏輯避免重復(fù)入庫。screenshot_path 存相對路徑盡量不存絕對路徑這樣整個數(shù)據(jù)目錄可以整體移動或備份。created_at 用 SQLite 默認時間記錄收錄時間后續(xù)做趨勢統(tǒng)計時也能用上。5.2 初始化數(shù)據(jù)庫在 Python 里執(zhí)行建表語句可以用 sqlite3 自帶的 executescript不需要引入 ORM。下面是最小實現(xiàn)import sqlite3 DB_PATH inspiration.db conn sqlite3.connect(DB_PATH) conn.executescript( CREATE TABLE IF NOT EXISTS websites ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT UNIQUE NOT NULL, title TEXT, description TEXT, screenshot_path TEXT, created_at TEXT DEFAULT (datetime(now)) ); CREATE TABLE IF NOT EXISTS tags ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT UNIQUE NOT NULL ); CREATE TABLE IF NOT EXISTS website_tags ( website_id INTEGER NOT NULL, tag_id INTEGER NOT NULL, PRIMARY KEY (website_id, tag_id), FOREIGN KEY (website_id) REFERENCES websites(id), FOREIGN KEY (tag_id) REFERENCES tags(id) ); ) conn.commit() conn.close() print(database initialized:, DB_PATH)這段腳本執(zhí)行后當(dāng)前目錄會生成一個 inspiration.db 文件。后面每次采集時都先檢查這個文件是否存在不存在就重新初始化。這樣整個系統(tǒng)從零開始搭建時只需要一個 Python 文件和一個腳本就能跑通數(shù)據(jù)層。6. 批量采集Playwright 抓取截圖與元數(shù)據(jù)6.1 采集腳本接下來是核心環(huán)節(jié)批量瀏覽網(wǎng)站、截圖并提取元數(shù)據(jù)。這里用 Playwright 寫一個最簡腳本讀取一個 URL 列表逐個打開頁面等待頁面核心內(nèi)容加載后截取全屏截圖并寫入數(shù)據(jù)庫。這樣每個案例既有截圖又有 title 和 URL后續(xù)展示時不需要重新打開原站。import asyncio import sqlite3 from pathlib import Path from playwright.async_api import async_playwright DB_PATH inspiration.db SCREENSHOT_DIR Path(./screenshots) SCREENSHOT_DIR.mkdir(exist_okTrue) def init_db(): conn sqlite3.connect(DB_PATH) conn.executescript( CREATE TABLE IF NOT EXISTS websites ( id INTEGER PRIMARY KEY AUTOINCREMENT, url TEXT UNIQUE NOT NULL, title TEXT, description TEXT, screenshot_path TEXT, created_at TEXT DEFAULT (datetime(now)) ); ) return conn async def save_website(conn, data): conn.execute( INSERT INTO websites(url, title, description, screenshot_path) VALUES (?, ?, ?, ?), (data[url], data[title], data[description], data[screenshot_path]), ) conn.commit() async def main(): conn init_db() urls [https://example.com, https://example.org] async with async_playwright() as p: browser await p.chromium.launch() page await browser.new_page(viewport{width: 1440, height: 900}) for url in urls: try: await page.goto(url, wait_untildomcontentloaded, timeout30000) await page.wait_for_timeout(2000) title await page.title() description 示例描述字段可按需用 meta 標(biāo)簽解析 name url.replace(https://, ).replace(/, _)[:80] shot_path str(SCREENSHOT_DIR / f{name}.png) await page.screenshot(pathshot_path, full_pageTrue) save_website(conn, { url: url, title: title, description: description, screenshot_path: shot_path, }) print(OK:, url) except Exception as e: print(FAIL:, url, e) await browser.close() conn.close() if __name__ __main__: asyncio.run(main())腳本里用wait_untildomcontentloaded而不是networkidle目的是減少超時。很多 SPA 網(wǎng)站如果等 networkidle單個頁面可能 20 秒甚至更久都加載不完實際采集時會很痛苦。這里改為等待 DOM 加載后固定停頓 2 秒給渲染留出時間對多數(shù)網(wǎng)站來說已經(jīng)夠用。如果你發(fā)現(xiàn)某些頁面截圖時內(nèi)容還沒渲染出來可以把這個停頓時間調(diào)大到 4 到 5 秒。6.2 運行結(jié)果與驗證運行后終端會打印每個 URL 的結(jié)果。成功時可以在 screenshots 目錄里看到對應(yīng)頁面截圖失敗時會輸出異常信息。判斷批量任務(wù)是否成功不只看截圖是否生成還要看兩點數(shù)據(jù)庫里的 title 是否準(zhǔn)確screenshot_path 是否與文件路徑一致。如果 title 為空檢查目標(biāo)網(wǎng)站是否有 title 標(biāo)簽或頁面渲染是否被 JS 阻塞如果截圖是空白頁大概率是目標(biāo)網(wǎng)站只渲染首屏或者圖片、字體走了懶加載需要增加等待時間或滾動頁面后再截圖。如果運行時報錯Target page, context or browser has been closed通常是瀏覽器窗口被系統(tǒng)回收或者上一個頁面打開時間太長導(dǎo)致瀏覽器進程不穩(wěn)定。處理方式是減少單批 URL 數(shù)量、適當(dāng)降低超時時間并在異常分支里把失敗的 URL 記錄下來方便結(jié)束后重跑。批量采集本來就不是一次就能跑完的設(shè)計腳本時把失敗項留痕比追求 100% 成功率更重要。7. 檢索與接口Flask API 或 JSON 數(shù)據(jù)輸出7.1 Flask API 實現(xiàn)采集到數(shù)據(jù)之后不能只停留在靜態(tài)文件層面。為了讓團隊協(xié)作或后續(xù)二次開發(fā)加一個輕量 API 是合適的。這里用 Flask 提供一個/api/websites接口支持按標(biāo)簽篩選返回 JSON。這樣前端可以做搜索、篩選、分頁后端也只需要一個很小的服務(wù)進程。from flask import Flask, jsonify, request import sqlite3 app Flask(__name__) DB_PATH inspiration.db def query_db(sql, args()): conn sqlite3.connect(DB_PATH) conn.row_factory sqlite3.Row rows conn.execute(sql, args).fetchall() conn.close() return [dict(row) for row in rows] app.route(/api/websites) def list_websites(): tag request.args.get(tag) if tag: rows query_db( SELECT w.* FROM websites w JOIN website_tags wt ON w.id wt.website_id JOIN tags t ON t.id wt.tag_id WHERE t.name ? , (tag,)) else: rows query_db(SELECT * FROM websites ORDER BY id DESC) return jsonify({data: rows}) app.route(/api/websites/int:website_id) def get_website(website_id): rows query_db(SELECT * FROM websites WHERE id ?, (website_id,)) if not rows: return jsonify({error: not found}),