animateboot开发日记

animateboot开发日记

十二月 15, 2025

Animateboot.com开发日记

需求背景:

  1. 摸鱼时候会打开网页版的音乐网站听歌。
  2. 部分公司直接把网易云域名屏蔽,电脑无法访问,所以需要网易云以外的音乐平台。
  3. 动漫歌曲希望有个汇总的地方,而且点击直达,不需要我再去复制名字去搜索。
    在十一月的时候,正好换工作,时间开始充裕起来,所以着手开发。

准备阶段-整理所需数据以及可行性探索

  1. 每个季度的动漫数据:爬虫抓取。
  2. 每个季度动漫对应的歌曲:第二个爬虫。
  3. 每个歌曲对应的链接:问了Gemini,对于YouTube,网易云,spotify,都给出了代码。
  4. ui设计:让Gemini设计,不懂前端,所以本地通过ai-coding完成。

做了一半发现可以做歌单,因此也有了下面需求。
5. 对于动漫进行排名。爬虫抓取中、日、美的网站,计算观看人数进行排名。
6. 根据排名结果,筛选前50%的动漫的歌曲。

相关技术

语义相似度的判断

爬取动漫季度和爬取动漫歌曲,这是两个爬虫,两个爬虫得到的动漫名字可能不一样,而且可能有的在a爬虫存在,b爬虫中不存在。

假设以a爬虫数据为准,需要在b爬虫中找出来对应的动漫,需要对b的每个动漫进行相似度判断。

这个技术在RAG中也有应用。引入模型->转成向量->余弦算数。

为了增加准确度,用多个语言都计算相似度,选最准确的:

1
2
3
score1 = util.cos_sim(emb_a_jp, emb_b_jp).item()  # 日 vs 日
score2 = util.cos_sim(emb_a_name, emb_b_name).item() # 中 vs 中
score3 = util.cos_sim(emb_a_jp, emb_b_name).item() # 日 vs 中

这里面Gemini给了一个优化点,就是 先进行向量化,而不是每次都计算向量

比如下面这样就是每次都计算,但是b里面单词向量会计算多次,所以时间非常慢。

1
2
3
4
5
6
7
8
9
def cmp_s1_s2(s1, s2):
if s2 is None:
return False
start = time.time()
embedding1 = model.encode(s1, convert_to_tensor=True)
embedding2 = model.encode(s2, convert_to_tensor=True)
cosine_score = util.cos_sim(embedding1, embedding2)[0][0]
end = time.time()
return cosine_score > 0.7

改成直接传两个对象,然后只计算一遍向量。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
# 部分代码已经省略
def cmp_json_main(json_a, json_b):
embeddings_a_name, embeddings_a_jp, embeddings_b_name, embeddings_b_jp = init(json_a, json_b)

for i in range(len(json_a)):
emb_a_name = embeddings_a_name[i]
emb_a_jp = embeddings_a_jp[i]
...
for j in range(len(json_b)):
emb_b_name = embeddings_b_name[j]
emb_b_jp = embeddings_b_jp[j]
...
score1 = util.cos_sim(emb_a_jp, emb_b_jp).item() # 日 vs 日
score2 = util.cos_sim(emb_a_name, emb_b_name).item() # 中 vs 中
score3 = util.cos_sim(emb_a_jp, emb_b_name).item() # 日 vs 中
...

def init(json_a, json_b):
print("正在进行批量向量化 (这可能需要几秒钟)...")
start_time = time.time()
a_names = [item.get('name', '') for item in json_a]
a_jp_names = [item.get('jp_name', '') for item in json_a]

# 提取 json_b 的所有文本
b_names = [item.get('name', '') for item in json_b]
b_jp_names = [item.get('jp_name', '') for item in json_b]

embeddings_a_name = model.encode(a_names, convert_to_tensor=True)
embeddings_a_jp = model.encode(a_jp_names, convert_to_tensor=True)

embeddings_b_name = model.encode(b_names, convert_to_tensor=True)
embeddings_b_jp = model.encode(b_jp_names, convert_to_tensor=True)
end_time = time.time()
print(f"向量化完成,cost {end_time - start_time}s, 开始比对...")
return embeddings_a_name, embeddings_a_jp, embeddings_b_name, embeddings_b_jp

这个优化非常明显,原来可能两分钟,修改完之后,这一步十几秒。

