转载请注明出处!
原文链接:https://blog.csdn.net/zgyulongfei/article/details/140524578
本文适合的读者为:
- 快手小店个体商家;
- 快手小店店群商家(店群商家:指的是开了几十个快手小店的商家);
- 想要开发一款快手小店自动回复工具的技术人员/团队;
- 想学习浏览器自动回复技术的程序员;
省流介绍:
- 如果您是快手小店商家,可以下载优化过的完善版工具体验,不用查看技术教程。(非商家不要下载!非商家不要下载!非商家不要下载!因为需要有店铺才能用)下载地址:https://www.yunpan.com/surl_y9s92WVjfRn (提取码:46f2)
- 如果您是技术人员,可以细细查看文章,可在文末下载源代码。
目录
一、快手小店客服系统简介
快手小店是快手官方推出的购买产品的服务程序,商家在快手平台上开店后需要与顾客沟通,快手小店提供了客服系统让商家与顾客进行有效地沟通。
快手小店客服系统界面展示:
目前快手小店客服系统已经非常完善了,快手官方也开发了【智能客服】功能,帮助商家更高效地回复顾客。
对于咨询量不大的商家而言,【智能客服】功能已经足够使用了。
然而对于店铺数量多,日常咨询量大的商家而言,【智能客服】有时就显得心有余而力不足了。因为顾客发送给商家的消息,是有3分钟回复率要求的。当【智能客服】遇到不会回答的问题时,系统会转交咨询会话给人工处理,而商家繁忙时来不及给顾客回复消息就会导致3分钟回复率降低。
什么是3分钟回复率?
顾客发送消息给商家后,商家需要在3分钟内答复顾客的咨询。3分钟回复率的计算方式是:在3分钟内回复的顾客咨询消息数,除以顾客咨询消息总数。快手平台要求这一比率不得低于80%,否则会影响店铺评分。
提高客服3分钟回复率的通常方法是请足够数量的人工客服专员接待顾客的咨询。
但对于许多利润微薄的快手小店商家而言,请客服专员的成本实在太高。
此时一款好用的客服自动回复工具就可以极大地减轻商家的工作量,减少商家的成本,提高商家的利润。
二、自动回复工具的类型
目前市面上快手小店自动回复工具的种类主要有两种:
第一种:对接快手小店商家工作台客户端软件,通过分析客户端消息发送协议进行回复,也有通过识别桌面上消息弹窗中的文字再进行回复。
第二种:对接快手商家网页版客服,通过识别网页上的文字,匹配回复规则后再进行回复。
本文采用第二种方法,对接快手客服网页版进行自动回复开发。其实本教程相当于教你做一款快手客服网页的浏览器插件,插件的主要功能是识别快手小店顾客发送的消息,然后匹配自定义的回复话术规则,再把匹配后的答复语发送给顾客。
三、开发环境与类库
编程语言与框架 | c# + WinForm |
开发工具 | Visual Studio 2017 |
应用程序运行环境 | .Net Framework 4.5.2 |
Json库 | Newtonsoft.Json v10.0.3 |
浏览器组件 | CefSharp.WinForms v85.3.130 |
四、创建Windows窗体应用
打开开发工具Visual Studio 2017后,创建一个Windows窗体应用(.Net Framework)。
接着选择目标框架为:.NET Framework 4.5.2,并填上应用名称:快手小店自动回复工具
五、安装Newtonsoft.Json
对于自动回复工具而言,必须要有一个回复规则库,用来存放问题关键词与对应的答复语。
当顾客咨询时,软件去规则库中查找消息是否包含规则关键词,检测到包含关系后,将关键词对应的答复语发送给顾客。
由于回复规则库由商家自行设置,因此需要一个本地数据库,SQLite是一款非常适合的免费的轻量级数据库。但由于本文只是开发范例,为了教程更易理解与实践,本文的规则库采用文本Json来保存,因此需要用到Newtonsoft.Json库来操作Json文件。
安装方法:点击Visual Studio 2017菜单栏上的【项目】-【管理 NuGet 程序包】
切换菜单为【浏览】,然后搜索:Newtonsoft.Json,选择版本v10.0.3并安装(注:如果有更新的版本也可以选择)
六、安装浏览器组件CefSharp.WinForms
由于我们对接的是快手小店网页版客服,因此需要一个浏览器组件用来加载快手小店的客服网页,之后通过操作浏览器组件来识别客服网页上的顾客消息并进行自动回复。
目前主流的浏览器开源框架是cef,WinForm的cef版本是CefSharp.WinForms。
安装CefSharp.WinForms的方法与安装Newtonsoft.Json的方法一致,在NuGet中搜索:CefSharp.WinForms,选择版本v85.3.130并安装(注:至文章发稿日CefSharp的版本已经更新到v126.2.70,我们用较早的版本v85.3.130就足够了,因为新版本的CefSharp对应用程序的运行环境有更高的要求)
安装完CefSharp之后,需要设置应用程序是以64位运行或是以32位运行,不能用Visual Studio 2017中默认的Any CPU运行程序。CefSharp开源项目网页对此有详细说明,有兴趣地可参考:https://github.com/cefsharp/CefSharp/releases
本教程以32位运行为例进行说明。
设置方法:点击Visual Studio 2017菜单中的【生成】-【配置管理器】
在配置管理器中点击【活动解决方案平台】的下拉框,并选择下拉列表的【新建】
接着在【新建解决方案平台中】点击下拉框选择【x86】并保存,Debug配置中也修改成【x86】,然后关闭【配置管理器】窗口。
说明:x86代表应用程序会以32位来运行。
看到菜单栏上的Debug由Any CPU变成x86,就代表设置成功。
(如果未设置成功,在下拉列表中手动选择x86即可)
七、主窗体设计
主窗体展示一个【快手小店客服网页】,和一些功能按钮【设置回复规则、开启自动回复、关闭自动回复】即可。
展示快手小店客服网页区域添加一个Panel,并将其Dock设置成Fill模式。
功能区添加一个MenuStrip,并在其上添加三个MenuItem对应图片中的三个按钮。
八、在主窗体显示快手小店客服网页
这部分内容为CefSharp框架的简要使用,算是一篇CefSharp的入门教程!
在项目上新建一个CefHelper的类,用来初始化一些CefSharp框架的配置。
CefHelper类中编写如下代码:
namespace 快手小店自动回复工具
{
class CefHelper
{
public static readonly string WebCachePath = Application.StartupPath + "\\cache";
public static void InitCef()
{
Cef.EnableHighDPISupport();
CefSettings settings = new CefSettings
{
Locale = "zh-CN",
AcceptLanguageList = "zh-CN,zh;q=0.9",
UserAgent = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.121 Safari/537.36",
PersistUserPreferences = true,
PersistSessionCookies = true,
RootCachePath = WebCachePath
};
settings.CefCommandLineArgs.Add("disable-features", "WebRtcHideLocalIpsWithMdns");
Cef.Initialize(settings);
}
}
}
然后在应用程序主入口点调用Cef初始化代码:
Cef初始化完毕之后,需要在Panel中添加浏览器控件ChromiumWebBrowser。
注意:ChromiumWebBrowser控件是无法拖拽到窗体上完成控件添加的,必须通过编写代码的方式创建。
因此,在主窗体Form1中编写一个方法CreateWebBrowser()来创建一个ChromiumWebBrowser控件,并初始化加载快手小店客服网址:
private ChromiumWebBrowser CreateWebBrowser()
{
//浏览器启动时,初始化显示的快手小店客服网址
string initUrl = "https://im.kwaixiaodian.com";
ChromiumWebBrowser ksBrowser = new ChromiumWebBrowser(initUrl)
{
RequestContext = new RequestContext(new RequestContextSettings()
{
PersistSessionCookies = true,
PersistUserPreferences = true,
CachePath = CefHelper.WebCachePath
})
};
//将浏览器添加到Panel
panel_Browser.Controls.Add(ksBrowser );
//
return ksBrowser;
}
在主窗体Form1的构造函数中调用CreateWebBrowser()方法:
启动程序后,看到界面如下:
快手小店客服页面已经显示,为了进行下一步开发,我们先在页面上登录快手小店账号。
九、获取顾客发送的消息并回复
上一步我们已经登录了快手小店客服,接下来就是获取顾客的未回复消息了。毕竟要做自动回复功能,首先得知道顾客问了什么内容。
本章节将用到JavaScript的基础技术,通过编写js代码获取快手小店客服网页上的元素,进一步获取元素中的文字。
为了更方便的获取网页上的文字,我们电脑需要先安装一个谷歌浏览器,在谷歌浏览器上登录快手小店客服,然后利用谷歌浏览器上的【审查元素】功能快速定位到元素的位置。谷歌浏览器的官方下载地址:https://www.google.cn/intl/zh-CN/chrome/
快手小店客服页面会将顾客的咨询会话分成四种类型:
- 请在3分钟内回复
- 已超时
- 已回复
- 已自动回复
本教程只关注第一种类型:【请在3分钟内回复】
在谷歌浏览器上,把鼠标移动到【请在3分钟内回复】文字上,然后按下鼠标右键,点击右键菜单中的【检查】:
在打开的浏览器调试器中,直接定位到了【请在3分钟内回复】这个元素上:
为了能够循环判断顾客是否发送了新消息,软件需要定时检测【请在3分钟内回复】菜单中是否有新的会话。要检测未回复的顾客数量,只要识别菜单上的数字即可:
获取【请在3分钟内回复】中数字的JavaScript代码如下:
var unreplyCount = 0;
var sessionDiv = document.getElementsByClassName('SessionListGroupItem')0].getElementsByClassName('SessionListGroupItem-count')[0];
if (sessionDiv) {
var strCount = sessionDiv.textContent.replace('(', '').replace(')', '').trim();
unreplyCount += parseInt(strCount);
}
unreplyCount;
数字大于0,说明有未回复的咨询,软件就要去聊天页面中获取顾客发送的最后一条消息。
获取顾客最后一条发送的消息的JavaScript代码如下:
var lastMsg = '';
var doms = document.getElementsByClassName('ChatMessageList-WrapperMsgItem');
for (var i = doms.length - 1; i >= 0; --i) {
var isMallSend = doms[i].getElementsByClassName('kwaishop-cs-LayoutDefaultWrapper__isMe').length == 1;
var isTip = doms[i].getElementsByClassName('kwaishop-cs-LayoutMiddleWrapper').length == 1;
if (!isMallSend && !isTip) {
lastMsg = doms[i].getElementsByClassName('kwaishop-cs-LayoutDefaultWrapper_msgBody')[0].textContent.trim();
break;
}
}
lastMsg;
获取到顾客的最后一条消息之后,软件需要进行自动回复,自动回复的方式需要先填充文本框:
在文本框填充文字“您好,请问需要什么帮助?”文字的JavaScript代码如下:
document.getElementsByClassName('ql-editor')[0].textContent='您好,请问需要什么帮助?'
回复语填充完之后,需要点击【发送Enter】按钮:
按下【发送Enter】按钮的JavaScript代码如下:
document.getElementsByClassName('ant-btn ant-btn-primary')[0].click();
【1 检测新消息 -> 2 获取顾客最后一条消息 -> 3 填充回复语 -> 4 点击发送】
整个自动回复的流程就已经完成了,将以上JavaScript的代码封装到c#代码的JsHelper类中调用。
在项目上新建JsHelper类:
在JsHelper中编写C#代码执行JavaScript代码:
namespace 快手小店自动回复工具
{
class JsHelper
{
/// <summary>
/// c#调用JavaScript获取其结果
/// </summary>
/// <param name="b"></param>
/// <param name="js"></param>
/// <returns></returns>
public static string ExecJavaScriptResult(ChromiumWebBrowser b, string js)
{
string result = string.Empty;
try
{
var jsTask = b.EvaluateScriptAsync(js);
jsTask.Wait(1000);
if (jsTask.Result != null && jsTask.Result.Result != null)
{
result = jsTask.Result.Result.ToString().Trim();
}
}
catch (Exception exp)
{
Console.WriteLine(exp.ToString());
}
return result;
}
/// <summary>
/// 获取【请在3分钟内回复】的咨询数量
/// </summary>
/// <param name="b"></param>
public static int GetUnreplyMsgCount(ChromiumWebBrowser b)
{
string js = @"
var unreplyCount = 0;
var sessionDiv = document.getElementsByClassName('SessionListGroupItem')0].getElementsByClassName('SessionListGroupItem-count')[0];
if (sessionDiv) {
var strCount = sessionDiv.textContent.replace('(', '').replace(')', '').trim();
unreplyCount += parseInt(strCount);
}
unreplyCount;";
string jsResult = ExecJavaScriptResult(b, js);
int.TryParse(jsResult, out int msgCount);
return msgCount;
}
/// <summary>
/// 获取顾客最后问的消息
/// </summary>
/// <param name="b"></param>
public static string GetCustomerLastAskMsg(ChromiumWebBrowser b)
{
string js = @"
var lastMsg = '';
var doms = document.getElementsByClassName('ChatMessageList-WrapperMsgItem');
for (var i = doms.length - 1; i >= 0; --i) {
var isMallSend = doms[i].getElementsByClassName('kwaishop-cs-LayoutDefaultWrapper__isMe').length == 1;
var isTip = doms[i].getElementsByClassName('kwaishop-cs-LayoutMiddleWrapper').length == 1;
if (!isMallSend && !isTip) {
lastMsg = doms[i].getElementsByClassName('kwaishop-cs-LayoutDefaultWrapper_msgBody')[0].textContent.trim();
break;
}
}
lastMsg;";
return ExecJavaScriptResult(b, js);
}
/// <summary>
/// 在文本框中填充需要回复顾客的内容
/// </summary>
/// <param name="b"></param>
/// <param name="replyMsg"></param>
public static void InputReplyMsg(ChromiumWebBrowser b, string replyMsg)
{
string js = $"document.getElementsByClassName('ql-editor')[0].textContent = '{replyMsg}'";
ExecJavaScriptResult(b, js);
}
/// <summary>
/// 点击【发送 Enter】按钮
/// </summary>
/// <param name="b"></param>
public static void ClickSendButton(ChromiumWebBrowser b)
{
string js = "document.getElementsByClassName('ant-btn ant-btn-primary')[0].click();";
ExecJavaScriptResult(b, js);
}
/// <summary>
/// 点击【请在3分钟内回复】的第一个顾客
/// </summary>
/// <param name="b"></param>
public static void ClickFirstCustomerIn3Minutes(ChromiumWebBrowser b)
{
string js = @"
var groups = document.getElementsByClassName('SessionListGroupItem');
for (var i = 0; i < groups.length; ++i) {
var groupContent = groups[i].getElementsByClassName('SessionListGroupItem-groupName')[0].textContent;
if (groupContent.indexOf('请在3分钟内回复') >= 0) {
groups[i].getElementsByClassName('SessionBaseCard-Static')[0].click()
break;
}
}";
ExecJavaScriptResult(b, js);
}
}
}
什么时候执行代码呢?
需要在主窗体Form1中开启一个线程,定时检测【请在3分钟内回复】菜单中是否有新消息,有新消息就回复。
创建bool变量OpenAutoReplyTask,用来识别是否需要开启自动回复功能。
private static bool OpenAutoReplyTask = false;
编写StartAutoReplyTask方法创建一个持久线程,来监控消息数:
private void StartAutoReplyTask(ChromiumWebBrowser b)
{
Task.Factory.StartNew(() =>
{
//等待一会儿再开始执行轮询线程
Thread.Sleep(2000);
//循环检测消息
while (true)
{
if (OpenAutoReplyTask)
{
//检测是否有新消息
while (JsHelper.GetUnreplyMsgCount(b) > 0)
{
//点击第一个顾客
JsHelper.ClickFirstCustomerIn3Minutes(b);
//等待聊天对话加载
Thread.Sleep(1000);
//获取此顾客最后一条消息
string lastMsg = JsHelper.GetCustomerLastAskMsg(b);
//根据顾客消息匹配回复语
string replyMsg = GetReplyMsg(lastMsg);
//在聊天文本框中填充回复语
JsHelper.InputReplyMsg(b, replyMsg);
//点击发送按钮
JsHelper.ClickSendButton(b);
//回复完每一个顾客都等几秒,防止页面反应慢,这个顾客还在【请在3分钟内回复】菜单内
Thread.Sleep(3000);
}
}
//执行完一遍流程后,等待5秒再继续执行下一轮
Thread.Sleep(5000);
}
}, TaskCreationOptions.LongRunning);
}
并在主窗体Form1中构造函数中调用此方法:
自此,一个完整的自动回复流程代码就编写完成了。
喂!等等!StartAutoReplyTask()代码中的 string replyMsg = GetReplyMsg(lastMsg); 是啥意思,前文一点都没提到啊?
private string GetReplyMsg(string customerMsg)
{
return "您好,欢迎光临!";
}
这个方法是把顾客问题丢到自定义规则库中去匹配,如果匹配到回复话术就将回复语返回。
此时,我们的工具还只会回答这一句话“您好,欢迎光临!”,还很笨!
十、创建自动回复规则库
为了能让工具根据顾客的问题回答不同的回复语,必须创建一个回复规则库。
前文提到为了简化教程,本文采用Json文件存储规则库。
新建一个Rules文件,用来存储关键词与回复语的对应关系。
namespace 快手小店自动回复工具
{
/// <summary>
/// 单条规则
/// </summary>
class RuleItem
{
/// <summary>
/// 规则关键词
/// </summary>
public string Keyword { get; set; }
/// <summary>
/// 关键词匹配的回复语
/// </summary>
public string ReplyMsg { get; set; }
}
/// <summary>
/// 规则库
/// </summary>
class RuleBank
{
/// <summary>
/// 多个规则项目列表库
/// </summary>
public List<RuleItem> RuleList { get; set; } = new List<RuleItem>();
}
}
创建一个RuleHelper类,用来操作规则库。
为了简化教程,RuleHelper只做:添加规则、匹配规则、保存规则功能。
至于修改规则、删除规则等其他更加复杂的操作,就交给您去完善!
namespace 快手小店自动回复工具
{
class RuleHeLper
{
/// <summary>
/// 匹配规则的操作在内存中进行
/// </summary>
public static readonly RuleBank RBank = new RuleBank();
/// <summary>
/// 规则库存储在应用程序根目录
/// </summary>
private static readonly string JsonRulePath = Application.StartupPath + "\\rules.json";
static RuleHeLper()
{
//从rules.json文件中读取json文件到内存中
if (File.Exists(JsonRulePath))
{
string json = File.ReadAllText(JsonRulePath);
if (!string.IsNullOrEmpty(json))
{
try
{
RBank = JsonConvert.DeserializeObject<RuleBank>(
json,
new JsonSerializerSettings()
{
MissingMemberHandling = MissingMemberHandling.Ignore
});
}
catch (Exception exp)
{
Console.WriteLine(exp.ToString());
}
}
}
}
public static void SaveJsonToFile()
{
string json = JsonConvert.SerializeObject(RBank);
File.WriteAllText(JsonRulePath, json);
}
public static bool AddRule(RuleItem r)
{
if (!RBank.RuleList.Exists(x => x.Keyword == r.Keyword))
{
RBank.RuleList.Add(r);
SaveJsonToFile();
return true;
}
return false;
}
public static string FindRuleReply(string customerMsg)
{
RuleItem r = RBank.RuleList.FirstOrDefault(x => customerMsg.Contains(x.Keyword));
return r == null ? string.Empty : r.ReplyMsg;
}
}
}
然后修改Form1中的方法GetReplyMsg:
private string GetReplyMsg(string customerMsg)
{
string defaultReplyMsg = "亲,请您稍等片刻~";
string replyMsg = RuleHeLper.FindRuleReply(customerMsg);
return string.IsNullOrEmpty(replyMsg) ? defaultReplyMsg : replyMsg;
}
这段代码首先去规则库中查找规则回复语,如果找到回复语就返回回复语,如果顾客的消息规则库中无法匹配,就返回默认的内容“亲,请您稍等片刻~”
规则库核心代码已经编写完成,接下来创建一个UI窗口,用来操作规则库。
在项目中新建窗体FormRules:
此窗体主要功能为查看规则列表、添加规则。设计如下:
窗体初始化时,将已有的规则库显示到表格中。添加规则后,将新规则添加到表格末尾。
FormRules窗体代码如下:
namespace 快手小店自动回复工具
{
public partial class FormRules : Form
{
public FormRules()
{
InitializeComponent();
//将规则添加到表格
RuleHeLper.RBank.RuleList.ForEach(x => CreateTableRow(x));
}
private void CreateTableRow(RuleItem r)
{
DataGridViewRow row = new DataGridViewRow();
row.CreateCells(dataGridView1);
dataGridView1.Rows.Add(row);
row = dataGridView1.Rows[dataGridView1.RowCount - 1];
row.Cells["rule_keyword"].Value = r.Keyword;
row.Cells["rule_reply"].Value = r.ReplyMsg;
}
private void button_AddRule_Click(object sender, EventArgs e)
{
string keyword = textBox_AddKeyword.Text.Trim();
string reply = textBox_AddReply.Text.Trim();
RuleItem r = new RuleItem()
{
Keyword = keyword,
ReplyMsg = reply
};
if (RuleHeLper.AddRule(r))
{
CreateTableRow(r);
textBox_AddKeyword.Text = string.Empty;
textBox_AddReply.Text = string.Empty;
}
else
{
MessageBox.Show("此规则关键词已经存在!");
}
}
}
}
然后在窗体Form1中给菜单项添加事件,点击后打开回复规则窗体。
运行软件后,我们打开规则设置窗体添加几条规则:
打开存储的rules.json看看:
至此,简易版本的回复规则库已经打造完成!
发个消息测试下!
给商家发送规则库关键词【您好】:
给商家发送规则库关键词【在吗】:
给商家发送规则库不存在的关键词【可以便宜点吗】:
至此,整个自动回复工具已经开发完毕!
十一、功能优化与扩展
这个快手小店自动回复工具只是一个简单的样例,离功能完善还有很大一步距离。
功能可继续优化和扩展的地方:
- 多个关键词匹配相同回复语。例:【您好】与【你好】,应匹配同一个规则库;
- 区分【关键词包含匹配】和【关键词完全匹配】;
- 规则库可修改与删除;
- 将Json文件规则库修改成SQLite数据库;
- 能回复【已超时】的消息;
- 能记录自动回复的历史记录(什么时间、顾客说的话、软件回复的内容);
- 能同时回复多个快手小店店铺;
- 能根据不同店铺回复不同的话术;
- ……
- ……
十二、结束语
非常感谢您的耐心观看,不知道这份手把手开发教程是否能给您有所启发呢?
可以在评论区分享您的感受、意见和建议!
完善版本的快手小店自动回复工具已经开发出来了,由于功能太多,这里就不展开讲解了。
这份基本自动回复的开发教程已经把核心模块讲解清楚了,足够您自行扩展与使用。
十三、源代码下载
本文教程已经把工具开发的过程详细说明了,源代码已经粘贴或者截图在文中。
按照文中的步骤完全可以自己建立好一个完整的项目。
如要下载完整的工程源码,可在此下载!
CSDN下载地址:https://download.csdn.net/download/zgyulongfei/89559258