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的期望返回结果:
Text
🤠【<作者>】于【<加入掘金的日期>】加入掘金,共计发布了【<文章数>】篇文章,收获了:🙋♂️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,如:juejin.cn/user/414261...,加载完随手搜下 掘力值 对应的数字,定位到下述接口:
🐍 简单爬取原则 :优先选 没有加密参数 + 有所需数据 的接口,后面三个返回Json的接口都有加密参数:
😟 那就只能 代码节点模拟请求爬页面html + 正则 提取目标数据了,直接新建一个工作流,添加一个 代码节点 开爬:
python
import requests_async
import re
from datetime import datetime
# 请求头
request_headers = {
"Origin":"https://juejin.cn",
"Referer":"https://juejin.cn/editor/drafts/new?v=2",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
}
# 用户信息类
class UserInfo:
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_list if article_list else []
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)
def to_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中的用户信息
async def fetch_user_info(user_url):
# 获取作者名的正则
author_name_pattern = re.compile(r'<title>(.*?) 的个人主页', re.S)
# 请求用户主页
resp = await requests_async.get(user_url, headers=request_headers)
content = resp.text
# 提取作者名称
author_result = author_name_pattern.search(content)
if author_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)
if results and len(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]
return user_info.to_json()
else:
return None
async def main(args: Args) -> Output:
params = args.params
# 写死一个url调用试试看
result = await fetch_user_info("https://juejin.cn/user/4142615541321928")
ret: Output = {
"result": result if result else {}
}
return ret
测试代码 处点击 运行,输出结果如下:
👌 Nice,该拿的信息都拿到了,接着搞下文章信息~
2.2. 文章信息
在个人主页,点击 文章 的Tab,下拉加载更多看请求,不难看出这就是 目标接口:
看下请求参数:
阔以,木有加密,试下了uuid是可以不填的,咦,🤔 返回数据是json的话,是不是能 自定义插件?试试?新建插件,添加下述请求参数:
保存并接续,点击自动解析生成输出参数,保存并继续,试下输入参数:
😟 并不太行,又尝试了一下,设置请求头里的 Content-Type 为 application/json ,指定传递的数据编码为JSON格式,结果还是不行。试下 代码节点 模拟请求:
python
async def fetch_article_infos():
resp = await requests_async.post("https://api.juejin.cn/content_api/v1/article/query_list",
json={"user_id": "4142615541321928", "sort_type": 2, "cursor": "0"})
return resp.json()
async def main(args: Args) -> Output:
params = args.params
result = await fetch_article_infos()
ret: Output = {
"result": str(result)
}
return ret
运行输出结果:
好吧,可以,🙁em... 也不知道是扣子的插件POST不支持JSON数据只支持表单数据,还是我的用法不对。算咯,直接写代码爬:
python
import requests_async
import re
import time
# 请求头
request_headers = {
"Origin":"https://juejin.cn",
"Referer":"https://juejin.cn/editor/drafts/new?v=2",
"User-Agent":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36"
}
# 文章信息类
class ArticleInfo:
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)
def to_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.tags if self.tags else [],
"link_url": self.link_url
}
# 爬取作者的文章列表
async def fetch_article_infos(user_url):
# 正则提取下user_id
user_id_result = re.search(r"(\d+)", user_url)
if user_id_result:
user_id = user_id_result.group(1)
cursor = 0
article_info_list = []
# 死循环,当返回的条数少于10条时跳出循环
while True:
# 虚假的休眠防封
time.sleep(2)
# 拼接请求的json
request_json = {"user_id": user_id, "sort_type": 2, "cursor": str(cursor)}
resp = await requests_async.post("https://api.juejin.cn/content_api/v1/article/query_list", headers=request_headers, json=request_json)
if resp:
article_info = resp.json()
if article_info and article_info["data"]:
# 遍历提取文章数据
for article in article_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(lambda x: x["tag_name"], article["tags"])),
link_url="https://juejin.im/post/" + article["article_id"]).to_json())
# 长度少于10跳出死循环
if len(article_info["data"]) < 10:
break
# 否则游标+10
else:
cursor += 10
else:
break
else:
break
return article_info_list
async def main(args: Args) -> Output:
params = args.params
result = await fetch_article_infos("https://juejin.cn/user/3178040962848480/posts")
ret: Output = {
"result": result if result else []
}
return ret
运行看下输出结果:
✌️ Nice,数据同样拿到了~
2.3. 并行爬取
🤔上面通过死循环的方式获取作者的所有文章,存在一个问题,当作者文章比较多时,比如 200篇 ,需要循环执行20次 ,而单次休眠2s,假设请求和响应的时间为2s,那么完成所有请求,需要至少80s,即1分20秒。这样玩大概率的输出结果会是:节点响应超时。扣子应该是为每个节点设置了一个最长响应事件,超过多久没返回结果,就认为节点任务执行失败。
😏 所以,我们需要将 爬取任务队列 拆分成几个,然后复制粘贴多个上述 代码节点 ,充分利用工作流支持多个节点 并行 的特性,然后追加一个代码节点,对数据进行汇总。爬取作者信息那个代码节点加五个输出参数:
写个简单的循环生成cursor参数列表,右侧是点击运行后的输出结果:
接着修改下爬取文章信息的结点,入参和代码做下调整,然后运行试试看:
行吧,拿到数据了,接着复制粘贴三个,改下入参,并追加一个代码节点,用于整个四个节点的返回数据:
python
async def main(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,
}
return ret
最终的工作流如下:
点击试运行,输入作者的主页url,然后看下输出结果:
🤏 Nice,作者的176篇文章信息都成功获取到啦,而且整体耗时也才4s~
3. 处理数据
3.1. 生成作者画像
这里本来想用 云雀大模型 的,折腾了近一个小时的 提示词 ,一直 答非所问,实在是太拉跨了😭:
无奈,只能试试其它的模型了,试了下国产AI-智谱AI****,输出结果还算可以,新注册+实名送500w Token~
应该能玩几下吧,直接新起一个代码节点,直接HTTP调用的方式调它的API,扣子的节点竟然不支持 jwt 库,只能调内置的 hmac 、base64 、hashlib 库来生成鉴权Token,这部直接让GPT代劳,最终写出模拟请求代码~
python
import requests_async
import hmac
import json
import time
import base64
import hashlib
# 生成鉴权Token
def generate_token(apikey: str, exp_seconds: int):
try:
kid, secret = apikey.split(".")
except Exception as e:
raise Exception("invalid apikey", 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)),
}
# Encode Header
header_encoded = base64.urlsafe_b64encode(json.dumps(header).encode()).decode().rstrip('=')
# Encode Payload
payload_encoded = base64.urlsafe_b64encode(json.dumps(payload).encode()).decode().rstrip('=')
# Create Signature
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('=')
# Create JWT
jwt_token = f'{header_encoded}.{payload_encoded}.{signature_encoded}'
return jwt_token
async def main(args: Args) -> Output:
params = args.params
secret_key ="智谱AI的Key"
input_content = params['input_content']
ai_response = await requests_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,
}
return ret
运行看下输出结果:
卧槽,牛批,毕竟当前 国产第一梯队的AI !!!强无敌,对比之下的 云雀:
🤏 生成作者画像 这部分算是拿捏了,接着处理下其它数据~
3.2. 擅长领域
这部分就是遍历文章列表,统计标签然后计算百分比啥的,比较简单,直接写出处理代码:
python
import operator
async def main(args: Args) -> Output:
params = args.params
article_list = params["input"]
# 文章总数
article_count = len(article_list)
# 标签计数字典
tag_count_dict = {}
# 遍历所有文章,根据tag进行计数
for article in article_list:
for tag in article["tags"]:
tag_count = 0
if tag in tag_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后的元素进行合并
if len(sorted_list) > 5:
end_list = sorted_list[4:]
percent = 0.0
for element in end_list:
percent += element[1]
result_list.extend(sorted_list[:4])
result_list.append(("其它", percent))
else:
result_list = sorted_list
# 输出字符串拼接
result_content = "⭐ TA擅长的领域:\n"
for pos, element in enumerate(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,
}
return ret
运行输出结果:
🤡 Tips:这里篇数为小数的原因:一篇文章能有多个标签,比如三个,单个标签的权重就是0.33~
3.3. 最热文章
☹️ 这边不知道掘金文章的 热度值计算公式,那就根据自己的感觉来拟咯~
H(热度值) = 0.15 * R(阅读量)/100 + 0.25 * L(点赞量) + 0.35 * C(评论量) + 0.25 * F(收藏量)
接着就是遍历文章列表,然后套公式算每篇文章的热力值,最后排序筛出前10篇文章,格式化输出~
python
import operator
async def main(args: Args) -> Output:
params = args.params
article_list = params["input"]
# 文章计数字典
article_count_dict = {}
for article in article_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"
for result in sorted_list[:10]:
result_content += "- {}\n".format(result)
ret: Output = {
"result": result_content,
}
return ret
运行输出结果如下:
😆 行吧,到此就把数据都处理完了~
4. 汇总输出
💥 最后就是各种微调,然后汇总输出了,调整下用户画像那里,追加下返回头:
python
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']
)
结束结点直接汇总下三个结果合并输出:
试运行后,输入笔者的主页链接,最终输出结果如下:
text
🤠【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而且包含 juejin.cn/user/ 就调工作流:
接着同样AI生成下开场白文案,预设问题随便填两个url:
点击其中一个试试看~
2333,哈哈哈,🤣这作者画像...
行吧,接着把Bot发布到 掘金,如果前面的打钩是不可点,配置然后授权就好了~
稍等片刻,成功提交发布后,点击 立即对话:
跳转页面后,随便输入一个作者的主页:
因为沸点要 审核,所以等了一会儿机器人的评论才放出来,评论不支持md,这稀烂的排版😂...
然后豆包和飞书都是正常的~
行吧,以上就是本节的全部内容,应该是目前写过复杂度最高的Bot了,赶紧来 @掘金作者画像 试试吧😆~