构建智能联系人筛选系统:从决策模型到Python工程实践
2026/8/21 3:16:45 网站建设 项目流程

1. 先搞清楚“打电话给谁呢”到底是个什么需求

“打电话给谁呢”这个标题,乍一看像是个生活化的疑问句,但在技术博客的语境下,它指向的其实是一个非常具体且高频的工程问题:如何从一堆联系人、数据或任务中,智能、高效地筛选出当前最需要联系的目标对象。这绝不是一个简单的通讯录查询,而是涉及数据筛选、优先级判断、场景适配甚至自动化触发的决策过程。

无论是做客户回访、活动通知、故障告警,还是日常的团队协作,我们都会面临“现在该联系谁”的困境。手动翻列表效率低,随机选择不靠谱。所以,这个主题的核心价值在于,把“找谁”这个主观决策,变成一个可定义、可配置、可自动化的技术流程。它适合所有需要处理批量外联、通知或协作任务的开发者、运维、运营和项目经理。

最关键的,它不是要你造一个电话拨号器,而是要你建立一套联系人筛选与触发的逻辑引擎。理解了这一点,才能避免陷入“如何调用电话API”这种狭窄的技术实现,转而关注更本质的决策模型。

2. 从零构建:定义你的“筛选-触发”决策模型

在动手写任何代码之前,必须先把你脑子里模糊的“该联系谁”转化为清晰的规则。我一般会建议从三个维度来拆解这个模型:筛选条件、优先级排序、触发时机。这比一上来就纠结用Python还是Go重要得多。

2.1 明确筛选条件:你的联系人都有哪些“标签”

“打电话给谁”首先取决于“谁符合条件”。你需要为你的联系人数据打上结构化标签。不要只存个名字和电话。

一个基础的联系人数据结构至少应该包含这些字段(以JSON为例):

{ “contact_id”: “001”, “name”: “张三”, “phone”: “13800138000”, “tags”: [“VIP客户”, “产品A用户”, “未付款”, “华东区”], “last_contact_time”: “2023-10-01 14:30:00”, “priority_score”: 85, “status”: “active” // 可能还有 inactive, dnd (请勿打扰) 等 }

标签(tags)是灵活筛选的关键。比如,你想联系“所有购买了产品A但尚未付款的VIP客户”,对应的筛选逻辑就是:tags同时包含“产品A用户”“未付款”,并且也包含“VIP客户”

最后联系时间(last_contact_time)用于避免骚扰。你可以设定规则:“距离上次联系超过7天的客户”。

状态(status)用于硬性过滤,比如标记为“请勿打扰”的联系人应该被排除。

2.2 设定优先级:为什么先打给他而不是她

符合条件的人可能很多,但资源(时间、人力)有限。这时就需要优先级排序。优先级规则需要量化。

常见的优先级计算维度:

  1. 业务价值:客户等级(VIP > 普通)、订单金额大小。
  2. 紧急程度:服务即将到期(3天内 > 7天内)、故障等级(P0 > P1)。
  3. 时间衰减:越久未联系,优先级越高(可设置一个上限,比如30天后优先级不再增加)。
  4. 行为反馈:上次联系后产生了积极行为(如登录、点击)的,优先级提升。

你可以设计一个简单的优先级分数公式:最终分数 = 基础分(如客户等级) + 紧急度加分 + 时间衰减加分 + 行为反馈加分

然后,对所有筛选出的联系人按此分数降序排列,分数最高的就是“第一个该打电话的人”。

2.3 确定触发时机:什么时候启动这个筛选流程

触发机制决定了系统是“被动响应”还是“主动出击”。

  • 被动响应(Pull):由人工在管理后台点击“筛选待联系客户”按钮,或通过API接口调用。
  • 主动出击(Push):由事件或定时任务驱动。这是自动化的核心。
    • 定时任务:每天上午9点,自动筛选出当天需要回访的客户列表。
    • 事件驱动:当用户订单状态变为“发货”时,自动筛选出“物流客服”角色且当前空闲的员工进行联系。

我建议的落地顺序是:先实现手动触发,把筛选和排序逻辑跑通;再根据业务需求,增加定时或事件触发的调度能力。

3. 技术实现选型与核心代码拆解

模型定义清楚后,我们来选择技术栈。对于大多数应用场景,一个轻量级的后端服务加上一个数据库就足够了。这里以最通用的Python + Flask + SQLite/MySQL组合为例,演示核心环节。

3.1 环境与数据准备

首先,你需要一个存放联系人的数据库表。

-- contacts 表结构示例 CREATE TABLE contacts ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, phone TEXT NOT NULL, tags TEXT, -- 可以用JSON字符串存储,如 '["VIP", "未付款"]' last_contact_time DATETIME, priority_score INTEGER DEFAULT 0, status TEXT DEFAULT 'active', -- 其他业务字段,如 customer_level, order_amount 等 customer_level INTEGER, last_order_time DATETIME );