歌曲的获取

也是问Gemini得到的,只有spotify提供了官方接口,其他的都是开源的逆向工程写的依赖包,通过Gemini给出示例代码,复制粘贴就能执行。

拿到了歌曲后,也不是直接就是正确的,还是需要人工校对。

在后期尝试引入llm,增大校对正确率。这种方式对于网易云比较好,因为网易云本身的搜索引擎不够准确,YouTube和spotify本身搜索引擎就很强大了,很少会出现错误情况,所以llm提升不大。

而且时间很长,因为需要retry,一旦retry,和llm的交互就变多了,140首歌曲要跑20min。这部分其实可以做优化,减少和llm的通信次数,但是懒得调试了,也不是不能用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
def deepseek_match(original_text: str, cand: Dict[str, Any], anime_names: List[str]) -> bool:
api_key = os.environ.get('DEEPSEEK')
if not api_key:
return False
model = 'deepseek-chat'
system = 'You are a strict music matcher. Return JSON {"is_same": true|false} only.'
cand_title = cand.get('name') or ''
cand_artists = ', '.join(cand.get('artists') or [])
cand_alias = cand.get('alias') or []
prompt = (
'Determine if the candidate song matches the intended song described by the original text. '
'In original text, the song name is inside the parentheses, and the artist name is outside.'
'Consider candidate song name and artists, they must both match.'
'Some artists\' names may be converted to Roman sounds, but they are essentially the same person.'
'Additionally, also consider candidate aliases; '
'if alias contains anime Chinese names or Japanese names, treat as match. Return strictly JSON with key is_same.\n'
f'Original text: {original_text}\n'
f'Anime names (CN/JP): {json.dumps([n for n in anime_names if n], ensure_ascii=False)}\n'
f'Candidate title: {cand_title}\n'
f'Candidate artist: {cand_artists}\n'
f'Candidate aliases: {json.dumps(cand_alias, ensure_ascii=False)}\n'
)
try:
r = requests.post(
'https://api.deepseek.com/v1/chat/completions',
headers={
'Authorization': f'Bearer {api_key}',
'Content-Type': 'application/json'
},
json={
'model': model,
'messages': [
{'role': 'system', 'content': system},
{'role': 'user', 'content': prompt}
],
'temperature': 0
},
timeout=30
)
r.raise_for_status()
data = r.json()
content = data.get('choices', [{}])[0].get('message', {}).get('content', '')
try:
parsed = json.loads(content)
print(f"deepseek_answer: {parsed}")
return bool(parsed.get('is_same'))
except Exception:
return False
except Exception:
return False

动漫的观看数和排名的计算

观看数也是爬虫得到,然后统计观看数进行排名。算法由Gemini提供。

每一步都需要人工校对

动漫歌曲的搜索、观看数的搜索,都是要人工校对的。但是由于准确度比较高,80%左右,人工修改的点很少。

也考虑过接入llm,但是接入之后准确率又不敢保证是100%,最后还是得上人工。

ui设计

旧的设计选用material-ui,感觉不好看,发给Gemini写了一版,Gemini直接给出代码,并且生成界面,于是沿用下来。

旧的ui

Gemini改过之后的:

新的ui

还加上了关灯效果

新的ui

cloudflare部署

这部分没接触过,所以也相当于是问Gemini来了解。

部署的流程比较简单,把域名绑定上去就行了。

之后出现了两个问题:

  1. 爬虫
  2. 主页面加载太慢

爬虫

监控面板看到一个时间点有1M的请求,明显是爬虫在访问网站,所以问Gemini做一些配置。

比如配置bot、配置contry限制,等等等等。
gemini1

gemini2

主页面加载慢

问了Gemini:
gemini1

gemini2

f12看了一下请求的模板,有很多woff2文件让trae去掉这部分代码,同时重复的ico、png请求也削减。

总结

Gemini能做到大部分事情,可以帮助小白从零开始搭建网站。

但是具体的调试、思路、工具的使用结果还是要自己思考。

并且一部分术语起码要知道,假设我命令trae去写前端界面,那么起码要知道组件都有哪些,能说出来,ai才能理解。

整个项目耗时一个月左右,每天都在爬取数据、人工校对数据才算是弄到一个自己满意的水平,也有一点小小的满足感。