Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
譯自: https://opensource.com/article/18/6/mqtt
作者: Sean Dague
譯者: Andy Song
從開(kāi)源數(shù)據(jù)到開(kāi)源事件流,了解一下 MQTT 發(fā)布/訂閱(pubsub)線路協(xié)議。
去年 11 月我們購(gòu)買(mǎi)了一輛電動(dòng)汽車(chē),同時(shí)也引發(fā)了有趣的思考:我們應(yīng)該什么時(shí)候?yàn)殡妱?dòng)汽車(chē)充電?對(duì)于電動(dòng)汽車(chē)充電所用的電,我希望能夠?qū)?yīng)最小的二氧化碳排放,歸結(jié)為一個(gè)特定的問(wèn)題:對(duì)于任意給定時(shí)刻,每千瓦時(shí)對(duì)應(yīng)的二氧化碳排放量是多少,一天中什么時(shí)間這個(gè)值最低?
我住在紐約州,大約 80% 的電力消耗可以自給自足,主要來(lái)自天然氣、水壩(大部分來(lái)自于 尼亞加拉(Niagara)大瀑布)、核能發(fā)電,少部分來(lái)自風(fēng)力、太陽(yáng)能和其它化石燃料發(fā)電。非盈利性組織 紐約獨(dú)立電網(wǎng)運(yùn)營(yíng)商(New York Independent System Operator) (NYISO)負(fù)責(zé)整個(gè)系統(tǒng)的運(yùn)作,實(shí)現(xiàn)發(fā)電機(jī)組發(fā)電與用電之間的平衡,同時(shí)也是紐約路燈系統(tǒng)的監(jiān)管部門(mén)。
盡管沒(méi)有為公眾提供公開(kāi) API,NYISO 還是盡責(zé)提供了 不少公開(kāi)數(shù)據(jù) 供公眾使用。每隔 5 分鐘匯報(bào)全州各個(gè)發(fā)電機(jī)組消耗的燃料數(shù)據(jù)。數(shù)據(jù)以 CSV 文件的形式發(fā)布于公開(kāi)的檔案庫(kù)中,全天更新。如果你了解不同燃料對(duì)發(fā)電瓦數(shù)的貢獻(xiàn)比例,你可以比較準(zhǔn)確的估計(jì)任意時(shí)刻的二氧化碳排放情況。
在構(gòu)建收集處理公開(kāi)數(shù)據(jù)的工具時(shí),我們應(yīng)該時(shí)刻避免過(guò)度使用這些資源。相比將這些數(shù)據(jù)打包并發(fā)送給所有人,我們有更好的方案。我們可以創(chuàng)建一個(gè)低開(kāi)銷(xiāo)的 事件流(event stream),人們可以訂閱并第一時(shí)間得到消息。我們可以使用 MQTT 實(shí)現(xiàn)該方案。我的項(xiàng)目( ny-power.org )目標(biāo)是收錄到 Home Assistant 項(xiàng)目中;后者是一個(gè)開(kāi)源的 家庭自動(dòng)化(home automation)平臺(tái),擁有數(shù)十萬(wàn)用戶(hù)。如果所有用戶(hù)同時(shí)訪問(wèn) CSV 文件服務(wù)器,估計(jì) NYISO 不得不增加訪問(wèn)限制。
MQTT 是一個(gè) 發(fā)布訂閱線路協(xié)議(publish/subscription wire protocol),為小規(guī)模設(shè)備設(shè)計(jì)。發(fā)布訂閱系統(tǒng)工作原理類(lèi)似于消息總線。你將一條消息發(fā)布到一個(gè) 主題(topic)上,那么所有訂閱了該主題的客戶(hù)端都可以獲得該消息的一份拷貝。對(duì)于消息發(fā)送者而言,無(wú)需知道哪些人在訂閱消息;你只需將消息發(fā)布到一系列主題,并訂閱一些你感興趣的主題。就像參加了一場(chǎng)聚會(huì),你選取并加入感興趣的對(duì)話。
MQTT 能夠構(gòu)建極為高效的應(yīng)用??蛻?hù)端訂閱有限的幾個(gè)主題,也只收到它們感興趣的內(nèi)容。不僅節(jié)省了處理時(shí)間,還降低了網(wǎng)絡(luò)帶寬使用。
作為一個(gè)開(kāi)放標(biāo)準(zhǔn),MQTT 有很多開(kāi)源的客戶(hù)端和服務(wù)端實(shí)現(xiàn)。對(duì)于你能想到的每種編程語(yǔ)言,都有對(duì)應(yīng)的客戶(hù)端庫(kù);甚至有嵌入到 Arduino 的庫(kù),可以構(gòu)建傳感器網(wǎng)絡(luò)。服務(wù)端可供選擇的也很多,我的選擇是 Eclipse 項(xiàng)目提供的 Mosquitto 服務(wù)端,這是因?yàn)樗w積小、用 C 編寫(xiě),可以承載數(shù)以萬(wàn)計(jì)的訂閱者。
在過(guò)去二十年間,我們?yōu)檐浖?yīng)用設(shè)計(jì)了可靠且準(zhǔn)確的模型,用于解決服務(wù)遇到的問(wèn)題。我還有其它郵件嗎?當(dāng)前的天氣情況如何?我應(yīng)該此刻購(gòu)買(mǎi)這種產(chǎn)品嗎?在絕大多數(shù)情況下,這種 問(wèn)答式(ask/receive)的模型工作良好;但對(duì)于一個(gè)數(shù)據(jù)爆炸的世界,我們需要其它的模型。MQTT 的發(fā)布訂閱模型十分強(qiáng)大,可以將大量數(shù)據(jù)發(fā)送到系統(tǒng)中??蛻?hù)可以訂閱數(shù)據(jù)中的一小部分并在訂閱數(shù)據(jù)發(fā)布的第一時(shí)間收到更新。
MQTT 還有一些有趣的特性,其中之一是 遺囑(last-will-and-testament)消息,可以用于區(qū)分兩種不同的靜默,一種是沒(méi)有主題相關(guān)數(shù)據(jù)推送,另一種是你的數(shù)據(jù)接收器出現(xiàn)故障。MQTT 還包括 保留消息(retained message),當(dāng)客戶(hù)端初次連接時(shí)會(huì)提供相關(guān)主題的最后一條消息。這對(duì)那些更新緩慢的主題來(lái)說(shuō)很有必要。
我在 Home Assistant 項(xiàng)目開(kāi)發(fā)過(guò)程中,發(fā)現(xiàn)這種消息總線模型對(duì) 異構(gòu)系統(tǒng)(heterogeneous systems)尤為適合。如果你深入 物聯(lián)網(wǎng)(Internet of Things)領(lǐng)域,你會(huì)發(fā)現(xiàn) MQTT 無(wú)處不在。
NYSO 公布的 CSV 文件中有一個(gè)是實(shí)時(shí)的燃料混合使用情況。每 5 分鐘,NYSO 發(fā)布這 5 分鐘內(nèi)發(fā)電使用的燃料類(lèi)型和相應(yīng)的發(fā)電量(以兆瓦為單位)。
這個(gè) CSV 文件看起來(lái)像這樣:
表中唯一令人不解就是燃料類(lèi)別中的混合燃料。紐約的大多數(shù)天然氣工廠也通過(guò)燃燒其它類(lèi)型的化石燃料發(fā)電。在冬季寒潮到來(lái)之際,家庭供暖的優(yōu)先級(jí)高于發(fā)電;但這種情況出現(xiàn)的次數(shù)不多,(在我們計(jì)算中)可以將混合燃料類(lèi)型看作天然氣類(lèi)型。
CSV 文件全天更新。我編寫(xiě)了一個(gè)簡(jiǎn)單的數(shù)據(jù)泵,每隔 1 分鐘檢查是否有數(shù)據(jù)更新,并將新條目發(fā)布到 MQTT 服務(wù)器的一系列主題上,主題名稱(chēng)基本與 CSV 文件有一定的對(duì)應(yīng)關(guān)系。數(shù)據(jù)內(nèi)容被轉(zhuǎn)換為 JSON 對(duì)象,方便各種編程語(yǔ)言處理。
ny-power/upstream/fuel-mix/Hydro {"units": "MW", "value": 3229, "ts": "05/09/2018 00:05:00"}
ny-power/upstream/fuel-mix/Dual Fuel {"units": "MW", "value": 1400, "ts": "05/09/2018 00:05:00"}
ny-power/upstream/fuel-mix/Natural Gas {"units": "MW", "value": 2144, "ts": "05/09/2018 00:05:00"}
ny-power/upstream/fuel-mix/Other Fossil Fuels {"units": "MW", "value": 4, "ts": "05/09/2018 00:05:00"}
ny-power/upstream/fuel-mix/Wind {"units": "MW", "value": 41, "ts": "05/09/2018 00:05:00"}
ny-power/upstream/fuel-mix/Other Renewables {"units": "MW", "value": 226, "ts": "05/09/2018 00:05:00"}
ny-power/upstream/fuel-mix/Nuclear {"units": "MW", "value": 4114, "ts": "05/09/2018 00:05:00"}
這種直接的轉(zhuǎn)換是種不錯(cuò)的嘗試,可將公開(kāi)數(shù)據(jù)轉(zhuǎn)換為公開(kāi)事件。我們后續(xù)會(huì)繼續(xù)將數(shù)據(jù)轉(zhuǎn)換為二氧化碳排放強(qiáng)度,但這些原始數(shù)據(jù)還可被其它應(yīng)用使用,用于其它計(jì)算用途。
主題和 主題結(jié)構(gòu)(topic structure)是 MQTT 的一個(gè)主要特色。與其它標(biāo)準(zhǔn)的企業(yè)級(jí)消息總線不同,MQTT 的主題無(wú)需事先注冊(cè)。發(fā)送者可以憑空創(chuàng)建主題,唯一的限制是主題的長(zhǎng)度,不超過(guò) 220 字符。其中 / 字符有特殊含義,用于創(chuàng)建主題的層次結(jié)構(gòu)。我們即將看到,你可以訂閱這些層次中的一些分片。
基于開(kāi)箱即用的 Mosquitto,任何一個(gè)客戶(hù)端都可以向任何主題發(fā)布消息。在原型設(shè)計(jì)過(guò)程中,這種方式十分便利;但一旦部署到生產(chǎn)環(huán)境,你需要增加 訪問(wèn)控制列表(access control list)(ACL)只允許授權(quán)的應(yīng)用發(fā)布消息。例如,任何人都能以只讀的方式訪問(wèn)我的應(yīng)用的主題層級(jí),但只有那些具有特定 憑證(credentials)的客戶(hù)端可以發(fā)布內(nèi)容。
主題中不包含 自動(dòng)樣式(automatic schema),也沒(méi)有方法查找客戶(hù)端可以發(fā)布的全部主題。因此,對(duì)于那些從 MQTT 總線消費(fèi)數(shù)據(jù)的應(yīng)用,你需要讓其直接使用已知的主題和消息格式樣式。
那么應(yīng)該如何設(shè)計(jì)主題呢?最佳實(shí)踐包括使用應(yīng)用相關(guān)的根名稱(chēng),例如在我的應(yīng)用中使用 ny-power。接著,為提高訂閱效率,構(gòu)建足夠深的層次結(jié)構(gòu)。upstream 層次結(jié)構(gòu)包含了直接從數(shù)據(jù)源獲取的、不經(jīng)處理的原始數(shù)據(jù),而 fuel-mix 層次結(jié)構(gòu)包含特定類(lèi)型的數(shù)據(jù);我們后續(xù)還可以增加其它的層次結(jié)構(gòu)。
在 MQTT 中,訂閱僅僅是簡(jiǎn)單的字符串匹配。為提高處理效率,只允許如下兩種通配符:
為便于理解,下面給出幾個(gè)例子:
ny-power/# - 匹配 ny-power 應(yīng)用發(fā)布的全部主題
ny-power/upstream/# - 匹配全部原始數(shù)據(jù)的主題
ny-power/upstream/fuel-mix/+ - 匹配全部燃料類(lèi)型的主題
ny-power/+/+/Hydro - 匹配全部?jī)纱螌蛹?jí)之后為 Hydro 類(lèi)型的主題(即使不位于 upstream 層次結(jié)構(gòu)下)
類(lèi)似 ny-power/# 的大范圍訂閱適用于 低數(shù)據(jù)量(low-volume)的應(yīng)用,應(yīng)用從網(wǎng)絡(luò)獲取全部數(shù)據(jù)并處理。但對(duì) 高數(shù)據(jù)量(high-volume)應(yīng)用而言則是一個(gè)災(zāi)難,由于絕大多數(shù)消息并不會(huì)被使用,大部分的網(wǎng)絡(luò)帶寬被白白浪費(fèi)了。
在大數(shù)據(jù)量情況下,為確保性能,應(yīng)用需要使用恰當(dāng)?shù)闹黝}篩選(如 ny-power/+/+/Hydro)盡量準(zhǔn)確獲取業(yè)務(wù)所需的數(shù)據(jù)。
接下來(lái),應(yīng)用中的一切都依賴(lài)于已有的 MQTT 流并構(gòu)建新流。第一個(gè)額外的數(shù)據(jù)層用于計(jì)算發(fā)電對(duì)應(yīng)的二氧化碳排放。
利用 美國(guó)能源情報(bào)署(U.S. Energy Information Administration) 給出的 2016 年紐約各類(lèi)燃料發(fā)電及排放情況,我們可以給出各類(lèi)燃料的 平均排放率 ,單位為克/兆瓦時(shí)。
上述結(jié)果被封裝到一個(gè)專(zhuān)用的微服務(wù)中。該微服務(wù)訂閱 ny-power/upstream/fuel-mix/+,即數(shù)據(jù)泵中燃料組成情況的原始數(shù)據(jù),接著完成計(jì)算并將結(jié)果(單位為克/千瓦時(shí))發(fā)布到新的主題層次結(jié)構(gòu)上:
ny-power/computed/co2 {"units": "g / kWh", "value": 152.9486, "ts": "05/09/2018 00:05:00"}
接著,另一個(gè)服務(wù)會(huì)訂閱該主題層次結(jié)構(gòu)并將數(shù)據(jù)打包到 InfluxDB 進(jìn)程中;同時(shí),發(fā)布 24 小時(shí)內(nèi)的時(shí)間序列數(shù)據(jù)到 ny-power/archive/co2/24h 主題,這樣可以大大簡(jiǎn)化當(dāng)前變化數(shù)據(jù)的繪制。
這種層次結(jié)構(gòu)的主題模型效果不錯(cuò),可以將上述程序之間的邏輯解耦合。在復(fù)雜系統(tǒng)中,各個(gè)組件可能使用不同的編程語(yǔ)言,但這并不重要,因?yàn)榻粨Q格式都是 MQTT 消息,即主題和 JSON 格式的消息內(nèi)容。
為了更好的了解 MQTT 完成了什么工作,將其綁定到一個(gè)消息總線并查看消息流是個(gè)不錯(cuò)的方法。mosquitto-clients 包中的 mosquitto_sub 可以讓我們輕松實(shí)現(xiàn)該目標(biāo)。
安裝程序后,你需要提供服務(wù)器名稱(chēng)以及你要訂閱的主題。如果有需要,使用參數(shù) -v 可以讓你看到有新消息發(fā)布的那些主題;否則,你只能看到主題內(nèi)的消息數(shù)據(jù)。
mosquitto_sub -h mqtt.ny-power.org -t ny-power/# -v
只要我編寫(xiě)或調(diào)試 MQTT 應(yīng)用,我總會(huì)在一個(gè)終端中運(yùn)行 mosquitto_sub。
到目前為止,我們已經(jīng)有提供公開(kāi)事件流的應(yīng)用,可以用微服務(wù)或命令行工具訪問(wèn)該應(yīng)用。但考慮到互聯(lián)網(wǎng)仍占據(jù)主導(dǎo)地位,因此讓用戶(hù)可以從瀏覽器直接獲取事件流是很重要。
MQTT 的設(shè)計(jì)者已經(jīng)考慮到了這一點(diǎn)。協(xié)議標(biāo)準(zhǔn)支持三種不同的傳輸協(xié)議: TCP 、 UDP 和 WebSockets 。主流瀏覽器都支持 WebSockets,可以維持持久連接,用于實(shí)時(shí)應(yīng)用。
Eclipse 項(xiàng)目提供了 MQTT 的一個(gè) JavaScript 實(shí)現(xiàn),叫做 Paho ,可包含在你的應(yīng)用中。工作模式為與服務(wù)器建立連接、建立一些訂閱,然后根據(jù)接收到的消息進(jìn)行響應(yīng)。
// ny-power web console application
var client = new Paho.MQTT.Client(mqttHost, Number("80"), "client-" + Math.random());
// set callback handlers
client.onMessageArrived = onMessageArrived;
// connect the client
client.reconnect = true;
client.connect({onSuccess: onConnect});
// called when the client connects
function onConnect() {
// Once a connection has been made, make a subscription and send a message.
console.log("onConnect");
client.subscribe("ny-power/computed/co2");
client.subscribe("ny-power/archive/co2/24h");
client.subscribe("ny-power/upstream/fuel-mix/#");
}
// called when a message arrives
function onMessageArrived(message) {
console.log("onMessageArrived:"+message.destinationName + message.payloadString);
if (message.destinationName == "ny-power/computed/co2") {
var data = JSON.parse(message.payloadString);
$("#co2-per-kwh").html(Math.round(data.value));
$("#co2-units").html(data.units);
$("#co2-updated").html(data.ts);
}
if (message.destinationName.startsWith("ny-power/upstream/fuel-mix")) {
fuel_mix_graph(message);
}
if (message.destinationName == "ny-power/archive/co2/24h") {
var data = JSON.parse(message.payloadString);
var plot = [
{
x: data.ts,
y: data.values,
type: 'scatter'
}
];
var layout = {
yaxis: {
title: "g CO2 / kWh",
}
};
Plotly.newPlot('co2_graph', plot, layout);
}
上述應(yīng)用訂閱了不少主題,因?yàn)槲覀儗⒁尸F(xiàn)若干種不同類(lèi)型的數(shù)據(jù);其中 ny-power/computed/co2 主題為我們提供當(dāng)前二氧化碳排放的參考值。一旦收到該主題的新消息,網(wǎng)站上的相應(yīng)內(nèi)容會(huì)被相應(yīng)替換。
ny-power.org 網(wǎng)站提供的 NYISO 二氧化碳排放圖。
ny-power/archive/co2/24h 主題提供了時(shí)間序列數(shù)據(jù),用于為 Plotly 線表提供數(shù)據(jù)。ny-power/upstream/fuel-mix 主題提供當(dāng)前燃料組成情況,為漂亮的柱狀圖提供數(shù)據(jù)。
ny-power.org 網(wǎng)站提供的燃料組成情況。
這是一個(gè)動(dòng)態(tài)網(wǎng)站,數(shù)據(jù)不從服務(wù)器拉取,而是結(jié)合 MQTT 消息總線,監(jiān)聽(tīng)對(duì)外開(kāi)放的 WebSocket。就像數(shù)據(jù)泵和打包器程序那樣,網(wǎng)站頁(yè)面也是一個(gè)發(fā)布訂閱客戶(hù)端,只不過(guò)是在你的瀏覽器中執(zhí)行,而不是在公有云的微服務(wù)上。
你可以在 http://ny-power.org 站點(diǎn)點(diǎn)看到動(dòng)態(tài)變更,包括圖像和可以看到消息到達(dá)的實(shí)時(shí) MQTT 終端。
ny-power.org 應(yīng)用的完整內(nèi)容開(kāi)源在 GitHub 中。你也可以查閱 架構(gòu)簡(jiǎn)介 ,學(xué)習(xí)如何使用 Helm 部署一系列 Kubernetes 微服務(wù)構(gòu)建應(yīng)用。另一個(gè)有趣的 MQTT 示例使用 MQTT 和 OpenWhisk 進(jìn)行實(shí)時(shí)文本消息翻譯, 代碼模式(code pattern)參考 鏈接 。
MQTT 被廣泛應(yīng)用于物聯(lián)網(wǎng)領(lǐng)域,更多關(guān)于 MQTT 用途的例子可以在 Home Assistant 項(xiàng)目中找到。
如果你希望深入了解協(xié)議內(nèi)容,可以從 mqtt.org 獲得該公開(kāi)標(biāo)準(zhǔn)的全部細(xì)節(jié)。
想了解更多,可以參加 Sean Dague 在 OSCON 上的演講,主題為 將 MQTT 加入到你的工具箱 ,會(huì)議將于 7 月 16-19 日在奧爾良州波特蘭舉辦。
via: https://opensource.com/article/18/6/mqtt
作者: Sean Dague 選題: lujun9972 譯者: pinewall 校對(duì): wxy
本文由 LCTT 原創(chuàng)編譯, Linux中國(guó) 榮譽(yù)推出
or...in語(yǔ)句解析
<script>
var json={a: 12, b: 5};
for(var i in json)
{
alert(i+'='+json[i]);
}
</script>
eval() 函數(shù)可計(jì)算某個(gè)字符串, 并執(zhí)行其中的的 JavaScript 代碼。
服務(wù)器端腳本代碼:
<?php
$row=array('username'=>'lisi','password'=>'222222');
echo json_encode($row);
/*$data=array(
array('name'=>'zhangsan','age'=>18),
array('name'=>'lisi','age'=>30)
);
echo json_encode($data);
*/
?>
var json=eval('('+value+')'); 主要是針對(duì)關(guān)聯(lián)數(shù)組
返回:"{name:'zhangsan',age:18}"
訪問(wèn)方式:json.username+json.password
var json=eval(value); 主要是針對(duì)索引數(shù)組
返回:"[{name:'zhangsan',age:18},{name:'lisi',age:20}]"
訪問(wèn)方式:json[0].name+json[0].age
注意:索引數(shù)組的解析也可以采用 var json=eval(value);
<script language="javascript" src="public.js"></script>
<script>
var xhr=createxhr(); //創(chuàng)建ajax對(duì)象, 代碼見(jiàn)ajax | ajax封裝GET和POST
xhr.open('post','demo05.php');
xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');
xhr.onreadystatechange=function(){
if(xhr.readyState==4 && xhr.status==200){
var value=xhr.responseText; //返回的是字符串
//1)
var json=eval('('+value+')'); //返回是json對(duì)象
alert(json.username+json.password);
//2)
//var json=eval(value); //返回是json數(shù)組對(duì)象
//alert(json[1].name+json[1].age);
}
};
xhr.send(null);
</script>
返回:"{name:’zhangsan’,age:18}"
解析格式:eval('('+value+')');
返回:"[{name:'zhangsan',age:18},{name:'lisi',age:20}]"
解析格式:eval(value);
也可以采用eval('('+value+')');
實(shí)例1:
<html>
<head>
<title>新建網(wǎng)頁(yè)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="description" content="" />
<meta name="keywords" content="" />
<script type="text/javascript">
function f1(){
//ajax去服務(wù)器獲得json信息
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState==4 && xhr.status==200){
//alert(xhr.responseText);//字符串{"north":"wolf","helan":"pig","germany":"dog"}
var info = eval('('+xhr.responseText+')');
//也可寫(xiě)成:eval("var info="+xhr.responseText);
document.write(info.north);
document.write(info.helan);
document.write(info.germany);
}
}
xhr.open('get','03.php');
xhr.send(null);
}
//javascript把一個(gè)字符串變?yōu)閷?duì)象
//var a = '{"north":"wolf","helan":"pig","germany":"dog"}';
//eval(參數(shù)字符串)
//eval("var obj="+a);//eval('var obj={"north":"wolf","helan":"pig","germany":"dog"}');
//document.write(obj);//訪問(wèn)對(duì)象
</script>
</head>
<body>
<h2>靜態(tài)網(wǎng)站,javascript對(duì)json的接收處理</h2>
<input type="button" value="觸發(fā)" onclick="f1()" />
</body>
</html>
<?php
//對(duì)外提供json信息
header("Cache-Control:no-cache,must-revalidate");
$animal = array('north'=>'wolf','helan'=>'pig','germany'=>'dog');
echo json_encode($animal); //{"north":"wolf","helan":"pig","germany":"dog"}
?>
在javascript解析{"north":"wolf","helan":"pig","germany":"dog"}
采用:var info = eval('('+xhr.responseText+')'); 語(yǔ)法
也可寫(xiě)成:eval("var info="+xhr.responseText);
實(shí)例2:
<html>
<head>
<title>新建網(wǎng)頁(yè)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="description" content="" />
<meta name="keywords" content="" />
<script type="text/javascript">
function f1(){
//ajax去服務(wù)器獲得json信息
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState==4 && xhr.status==200){
//alert(xhr.responseText);//數(shù)組 ["wolf","pig","dog"]
var info = eval(xhr.responseText);
document.write(info[0]+info[1]+info[2]);
}
}
xhr.open('get','03.php');
xhr.send(null);
}
</script>
</head>
<body>
<h2>靜態(tài)網(wǎng)站,javascript對(duì)json的接收處理</h2>
<input type="button" value="觸發(fā)" onclick="f1()" />
</body>
</html>
<?php
//對(duì)外提供json信息
header("Cache-Control:no-cache,must-revalidate");
$animal = array('wolf','pig','dog');
echo json_encode($animal); //["wolf","pig","dog"]
?>
在javascript解析["wolf","pig","dog"]時(shí)
采用:var info = eval(xhr.responseText);語(yǔ)法
實(shí)例3:
<html>
<head>
<title>新建網(wǎng)頁(yè)</title>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<meta name="description" content="" />
<meta name="keywords" content="" />
<script type="text/javascript">
function f1(){
//ajax去服務(wù)器獲得json信息
var xhr = new XMLHttpRequest();
xhr.onreadystatechange = function(){
if(xhr.readyState==4 && xhr.status==200){
var s = "";
//alert(xhr.responseText);//數(shù)組對(duì)象[{"id":1,"name":"xutao","sex":"\u7537","age":30},...]
var info = eval(xhr.responseText);
for(var i=0;i<info.length;i++){
s += info[i].id + "--" + info[i].name + "--" + info[i].sex + "--" + info[i].age +"<br />";
}
document.getElementById("user").innerHTML = s;
}
}
xhr.open('get','info.php');
xhr.send(null);
}
</script>
</head>
<body>
<h2>靜態(tài)網(wǎng)站,javascript對(duì)json的接收處理</h2>
<input type="button" value="觸發(fā)" onclick="f1()" />
<div id="user"></div>
</body>
</html>
<?php
$info = array(
array("id"=>1,"name"=>"zhangsan","sex"=>"男","age"=>30),
array("id"=>2,"name"=>"lisi","sex"=>"女","age"=>27),
array("id"=>3,"name"=>"wangwu","sex"=>"男","age"=>6)
);
echo json_encode($info);
/* [{"id":1,"name":"zhangsan","sex":"\u7537","age":30},
{"id":2,"name":"lisi","sex":"\u5973","age":27},
{"id":3,"name":"wuwang","sex":"\u7537","age":6}] */
?>
在javascript解析[{"id":1,"name":"zhangsan","sex":"\u7537","age":30},
{"id":2,"name":"lisi","sex":"\u5973","age":27},
{"id":3,"name":"wuwang","sex":"\u7537","age":6}]時(shí)
采用:var info = eval(xhr.responseText);語(yǔ)法
從數(shù)據(jù)庫(kù)讀取出來(lái)的二維數(shù)組,通過(guò)json_encode()編碼后, 在javascript進(jìn)行解析時(shí)也是采用上述語(yǔ)法。
章開(kāi)始之前,我們先來(lái)看一個(gè)常見(jiàn)的問(wèn)題:
接到一個(gè)任務(wù),需要抓取某個(gè)網(wǎng)站上的數(shù)據(jù)內(nèi)容,網(wǎng)頁(yè)上需要輸入搜索關(guān)鍵詞,然后點(diǎn)擊搜索按鈕,等待頁(yè)面加載完畢,獲取網(wǎng)頁(yè)上的搜索結(jié)果,而每一個(gè)搜索結(jié)果項(xiàng),都需要點(diǎn)擊展開(kāi)才能查看到具體內(nèi)容。
對(duì)于該問(wèn)題,我們可以從網(wǎng)上找到一些解決問(wèn)題的途徑,但是大都不是很全面。這里小編對(duì)所有可能出現(xiàn)的問(wèn)題做了一次調(diào)研,并匯總成如下的解決方案,希望對(duì)大家有所幫助。
首先,我們先來(lái)匯總一下python生態(tài)下爬取網(wǎng)站數(shù)據(jù)需要用到的工具。
1,selenium chromedriver, 這個(gè)標(biāo)準(zhǔn)的自動(dòng)化工具,可以幫助我們從后臺(tái)操控google瀏覽器,并能模擬鼠標(biāo)移動(dòng)和點(diǎn)擊事件,配合x(chóng)path快速定位網(wǎng)頁(yè)元素,從根本上解放了我們的雙手。
2,ajax-hook, 這個(gè)是用來(lái)攔截xhr的鉤子,可以快速獲取網(wǎng)站服務(wù)器的響應(yīng)內(nèi)容,而無(wú)需我們被動(dòng)地從網(wǎng)頁(yè)上來(lái)獲取。
明確了我們處理任務(wù)的可用資源,下一步就是各種踩坑了。
這里小編先列舉一下,我所遇到的坑:
1,selenium的webdriver加載網(wǎng)頁(yè),怎么判斷頁(yè)面是否加載完全,使用
wait = WebDriverWait(self.driver, timeout=10)
wait.until(lambda my_driver: my_driver.execute_script('return document.readyState') == 'complete')
發(fā)現(xiàn)這種方案不可行,遇到動(dòng)態(tài)網(wǎng)頁(yè)異步加載就gameover,不通用,可行的方案應(yīng)該wait for webelement loaded,也就是等待你所關(guān)注的網(wǎng)頁(yè)元素加載完畢才行。
2,xpath的相對(duì)路徑查找,是帶了position的向下查找,也就是說(shuō)每次調(diào)用xpath find, 其position就往下移動(dòng)一次。所以如果用xpath在同一個(gè)流程調(diào)用多次只有第一次可以找到元素,后面都是失敗的。所以對(duì)于需要反復(fù)查找的元素,最好要用絕對(duì)路徑,獲取xpath絕對(duì)路徑很簡(jiǎn)單
打開(kāi)瀏覽器的開(kāi)發(fā)者工具,點(diǎn)擊左上角的小箭頭,移動(dòng)鼠標(biāo)到具體某個(gè)按鈕或者輸入框點(diǎn)擊一下就可以定位到html的標(biāo)簽源代碼位置,鼠標(biāo)右鍵,Copy xpath即可
3,怎么截獲并過(guò)濾xhr請(qǐng)求,將響應(yīng)內(nèi)容保存下來(lái),而不會(huì)造成內(nèi)存暴漲。
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.support.ui import WebDriverWait
chrome_options = Options()
chrome_options.add_argument("--disable-extensions")
chrome_options.add_argument("--disable-gpu")
chrome_options.add_argument("--headless")
chrome_options.add_argument("--no-sandbox")
prefs = {
'download.default_directory': os.getenv('OS_LOG_PATH')
}
chrome_options.add_experimental_option('prefs', prefs)
capabilities = webdriver.DesiredCapabilities().CHROME
capabilities['acceptSslCerts'] = True
driver = webdriver.Chrome(options=chrome_options, desired_capabilities=capabilities)
我們先來(lái)初始化一下webdriver,網(wǎng)上有很多資料,但是都不是很全面,上面是我整理的初始化內(nèi)容,大家按照這個(gè)來(lái),就不會(huì)有問(wèn)題。
截獲https/http響應(yīng)body內(nèi)容,網(wǎng)上大致有三種實(shí)現(xiàn)思路:
1,開(kāi)啟browser, performance log,再根據(jù)requestId,調(diào)用chrome-devtools protocal Network.getResponseBody, 獲取響應(yīng)body。但是問(wèn)題來(lái)了,我們需要大量采集response,而performance log都是存在內(nèi)存中,還需要我們?nèi)藶榍謇恚⑶襭erformance log采集的信息很雜,沒(méi)有過(guò)濾的入口提供給開(kāi)發(fā)者。而且最煩的是,還需要去解析log,需要為了查詢(xún)一個(gè)repsonse body,有時(shí)候需要人為過(guò)濾N行日志。。。。。(直接無(wú)語(yǔ)凝噎)
2,使用代理,網(wǎng)上傳的最多的是browsermob-proxy,但是這個(gè)玩意很無(wú)語(yǔ)的是只提供response的統(tǒng)計(jì)信息,不返回實(shí)質(zhì)性的response body內(nèi)容,配置各種參數(shù)都不會(huì)返回content.text字段,氣得跳腳,去github上看源碼發(fā)現(xiàn),text被下放了,當(dāng)前穩(wěn)定版本都會(huì)省略這個(gè)參數(shù)。所以你辛辛苦苦安裝proxy,調(diào)整ssl參數(shù),在python中配置各種proxy har,設(shè)置captureContent,結(jié)果卻得不到response body,不自覺(jué)就口吐芬芳了。所以想要其返回text字段,你需要改它的源碼,改源碼不難,但是很煩。
3,使用ajax-hook, 這個(gè)方案的思路是:在源網(wǎng)頁(yè)加載之前,實(shí)現(xiàn)一個(gè)XMLHttpRequest的代理對(duì)象,然后覆蓋全局的XMLHttpRequest,這樣一但上層調(diào)用 new XMLHttpRequest這樣的代碼時(shí),其實(shí)創(chuàng)建的是Ajax-hook的代理對(duì)象實(shí)例
我們先新建一個(gè)hook.js腳本
!function(t,e){for(var n in e)t[n]=e[n]}(window,function(t){function e(r){if(n[r])return n[r].exports;var o=n[r]={i:r,l:!1,exports:{}};return t[r].call(o.exports,o,o.exports,e),o.l=!0,o.exports}var n={};return e.m=t,e.c=n,e.i=function(t){return t},e.d=function(t,n,r){e.o(t,n)||Object.defineProperty(t,n,{configurable:!1,enumerable:!0,get:r})},e.n=function(t){var n=t&&t.__esModule?function(){return t.default}:function(){return t};return e.d(n,"a",n),n},e.o=function(t,e){return Object.prototype.hasOwnProperty.call(t,e)},e.p="",e(e.s=3)}([function(t,e,n){"use strict";function r(t,e){var n={};for(var r in t)n[r]=t[r];return n.target=n.currentTarget=e,n}function o(t){function e(e){return function(){var n=this.hasOwnProperty(e+"_")?this[e+"_"]:this.xhr[e],r=(t[e]||{}).getter;return r&&r(n,this)||n}}function n(e){return function(n){var o=this.xhr,i=this,u=t[e];if("on"===e.substring(0,2))i[e+"_"]=n,o[e]=function(u){u=r(u,i),t[e]&&t[e].call(i,o,u)||n.call(i,u)};else{var s=(u||{}).setter;n=s&&s(n,i)||n,this[e+"_"]=n;try{o[e]=n}catch(t){}}}}function o(e){return function(){var n=[].slice.call(arguments);if(t[e]){var r=t[e].call(this,n,this.xhr);if(r)return r}return this.xhr[e].apply(this.xhr,n)}}return window[s]=window[s]||XMLHttpRequest,XMLHttpRequest=function(){var t=new window[s];for(var r in t){var i="";try{i=u(t[r])}catch(t){}"function"===i?this[r]=o(r):Object.defineProperty(this,r,{get:e(r),set:n(r),enumerable:!0})}var a=this;t.getProxy=function(){return a},this.xhr=t},window[s]}function i(){window[s]&&(XMLHttpRequest=window[s]),window[s]=void 0}Object.defineProperty(e,"__esModule",{value:!0});var u="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t};e.configEvent=r,e.hook=o,e.unHook=i;var s="_rxhr"},function(t,e,n){"use strict";function r(t){if(h)throw"Proxy already exists";return h=new f(t)}function o(){h=null,(0,d.unHook)()}function i(t){return t.replace(/^\s+|\s+$/g,"")}function u(t){return t.watcher||(t.watcher=document.createElement("a"))}function s(t,e){var n=t.getProxy(),r="on"+e+"_",o=(0,d.configEvent)({type:e},n);n[r]&&n[r](o);var i;"function"==typeof Event?i=new Event(e,{bubbles:!1}):(i=document.createEvent("Event"),i.initEvent(e,!1,!0)),u(t).dispatchEvent(i)}function a(t){this.xhr=t,this.xhrProxy=t.getProxy()}function c(t){function e(t){a.call(this,t)}return e[b]=Object.create(a[b]),e[b].next=t,e}function f(t){function e(t,e){var n=new P(t);if(!f)return n.resolve();var r={response:e.response,status:e.status,statusText:e.statusText,config:t.config,headers:t.resHeader||t.getAllResponseHeaders().split("\r\n").reduce(function(t,e){if(""===e)return t;var n=e.split(":");return t[n.shift()]=i(n.join(":")),t},{})};f(r,n)}function n(t,e,n){var r=new H(t),o={config:t.config,error:n};h?h(o,r):r.next(o)}function r(){return!0}function o(t,e){return n(t,this,e),!0}function a(t,n){return 4===t.readyState&&0!==t.status?e(t,n):4!==t.readyState&&s(t,w),!0}var c=t.onRequest,f=t.onResponse,h=t.onError;return(0,d.hook)({onload:r,onloadend:r,onerror:o,ontimeout:o,onabort:o,onreadystatechange:function(t){return a(t,this)},open:function(t,e){var r=this,o=e.config={headers:{}};o.method=t[0],o.url=t[1],o.async=t[2],o.user=t[3],o.password=t[4],o.xhr=e;var i="on"+w;e[i]||(e[i]=function(){return a(e,r)});var u=function(t){n(e,r,(0,d.configEvent)(t,r))};if([x,y,g].forEach(function(t){var n="on"+t;e[n]||(e[n]=u)}),c)return!0},send:function(t,e){var n=e.config;if(n.withCredentials=e.withCredentials,n.body=t[0],c){var r=function(){c(n,new m(e))};return!1===n.async?r():setTimeout(r),!0}},setRequestHeader:function(t,e){return e.config.headers[t[0].toLowerCase()]=t[1],!0},addEventListener:function(t,e){var n=this;if(-1!==l.indexOf(t[0])){var r=t[1];return u(e).addEventListener(t[0],function(e){var o=(0,d.configEvent)(e,n);o.type=t[0],o.isTrusted=!0,r.call(n,o)}),!0}},getAllResponseHeaders:function(t,e){var n=e.resHeader;if(n){var r="";for(var o in n)r+=o+": "+n[o]+"\r\n";return r}},getResponseHeader:function(t,e){var n=e.resHeader;if(n)return n[(t[0]||"").toLowerCase()]}})}Object.defineProperty(e,"__esModule",{value:!0}),e.proxy=r,e.unProxy=o;var h,d=n(0),l=["load","loadend","timeout","error","readystatechange","abort"],v=l[0],p=l[1],y=l[2],x=l[3],w=l[4],g=l[5],b="prototype";a[b]=Object.create({resolve:function(t){var e=this.xhrProxy,n=this.xhr;e.readyState=4,n.resHeader=t.headers,e.response=e.responseText=t.response,e.statusText=t.statusText,e.status=t.status,s(n,w),s(n,v),s(n,p)},reject:function(t){this.xhrProxy.status=0,s(this.xhr,t.type),s(this.xhr,p)}});var m=c(function(t){var e=this.xhr;t=t||e.config,e.withCredentials=t.withCredentials,e.open(t.method,t.url,!1!==t.async,t.user,t.password);for(var n in t.headers)e.setRequestHeader(n,t.headers[n]);e.send(t.body)}),P=c(function(t){this.resolve(t)}),H=c(function(t){this.reject(t)})},,function(t,e,n){"use strict";Object.defineProperty(e,"__esModule",{value:!0}),e.ah=void 0;var r=n(0),o=n(1);e.ah={proxy:o.proxy,unProxy:o.unProxy,hook:r.hook,unHook:r.unHook}}]));
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.axios=t():e.axios=t()}(this,function(){return function(e){function t(r){if(n[r])return n[r].exports;var o=n[r]={exports:{},id:r,loaded:!1};return e[r].call(o.exports,o,o.exports,t),o.loaded=!0,o.exports}var n={};return t.m=e,t.c=n,t.p="",t(0)}([function(e,t,n){e.exports=n(1)},function(e,t,n){"use strict";function r(e){var t=new s(e),n=i(s.prototype.request,t);return o.extend(n,s.prototype,t),o.extend(n,t),n}var o=n(2),i=n(3),s=n(4),a=n(22),u=n(10),c=r(u);c.Axios=s,c.create=function(e){return r(a(c.defaults,e))},c.Cancel=n(23),c.CancelToken=n(24),c.isCancel=n(9),c.all=function(e){return Promise.all(e)},c.spread=n(25),e.exports=c,e.exports.default=c},function(e,t,n){"use strict";function r(e){return"[object Array]"===j.call(e)}function o(e){return"undefined"==typeof e}function i(e){return null!==e&&!o(e)&&null!==e.constructor&&!o(e.constructor)&&"function"==typeof e.constructor.isBuffer&&e.constructor.isBuffer(e)}function s(e){return"[object ArrayBuffer]"===j.call(e)}function a(e){return"undefined"!=typeof FormData&&e instanceof FormData}function u(e){var t;return t="undefined"!=typeof ArrayBuffer&&ArrayBuffer.isView?ArrayBuffer.isView(e):e&&e.buffer&&e.buffer instanceof ArrayBuffer}function c(e){return"string"==typeof e}function f(e){return"number"==typeof e}function p(e){return null!==e&&"object"==typeof e}function d(e){return"[object Date]"===j.call(e)}function l(e){return"[object File]"===j.call(e)}function h(e){return"[object Blob]"===j.call(e)}function m(e){return"[object Function]"===j.call(e)}function y(e){return p(e)&&m(e.pipe)}function g(e){return"undefined"!=typeof URLSearchParams&&e instanceof URLSearchParams}function v(e){return e.replace(/^\s*/,"").replace(/\s*$/,"")}function x(){return("undefined"==typeof navigator||"ReactNative"!==navigator.product&&"NativeScript"!==navigator.product&&"NS"!==navigator.product)&&("undefined"!=typeof window&&"undefined"!=typeof document)}function w(e,t){if(null!==e&&"undefined"!=typeof e)if("object"!=typeof e&&(e=[e]),r(e))for(var n=0,o=e.length;n<o;n++)t.call(null,e[n],n,e);else for(var i in e)Object.prototype.hasOwnProperty.call(e,i)&&t.call(null,e[i],i,e)}function b(){function e(e,n){"object"==typeof t[n]&&"object"==typeof e?t[n]=b(t[n],e):t[n]=e}for(var t={},n=0,r=arguments.length;n<r;n++)w(arguments[n],e);return t}function E(){function e(e,n){"object"==typeof t[n]&&"object"==typeof e?t[n]=E(t[n],e):"object"==typeof e?t[n]=E({},e):t[n]=e}for(var t={},n=0,r=arguments.length;n<r;n++)w(arguments[n],e);return t}function S(e,t,n){return w(t,function(t,r){n&&"function"==typeof t?e[r]=C(t,n):e[r]=t}),e}var C=n(3),j=Object.prototype.toString;e.exports={isArray:r,isArrayBuffer:s,isBuffer:i,isFormData:a,isArrayBufferView:u,isString:c,isNumber:f,isObject:p,isUndefined:o,isDate:d,isFile:l,isBlob:h,isFunction:m,isStream:y,isURLSearchParams:g,isStandardBrowserEnv:x,forEach:w,merge:b,deepMerge:E,extend:S,trim:v}},function(e,t){"use strict";e.exports=function(e,t){return function(){for(var n=new Array(arguments.length),r=0;r<n.length;r++)n[r]=arguments[r];return e.apply(t,n)}}},function(e,t,n){"use strict";function r(e){this.defaults=e,this.interceptors={request:new s,response:new s}}var o=n(2),i=n(5),s=n(6),a=n(7),u=n(22);r.prototype.request=function(e){"string"==typeof e?(e=arguments[1]||{},e.url=arguments[0]):e=e||{},e=u(this.defaults,e),e.method?e.method=e.method.toLowerCase():this.defaults.method?e.method=this.defaults.method.toLowerCase():e.method="get";var t=[a,void 0],n=Promise.resolve(e);for(this.interceptors.request.forEach(function(e){t.unshift(e.fulfilled,e.rejected)}),this.interceptors.response.forEach(function(e){t.push(e.fulfilled,e.rejected)});t.length;)n=n.then(t.shift(),t.shift());return n},r.prototype.getUri=function(e){return e=u(this.defaults,e),i(e.url,e.params,e.paramsSerializer).replace(/^\?/,"")},o.forEach(["delete","get","head","options"],function(e){r.prototype[e]=function(t,n){return this.request(o.merge(n||{},{method:e,url:t}))}}),o.forEach(["post","put","patch"],function(e){r.prototype[e]=function(t,n,r){return this.request(o.merge(r||{},{method:e,url:t,data:n}))}}),e.exports=r},function(e,t,n){"use strict";function r(e){return encodeURIComponent(e).replace(/%40/gi,"@").replace(/%3A/gi,":").replace(/%24/g,"$").replace(/%2C/gi,",").replace(/%20/g,"+").replace(/%5B/gi,"[").replace(/%5D/gi,"]")}var o=n(2);e.exports=function(e,t,n){if(!t)return e;var i;if(n)i=n(t);else if(o.isURLSearchParams(t))i=t.toString();else{var s=[];o.forEach(t,function(e,t){null!==e&&"undefined"!=typeof e&&(o.isArray(e)?t+="[]":e=[e],o.forEach(e,function(e){o.isDate(e)?e=e.toISOString():o.isObject(e)&&(e=JSON.stringify(e)),s.push(r(t)+"="+r(e))}))}),i=s.join("&")}if(i){var a=e.indexOf("#");a!==-1&&(e=e.slice(0,a)),e+=(e.indexOf("?")===-1?"?":"&")+i}return e}},function(e,t,n){"use strict";function r(){this.handlers=[]}var o=n(2);r.prototype.use=function(e,t){return this.handlers.push({fulfilled:e,rejected:t}),this.handlers.length-1},r.prototype.eject=function(e){this.handlers[e]&&(this.handlers[e]=null)},r.prototype.forEach=function(e){o.forEach(this.handlers,function(t){null!==t&&e(t)})},e.exports=r},function(e,t,n){"use strict";function r(e){e.cancelToken&&e.cancelToken.throwIfRequested()}var o=n(2),i=n(8),s=n(9),a=n(10);e.exports=function(e){r(e),e.headers=e.headers||{},e.data=i(e.data,e.headers,e.transformRequest),e.headers=o.merge(e.headers.common||{},e.headers[e.method]||{},e.headers),o.forEach(["delete","get","head","post","put","patch","common"],function(t){delete e.headers[t]});var t=e.adapter||a.adapter;return t(e).then(function(t){return r(e),t.data=i(t.data,t.headers,e.transformResponse),t},function(t){return s(t)||(r(e),t&&t.response&&(t.response.data=i(t.response.data,t.response.headers,e.transformResponse))),Promise.reject(t)})}},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t,n){return r.forEach(n,function(n){e=n(e,t)}),e}},function(e,t){"use strict";e.exports=function(e){return!(!e||!e.__CANCEL__)}},function(e,t,n){"use strict";function r(e,t){!i.isUndefined(e)&&i.isUndefined(e["Content-Type"])&&(e["Content-Type"]=t)}function o(){var e;return"undefined"!=typeof XMLHttpRequest?e=n(12):"undefined"!=typeof process&&"[object process]"===Object.prototype.toString.call(process)&&(e=n(12)),e}var i=n(2),s=n(11),a={"Content-Type":"application/x-www-form-urlencoded"},u={adapter:o(),transformRequest:[function(e,t){return s(t,"Accept"),s(t,"Content-Type"),i.isFormData(e)||i.isArrayBuffer(e)||i.isBuffer(e)||i.isStream(e)||i.isFile(e)||i.isBlob(e)?e:i.isArrayBufferView(e)?e.buffer:i.isURLSearchParams(e)?(r(t,"application/x-www-form-urlencoded;charset=utf-8"),e.toString()):i.isObject(e)?(r(t,"application/json;charset=utf-8"),JSON.stringify(e)):e}],transformResponse:[function(e){if("string"==typeof e)try{e=JSON.parse(e)}catch(e){}return e}],timeout:0,xsrfCookieName:"XSRF-TOKEN",xsrfHeaderName:"X-XSRF-TOKEN",maxContentLength:-1,validateStatus:function(e){return e>=200&&e<300}};u.headers={common:{Accept:"application/json, text/plain, */*"}},i.forEach(["delete","get","head"],function(e){u.headers[e]={}}),i.forEach(["post","put","patch"],function(e){u.headers[e]=i.merge(a)}),e.exports=u},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){r.forEach(e,function(n,r){r!==t&&r.toUpperCase()===t.toUpperCase()&&(e[t]=n,delete e[r])})}},function(e,t,n){"use strict";var r=n(2),o=n(13),i=n(5),s=n(16),a=n(19),u=n(20),c=n(14);e.exports=function(e){return new Promise(function(t,f){var p=e.data,d=e.headers;r.isFormData(p)&&delete d["Content-Type"];var l=new XMLHttpRequest;if(e.auth){var h=e.auth.username||"",m=e.auth.password||"";d.Authorization="Basic "+btoa(h+":"+m)}var y=s(e.baseURL,e.url);if(l.open(e.method.toUpperCase(),i(y,e.params,e.paramsSerializer),!0),l.timeout=e.timeout,l.onreadystatechange=function(){if(l&&4===l.readyState&&(0!==l.status||l.responseURL&&0===l.responseURL.indexOf("file:"))){var n="getAllResponseHeaders"in l?a(l.getAllResponseHeaders()):null,r=e.responseType&&"text"!==e.responseType?l.response:l.responseText,i={data:r,status:l.status,statusText:l.statusText,headers:n,config:e,request:l};o(t,f,i),l=null}},l.onabort=function(){l&&(f(c("Request aborted",e,"ECONNABORTED",l)),l=null)},l.onerror=function(){f(c("Network Error",e,null,l)),l=null},l.ontimeout=function(){var t="timeout of "+e.timeout+"ms exceeded";e.timeoutErrorMessage&&(t=e.timeoutErrorMessage),f(c(t,e,"ECONNABORTED",l)),l=null},r.isStandardBrowserEnv()){var g=n(21),v=(e.withCredentials||u(y))&&e.xsrfCookieName?g.read(e.xsrfCookieName):void 0;v&&(d[e.xsrfHeaderName]=v)}if("setRequestHeader"in l&&r.forEach(d,function(e,t){"undefined"==typeof p&&"content-type"===t.toLowerCase()?delete d[t]:l.setRequestHeader(t,e)}),r.isUndefined(e.withCredentials)||(l.withCredentials=!!e.withCredentials),e.responseType)try{l.responseType=e.responseType}catch(t){if("json"!==e.responseType)throw t}"function"==typeof e.onDownloadProgress&&l.addEventListener("progress",e.onDownloadProgress),"function"==typeof e.onUploadProgress&&l.upload&&l.upload.addEventListener("progress",e.onUploadProgress),e.cancelToken&&e.cancelToken.promise.then(function(e){l&&(l.abort(),f(e),l=null)}),void 0===p&&(p=null),l.send(p)})}},function(e,t,n){"use strict";var r=n(14);e.exports=function(e,t,n){var o=n.config.validateStatus;!o||o(n.status)?e(n):t(r("Request failed with status code "+n.status,n.config,null,n.request,n))}},function(e,t,n){"use strict";var r=n(15);e.exports=function(e,t,n,o,i){var s=new Error(e);return r(s,t,n,o,i)}},function(e,t){"use strict";e.exports=function(e,t,n,r,o){return e.config=t,n&&(e.code=n),e.request=r,e.response=o,e.isAxiosError=!0,e.toJSON=function(){return{message:this.message,name:this.name,description:this.description,number:this.number,fileName:this.fileName,lineNumber:this.lineNumber,columnNumber:this.columnNumber,stack:this.stack,config:this.config,code:this.code}},e}},function(e,t,n){"use strict";var r=n(17),o=n(18);e.exports=function(e,t){return e&&!r(t)?o(e,t):t}},function(e,t){"use strict";e.exports=function(e){return/^([a-z][a-z\d\+\-\.]*:)?\/\//i.test(e)}},function(e,t){"use strict";e.exports=function(e,t){return t?e.replace(/\/+$/,"")+"/"+t.replace(/^\/+/,""):e}},function(e,t,n){"use strict";var r=n(2),o=["age","authorization","content-length","content-type","etag","expires","from","host","if-modified-since","if-unmodified-since","last-modified","location","max-forwards","proxy-authorization","referer","retry-after","user-agent"];e.exports=function(e){var t,n,i,s={};return e?(r.forEach(e.split("\n"),function(e){if(i=e.indexOf(":"),t=r.trim(e.substr(0,i)).toLowerCase(),n=r.trim(e.substr(i+1)),t){if(s[t]&&o.indexOf(t)>=0)return;"set-cookie"===t?s[t]=(s[t]?s[t]:[]).concat([n]):s[t]=s[t]?s[t]+", "+n:n}}),s):s}},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){function e(e){var t=e;return n&&(o.setAttribute("href",t),t=o.href),o.setAttribute("href",t),{href:o.href,protocol:o.protocol?o.protocol.replace(/:$/,""):"",host:o.host,search:o.search?o.search.replace(/^\?/,""):"",hash:o.hash?o.hash.replace(/^#/,""):"",hostname:o.hostname,port:o.port,pathname:"/"===o.pathname.charAt(0)?o.pathname:"/"+o.pathname}}var t,n=/(msie|trident)/i.test(navigator.userAgent),o=document.createElement("a");return t=e(window.location.href),function(n){var o=r.isString(n)?e(n):n;return o.protocol===t.protocol&&o.host===t.host}}():function(){return function(){return!0}}()},function(e,t,n){"use strict";var r=n(2);e.exports=r.isStandardBrowserEnv()?function(){return{write:function(e,t,n,o,i,s){var a=[];a.push(e+"="+encodeURIComponent(t)),r.isNumber(n)&&a.push("expires="+new Date(n).toGMTString()),r.isString(o)&&a.push("path="+o),r.isString(i)&&a.push("domain="+i),s===!0&&a.push("secure"),document.cookie=a.join("; ")},read:function(e){var t=document.cookie.match(new RegExp("(^|;\s*)("+e+")=([^;]*)"));return t?decodeURIComponent(t[3]):null},remove:function(e){this.write(e,"",Date.now()-864e5)}}}():function(){return{write:function(){},read:function(){return null},remove:function(){}}}()},function(e,t,n){"use strict";var r=n(2);e.exports=function(e,t){t=t||{};var n={},o=["url","method","params","data"],i=["headers","auth","proxy"],s=["baseURL","url","transformRequest","transformResponse","paramsSerializer","timeout","withCredentials","adapter","responseType","xsrfCookieName","xsrfHeaderName","onUploadProgress","onDownloadProgress","maxContentLength","validateStatus","maxRedirects","httpAgent","httpsAgent","cancelToken","socketPath"];r.forEach(o,function(e){"undefined"!=typeof t[e]&&(n[e]=t[e])}),r.forEach(i,function(o){r.isObject(t[o])?n[o]=r.deepMerge(e[o],t[o]):"undefined"!=typeof t[o]?n[o]=t[o]:r.isObject(e[o])?n[o]=r.deepMerge(e[o]):"undefined"!=typeof e[o]&&(n[o]=e[o])}),r.forEach(s,function(r){"undefined"!=typeof t[r]?n[r]=t[r]:"undefined"!=typeof e[r]&&(n[r]=e[r])});var a=o.concat(i).concat(s),u=Object.keys(t).filter(function(e){return a.indexOf(e)===-1});return r.forEach(u,function(r){"undefined"!=typeof t[r]?n[r]=t[r]:"undefined"!=typeof e[r]&&(n[r]=e[r])}),n}},function(e,t){"use strict";function n(e){this.message=e}n.prototype.toString=function(){return"Cancel"+(this.message?": "+this.message:"")},n.prototype.__CANCEL__=!0,e.exports=n},function(e,t,n){"use strict";function r(e){if("function"!=typeof e)throw new TypeError("executor must be a function.");var t;this.promise=new Promise(function(e){t=e});var n=this;e(function(e){n.reason||(n.reason=new o(e),t(n.reason))})}var o=n(23);r.prototype.throwIfRequested=function(){if(this.reason)throw this.reason},r.source=function(){var e,t=new r(function(t){e=t});return{token:t,cancel:e}},e.exports=r},function(e,t){"use strict";e.exports=function(e){return function(t){return e.apply(null,t)}}}])});
(function(console){
console.save = function(data, filename){
if(!data) {
console.error('Console.save: No data')
return;
}
if(!filename) filename = 'console.json'
if(typeof data === "object"){
data = JSON.stringify(data, undefined, 4)
}
var blob = new Blob([data], {type: 'text/json'}),
e = document.createEvent('MouseEvents'),
a = document.createElement('a')
a.download = filename
a.href = window.URL.createObjectURL(blob)
a.dataset.downloadurl = ['text/json', a.download, a.href].join(':')
e.initMouseEvent('click', true, false, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null)
a.dispatchEvent(e)
}
})(console)
ah.proxy({
//請(qǐng)求發(fā)起前進(jìn)入
onRequest: (config, handler) => {
console.log(config.url)
handler.next(config);
},
//請(qǐng)求發(fā)生錯(cuò)誤時(shí)進(jìn)入,比如超時(shí);注意,不包括http狀態(tài)碼錯(cuò)誤,如404仍然會(huì)認(rèn)為請(qǐng)求成功
onError: (err, handler) => {
console.log(err.type)
handler.next(err)
},
//請(qǐng)求成功后進(jìn)入
onResponse: (response, handler) => {
if (response.config.url.startsWith('api/apps') && response.config.url.endsWith('/search')) {
timestamp = new Date().getTime().toString()
console.save(response.response, timestamp+'.json')
}
handler.next(response)
}
})
該hook.js是我改良之后的,可以拿來(lái)直接用,功能就是根據(jù)response.config.url過(guò)濾我們需要的請(qǐng)求,并將response body保存到本地的json文件中,注意到上文中webdriver初始化的字段中有個(gè)選項(xiàng),download.default_directory
prefs = {
'download.default_directory': os.getenv('OS_LOG_PATH')
}
os.getenv('OS_LOGPATH')這個(gè)是獲取環(huán)境變量,環(huán)境變量的值為我們需要保存response body的文件夾路徑,每一個(gè)response body都是一個(gè)json文件。大功告成!!簡(jiǎn)潔明了,不需要依賴(lài)于其他任何東西,就可以實(shí)現(xiàn)了,長(zhǎng)舒一口氣。
總結(jié)經(jīng)驗(yàn):
1,不要盲目使用網(wǎng)上的源碼,也不要死扣一種解決方案,不要輕易就相信網(wǎng)上的博文資料而不親自實(shí)踐,人云亦云。
2,解決問(wèn)題最有效的途徑是:看清問(wèn)題的本質(zhì),從問(wèn)題的內(nèi)部挖掘出解決文圖的思路,拆解成更容易解決的小問(wèn)題。當(dāng)一個(gè)問(wèn)題關(guān)聯(lián)了太多變量因素時(shí),要學(xué)會(huì)控制單一變量,用排除法一步步解決,省時(shí)省力,不容易走彎路。
*請(qǐng)認(rèn)真填寫(xiě)需求信息,我們會(huì)在24小時(shí)內(nèi)與您取得聯(lián)系。