写个有点用的Bot:掘金作者画像
1. 引言??上周五,掘金又发布了 Coze(扣子) 有关的活动 → 《?? 玩转沸点 | 成为AI魔法大师,释放你的完美创造力!》,不需要写文章,只需要 建Bot发布到掘金,并在 AI聊天室 中发 沸点@ 该Bot进行对话就能参与。
?? 活动参与门槛大大降低了啊,然后交流群就有小??汁开始整活,有喜欢对骂的 隔壁老王:
还有各种 优弧 的Bot:
?? 不得不说 群友个个都是人才~
?? Coze搭Bot的玩法很 简单,难点是 Bot的创意,即 应用场景,你打算用它来 解决什么问题。先确定 思路,再来 搭。具体玩法和案例可以参考我之前写的几篇文章:
《用Coze扣子轻松搭个Bot,从此告别"标题党"》《【踩坑】用Coze订制一只专属AI小秘??》《???♂?Coze国内版插件汇总-By油猴》《Coze + 爬虫 = 周末去哪不用愁??》《???♀?Coze官方插件不够用?手把手教你自己造(白嫖)》?? 如果你刚开始尝试搭Bot,应该能对你有所 启发,当然,也可以参考 《官方:扣子帮助文档》来搭建~
?? 最近在梳理自己的知识体系,看到其中一个分类 → 个人品牌/标签,让我想到了一个 idea??,能否写一个Bot来 帮助读者快速了解一个掘金作者的用户画像。即:用户@Bot并发送一个 作者主页的url,Bot对该作者的 所有文章的标题、摘要、文章标签 进行分析,构建一些 描述作者特征的概要 (如擅长的专业知识领域),同时提供一些 附加信息,如文章数量、阅读量、点赞量等,以及提供一些 阅读推荐 (如该作者最热的10篇文章链接)。
?? 以下是对Bot的期望返回结果:
??【】于【加入掘金的日期】加入掘金,共计发布了【文章数】篇文章,收获了:???♂?x关注数|??x点赞数|??x阅读量|??x掘力值,它的作者画像概要如下:
大模型对作者所有的文章标题、概要、文章标签等进行文本分析,生成一段符合作者特征的概要
? TA擅长的领域 (取前四个,剩下的归类为其它):
- 1、前端 (符合标签的文章占比%, 10篇) → 如:前端 (18.2%, 5篇)
- 2、xxx
?? TA热度最高的文章 (不足十篇有多少取多少):
- [《标题》](链接)
- ...
接着以 返回结果 为导向,思考下具体实现这个Bot,?? 本质是还是那两步:获取数据、处理数据,接着按部就班实现一波~!
2. 获取数据???♂? 需要获取的数据有:作者的用户信息 + 所有文章信息,老规矩,先看下有没有现成的,没有再自己捣鼓。在 Coze插件商店 搜了下 掘金,发现只有一个 掘金热榜 的插件,只提供了:优质作者榜、掘金文章榜和文章收藏榜。
明显满足不了我们的需求,那就自己造噜,?? 看过我文章的都知道,Coze里自己搞 数据源 的两种玩法:
自定义插件调接口,但只支持json格式的返回数据;工作流代码节点 里用 requests_async库 写爬虫模拟请求;掘金的api网上还挺多,Github上就有好些,这里还是自己抓,授之于渔嘛~
2.1. 用户信息浏览器F12进 开发者模式,开下抓包,随便输入个人主页的url,如:https://juejin.cn/user/4142615541321928,加载完随手搜下 掘力值 对应的数字,定位到下述接口:
?? 简单爬取原则:优先选 没有加密参数 + 有所需数据 的接口,后面三个返回Json的接口都有加密参数:
?? 那就只能 代码节点模拟请求爬页面html + 正则 提取目标数据了,直接新建一个工作流,添加一个 代码节点 开爬:
importrequests_async
importre
fromdatetimeimportdatetime
#请求头
request_headers={
"Origin":"https://juejin.cn",
"Referer":"https://juejin.cn/editor/drafts/new?v=2",
"User-Agent":"Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/122.0.0.0Safari/537.36"
}
#用户信息类
classUserInfo:
def__init__(self,user_name=None,description=None,register_time=None,follower_count=None,
post_article_count=None,got_digg_count=None,got_view_count=None,article_list=None):
self.user_name=user_name
self.description=description
self.register_time=register_time
self.follower_count=follower_count
self.post_article_count=post_article_count
self.got_digg_count=got_digg_count
self.got_view_count=got_view_count
self.article_list=article_listifarticle_listelse[]
def__str__(self):
return"作者名:{},作者描述:{},注册时间:{},粉丝数:{},文章数:{},获得点赞数:{},获得阅读数:{},文章列表:{}".format(
self.user_name,self.description,self.register_time,self.follower_count,self.post_article_count,
self.got_digg_count,self.got_view_count,self.article_list)
defto_json(self):
return{
"user_name":self.user_name,
"description":self.description,
"register_time":self.register_time,
"follower_count":self.follower_count,
"post_article_count":self.post_article_count,
"got_digg_count":self.got_digg_count,
"got_view_count":self.got_view_count,
"article_list":[]
}
#提取输入url中的用户信息
asyncdeffetch_user_info(user_url):
#获取作者名的正则
author_name_pattern=re.compile(r'title(.*?)的个人主页',re.S)
#请求用户主页
resp=awaitrequests_async.get(user_url,headers=request_headers)
content=resp.text
#提取作者名称
author_result=author_name_pattern.search(content)
ifauthor_result:
#获取作者名
user_name=author_result.group(1)
#拼接获取用户信息的正则
user_info_patter_str=r'user_name:"{}".*?'.format(
user_name)+r'description:"(.*?)",.*?register_time:(\d+).*?follower_count:(\d+),post_article_count:('\
r'\d+).*?got_digg_count:(\d+),got_view_count:(\d+)'
user_info_pattern=re.compile(user_info_patter_str,re.S)
#提取匹配的用户信息,只要第一个匹配结果
results=user_info_pattern.findall(content)
ifresultsandlen(results)0:
user_info=UserInfo()
user_info.user_name=user_name
user_info.description=results[0][0]
user_info.register_time=datetime.fromtimestamp(int(results[0][1])).strftime('%Y-%m-%d%H:%M:%S')
user_info.follower_count=results[0][2]
user_info.post_article_count=results[0][3]
user_info.got_digg_count=results[0][4]
user_info.got_view_count=results[0][5]
returnuser_info.to_json()
else:
returnNone
asyncdefmain(args:Args)-Output:
params=args.params
#写死一个url调用试试看
result=awaitfetch_user_info("https://juejin.cn/user/4142615541321928")
ret:Output={
"result":resultifresultelse{}
}
returnret
测试代码 处点击 运行,输出结果如下:
?? Nice,该拿的信息都拿到了,接着搞下文章信息~
2.2. 文章信息在个人主页,点击 文章 的Tab,下拉加载更多看请求,不难看出这就是 目标接口:
看下请求参数:
阔以,木有加密,试下了uuid是可以不填的,咦,?? 返回数据是json的话,是不是能 自定义插件?试试?新建插件,添加下述请求参数:
保存并接续,点击自动解析生成输出参数,保存并继续,试下输入参数:
?? 并不太行,又尝试了一下,设置请求头里的 Content-Type 为 application/json,指定传递的数据编码为JSON格式,结果还是不行。试下 代码节点 模拟请求:
asyncdeffetch_article_infos():
resp=awaitrequests_async.post("https://api.juejin.cn/content_api/v1/article/query_list",
json={"user_id":"4142615541321928","sort_type":2,"cursor":"0"})
returnresp.json()
asyncdefmain(args:Args)-Output:
params=args.params
result=awaitfetch_article_infos()
ret:Output={
"result":str(result)
}
returnret
运行输出结果:
好吧,可以,??em... 也不知道是扣子的插件POST不支持JSON数据只支持表单数据,还是我的用法不对。算咯,直接写代码爬:
importrequests_async
importre
importtime
#请求头
request_headers={
"Origin":"https://juejin.cn",
"Referer":"https://juejin.cn/editor/drafts/new?v=2",
"User-Agent":"Mozilla/5.0(WindowsNT10.0;Win64;x64)AppleWebKit/537.36(KHTML,likeGecko)Chrome/122.0.0.0Safari/537.36"
}
#文章信息类
classArticleInfo:
def__init__(self,title=None,brief_content=None,view_count=0,collect_count=0,digg_count=0,
comment_count=0,tags=None,link_url=None):
self.title=title
self.brief_content=brief_content
self.view_count=view_count
self.collect_count=collect_count
self.digg_count=digg_count
self.comment_count=comment_count
self.tags=tags
self.link_url=link_url
def__str__(self):
return"文章标题:{},文章简介:{},阅读数:{},收藏数:{},点赞数:{},评论数:{},标签:{},文章链接:{}".format(
self.title,self.brief_content,self.view_count,self.collect_count,self.digg_count,self.comment_count,
self.tags,self.link_url)
defto_json(self):
return{
"title":self.title,
"brief_content":self.brief_content,
"view_count":self.view_count,
"collect_count":self.collect_count,
"digg_count":self.digg_count,
"comment_count":self.comment_count,
"tags":self.tagsifself.tagselse[],
"link_url":self.link_url
}
#爬取作者的文章列表
asyncdeffetch_article_infos(user_url):
#正则提取下user_id
user_id_result=re.search(r"(\d+)",user_url)
ifuser_id_result:
user_id=user_id_result.group(1)
cursor=0
article_info_list=[]
#死循环,当返回的条数少于10条时跳出循环
whileTrue:
#虚假的休眠防封
time.sleep(2)
#拼接请求的json
request_json={"user_id":user_id,"sort_type":2,"cursor":str(cursor)}
resp=awaitrequests_async.post("https://api.juejin.cn/content_api/v1/article/query_list",headers=request_headers,json=request_json)
ifresp:
article_info=resp.json()
ifarticle_infoandarticle_info["data"]:
#遍历提取文章数据
forarticleinarticle_info["data"]:
article_info_list.append(
ArticleInfo(title=article["article_info"]["title"],
brief_content=article["article_info"]["brief_content"],
view_count=article["article_info"]["view_count"],
collect_count=article["article_info"]["collect_count"],
digg_count=article["article_info"]["digg_count"],
comment_count=article["article_info"]["comment_count"],
tags=list(map(lambdax:x["tag_name"],article["tags"])),
link_url="https://juejin.im/post/"+article["article_id"]).to_json())
#长度少于10跳出死循环
iflen(article_info["data"])10:
break
#否则游标+10
else:
cursor+=10
else:
break
else:
break
returnarticle_info_list
asyncdefmain(args:Args)-Output:
params=args.params
result=awaitfetch_article_infos("https://juejin.cn/user/3178040962848480/posts")
ret:Output={
"result":resultifresultelse[]
}
returnret
运行看下输出结果:
?? Nice,数据同样拿到了~
2.3. 并行爬取??上面通过死循环的方式获取作者的所有文章,存在一个问题,当作者文章比较多时,比如 200篇,需要循环执行20次,而单次休眠2s,假设请求和响应的时间为2s,那么完成所有请求,需要至少80s,即1分20秒。这样玩大概率的输出结果会是:节点响应超时。扣子应该是为每个节点设置了一个最长响应事件,超过多久没返回结果,就认为节点任务执行失败。
?? 所以,我们需要将 爬取任务队列 拆分成几个,然后复制粘贴多个上述 代码节点,充分利用工作流支持多个节点 并行 的特性,然后追加一个代码节点,对数据进行汇总。爬取作者信息那个代码节点加五个输出参数:
写个简单的循环生成cursor参数列表,右侧是点击运行后的输出结果:
接着修改下爬取文章信息的结点,入参和代码做下调整,然后运行试试看:
行吧,拿到数据了,接着复制粘贴三个,改下入参,并追加一个代码节点,用于整个四个节点的返回数据:
asyncdefmain(args:Args)-Output:
params=args.params
first_article_info_list=params["first_article_info_list"]
second_article_info_list=params["second_article_info_list"]
third_article_info_list=params["third_article_info_list"]
forth_article_info_list=params["forth_article_info_list"]
#合并四个数组中的数据
article_info_list=[]
article_info_list.extend(first_article_info_list)
article_info_list.extend(second_article_info_list)
article_info_list.extend(third_article_info_list)
article_info_list.extend(forth_article_info_list)
ret:Output={
"article_info_list":article_info_list,
}
returnret
最终的工作流如下:
点击试运行,输入作者的主页url,然后看下输出结果:
?? Nice,作者的176篇文章信息都成功获取到啦,而且整体耗时也才4s~
3. 处理数据3.1. 生成作者画像这里本来想用 云雀大模型 的,折腾了近一个小时的 提示词,一直 答非所问,实在是太拉跨了??:
无奈,只能试试其它的模型了,试了下国产AI-智谱AI****,输出结果还算可以,新注册+实名送500w Token~
应该能玩几下吧,直接新起一个代码节点,直接HTTP调用的方式调它的API,扣子的节点竟然不支持 jwt 库,只能调内置的 hmac、base64、hashlib 库来生成鉴权Token,这部直接让GPT代劳,最终写出模拟请求代码~
importrequests_async
importhmac
importjson
importtime
importbase64
importhashlib
#生成鉴权Token
defgenerate_token(apikey:str,exp_seconds:int):
try:
kid,secret=apikey.split(".")
exceptExceptionase:
raiseException("invalidapikey",e)
#Header
header={
"alg":"HS256",
"sign_type":"SIGN"
}
#Payload
payload={
"api_key":kid,
"exp":int(round(time.time()*1000))+exp_seconds*1000,
"timestamp":int(round(time.time()*1000)),
}
#EncodeHeader
header_encoded=base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip('=')
#EncodePayload
payload_encoded=base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip('=')
#CreateSignature
to_sign=f'{header_encoded}.{payload_encoded}'.encode()
signature=hmac.new(secret.encode(),to_sign,hashlib.sha256)
signature_encoded=base64.urlsafe_b64encode(signature.digest()).decode().rstrip('=')
#CreateJWT
jwt_token=f'{header_encoded}.{payload_encoded}.{signature_encoded}'
returnjwt_token
asyncdefmain(args:Args)-Output:
params=args.params
secret_key="智谱AI的Key"
input_content=params['input_content']
ai_response=awaitrequests_async.post("https://open.bigmodel.cn/api/paas/v4/chat/completions",headers={
"Content-Type":"application/json",
"Authorization":"Bearer"+generate_token(secret_key,60)
},data=json.dumps({
"model":"glm-3-turbo",
"stream":False,
"messages":[
{
"role":"user",
"content":"基于以下文章摘要列表,请你分析并生成一个尽可能准确的作者画像,不要超过200个字。请考虑作者的写作风格、专业知识领域等方面。{}".format(input_content)
}
]
}))
result_content=ai_response.json()['choices'][0]['message']['content']
ret:Output={
"result":result_content,
}
returnret
运行看下输出结果:
卧槽,牛批,毕竟当前 国产第一梯队的AI!!!强无敌,对比之下的 云雀:
?? 生成作者画像 这部分算是拿捏了,接着处理下其它数据~
3.2. 擅长领域这部分就是遍历文章列表,统计标签然后计算百分比啥的,比较简单,直接写出处理代码:
importoperator
asyncdefmain(args:Args)-Output:
params=args.params
article_list=params["input"]
#文章总数
article_count=len(article_list)
#标签计数字典
tag_count_dict={}
#遍历所有文章,根据tag进行计数
forarticleinarticle_list:
fortaginarticle["tags"]:
tag_count=0
iftagintag_count_dict:
tag_count=tag_count_dict[tag]
tag_count_dict[tag]=tag_count+1/len(article["tags"])/article_count
#字典按照降序排列成新的列表
sorted_list=sorted(tag_count_dict.items(),key=operator.itemgetter(1),reverse=True)
result_list=[]
#判断列表长度是否5,是截取游标4后的元素进行合并
iflen(sorted_list)5:
end_list=sorted_list[4:]
percent=0.0
forelementinend_list:
percent+=element[1]
result_list.extend(sorted_list[:4])
result_list.append(("其它",percent))
else:
result_list=sorted_list
#输出字符串拼接
result_content="?TA擅长的领域:\n"
forpos,elementinenumerate(result_list):
result_content+="-{}、{}({}%,{}篇)\n".format(pos+1,element[0],format(element[1]*100,'.2f'),format(element[1]*article_count,'.2f'))
ret:Output={
"result":result_content,
}
returnret
运行输出结果:
?? Tips:这里篇数为小数的原因:一篇文章能有多个标签,比如三个,单个标签的权重就是0.33~
3.3. 最热文章?? 这边不知道掘金文章的 热度值计算公式,那就根据自己的感觉来拟咯~
H(热度值) = 0.15 * R(阅读量)/100 + 0.25 * L(点赞量) + 0.35 * C(评论量) + 0.25 * F(收藏量)
接着就是遍历文章列表,然后套公式算每篇文章的热力值,最后排序筛出前10篇文章,格式化输出~
importoperator
asyncdefmain(args:Args)-Output:
params=args.params
article_list=params["input"]
#文章计数字典
article_count_dict={}
forarticleinarticle_list:
score=(article["view_count"]/100*0.15
+article["digg_count"]*0.25
+article["comment_count"]*0.35
+article["collect_count"]*0.25)
article_count_dict["[《{}》-{}]({})".format(article["title"],score,article["link_url"])]=score
#按照value降序排列
sorted_list=sorted(article_count_dict.items(),key=operator.itemgetter(1),reverse=True)
#字符串拼接,只取前十
result_content="??TA热度最高的文章:\n"
forresultinsorted_list[:10]:
result_content+="-{}\n".format(result)
ret:Output={
"result":result_content,
}
returnret
运行输出结果如下:
?? 行吧,到此就把数据都处理完了~
4. 汇总输出?? 最后就是各种微调,然后汇总输出了,调整下用户画像那里,追加下返回头:
user_info=params["user_info"]
result_content="??【{}】于【{}】加入掘金,共计发布了【{}】篇文章,收获了???♂?x{}|??x{}|??x{}|??x{},它的作者画像:\n\n{}\n\n".format(
user_info["user_name"],user_info["register_time"],user_info["post_article_count"],user_info["follower_count"],
user_info["got_digg_count"],user_info["got_view_count"],user_info["jpower"],ai_response.json()['choices'][0]['message']['content']
)
结束结点直接汇总下三个结果合并输出:
试运行后,输入笔者的主页链接,最终输出结果如下:
??【coder_pig】于【2016-04-11 09:18:44】加入掘金,共计发布了【176】篇文章,收获了???♂?x10329|??x7787|??x814304|??x29071,它的作者画像:
根据文章摘要列表,作者是一位技术博客作者,主要关注移动应用开发,特别是Android和Flutter技术。作者对移动应用开发有深入了解,擅长使用各种技术和工具进行应用开发和自动化,如AccessibilityService、Xposed、爬虫技术等。此外,作者还关注代码优化、性能提升以及各种编程语言和技术的学习,如Python、Java、Kotlin、Dart等。文章风格幽默风趣,语言通俗易懂,适合初学者和有一定基础的开发者阅读。
? TA擅长的领域:
- 1、Python (20.50%, 36.08篇)
- 2、Android (17.19%, 30.25篇)
- 3、架构 (6.49%, 11.42篇)
- 4、设计模式 (6.06%, 10.67篇)
- 5、其它 (49.76%, 87.58篇)
?? TA热度最高的文章:
- [《因一纸设计稿,我把竞品APP扒得裤衩不剩(上)》-274.3755](https://juejin.im/post/6844903989603991565)
- [《纳尼?我的Gradle build编译只要1s》-232.4535](https://juejin.im/post/6844903728282091527)
- [《忘了他吧!我偷别人APP的代码养你》-219.6495](https://juejin.im/post/6844903904547700743)
- [《补齐Android技能树 - 从害怕到玩转Android代码混淆》-208.29500000000002](https://juejin.im/post/6966526844552085512)
- [《我写小程序像菜虚鲲——1、唱,跳,rap,篮球》-170.143](https://juejin.im/post/6844903862533373959)
- [《【杰哥带你玩转Android自动化】学穿:ADB》-156.187](https://juejin.im/post/7147631074183479303)
- [《换个姿势,带着问题看Handler》-146.39350000000002](https://juejin.im/post/6844904150140977165)
- [《??补齐Android技能树——从AGP构建过程到APK打包过程》-136.60899999999998](https://juejin.im/post/6963527524609425415)
- [《浅谈"李跳跳"停更 & 简单七步跳过Android开屏广告》-132.633](https://juejin.im/post/7272735633457086498)
- [《逮虾户!Android程序调试竟简单如斯》-117.2555](https://juejin.im/post/6844903733088747528)
卧槽,屌啊,此处应该有掌声??????
5. 发布Bot到掘金2333,工作流写完,才发现自己还建Bot,直接新建一个 掘金作者画像 的Bot,随便写下功能介绍:
随便写两句提示词,接着AI润色下,自己调整一波,主要是检测到用户输入了url而且包含 https://juejin.cn/user/ 就调工作流:
接着同样AI生成下开场白文案,预设问题随便填两个url:
点击其中一个试试看~
2333,哈哈哈,??这作者画像...
行吧,接着把Bot发布到 掘金,如果前面的打钩是不可点,配置然后授权就好了~
稍等片刻,成功提交发布后,点击 立即对话:
跳转页面后,随便输入一个作者的主页:
因为沸点要 审核,所以等了一会儿机器人的评论才放出来,评论不支持md,这稀烂的排版??...
然后豆包和飞书都是正常的~
行吧,以上就是本节的全部内容,应该是目前写过复杂度最高的Bot了,赶紧来 @掘金作者画像 试试吧??~
阅读原文
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设、网站改版、域名注册、主机空间、手机网站建设、网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。 项目经理在线