1. 灵感乍现的瞬间
上节《♂️Coze国内版插件汇总-By油猴》中经过 油猴 + Python 扒了官方供给 68个插件东西 生成了汇总表,便利大伙在写Bot时能快速检索到心仪的插件。
深度运用下来发现:Coze搭Bot的玩法很简略,难点是 Bot的创意,即 应用场景,你计划用它来处理什么问题?
昨晚和老婆闲聊,她又问了那个每周必问的问题:周末去哪呢?假如不找当地去的话:
- 会被叨叨: 你都不带我出去玩的,就知道宅在家里打游戏!!!
- 我心里OS: 当了一周的 帕鲁,可贵歇息,宅在家里摊着不香吗???
- 当然,为了 平缓夫妻矛盾,维持家庭稳定,这肯定是不能说的…
- 只能在周五晚,不太甘愿地翻开小红书、抖音、微信工号查找 “深圳周末好去处“,然后重复履行 点开-看-关,最终仓促决议要去哪里玩…
咦,这灵感不就来了吗?用Coze搭个 供给周末好去处主张的Bot,处理这个长期问题~
2. 随手建Bot
试下 仅经过提示词,能否完成咱们的需求,新建Bot,随手写下功能介绍,Dreamina 生成一个1:1的靓妹图标:
简略写下提示词:
让AI优化下提示词:
# 人物
你是一个周末活动引荐官,能依据用户供给的城市和区,引荐一些好去处,包括但不限于:景点、展览、音乐、戏曲、电影等。
## 技能
- 依据用户供给的信息,在周末活动引荐平台上查找相关活动,并按类型整理好。
- 从查找成果中筛选出合适用户的活动,如:距离用户较近、评价较好、用户或许感兴趣的活动。
- 将筛选出的活动引荐给用户,并供给活动的根本信息,如:时刻、地址、费用等。
## 束缚
- 只能引荐周末的活动。
- 只承受用户供给的城市和区信息,不承受其他信息。
开场白也主动生成下:
随意聊一句:
2333,一如既往的不靠谱,接着问下有什么展能够看:
笑死,也不知道调的啥插件,单纯靠提示词来完成这个Bot明显不太行,仍是的自己做下定制~
先捋下大约的希望:
发送 城市+区+类型(景点、展览、音乐、话剧等) ,Bot输出对应区域的 周末活动信息,如:深圳市南山区-景点,Bot输出景点列表:【景点称号】景点地址;深圳市南山区-展览,Bot输出这周末的展览信息列表:【展称号】时刻|票价|地址。
其实这种问答,本质上便是 数据源的检索 而已,细分下咱们这儿的数据源,能够划分为两类:
- 景点:很长一段时刻不会改变,能够把这部分数据当作 不变 的,比方:深圳湾公园这个景点几十年都不会变。
- 周末同城活动:这部分则是 动态改变 的,需求 实时获取,比方:这周六有阿猫的演唱会,下周六有阿狗的脱口秀。
接着是写Bot时 数据源的获取思路:
- 假如自己有相关的数据源,能够上传下 常识库。
- 没有的话,能够看下 官方/商铺是否供给了相关插件,输入参数和输出成果是否能满意咱们的诉求。
- 假如都没有或许都不满意,就需求自己捣鼓了,去哪里搞?无非这三个途径:开源/免费接口、付费接口、爬虫获取 (纽扣里能够经过 自定义插件 或 代码节点中运用request_async库 来模仿恳求获取数据。当然,假如想做得隐秘一些,能够将爬虫脚本部署到服务器上,把数据存到数据库中,暴露一个数据查询的API,写个Coze插件供Bot调用)。
思路有了,接着就该捣鼓捣鼓,怎样取得中意的数据源了~
3. 景点信息源获取
新建工作流,简略设置下形成和描述:
3.1. 官方插件东西能否满意需求?
在上节插件东西的汇总表搜下 “地图“:
试试 searchLocation 这个插件东西,翻开看看输入参数:
咦,没供给 分页参数?该不会只需10条数据吧?工作流中增加下这个插件,试运转输入 “深圳南山” 看看:
果然,只回来了10条数据,不过是咱们心仪的数据 (景点称号+地址),只是数据量有点少,得想方法搞到更多的数据。
️ 其中一个思路便是:让 地址的粒度更细些,城市-区 往下是 大街/城镇级别,先经过大模型获取一切某个区下一切的大街/城镇,然后searchLocation插件履行批处理,最终写代码做下整合。
3.1.1. 大模型结点取得一切大街/城镇
工作流中增加大模型结点,增加下述提示词:
提取{{input}}中的城市、区,然后检索<城市区>由哪些大街/城镇组成,要全,不要遗失任何一个。
束缚:
严格依照这样的json格局输出:
{
"city": <城市,需求以市结束>,
"district":<区,需求以区结束>,
"streeOrTown": <大街/城镇列表>
}
试运转,输入 “珠海拱北” 试试:
能够,可贵云雀大模型正确理解了咱们的目的,并依照咱们预设的束缚进行回复。
3.1.2. 挑选器节点 + searchLocation批处理
上面的回来的大街/城镇字段数据有14条,而批处理最多履行10次,所以需求 判别下成果长度,超过10个,拆成调用两个searchLocation东西。这儿做下简化处理,只需数据超过2条,就拆成两组,理想中的履行流程:
照着流程堆节点,可是运转成果并不契合预期,先是挑选器节点的断定:
分明 数组长度>0,仍是走的else???没懂这儿的长度具体指的啥… 行吧,只能把挑选条件为改成了 不为空,测了下能跑通,就先这样吧,然后补全下节点:
看下成果节点的输入数据:
行吧,数据都拿到了,前面再加个代码节点,做下数据兼并去重,并格局化输出:
async def main(args: Args) -> Output:
params = args.params
if_condition_result1 = params["ifConditionResult1"]
if_condition_result2 = params["ifConditionResult1"]
else_condition_result1 = params["elseConditionResult"]
# 运用字典key的能够去重,遍历三个成果兼并
result_dict = {}
if if_condition_result1 and len(if_condition_result1) > 0:
for condition_result in if_condition_result1:
for place in condition_result["places"]:
result_dict[place["name"]] = {"name": place["name"], "address": place["address"]}
if if_condition_result2 and len(if_condition_result2) > 0:
for condition_result in if_condition_result2:
for place in condition_result["places"]:
result_dict[place["name"]] = {"name": place["name"], "address": place["address"]}
if else_condition_result1 and len(else_condition_result1) > 0:
for condition_result in else_condition_result1:
for place in condition_result["places"]:
result_dict[place["name"]] = {"name": place["name"], "address": place["address"]}
ret: Output = {
"result": list(result_dict.values())
}
return ret
运转后 挑选器节点 又抽风了,我真的是裂开了…
算了,放弃挑选器节点,默认运用两个searchLocation插件,反正做了去重,不必的担心数据重复问题,改下代码:
import json
async def main(args: Args) -> Output:
params = args.params
input_json = json.loads(params["input"])
stree_or_town_list = input_json['streeOrTown']
list_len = int(len(stree_or_town_list))
first_stree_or_town_List = []
second_stree_or_town_List = []
# 假如长度大于2,拆分成两组
if list_len > 2:
center_pos = int(list_len /2)
first_stree_or_town_List = stree_or_town_list[:center_pos]
second_stree_or_town_List = stree_or_town_list[center_pos:]
# 只需一条的话,两个列表都进行赋值
else:
first_stree_or_town_List = stree_or_town_list
second_stree_or_town_List = first_stree_or_town_List
ret: Output = {
'city': input_json['city'],
'district': input_json['district'],
'cityDistrict': input_json['city'] + input_json['district'],
'firstStreeOrTownList': first_stree_or_town_List,
'secondStreeOrTownList': second_stree_or_town_List,
}
return ret
运转输出成果:
接着回到 编排-人设与回复逻辑,调用下工作流,然后随意发送:城市+区:
Bot就会给咱们回复对应城市区的一切景点信息啦。不过昨天早上是好的,下午想再试试,地图插件却一直报错,批处理、单词调用,人设与回复逻辑处经过提示词主动调用,都是报错:
感觉跟昨天下午平台答应提交插件有关,唉,仍是做下兜底,自己收集下数据,搞个城市景点的常识库,未雨绸缪嘛~
3.2. 自己收集数据弄常识库
网上看了一圈没有供给景点查询的相关接口,那就只能自己写爬虫收集咯,去哪采?答:地图站点/软件。以某地图为例,搜下”深圳市南山区景点“,能够看到相关的景点,做了分页,一页10个:
F12抓包,查找 “南山公园” 定位到加载数据XHR,URL中包括了下述字符串:
newmap=1&reqflag=pcmap&biz=1&from=webmap&da_par=direct&pcevaname=pc4.1&qt=con&from=webmap
略微改下上节写的油猴脚本,手动点下一页,直到最终一页,爬取后的json文件:
用Python写下处理脚本:过滤非景点或公园的数据,提取下景点称号、标签和地址,做下去重,最终保存为csv文件,具体代码如下:
# 提取城市_区一切景点信息列表
def fetch_attractions_info_list(city_county):
json_dir = "{}{}{}{}{}".format(os.getcwd(), os.sep, "baidu_jd", os.sep, city_county, os.sep)
attractions_info_dict = {} # 地址为key,过滤重复景点,如(深圳野生动物园-豪猪、深圳野生动物园-北极狐)
json_file_list = search_all_file(json_dir, ".json")
for json_file in json_file_list:
data_list = json.loads(read_file_text_content(json_file))['content']
for data in data_list:
attractions_name = data['name'] # 景点称号
attractions_tag = data['std_tag'] # 景点标签
attractions_addr = data['addr'] # 景点地址
# 过滤废物数据
if attractions_tag:
if "景点" in attractions_tag or "公园" in attractions_tag:
# 景点记录
attractions_dict = {"景点称号": attractions_name, "景点标签": attractions_tag,
"景点地址": attractions_addr}
if attractions_addr in attractions_info_dict:
cur_attractions_name = attractions_info_dict[attractions_addr]['景点称号']
# 取景点称号较短的那一个
if len(attractions_name) < len(cur_attractions_name):
attractions_info_dict[attractions_addr] = attractions_dict
else:
attractions_info_dict[attractions_addr] = attractions_dict
# 保存为csv文件
with open(os.path.join(output_dir, "{}.csv".format(city_county)), 'w+', newline='', encoding='utf-8') as f:
writer = csv.DictWriter(f, fieldnames=['景点称号', "景点标签", "景点地址"])
writer.writeheader()
for item in list(attractions_info_dict.values()):
writer.writerow(item)
其它城市-区也是如法炮制,接着翻开Coze的常识库 → 新建常识库:
然后上传每个csv,顺次点击:新增单元 → 表格格局 → 本地文档 → 上传上面生成的csv文件 → 下一步 → 挑选景点地址作为索引 (用于进步常识库召回的准确率):
、
下一步 → 等候数据处理完结,然后能够翻开看下生成的单元了:
常识库准备就绪,接着新建一个工作流 (search_city_attraction_info),增加 常识库节点:
常识库节点最多召回10条数据,这点数据量肯定是不够的,而且这个节点不支持批处理。那就靠数量来凑吧,直接CV五个常识库节点,然后试下加上分页查询,连接好的节点图如下:
前面的代码节点用于生成分页Query字符串:
async def main(args: Args) -> Output:
params = args.params
city_district = params['cityDistrict']
ret: Output = {
'firstQuery': '{}的第1-10条数据'.format(city_district),
'SecondQuery': '{}的第11-20条数据'.format(city_district),
'ThirdQuery': '{}的第21-30条数据'.format(city_district),
'ForthQuery': '{}的第31-40条数据'.format(city_district),
'FifthQuery': '{}的第41-50条数据'.format(city_district)
}
return ret
后边的代码节点用于景点数据兼并:
import json
# 兼并列表的函数
def merge_list(input_param, origin_set):
for data in input_param:
data_json = json.loads(data['output'])
origin_set.add("-【{}】{}".format(data_json['景点称号'], data_json['景点地址']))
async def main(args: Args) -> Output:
params = args.params
sum_set = set()
result = ''
merge_list(params["input1"], sum_set)
merge_list(params["input2"], sum_set)
merge_list(params["input3"], sum_set)
merge_list(params["input4"], sum_set)
merge_list(params["input5"], sum_set)
for data in sum_set:
result += data + "n"
ret: Output = {
"result": result,
}
return ret
运转输出成果如下:
怎样才这点数据?常识库里深圳南山区的景点数据可是有68条的,看了下数据兼并代码节点的输入参数,发现许多数据是重复的,看来是常识库不支持分页Query。em… 试下给每条记录增加一个 景点序号 的列吧。配置表结构只支持删去列,不支持新增列,直接删掉从头导下csv吧,挑选景点序号作为索引:
改了下Query生成的字符串:
成果并没什么卵用,而且一个”深圳市_南山区”的数据都没射中,感觉也或许跟缓存有关…
算了,直接使出最终一招吧,把景点数据输出为txt文件:
点击 新增单元,选中 文本格局-本地文档:
认为数据结构比较简略,分段设置 这儿直接选 主动分段与清洗 就好了:
确认后等它处理完,再次翻开咱们新建的 单元,能够看到数据被主动拆分成3个 分段:
吼,那就不需求 5个常识库节点 了,一个就够了,后边增加一个代码节点用于输出成果格局化:
格局化代码如下:
async def main(args: Args) -> Output:
params = args.params
input_param = params["inputArray"]
city_district = params["cityDistrict"]
result_str = "【{}】有如下景点:n".format(city_district)
for param in input_param:
info_list = param["output"].split("n")
for info in info_list:
result_str += "- {}n".format(info)
ret: Output = {
"result_str": result_str,
}
return ret
试运转输入:深圳南山,输出成果:
发布下工作流,然后 人设与回复逻辑 调用下,输出成果如下:
输出成果和官方插件东西回来的根本共同,当然,速度会快许多,毕竟是依据已有数据的检索。行吧,景点信息源获取这块就折腾到这,接着搞下周末同城数据的获取。
4. 周末同城活动数据源获取
相同没找着接口,那就写爬虫爬吧,以 XX同城 为例,F12翻开抓包,恳求页面发现数据直接回来了:
本地写了段Python代码,设置了User-Agent恳求头,恳求url,发现页面代码都有回来。行吧,试下创立 自定义插件 恳求能否行得通,然而在调试时就报错了:
em… 形似插件 只支持json格局的回来数据,那就只能在代码节点编写代码来模仿恳求了,新建下工作流:
新建一个代码结点,代码模仿恳求下:
运转后输出成果如下:
仿制保存下呼应数据,本地写下 提取活动称号、时刻、地址、费用的正则,提取一波数据:
import re
from util.file_util import read_file_text_content
activity_pattern = re.compile(
r'temprop="summary">(.*?)<.*?时刻:</span>(.*?)<.*?<li title="(.*?)">.*?地址:<.*?费用:</span>(.*?)</strong>', re.S)
if __name__ == '__main__':
content = read_file_text_content("test.html")
match_results = activity_pattern.findall(content)
result_str = ""
for result in match_results:
activity_name = result[0].replace("n", "").strip() if result[0] else "暂无数据"
activity_time = result[1].replace("n", "").strip() if result[1] else "暂无数据"
activity_address = result[2].replace("n", "").strip() if result[3] else "暂无数据"
activity_cost = result[3].replace("n", "").strip().replace("<strong>", "") if result[3] else "暂无数据"
result_str += "-【{}】| {} | {} | {}n".format(activity_name, activity_cost, activity_time, activity_address)
print(result_str)
运转输出成果如下:
行吧,数据能提取到,接着便是匹配恳求参数,拼接url了,比较简略,直接给出爬取代码:
import json
import requests_async
import re
import time
# 提取活动信息的正则
activity_pattern = re.compile(
r'temprop="summary">(.*?)<.*?时刻:</span>(.*?)<.*?<li title="(.*?)">.*?地址:<.*?费用:</span>(.*?)</strong>', re.S)
# 城市和区的Bean
class City:
def __init__(self, name_cn, name_req_param, district_dict):
self.name_cn = name_cn
self.name_req_param = name_req_param
self.district_dict = district_dict
# 建议恳求
async def send_request(url):
headers = {
"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",
"Host": "www.douban.com",
"Refer": url
}
# 恳求呼应成果
response_data = await requests_async.get(url, headers=headers)
# 提取呼应成果中的活动信息
match_results = activity_pattern.findall(response_data.text)
result_str = ""
for result in match_results:
activity_name = result[0].replace("n", "").strip() if result[0] else "暂无数据"
activity_time = result[1].replace("n", "").strip() if result[1] else "暂无数据"
activity_address = result[2].replace("n", "").strip() if result[3] else "暂无数据"
activity_cost = result[3].replace("n", "").strip().replace("<strong>", "") if result[3] else "暂无数据"
result_str += "-【{}】| {} | {} | {}n".format(activity_name, activity_cost, activity_time, activity_address)
return result_str
async def main(args: Args) -> Output:
# 城市和区的恳求参数
city_list = [
City("深圳市", "shenzhen", {
"罗湖区": 130288, "福田区": 130289, "南山区": 130290, "宝安区": 130291, "龙岗区": 130292,
"盐田区": 130293, "坪山区": 131682, "龙华区": 131683, "光明区": 131691,
}), City("广州市", "guangzhou", {
"从化": 130277, "荔湾区": 130266, "越秀区": 130267, "海珠区": 130268, "天河区": 130269, "白云区": 130270,
"黄埔区": 130271, "番禺区": 130272, "花都区": 130273, "南沙区": 130274, "萝岗区": 130275, "增城区": 130276
}), City("上海市", "shanghai", {
"黄浦区": 129242, "徐汇区": 129244, "长宁区": 129245, "静安区": 129246, "普陀区": 129247,
"闸北区": 129248, "虹口区": 129249, "杨浦区": 129250, "闵行区": 129251, "宝山区": 129252,
"嘉定区": 129253, "浦东新区": 129254, "金山区": 129255, "松江区": 129256, "青浦区": 129257,
"奉贤区": 129259, "崇明县": 129260
}), City("北京市", "beijing", {
"东城区": 128519, "西城区": 128520, "朝阳区": 128523, "丰台区": 128524, "石景山区": 128525,
"海淀区": 128526, "门头沟区": 128527, "房山区": 128528, "通州区": 128529, "顺义区": 128530,
"昌平区": 128531, "大兴区": 128532, "怀柔区": 128533, "平谷区": 128534, "密云县": 128535, "延庆县": 128536
})
]
# 活动类型参数
category_dict = {
"音乐": "music", "戏曲": "drama", "讲座": "salon", "集会": "party", "电影": "film",
"展览": "exhibition", "运动": "sports", "公益": "commonweal", "游览": "travel",
"赛事": "competition", "课程": "course", "亲子": "kids", "其它": "others"
}
params = args.params
input_json = json.loads(params["input"])
request_url = "https://www.xxx.com/location/{}/events/weekend"
for city in city_list:
if city.name_cn == input_json['city']:
# 拼接城市参数
request_url = request_url.format(city.name_req_param)
# 拼接活动类型参数
if input_json['category'] is not None and len(input_json['category']) > 0:
categoty_req_param = category_dict[input_json['category']]
if category_dict:
categoty_req_param = categoty_req_param
else:
categoty_req_param = "all"
else:
categoty_req_param = "all"
request_url += "-{}".format(categoty_req_param)
# 拼接区
district = city.district_dict.get(input_json['district'])
if district:
request_url += "-{}".format(district)
result_str = "为您检索到【{}-{}-{}】的周末活动信息:n".format(
input_json['city'], input_json['district'],
input_json['category'] if len(input_json['category']) > 0 else "悉数"
)
# 恳求三次接口获取前三页数据,休眠0.5秒防封
r1 = await send_request(request_url)
time.sleep(0.5)
r2 = await send_request(request_url + "?start=10")
time.sleep(0.5)
r3 = await send_request(request_url + "?start=20")
result_str += r1
result_str += r2
result_str += r3
ret: Output = {
"result": result_str,
"request_url": request_url,
'r1': r1,
'r2': r2,
'r3': r3,
}
return ret
这儿设置r1、r2、r3只是想看下恳求的数据是否正常,实际运用能够干掉,测验代码那里粘贴下输入参数:
{
"input": "{"category":"","city":"深圳市","district":"福田区"}"
}
运转后,看下输出成果:
能够,输出成果契合咱们的预期,接着在爬虫代码节点前加个大数据节点,提取下城市、区和活动类型,提示词:
提取{{input}}中的城市、区,活动类别 (非必要,可选值:音乐、戏曲、讲座、集会、电影、展览、运动、公益、游览、赛事、课程、亲子、其它)
束缚:
严格依照这样的json格局输出:
{
"city": <城市,需求以市结束>,
"district":<区,需求以区结束>,
"category": <活动类别,没有的话显现空字符串>
}
发布下工作流,然后Bot增加下这个工作流,修改下 人设与回复逻辑 的提示词,调用对应的工作流:
# 人物
你是一个周末活动引荐官,假如识别到用户发送的信息里有城市和区,调用技能1,否则依据上下文回复一句带有颜文字的话。
# 技能
## 技能1:周末好去处
判别用户发送的信息中是否包括"景点"这个字符串:
- 假如包括,调用search_city_attraction_info工作流。
- 假如不包括,调用weekend_go_good_places工作流。
# 束缚
- 信息中必须包括"景点"两个字才调用search_city_attraction_info工作流。
手敲下 开场白案牍:
你好!我是一名周末活动引荐官,能为你引荐一些城市区的景点和周末同城活动信息。查询指令示例:
- 景点 → "深圳市南山区景点"
- 同城活动 → "深圳市南山区音乐",后边跟着的音乐是活动类型,不设置默认是悉数,可选值有:音乐、戏曲、讲座、集会、电影、展览、运动、公益、游览、赛事、课程、亲子、其它。
赶紧试试吧~
自定义几个 开场白预置问题:
顺带选个蛙蛙甜妹的 音色:
预览和调试随意输入一个城市区,如 “北京朝阳区“:
行吧,查询景点也能正确回来,发布下Bot:
5. Bot作用
发布到豆包后,就等审阅了,期间自个是能够偷着乐的,建议谈天试试:
哇塞~ 作用是真不戳啊!!!信息是真的全,当然,美中不足的当地或许是 排版 了,后续能够美化下数据的输出格局。最终,关于 纽扣里写爬虫 说一嘴:
虽然上面运用了休眠,但多次调用后站点大约率仍是会 时间短封IP,2333,只是封的纽扣服务器的IP,不是咱们自己的IP,主张仍是设置下 Cookie字段,让用户设置下,经过纽扣供给的数据库进行关联。当然,更好的方法是,自己整个 云服务器部署爬虫脚本,定时爬数据存数据库,供给API接口给Bot调用。这种方法有一定的技能门槛,读者也能够试试 云存储计划 (如运用Bmob,经过API方法对云数据库进行增删改差) ,Mock API + 写死回来数据 (如运用Apifox ) 等,完成方式有许多种,自己怎样便利怎样来。
BotID: 7337619854435876876
纽扣商铺链接:www.coze.cn/store/bot/7…