注意tags字段存JSON字符串,在查询时数据库(如MySQL 5.7+或SQLite with JSON1扩展)可以支持JSON查询。如果数据库不支持,可以拆分成多对多的关系表,但初期用JSON更简单。

用Python插入一些测试数据:

import sqlite3 import json from datetime import datetime, timedelta conn = sqlite3.connect(‘contacts.db’) cursor = conn.cursor() # 插入示例数据 contacts = [ (‘张三’, ‘13800138000’, json.dumps([‘VIP客户’, ‘产品A用户’, ‘未付款’]), (datetime.now() - timedelta(days=10)).isoformat(), 85, ‘active’, 3, (datetime.now() - timedelta(days=5)).isoformat()), (‘李四’, ‘13900139000’, json.dumps([‘普通客户’, ‘产品B用户’]), (datetime.now() - timedelta(days=2)).isoformat(), 60, ‘active’, 1, None), (‘王五’, ‘13700137000’, json.dumps([‘VIP客户’, ‘产品A用户’, ‘已付款’]), (datetime.now() - timedelta(days=20)).isoformat(), 90, ‘active’, 3, (datetime.now() - timedelta(days=1)).isoformat()), (‘赵六’, ‘13600136000’, json.dumps([‘普通客户’, ‘未付款’]), None, 40, ‘dnd’, 1, None), # 状态为请勿打扰 ] cursor.executemany(‘’’ INSERT INTO contacts (name, phone, tags, last_contact_time, priority_score, status, customer_level, last_order_time) VALUES (?, ?, ?, ?, ?, ?, ?, ?) ‘‘’, contacts) conn.commit() conn.close()

3.2 核心筛选与排序逻辑实现

这是整个系统的“大脑”。我们实现一个函数,它接收筛选参数,返回排序后的联系人列表。

import sqlite3 import json from datetime import datetime, timedelta def find_contacts_to_call(required_tags=None, exclude_status=‘dnd’, days_since_last_contact=7, min_priority=50, limit=10): “”” 根据条件筛选并排序待联系联系人 :param required_tags: 必须包含的标签列表,如 [‘产品A用户‘, ‘未付款’] :param exclude_status: 排除的状态 :param days_since_last_contact: 距离上次联系至少多少天 :param min_priority: 最低优先级分数 :param limit: 返回结果数量上限 :return: 排序后的联系人列表 “”” conn = sqlite3.connect(‘contacts.db’) # 启用JSON支持(如果使用SQLite) conn.row_factory = sqlite3.Row cursor = conn.cursor() # 构建基础查询 query = “SELECT * FROM contacts WHERE status != ? AND priority_score >= ?” params = [exclude_status, min_priority] # 处理标签筛选 (JSON数组包含判断) if required_tags: # 这里是一个简化的JSON包含查询示例,适用于SQLite的json_each # 实际生产环境可能需要更严谨的JSON查询或使用关系表 tag_conditions = [] for tag in required_tags: # 注意:这种查询方式在数据量大时可能效率不高,适用于初期或数据量小的场景 query += f“ AND id IN (SELECT id FROM contacts, json_each(tags) WHERE json_each.value = ?)” params.append(tag) # 更优的做法是使用关系表,这里为了演示简化处理 # 处理时间筛选:上次联系时间早于设定天数,或者从未联系过 (last_contact_time IS NULL) cutoff_time = (datetime.now() - timedelta(days=days_since_last_contact)).isoformat() query += “ AND (last_contact_time IS NULL OR last_contact_time < ?)” params.append(cutoff_time) # 按优先级分数降序排序,并限制数量 query += “ ORDER BY priority_score DESC LIMIT ?” params.append(limit) cursor.execute(query, params) rows = cursor.fetchall() conn.close() # 将结果转换为字典列表 result = [dict(row) for row in rows] # 将tags字段的JSON字符串解析回列表 for contact in result: if contact[‘tags’]: contact[‘tags’] = json.loads(contact[‘tags’]) return result # 使用示例:找出产品A用户中未付款、至少7天未联系、且非请勿打扰状态的VIP客户,取前5个 target_contacts = find_contacts_to_call( required_tags=[‘产品A用户‘, ‘未付款’, ‘VIP客户’], # 注意:这个简化查询逻辑可能需要调整以适应多标签同时匹配 exclude_status=‘dnd’, days_since_last_contact=7, min_priority=70, limit=5 ) print(target_contacts)

