从零开始用Python做一个女朋友 • 第一弹
从零开始用Python做一个女朋友 • 第一弹
因为一只蝙蝠所引起的一系列连锁反应,导致一直宅在家中的自己突然有了重拾Python的勇气,一直以来想要做个智能对话机器人,于是说干就干,从最简单的关键词匹配做起,在目前进度中,通过调用公共接口实现了天气查询和实时新闻查询两个功能,后续功能将逐渐完善。由于本人Python学识不深,很多细节的处理上可能不尽完善,欢迎各位大佬指点~
由于目标是比较智能的机器人,所以借用了动漫刀剑的梗虚拟人物形象叫Alice 【宅属性暴露无遗】
总体思路还是模块化的思想,通过循环接收用户输入,然后通过自定义的__analyse()函数进行关键词匹配,将匹配结果通过数组返回,并在主过程内遍历数组内的事件标签,如果天气查询标签或新闻查询标签在内,则将用户输入传递给相应的自定义模块函数进行处理。话不多说,先呈上main.py模块代码:
# -*- coding:utf-8 -*-
import weather #自定义天气查询处理模块
import news #自定义新闻查询处理模块
def __analyse(raw_str):
analyse_result = []
weather_wors = ['天气', 'weather']
news_words = ['新闻', 'news', "头条"]
if any(word in raw_str for word in weather_wors):
analyse_result.append('weather') #为返回的结果数组添加'weather'标签
if any(word in raw_str for word in news_words):
analyse_result.append('news') #为返回的结果数组添加'news'标签
return analyse_result
if __name__ == '__main__':
while (True):
raw_str = input()
analyse_result = __analyse(raw_str)
if 'weather' in analyse_result:
weather.handle(raw_str) #调用自定义天气查询处理模块的处理函数
if 'news' in analyse_result:
news.handle(raw_str) #调用自定义新闻查询处理模块的处理函数
else:
print('Alice没能听懂主人说的是什么哇……')
下面以weather.py为例解释天气查询内部的处理过程(news.py模块同理):
自定义模块的handle()函数在接收到用户输入后,也是首先调用本模块内的__analyse()函数进行关键词匹配,与main模块内不同的是,由于返回的结果除去标签还需要一部分属性值,譬如说天气查询的地点、时间等,所以定义了Tag便签对象,用来作为返回的标签数组的元素。
class Tag:
name = '' #标签名
props = {} #属性值
def __init__(self, name, props):
self.name = name
self.props = props
为了更加方便地修改定制,我增加了读取目录下配置文件config.ini获取默认值及查询接口API的功能,相关代码如下:
import configparser #可以通过 pip install 进行安装
import os
__config = configparser.ConfigParser()
__config.read(os.getcwd() + "/config.ini", encoding='utf-8')
__weather_api_url = __config.get("weather", "api") #天气查询API链接
__weather_api_key = __config.get("weather", "key") #天气查询API的个人**
__weather_default_city = __config.get("weather", "default_city") #在未指定查询城市时默认的查询城市
__temp_threshold = __config.get("weather", "temp_threshold") #温差提醒阈值
__wind_threshold = __config.get("weather", "wind_threshold") #风力提醒阈值
config.ini文件的内容如下:
[weather]
; 天气查询API
api=http://apis.juhe.cn/simpleWeather/query
; 天气查询默认城市
default_city=长沙
; 天气查询API**
key=***************
; 温差提醒阈值
temp_threshold=15
; 风力提醒阈值
wind_threshold=6
天气查询及新闻查询API使用来源于 [聚合数据] ,个人作为开发者注册认证之后每天可以免费调用100次,个人使用完全足够了,在右上角“个人中心” -> “数据中心” -> “我的接口”内可以搜索和申请接口,接口申请成功后会自动分配一个Key,之后每次调用时需要携带这个Key进行访问。
点击图二右侧“测试”按钮可以进入测试页面,网页将模拟真实调用的情形对请求参数以及返回数据进行展示,为了辅助接下来的代码展示,先展示一下某次查询链接以及返回的json数据:
http://apis.juhe.cn/simpleWeather/query?city=%E9%95%BF%E6%B2%99&key=************
// 注意此处的city参数必须为 utf8 urlencode 形式
{
"reason":"查询成功",
"result":{
"city":"长沙",
"realtime":{
"temperature":"14",
"humidity":"73",
"info":"阴",
"wid":"02",
"direct":"西南风",
"power":"2级",
"aqi":"55"
},
"future":[
{
"date":"2020-02-22",
"temperature":"11\/18℃",
"weather":"多云",
"wid":{
"day":"01",
"night":"01"
},
"direct":"北风转南风"
},
{
"date":"2020-02-23",
"temperature":"11\/22℃",
"weather":"晴转多云",
"wid":{
"day":"00",
"night":"01"
},
"direct":"南风"
},
{
"date":"2020-02-24",
"temperature":"15\/26℃",
"weather":"多云",
"wid":{
"day":"01",
"night":"01"
},
"direct":"南风"
},
{
"date":"2020-02-25",
"temperature":"17\/25℃",
"weather":"阵雨",
"wid":{
"day":"03",
"night":"03"
},
"direct":"南风"
},
{
"date":"2020-02-26",
"temperature":"12\/23℃",
"weather":"阵雨",
"wid":{
"day":"03",
"night":"03"
},
"direct":"北风"
}
]
},
"error_code":0
}
然后接下来就是weather.py模块中文本分析函数__analyse()的实现了,为了方便阅读,此处将支持从城市关键词数组city_words只展示一部分,所有支持查询的城市大家也可以在 [聚合数据] 上进行查询,该网站也提供了查询支持的城市的API:
def __analyse(raw_str):
analyse_result = []
weather_query_words = ['?', '怎么样', '如何']
weather_talk_words = ['不错', '喜欢', '不喜欢']
city_words = ['北京', '海淀', '朝阳', '顺义', '怀柔', '通州', '昌平', '延庆', '丰台', '石景山', '大兴', '房山', '密云', '门头沟', '平谷', '上海'] #支持查询的城市关键词
if any(word in raw_str for word in weather_query_words):
query_props = {'time': 'today', 'city': __weather_default_city} #如果未指定查询时间和城市则默认查询今天以及config.ini配置文件中城市的天气
today_words = ['今天', 'today'] #查询时间为“今天”的关键词
tomorrow_words = ['明天', 'tomorrow'] #查询时间为“明天”的关键词
twodays_words = ['今天和明天', '今明'] #查询时间为“今明两天”的关键词
if any(word in raw_str for word in today_words):
query_props['time'] = 'TODAY'
if any(word in raw_str for word in tomorrow_words):
query_props['time'] = 'TOMORROW'
if any(word in raw_str for word in twodays_words):
query_props['time'] = 'TWODAYS'
for city in city_words:
if city in raw_str:
query_props['city'] = city
break
analyse_result.append(Tag('weather-query', query_props))
if any(word in raw_str for word in weather_talk_words):
analyse_result.append(Tag('weather-talk', None))
return analyse_result
在__analyse()调用分析之后,接下来就是handle()函数进行处理的部分了:
def handle(raw_str):
analyse_result = __analyse(raw_str) #对主过程传入的raw_str进行关键词分析
for tag in analyse_result:
if tag.name == 'weather-talk':
print('今天的天气真的很不错哦')
if tag.name == 'weather-query':
weather_complete_api = __weather_api_url + "?city=" + quote(
tag.props["city"]) + "&key=" + __weather_api_key #调用quote函数将城市名转为 utf8 urlencode 形式并拼凑URL
weather_response = json.loads(requests.get(weather_complete_api).content) #调用天气查询API并获取返回JSON格式的结果
error_code = weather_response['error_code']
#处理异常状态
if (error_code == 207301 or error_code == 207302):
print("Alice不知道这座城市是哪里的呢……要不主人再换一个试试?")
elif (error_code == 207303):
print("哔——电波在传输过程中好像出了点问题哎……")
elif (error_code == 0): #错误码为0代表查询成功
print("嘀哩哩~数据接收完成,下面为主人播报天气——")
city = weather_response["result"]["city"] #返回的JSON内反馈的查询结果城市
info_today = weather_response["result"]["future"][0]["weather"] #今日的天气状况
temperature_today = weather_response["result"]["future"][0]["temperature"] #今天的最低气温和最高气温
temperature_today_min = temperature_today[:temperature_today.find('/')] #处理temperature_today获得今日最低气温
temperature_today_max = temperature_today[temperature_today.find('/') + 1:temperature_today.find('℃')] #处理temperature_today获得今日高气温
info_tomorrow = weather_response["result"]["future"][1]["weather"] #明日天气状况
temperature_tomorrow = weather_response["result"]["future"][1]["temperature"] #明日的最低气温和最高气温
temperature_tomorrow_min = temperature_tomorrow[:temperature_tomorrow.find('/')] #处理temperature_today获得明日最低气温
temperature_tomorrow_max = temperature_tomorrow[
temperature_tomorrow.find('/') + 1:temperature_tomorrow.find('℃')] #处理temperature_today获得明日高气温
wind_direction_today = weather_response["result"]["future"][0]["direct"] #今日风向
wind_direction_tomorrow = weather_response["result"]["future"][1]["direct"] #明日风向
wind_power_now = weather_response["result"]["realtime"]["power"] #当前时间风力大小
#对标签内各属性值进行匹配处理
if tag.props['time'] == 'TODAY':
response_text = "今天[" + city + "]天气" + info_today + ",气温 " + temperature_today + ",今天风向为" + wind_direction + ",当前风力" + wind_power_now + "。"
if (int(temperature_today_max) - int(temperature_today_min) >= int(__temp_threshold)):
response_text = response_text + "\n今天昼夜温差很大的说,主人要多加注意穿衣保暖哦~"
if (int(wind_power_now[:-1]) >= int(__wind_threshold)):
response_text = response_text + "\n另外,现在的风也有点大呢,主人一定当心不要被风吹跑啦,嘻嘻~"
print(response_text)
if tag.props['time'] == 'TOMORROW':
response_text = "明天[" + city + "]天气" + info_tomorrow + ",气温 " + temperature_tomorrow + ",风向为" + wind_direction_tomorrow + "。"
if (int(temperature_tomorrow_max) - int(temperature_tomorrow_min) >= int(__temp_threshold)):
response_text = response_text + "\n明天昼夜温差很大的说,主人要多加注意穿衣保暖哦~"
print(response_text)
if tag.props['time'] == 'TWODAYS':
response_text = "今天[" + city + "]天气" + info_today + ",气温 " + temperature_today + ",今天风向为" + wind_direction_today + ",当前风力" + wind_power_now + ";\n明天天气" + info_tomorrow + ",气温 " + temperature_tomorrow + ",风向为" + wind_direction_tomorrow + "。"
if (int(temperature_today_max) - int(temperature_today_min) >= int(__temp_threshold) or int(
temperature_tomorrow_max) - int(temperature_tomorrow_min) >= int(__temp_threshold)):
response_text = response_text + "\n今明两天昼夜温差很大的说,主人要多加注意穿衣保暖哦~"
if (int(wind_power_now[:-1]) >= int(__wind_threshold)):
response_text = response_text + "\n另外,现在的风也有点大呢,主人一定当心不要被风吹跑啦,嘻嘻~"
print(response_text)
else:
print("发生了Alice也不清楚的错误呢(๑Ő௰Ő๑)……要不主人帮忙看一下\n错误代码:[Weather-API:" + error_code + "]") #对于API的系统性错误直接抛出错误代码处理
由于使用了文字转码以及JSON处理等操作,weather.py需要导入的全部包如下:
from urllib.parse import quote
import configparser
import requests
import json
import os
最后看一下实战效果吧: