"收藏了但是从来没看过",经常上网的朋友应该都深有感触。最近在整理收藏夹,突然发现很多收藏还是有价值的,如果只是收藏没有用起来,就显得十分浪费了。那么我们该如何解决这个问题呢?突然在去年的某一天,我突发奇想,能不能利用LLM技术让收藏夹成为我的私人知识库,通过实验验证答案是可以的。
流程图
读取收藏夹
工程化的第一步就是找到收藏夹并读出来,这里我们查阅了资料chrome浏览器的收藏夹一般存放在特定的位置。
在浏览器中输入
chrome://version/
进入浏览器信息界面,我们看到的个人资料路径就是收藏夹的存放路径啦。
这里我提取到了我的收藏夹路径如下所示。
rust
/Users/用户名称/Library/Application Support/Google/Chrome/Default/Bookmarks
找到文件之后,我们就需要解决第二个文件,如何读取收藏夹?
这里找到文件之后必须cat一手,通过读取文件,我们发现Chrome的收藏夹结构是个树,我们只需要按照其结构完成解析即可。
我的解析代码,如下所示。具体思路就是:分层递归解析树。
python
# get_bookmark 获取书签
def get_bookmark():
bookmark_list = []
unuseful_keyword = ['登录页'] # list
unuseful_url = ['docs.qq.com', 'doc.weixin.qq.com'] # list
def parse_bookmarks(bookmark_node):
if 'children' in bookmark_node:
for child in bookmark_node['children']:
parse_bookmarks(child)
else:
if 'url' in bookmark_node:
url = bookmark_node['url']
title = bookmark_node.get('name', 'No title')
# 提取时间(这里有个坑,Chrome的时间戳是windows时间戳,不是unix的)
date_added = bookmark_node.get('date_added', 0)
# 过滤无用url
if any(item in url for item in unuseful_url):
return
# 过滤无用关键词
if any(keyword in title for keyword in unuseful_keyword):
return
bookmark_list.append({
'title': bookmark_node.get('name', 'No title'),
'url': bookmark_node['url'],
'date_added': windows_timestamp_to_datetime(date_added),
})
bookmarks = read_chrome_bookmarks(path)
# 从书签数据中提取书签
if 'roots' in bookmarks:
for root in bookmarks['roots']:
parse_bookmarks(bookmarks['roots'][root])
return bookmark_list
- 过滤无用url,指的是需要把一些无价值,爬虫无法获取的页面过滤掉。
- 过滤无用关键词,指的是要过滤掉一些登录页,授权页,以及404 Not Found等。
获取页面内容
第二步就是解析收藏夹的内容,这一步就十分简单了,我们通过python的request库直接访问对应的页面即可,当然如果考虑反爬的话,这里可以考虑使用代理IP,来减少封控。
python
def http_request(url):
headers = {
'user-agent': UserAgent().get_random_user_agent() # 随机ua
}
try:
resp = requests.get(url, headers=headers)
if resp.status_code == HTTPStatus.OK:
try:
return resp_encode(resp)
except:
return resp.text
logger.warning(f"网页抓取失败, url: {url}")
return ""
except Exception as e:
logger.error(f"网页抓取失败, url: {url}, error: {e}")
return ""
获取到正文之后,我们通过trafilatura抽取网页正文,以获得网页的内容,具体代码也很简单如下所示。(这里目前的抽取其实比较简单粗暴,后续会切为基于正文区域的抽取,具体逻辑作者还在实验中)
python
def html_extract(html_text):
txt = trafilatura.extract(html_text, include_tables=False, output_format="txt")
if not txt:
return None
return txt
LLM抽取关键词
完成正文抽取过后,我计划使用LLM来抽取正文的关键词,不得不说LLM的发展真的日新月异,半年前我在做网站勘探的时候LLM抽取的关键词还出现幻觉,抽取质量不高的情况,当我最近有开始使用的时候,发现模型的抽取能力已经有了一个巨大的提升,下面是一个抽取效果:
原文
蚂蚁集团的企业级产品是一个庞大且复杂的系统,数量多且功能复杂,而且变动和并发频繁,常常需要设计者与开发者能快速做出响应。同时这类产品中存在很多类似的页面以及组件,我们可以通过抽象得到一些稳定且高复用性的内容。
我们提供完善的设计指引、最佳实践、设计资源和设计工具,来帮助设计者快速产出高质量产品原型。
我们采用 React 封装了一套 Ant Design 的组件库,也欢迎社区其他框架的实现版本。
如果你的公司和产品使用了 Ant Design,欢迎到 这里 留言。
我们欢迎任何形式的贡献,有任何建议或意见,请给我们 提问。
关键词
"前端开发","React","设计系统","工具","AntDesign","用户体验"
关键词很好的概括了上面的原文,恰好满足我们的需求,PS:这里的模型选择的是qwen系列的文生文模型。
向量化
这里我选择了huggingface.co/BAAI/bge-m3作为向量化的模型,选型原因如下:
1.支持多语言(网页内容涵盖了中英)。
2.开源(免费!!!)。
3.milvus对他有良好的适配。
一些准备就绪过后,我们开始定义我们需要存储的数据,大概的schema是这样的。
ini
schema = MilvusClient.create_schema(
auto_id=False,
enable_dynamic_field=True,
description="bookmark list",
)
schema.add_field(field_name="id", datatype=DataType.INT64, is_primary=True, auto_id=True)
schema.add_field(field_name="title", datatype=DataType.VARCHAR, max_length=4000)
schema.add_field(field_name="text", datatype=DataType.VARCHAR, max_length=10000)
schema.add_field(field_name="tags", datatype=DataType.ARRAY, element_type=DataType.VARCHAR, max_capacity=50, max_length=100) # 最多50个关键词
schema.add_field(field_name="text_embedding", datatype=DataType.FLOAT_VECTOR, dim=DIM) # 密集向量
schema.add_field(field_name="tags_embedding", datatype=DataType.SPARSE_FLOAT_VECTOR) # 稀疏向量
我们定义了两种向量,分别用于不同数据源的检索,内容使用密集向量,标签使用稀疏向量。至此我们就完成了录入知识库的前序操作。(此处省略了环境安装如有疑问欢迎咨询)
然后就是开始了漫长的入库工作,1000个链接大约用了60分钟。
入库效果:
查询效果
完成了上述设计之后,我们尝试下检索效果,看着还行,至此一个简单的查询收藏夹工程化便完成了。
优化方向
由于时间匆忙,本周仅仅是完成了一个粗糙的demo,此处仍还有很多可以提升的地方,下面是我的两个方向。
- 抽取正文更加准确,目前抽取是一个比较粗糙的全文抽取,影响大模型的关键词抽取,以及检索的准召,后续作者将采用正文识别算法(研究中),找到网页的正文区域,精准抽取。
- 尝试更多的rerank算法,提高召回质量。
如果有更好的想法,欢迎交流沟通,一起做点有趣的东西~~