想轉(zhuǎn)崗到爬蟲工程師辕翰,由于面試機(jī)會(huì)太少夺衍,而boss直聘又無法海投,決定做一個(gè)boss直聘機(jī)器人來幫我找工作喜命。
一沟沙、基本需求
- 一個(gè)爬蟲程序爬取需要的工作信息,存入數(shù)據(jù)庫壁榕。
- 聊天機(jī)器人每天上午10點(diǎn)向未聊天的boss打招呼矛紫。
- 如果有boss回復(fù)了,聊天機(jī)器人向boss發(fā)送簡歷(因?yàn)閎oss直聘設(shè)定必須雙方說話過后才能發(fā)簡歷)牌里。
二颊咬、涉及知識(shí)點(diǎn)
三、聊天機(jī)器人實(shí)現(xiàn)過程
1. 查看boss直聘網(wǎng)頁版即時(shí)通訊實(shí)現(xiàn)方式牡辽。
查看聊天界面ws下有無建立websocket喳篇。
發(fā)現(xiàn)無websocket建立,考慮難道是ajax輪訓(xùn)不會(huì)這么low吧态辛?
但是在與別人聊天時(shí)候麸澜,下面并無新增ajax請求。
打開抓包工具charles進(jìn)行抓包查看奏黑。
發(fā)現(xiàn)是有ws建立的炊邦。改用chrome瀏覽器编矾,
可以看到是有ws建立。(第一個(gè)坑馁害,以后默認(rèn)使用chrome瀏覽器)窄俏。
2.websocket連接建立方式及參數(shù)
查看連接建立的調(diào)用棧
將關(guān)聯(lián)的js都下載到本地,使用webstorm格式化代碼碘菜。再使用charles的map local功能將這三個(gè)文件代理凹蜈。這樣boss直聘使用的js就是本地格式化后的代碼了(否則在使用查看調(diào)用時(shí),所有代碼都堆在2行炉媒,不可讀)踪区。
仔細(xì)查看調(diào)用堆棧確認(rèn)是app.js的connection處傳入的連接參數(shù)。
搜索關(guān)鍵詞paho-mqtt了解mqtt的功能吊骤。所以boss直聘是使用的paho.mqtt這個(gè)第三方庫缎岗。
在本地使用paho.mqtt第三方庫做個(gè)連接的demo。參數(shù)通過修改app.js白粉,在建立連接前打印所有的參數(shù)獲得传泊。
代碼:
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");
//建立客戶端實(shí)例
var options = {
password: "Xxxxxx",
userName: "Xxxxxxxx",
useSSL: ssl,
onSuccess: onConnect,
onFailure: function (e) {
console.log(e);
}
};
client.connect(options);
//發(fā)送消息
message = new Paho.MQTT.Message("hello");
message.destinationName = topic;
client.send(message);
運(yùn)行后:
連接已經(jīng)可以建立。但是在發(fā)送信息后鸭巴,會(huì)報(bào)錯(cuò)并斷開連接眷细。因?yàn)閿?shù)據(jù)格式有誤。
3鹃祖、通信數(shù)據(jù)格式
websocket連接已經(jīng)可以建立溪椎,接下來就是查看boss直聘的通信數(shù)據(jù)格式了。
通過charles抓包恬口,取到當(dāng)我對一個(gè)boss發(fā)送“你好”時(shí)的數(shù)據(jù):
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進(jìn)制轉(zhuǎn)字符串后獲得:
3W?chat???K
?? ??51de7bf2b472a95f1HR_09W9EVo~?? ?????-(????-2?????你好X?????-
這不是一個(gè)簡單的json數(shù)據(jù)校读,查看數(shù)據(jù)發(fā)送時(shí)的調(diào)用堆棧
發(fā)現(xiàn)protobuf.js關(guān)鍵字,搜索并學(xué)習(xí)了protobuf祖能。通過打印發(fā)送的數(shù)據(jù)歉秫。
獲得數(shù)據(jù):
[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]
寫一個(gè)使用protobuf轉(zhuǎn)化的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);
});
打印數(shù)據(jù)為:
TechwolfChatProtocol {
messages:
[ TechwolfMessage {
from: [TechwolfUser],
to: [TechwolfUser],
type: 1,
mid: [Long],
time: [Long],
body: [TechwolfMessageBody],
cmid: [Long] } ],
messageSync: [],
messageRead: [],
type: 1 }
可以解析數(shù)據(jù)养铸。這樣數(shù)據(jù)格式就找到了雁芙。
4、發(fā)送一條消息钞螟。
考慮node運(yùn)行聊天機(jī)器人兔甘,但是 paho-mqtt.js不支持node方式,僅支持瀏覽器鳞滨。而python同時(shí)支持paho-mqtt和protobuf裂明。
所以使用python來結(jié)合完成。
但是在python中建立socket連接時(shí)總會(huì)報(bào)錯(cuò):
給出了報(bào)錯(cuò),但是沒有具體信息闽晦。以為是tls版本太低,最后在mqtt源碼報(bào)錯(cuò)處添加打印提岔,獲得信息
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')"
發(fā)現(xiàn)是在websocket握手時(shí)仙蛉,http請求直接403了,考慮是否與cookies有關(guān)碱蒙,在源碼找到header設(shè)置的地方荠瘪,添加cookies參數(shù)。
運(yùn)行后可以正常連接赛惩。
結(jié)合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") # 參數(shù)分別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()
運(yùn)行后喷兼,手機(jī)端以及可以看到給boss發(fā)送了“你好”篮绰。
5.總結(jié)
接下來只需要結(jié)合爬蟲,就可以完成最初的設(shè)想啦季惯。
完成這個(gè)聊天機(jī)器學(xué)到了protobuf吠各、mqtt、websocket相關(guān)的知識(shí)勉抓。
中間有卡殼的地方也很多贾漏。
但是一定要堅(jiān)信,web端所有的內(nèi)容藕筋,都是可爬的纵散。完成的所有功能,都是可以模擬的隐圾。
ps:
文章寫一半伍掀,就接到電話,有位大佬給推薦下工作了翎承。
再次感謝這位大佬硕盹。