关键点解释

  1. 标签查询的复杂性:上述代码中的多标签JSON查询是简化版,在SQLite中效率不高。生产环境中,如果标签筛选是核心功能,强烈建议使用联系人-标签关系表,或者使用支持高级JSON查询的数据库(如PostgreSQL)。
  2. 时间处理:我们使用了ISO格式的时间字符串,并用Python的datetime进行计算,确保时区一致(建议所有时间存UTC)。
  3. 排序:直接使用priority_score降序,这是业务规则计算后的结果。你可以在插入或更新数据时,通过另一个服务或定时任务来更新这个分数。

3.3 构建一个简单的API服务

将上面的逻辑包装成Web API,方便其他系统(如前端页面、CRM系统)调用。

from flask import Flask, request, jsonify import logging app = Flask(__name__) @app.route(‘/api/contacts/to-call’, methods=[‘GET’]) def get_contacts_to_call(): try: # 从请求参数中获取筛选条件 required_tags = request.args.get(‘tags’) if required_tags: required_tags = required_tags.split(‘,’) # 假设以逗号分隔 exclude_status = request.args.get(‘exclude_status’, ‘dnd’) days = int(request.args.get(‘days’, 7)) min_priority = int(request.args.get(‘min_priority’, 0)) limit = int(request.args.get(‘limit’, 20)) # 调用核心逻辑函数 from your_logic_module import find_contacts_to_call # 假设函数放在另一个模块 contacts = find_contacts_to_call( required_tags=required_tags, exclude_status=exclude_status, days_since_last_contact=days, min_priority=min_priority, limit=limit ) return jsonify({“code”: 0, “msg”: “success”, “data”: contacts, “count”: len(contacts)}) except Exception as e: logging.error(f“Failed to get contacts: {e}”) return jsonify({“code”: 500, “msg”: str(e), “data”: []}), 500 if __name__ == ‘__main__’: app.run(debug=True, port=5000)

现在,访问http://localhost:5000/api/contacts/to-call?tags=VIP客户,未付款&days=10&limit=5就能获取JSON格式的待联系列表。

4. 从“能跑通”到“用得稳”:生产级考量

单次API调用能返回数据,只是第一步。要真正用于生产,必须考虑稳定性、性能和可维护性。

4.1 性能优化:当联系人数据量变大时

上面的示例代码在数据量超过几万条后,性能瓶颈会立刻出现,尤其是在做复杂的JSON标签筛选时。

优化方案:

  1. 索引:在status,priority_score,last_contact_time字段上建立索引。
    CREATE INDEX idx_status ON contacts(status); CREATE INDEX idx_priority ON contacts(priority_score DESC); CREATE INDEX idx_last_contact ON contacts(last_contact_time);
  2. 标签查询优化:放弃JSON字段,使用关系表。
    CREATE TABLE contact_tags ( contact_id INTEGER, tag_name TEXT, PRIMARY KEY (contact_id, tag_name), FOREIGN KEY (contact_id) REFERENCES contacts(id) ); CREATE INDEX idx_tag_name ON contact_tags(tag_name);
    查询时使用JOINEXISTS,效率远高于JSON解析。
  3. 异步计算与缓存priority_score不要实时计算。通过定时任务(如Celery)在凌晨计算所有联系人的分数并更新到数据库。对于高频的筛选请求,结果可以缓存(如Redis)几分钟,避免重复扫描数据库。

4.2 任务队列与自动化触发

手动调用API还不够自动化。我们需要让系统在特定时间或事件发生后自动执行筛选。

方案:使用 Celery + Redis 实现定时任务

# tasks.py from celery import Celery from your_logic_module import find_contacts_to_call from your_notification_module import send_to_notification_center # 假设有一个通知模块 app = Celery(‘contact_tasks’, broker=‘redis://localhost:6379/0’) @app.task def daily_contact_screening(): “”“每天上午9点执行的定时任务”“” # 定义你的日常筛选规则 target_contacts = find_contacts_to_call( required_tags=[‘VIP客户’, ‘未付款’], days_since_last_contact=7, limit=50 ) if target_contacts: # 将结果推送给相关员工或系统 send_to_notification_center(‘daily_vip_unpaid’, target_contacts) return len(target_contacts) # 在Celery Beat配置中设置定时 # celery beat schedule from datetime import timedelta app.conf.beat_schedule = { ‘daily-vip-unpaid-screening’: { ‘task’: ‘tasks.daily_contact_screening’, ‘schedule’: timedelta(days=1), # 每天执行 ‘args’: (), }, }

这样,每天都会自动生成一个待联系VIP未付款客户列表,并推送到通知中心。

4.3 结果交付与集成

筛选出列表后,怎么用起来?这里有几个常见模式:

  1. 推送到工作台:通过WebSocket或内部消息系统,将列表推送给客服/销售的工作台界面。
  2. 生成外呼任务:将列表导入到专业的呼叫中心系统(通过其API),自动生成外呼任务。
  3. 发送提醒:通过企业微信、钉钉、Slack等机器人,将名单发送给指定员工或群组。
  4. 写入任务队列:将每个联系人作为一个任务,写入到如RQCelery的任务队列中,由工人进程逐个处理(如自动发送短信后,再标记为已处理)。

