此文由 Mix Space 同步更新至 xLog
为获得最佳浏览体验,建议访问原始链接
https://www.do1e.cn/posts/code/algolia-search
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:4304)
at Hr.Query.exec (entrypoints.js:784:5145)
at process.processTicksAndRejections (node:internal/process/task_queues:95:5)
at async Promise.all (index 0)
从上述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