想转岗到爬虫工程师,由于面试机会太少,而boss直聘又无法海投,决定做一个boss直聘机器人来帮我找工作。
一、基本需求
- 一个爬虫程序爬取需要的工作信息,存入数据库。
- 聊天机器人每天上午10点向未聊天的boss打招呼。
- 如果有boss回复了,聊天机器人向boss发送简历(因为boss直聘设定必须双方说话过后才能发简历)。
二、涉及知识点
三、聊天机器人实现过程
1. 查看boss直聘网页版即时通讯实现方式。
查看聊天界面ws下有无建立websocket。
发现无websocket建立,考虑难道是ajax轮训不会这么low吧?
但是在与别人聊天时候,下面并无新增ajax请求。
打开抓包工具charles进行抓包查看。
发现是有ws建立的。改用chrome浏览器,
可以看到是有ws建立。(第一个坑,以后默认使用chrome浏览器)。
2.websocket连接建立方式及参数
查看连接建立的调用栈
将关联的js都下载到本地,使用webstorm格式化代码。再使用charles的map local功能将这三个文件代理。这样boss直聘使用的js就是本地格式化后的代码了(否则在使用查看调用时,所有代码都堆在2行,不可读)。
仔细查看调用堆栈确认是app.js的connection处传入的连接参数。
搜索关键词paho-mqtt了解mqtt的功能。所以boss直聘是使用的paho.mqtt这个第三方库。
在本地使用paho.mqtt第三方库做个连接的demo。参数通过修改app.js,在建立连接前打印所有的参数获得。
代码:
var hostname = 'ws.zhipin.com', //'192.168.1.2',
port = 443,
ssl = true,
topic = '/chatws';
client = new Paho.MQTT.Client(hostname, port, topic, "ws-CD090DC8307DE0AC");
//建立客户端实例
var options = {
password: "Xxxxxx",
userName: "Xxxxxxxx",
useSSL: ssl,
onSuccess: onConnect,
onFailure: function (e) {
console.log(e);
}
};
client.connect(options);
//发送消息
message = new Paho.MQTT.Message("hello");
message.destinationName = topic;
client.send(message);
运行后:
连接已经可以建立。但是在发送信息后,会报错并断开连接。因为数据格式有误。
3、通信数据格式
websocket连接已经可以建立,接下来就是查看boss直聘的通信数据格式了。
通过charles抓包,取到当我对一个boss发送“你好”时的数据:
3357 0004 6368 6174 0001 0801 1A4B 0A02 0800 1220 0800 121C 3531 6465 3762 6632 6234 3732 6139 3566 3148 525F 3039 5739 4556 6F7E 1801 20EE FBC8 FB92 2D28 B8B7 8EF2 922D 320C 0801 1001 1A06 E4BD A0E5 A5BD 58EE FBC8 FB92 2D
简单的16进制转字符串后获得:
3W�chat���K
�� ��51de7bf2b472a95f1HR_09W9EVo~�� �����-(����-2�����你好X�����-
这不是一个简单的json数据,查看数据发送时的调用堆栈
发现protobuf.js关键字,搜索并学习了protobuf。通过打印发送的数据。
获得数据:
[8, 1, 26, 75, 10, 2, 8, 0, 18, 32, 8, 0, 18, 28, 53, 49, 100, 101, 55, 98, 102, 50, 98, 52, 55, 50, 97, 57, 53, 102, 49, 72, 82, 95, 48, 57, 87, 57, 69, 86, 111, 126, 24, 1, 32, 233, 193, 149, 232, 145, 45, 40, 179, 253, 218, 222, 145, 45, 50, 12, 8, 1, 16, 1, 26, 6, 228, 189, 160, 229, 165, 189, 88, 233, 193, 149, 232, 145, 45]
写一个使用protobuf转化的demo。
var protobuf = require("protobufjs");
protobuf.load("proto.proto")
.then(function (root) {
var Protocol = root.lookupType("TechwolfChatProtocol");
// var data = Buffer.from([8,2,34,87,8,1,16,182,196,186,9,26,76,10,0,18,0,26,0,34,0,42,18,49,53,53,48,56,49,55,53,57,51,55,49,49,49,52,53,50,53,50,12,53,57,46,49,48,57,46,55,55,46,57,52,56,187,70,66,3,119,101,98,74,2,45,49,82,0,90,0,97,0,0,0,0,0,0,0,0,105,0,0,0,0,0,0,0,0,40,0]);
var data = Buffer.from([8, 1, 26, 75, 10, 2, 8, 0, 18, 32, 8, 0, 18, 28, 53, 49, 100, 101, 55, 98, 102, 50, 98, 52, 55, 50, 97, 57, 53, 102, 49, 72, 82, 95, 48, 57, 87, 57, 69, 86, 111, 126, 24, 1, 32, 233, 193, 149, 232, 145, 45, 40, 179, 253, 218, 222, 145, 45, 50, 12, 8, 1, 16, 1, 26, 6, 228, 189, 160, 229, 165, 189, 88, 233, 193, 149, 232, 145, 45]);
// Decode an Uint8Array (browser) or Buffer (node) to a message
var message = Protocol.decode(data);
console.log(message);
});
打印数据为:
TechwolfChatProtocol {
messages:
[ TechwolfMessage {
from: [TechwolfUser],
to: [TechwolfUser],
type: 1,
mid: [Long],
time: [Long],
body: [TechwolfMessageBody],
cmid: [Long] } ],
messageSync: [],
messageRead: [],
type: 1 }
可以解析数据。这样数据格式就找到了。
4、发送一条消息。
考虑node运行聊天机器人,但是 paho-mqtt.js不支持node方式,仅支持浏览器。而python同时支持paho-mqtt和protobuf。
所以使用python来结合完成。
但是在python中建立socket连接时总会报错:
给出了报错,但是没有具体信息。以为是tls版本太低,最后在mqtt源码报错处添加打印,获得信息
b"bytearray(b'http/1.1 403 forbidden\\r\\n')"
b"bytearray(b'date: wed, 27 feb 2019 10:03:06 gmt\\r\\n')"
b"bytearray(b'transfer-encoding: chunked\\r\\n')"
b"bytearray(b'connection: keep-alive\\r\\n')"
发现是在websocket握手时,http请求直接403了,考虑是否与cookies有关,在源码找到header设置的地方,添加cookies参数。
运行后可以正常连接。
结合protobuf后,最终代码:
chat = {
'type': 1,
'messages': [
{
'from': {'uid': 0},
'to': {'uid': 0, 'name': 'xxxxxxxxx~'},#name为boss的id
'type': 1,
'mid': 1550970085609,
'time': 1550950252211,
'body': {'type': 1, 'templateId': 1, 'text': '你好'},
'cmid': 1550970085609
}
]
}
chat_protocol = protobuf_json.json2pb(TechwolfChatProtocol(), chat)
hostname = 'ws.zhipin.com'
port = 443
clientId = '19833398'
timeout = 60
keepAlive = 100
topic = '/chatws'
# The callback for when the client receives a CONNACK response from the server.
def on_connect(client, userdata, flags, rc):
print("Connected with result code " + str(rc))
# Subscribing in on_connect() means that if we lose the connection and
# reconnect then subscriptions will be renewed.
client.subscribe(topic)
client.publish(topic,payload=chat_protocol.SerializeToString(),qos=0)
def on_disconnect(client, userdata, rc):
if rc != 0:
print("Unexpected disconnection.")
# The callback for when a PUBLISH message is received from the server.
def on_message(client, userdata, msg):
print('received message -----'+client)
protocol = TechwolfChatProtocol()
protocol.ParseFromString(msg.payload)
print(protocol)
client = mqtt.Client(client_id="ws-CD090DC8307DE0AC", clean_session=True,
transport="websockets")
client.username_pw_set("xxxxxxx", "xxxxx") # 参数分别boss为用户生的mqtt账号密码。
client.ws_set_options(path=topic)
client.tls_set()
client.on_connect = on_connect
client.on_message = on_message
client.on_disconnect = on_disconnect
client.connect(hostname, port, 60)
client.loop_forever()
运行后,手机端以及可以看到给boss发送了“你好”。
5.总结
接下来只需要结合爬虫,就可以完成最初的设想啦。
完成这个聊天机器学到了protobuf、mqtt、websocket相关的知识。
中间有卡壳的地方也很多。
但是一定要坚信,web端所有的内容,都是可爬的。完成的所有功能,都是可以模拟的。
ps:
文章写一半,就接到电话,有位大佬给推荐下工作了。
再次感谢这位大佬。