5. 避坑指南与排查清单

在实际部署和运行中,你肯定会遇到问题。下面是我踩过坑后总结的排查顺序。

5.1 为什么筛选结果为空?

这是最常见的问题。不要急着怀疑代码逻辑,按这个顺序查:

  1. 查输入条件:确认传入的required_tagsdays等参数值是否符合预期?打印或日志记录下接收到的参数。
  2. 查数据状态:直接连上数据库,用你代码生成的SQL条件(或类似条件)手动查询,看是否有数据。重点检查status字段、last_contact_time字段的值。
  3. 查时间逻辑last_contact_time < cutoff_time这个条件很容易出错。确认你的cutoff_time计算是否正确,数据库中的时间格式是否一致(建议全部使用UTC时间戳或ISO格式字符串)。
  4. 查标签匹配:如果用了JSON字段,确认标签的存储格式和查询格式完全一致(包括中英文、空格)。[“VIP客户”][“vip客户”]不匹配。

5.2 为什么性能突然变慢?

如果接口响应时间从几十毫秒变成几秒:

  1. 看数据量:是否联系人数量暴增?超过10万行后,简单的全表扫描就不行了。
  2. 看SQL执行计划:在数据库中使用EXPLAIN命令分析你的筛选SQL,看是否用上了索引。重点检查WHERE条件和ORDER BY涉及的字段。
  3. 看标签查询:如果使用了JSON_CONTAINS或类似函数,且数据量大,这会是主要瓶颈。这是将标签存储从JSON迁移到关系表的最强信号
  4. 看并发量:是否同时有大量请求?考虑引入缓存(如Redis缓存1-5分钟的筛选结果)或对数据库查询进行限流。

5.3 自动化任务没有执行?

定时任务没跑起来:

  1. 查Worker进程:Celery Worker是否在运行?celery -A tasks worker --loglevel=info
  2. 查Beat调度器:Celery Beat是否在运行?celery -A tasks beat --loglevel=info
  3. 查消息队列:Redis服务是否正常?能否连接?
  4. 查任务日志:任务是否被接收?是否有执行报错?查看Celery Worker的日志输出。
  5. 查时区:定时任务的schedule配置是否考虑了服务器时区?建议所有系统内部都使用UTC时间。

5.4 如何应对需求变化?

业务方今天说要按“订单金额”排序,明天说要加一个“客户活跃度”权重。

  1. 抽象规则引擎:不要将规则硬编码在函数里。可以考虑将规则配置化,存储在数据库或配置文件中。例如,定义一个ScreeningRule表,包含条件字段、权重字段等。
  2. 可插拔的评分器:将优先级计算模块化。定义Scorer接口,实现OrderAmountScorerActivityScorer等,方便组合和调整权重。
  3. 版本化与A/B测试:对于重要的外联策略,可以同时运行两套筛选规则(A/B版本),通过对比联系后的转化率来选择更优方案。

6. 进阶思考:从“打电话给谁”到“智能触达”

当你把基础的筛选、排序、触发流程跑通后,可以进一步思考更智能化的方向,这会让你的系统价值倍增。

  1. 最佳联系时间预测:不仅仅是“该联系谁”,还包括“何时联系他最好”。可以分析历史联系记录,找出每个客户或某类客户接听率最高的时间段(如工作日下午),并将这个时间作为触发条件或优先级因子。
  2. 多渠道触达整合:除了电话,是否还有企业微信、短信、邮件?系统可以推荐“对于客户A,本次优先使用企业微信发送消息;如果1小时内未读,再自动转为电话联系”。这需要你维护每个联系人的渠道偏好和响应历史。
  3. 反馈闭环与模型迭代:每次联系的结果(接通/未接、成功/失败、客户反馈)应该记录回系统。这些数据可以用来优化你的筛选和排序模型。例如,如果发现“高优先级但多次未接”的客户,可以暂时降低其优先级或切换渠道。
  4. 资源约束下的最优分配:你有10个客服,系统筛选出了100个待联系客户。如何分配才能整体效率最高?这就变成了一个任务分配问题,可以引入简单的算法(如基于优先级轮询、基于技能匹配等)。

最后,也是最关键的一点:不要追求一步到位做出一个完美的“智能引擎”。我建议的路径永远是:先用最简单的方式(比如上面的Python脚本+数据库)把核心流程跑起来,解决业务部门“手动翻列表”的痛点。然后,根据实际使用中暴露出的性能问题、规则变化需求,再逐个迭代到优化方案和高级功能。先让系统用起来,产生价值,再让它变得更好用、更智能。否则,很容易陷入过度设计而迟迟无法交付。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询