🤏写个有点用的Bot:掘金作者画像

1. 引言

😳上周五,掘金又发布了 Coze(扣子) 有关的活动 → 《🌈 玩转沸点 | 成为AI魔法大师,释放你的完美创造力!》,不需要写文章,只需要 建Bot发布到掘金 ,并在 AI聊天室 中发 沸点@ 该Bot进行对话就能参与。

😄 活动参与门槛大大降低了啊,然后交流群就有小🔥汁开始整活,有喜欢对骂的 隔壁老王

还有各种 优弧 的Bot:

🤣 不得不说 群友个个都是人才~

😏 Coze搭Bot的玩法很 简单 ,难点是 Bot的创意 ,即 应用场景 ,你打算用它来 解决什么问题 。先确定 思路 ,再来 。具体玩法和案例可以参考我之前写的几篇文章:

😁 如果你刚开始尝试搭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-Typeapplication/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 库,只能调内置的 hmacbase64hashlib 库来生成鉴权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了,赶紧来 @掘金作者画像 试试吧😆~

相关推荐
KuaFuAI17 分钟前
微软推出的AI无代码编程微应用平台GitHub Spark和国产AI原生无代码工具CodeFlying比到底咋样?
人工智能·github·aigc·ai编程·codeflying·github spark·自然语言开发软件
abments1 小时前
JavaScript逆向爬虫教程-------基础篇之常用的编码与加密介绍(python和js实现)
javascript·爬虫·python
小白学大数据7 小时前
Python爬虫开发中的分析与方案制定
开发语言·c++·爬虫·python
扫地的小何尚7 小时前
NVIDIA RTX 系统上使用 llama.cpp 加速 LLM
人工智能·aigc·llama·gpu·nvidia·cuda·英伟达
数据小小爬虫8 小时前
如何用Java爬虫“偷窥”淘宝商品类目API的返回值
java·爬虫·php
B站计算机毕业设计超人13 小时前
计算机毕业设计Python+大模型农产品价格预测 ARIMA自回归模型 农产品可视化 农产品爬虫 机器学习 深度学习 大数据毕业设计 Django Flask
大数据·爬虫·python·深度学习·机器学习·课程设计·数据可视化
single_ffish13 小时前
XPath:网络爬虫中的数据提取利器
爬虫·python
程序员X小鹿15 小时前
免费,手机可用!一款AI数字人生成工具,200+数字人形象任选,3分钟定制专属数字人!(附教程)
aigc
abments16 小时前
JavaScript逆向爬虫教程-------基础篇之JavaScript密码学以及CryptoJS各种常用算法的实现
javascript·爬虫·密码学
小爬虫程序猿17 小时前
python爬虫获得淘宝商品类目 API 返回值说明
开发语言·爬虫·python