把 HaoNews 的熱榜存到本地之後,最煩的是「那篇講晶片補貼的帖子在哪」——站內搜尋只給當前列表,翻回去要一頁一頁找。我的做法是:每天把標題、連結、發佈時間、正文摘要寫進一個 SQLite 檔,用 FTS5 建全文索引,查詢用 MATCH 加 bm25() 排序。環境是 macOS 上的 SQLite 3.43,資料量不大,累積約 1.8 萬筆,單次查詢基本在 20ms 內。
建表就三句,content='' 表示外部內容模式,避免正文存兩份:
CREATE TABLE posts(id INTEGER PRIMARY KEY, url TEXT UNIQUE, title TEXT, body TEXT, ts INTEGER);
CREATE VIRTUAL TABLE posts_fts USING fts5(title, body, content='posts', content_rowid='id',
tokenize="unicode61 remove_diacritics 2");
寫入時我沒有用觸發器,而是在應用層同一個交易裡手動同步,因為抓取腳本會做 INSERT OR IGNORE,用觸發器反而容易在衝突時把索引寫歪:
cur.execute("INSERT OR IGNORE INTO posts(url,title,body,ts) VALUES(?,?,?,?)", row)
if cur.rowcount:
cur.execute("INSERT INTO posts_fts(rowid,title,body) VALUES(last_insert_rowid(),?,?)", (row[1], row[2]))
查詢時把使用者輸入的每個詞用引號包起來再 OR,避免 AND/NEAR 被當成語法:
q = ' OR '.join('"%s"' % w.replace('"','') for w in words)
cur.execute("SELECT p.title,p.url,bm25(posts_fts) FROM posts_fts JOIN posts p ON p.id=posts_fts.rowid WHERE posts_fts MATCH ? ORDER BY bm25(posts_fts) LIMIT 20", (q,))
為什麼手動同步:FTS5 的觸發器要寫三支(insert/delete/update),漏一支就會出現「搜得到但點進去 404」的鬼故事,我踩過一次,補了兩天才把索引重建乾淨。代價是應用層要自己保證原子性,抓取腳本一多,這段就得包在同一個 BEGIN 裡。
會失效的地方:中文沒有空白分詞,unicode61 只會把整串中文當一個 token,搜「晶片」搜不到「晶片補貼」。我現在的折衷是標題額外存一份按二字切分的欄位,查詢時對中文詞走那條路,英文走原表;但這樣召回率上去了,精確度掉得明顯,長標題容易誤中。另一個坑是 bm25 的權重參數,我一開始把 title 調到 10,結果短標題霸榜,正文裡真正有細節的帖子被壓下去,後來調回 3 才正常。
資料量再往上,重建索引會鎖表,我目前是半夜停抓取十分鐘重建,還沒遇到瓶頸。
你們在本地做這種聚合搜尋時,中文是怎麼分詞的?用 jieba 預切還是靠 SQLite 的 trigram tokenizer?
0 条评论
还没有评论,写下第一条。