此文由 Mix Space 同步更新至 xLog
為獲得最佳瀏覽體驗,建議訪問原始鏈接
https://www.do1e.cn/posts/code/algolia-search
Algolia 搜尋配置方法#
mx-space 的文檔中有比較詳細的配置教程,其他博客框架可能大同小異。
索引大小限制#
很不幸,在根據文檔配置完後,log 中報錯了:
16:40:40 ERROR [AlgoliaSearch] algolia 推送錯誤
16:40:40 ERROR [Event] Record at the position 10 objectID=xxxxxxxx is too big size=12097/10000 bytes. Please have a look at
https://www.algolia.com/doc/guides/sending-and-managing-data/prepare-your-data/in-depth/index-and-records-size-and-usage-limitations/#record-size-limits
出錯原因也很明確,有一篇博客太長了,而免費的 Algolia 每條數據僅有 10KB。對於我這種想白嫖的人怎麼能忍,馬上想辦法解決。
解決方案#
思路#
對於 mx-space 來說,可以配置 API Token 後從/api/v2/search/algolia/import-json
獲取到手動提交到 Algolia 索引的 json 文件。
其中是一個包含了posts, pages 和notes的列表,示例數據如下:
{
"title": "南京大學IPv4地址範圍",
"text": "# 動機\n\n<details>\n<summary>動機來自於搭建的網頁。由於校內和公網都有搭建....",
"slug": "nju-ipv4",
"categoryId": "abcdefg",
"category": {
"_id": "abcdefg",
"name": "其他",
"slug": "others",
"id": "abcdefg"
},
"id": "1234567",
"objectID": "1234567",
"type": "post"
},
其中objectID
比較關鍵,提交給 Algolia 的必須唯一。
這裡我能想到的思路便是分頁,將有過長text
的文章切分,同時修改objectID
不就可以了?!(顯然,此時並沒有想到問題的嚴重性)
另外我的一些頁面裡會寫<style>
和<script>
,這部分也可以直接使用正則匹配刪掉。
於是有了如下 Python 代碼,編輯從上述接口下載的 json 並提交給 Algolia。
from algoliasearch.search.client import SearchClientSync
import requests
import json
import math
import os
from copy import deepcopy
import re
MAXSIZE = 9990
APPID = "..."
APPKey = "..."
MXSPACETOKEN = "..."
url = "https://www.do1e.cn/api/v2/search/algolia/import-json"
headers = {
"Authorization": MXSPACETOKEN,
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36 Edg/131.0.0.0",
}
ret = requests.get(url, headers=headers)
ret = ret.json()
with open("data.json", "w", encoding="utf-8") as f:
json.dump(ret, f, ensure_ascii=False, indent=2)
to_push = []
def json_length(item):
content = json.dumps(item, ensure_ascii=False).encode("utf-8")
return len(content)
def right_text(text):
try:
text.decode("utf-8")
return True
except:
return False
def cut_json(item):
length = json_length(item)
text_length = len(item["text"].encode("utf-8"))
# 計算切分份數
n = math.ceil(text_length / (MAXSIZE - length + text_length))
start = 0
text_content = item["text"].encode("utf-8")
for i in range(n):
new_item = deepcopy(item)
new_item["objectID"] = f"{item['objectID']}_{i}"
end = start + text_length // n
# 切分時要注意確保能被正確解碼(中文占2個字節)
while not right_text(text_content[start:end]):
end -= 1
new_item["text"] = text_content[start:end].decode("utf-8")
start = end
to_push.append(new_item)
for item in ret:
# 刪除style和script標籤
item["text"] = re.sub(r"<style.*?>.*?</style>", "", item["text"], flags=re.DOTALL)
item["text"] = re.sub(r"<script.*?>.*?</script>", "", item["text"], flags=re.DOTALL)
if json_length(item) > MAXSIZE: # 超過限制,切分
print(f"{item['title']} is too large, cut it")
cut_json(item)
else: # 沒超限制也修改objectID以保持一致性
item["objectID"] = f"{item['objectID']}_0"
to_push.append(item)
with open("topush.json", "w", encoding="utf-8") as f:
json.dump(to_push, f, ensure_ascii=False, indent=2)
client = SearchClientSync(APPID, APPKey)
resp = client.replace_all_objects("mx-space", to_push)
print(resp)
如果你用的是其他博客框架,看到這裡就夠了,希望能給你提供點思路。
很好,用 Python 修改搜索索引後重新提交到 Algolia 並在 mx-space 後台啟用搜索功能,來試一試搜索超出限制的JPEG 編碼細節吧。
怎麼沒有結果?怎麼後台又報錯了?
17:03:46 ERROR [Catch] Cast to ObjectId failed for value "1234567_0" (type string) at path "_id" for model "posts"
at SchemaObjectId.cast (entrypoints.js:1073:883)
at SchemaType.applySetters (entrypoints.js:1187:226)
at SchemaType.castForQuery (entrypoints.js:1199:338)
at cast (entrypoints.js:159:5360)
at Query.cast (entrypoints.js:799:583)
at Query._castConditions (entrypoints.js:765:9879)
at Hr.Query._findOne (entrypoints.js:768:430)
at Hr.Query.exec (entrypoints.js:784:5145)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Promise.all (index 0)
來編輯 mx-space 代碼吧#
從上述 log 很容易看出,mx-space 使用ObjectId
作為了索引,而不是id
,定位到代碼中的這裡:
將其修改如下即可。
更進一步地完善#
僅僅如此也還是不太優雅,需要定時運行 Python 腳本將數據推送到 Algolia。既然都已經開始修改 mx-space 代碼了,不如一步到位把分頁集成進去算了,好在現在的各種 AI 能幫我快速上手原本不太會的編程語言。
在/apps/core/src/modules/search/search.service.ts中buildAlgoliaIndexData()
後添加下述代碼,邏輯與上述 Python 相同:
重新構建 docker 鏡像,然後從官方鏡像切換過來就 OK 了!
不過原始版本還定義了 3 種事件(增、刪、改)觸發單個元素的推送,這裡我就懶得改了,直接把裝飾器(ts 裡應該叫什麼?我只知道 Python 是這麼叫的)移到pushAllToAlgoliaSearch
就好了。
小插曲#
在編輯代碼的時候,我發現原來代碼中已經定義了超出限制長度後截斷。不過定義的是 100KB,看来開發者是付費玩家。個人覺得把這個設置為環境變量會更好,而不是寫死在代碼裡。
https://github.com/mx-space/core/blob/20a1eef/apps/core/src/modules/search/search.service.ts#L370
2024/12/21: 作者更新了可配置的截斷,不過我還是更喜歡分頁提交,畢竟可以全文搜索嘛。
https://github.com/mx-space/core/commit/6da1c13799174e746708844d0b149b4607e8f276