規(guī)則寫好后,采集測試下,看內容是否正常,內容正確就可以。
如:中國青年網(wǎng)-財經(jīng)
說明:找到要采集的新聞平臺的,某個分類的列表,必須是可以點擊分頁的,流加載的無法采集,這里就拿 中國青年網(wǎng)舉例 點擊體育分類 打開體育列表。注意必須是一相同性質的 列表模式,才方便采集,復制列表地址到插件列表地址。
先點擊分頁,獲得分頁地址:如 http://news.youth.cn/zc/index_1.htm 可知道 index_1 是分頁參數(shù) 把 index_1 改成index_{page}
插件中的列表地址就是:http://news.youth.cn/zc/index_{page}.htm
鍵盤按f12 然后點擊圖中小圖標,把鼠標指向 頁面中需要采集的 文章列表標題中
比如這里的列表規(guī)則可以寫成: 格式 JQuery選擇名稱,采集的屬性
列表規(guī)則所以是 .tj3_1>li>a, href ( 從外層到內層尋找定位的。)
在列表中隨便打開一個文章信息
這里主要獲取文章標題和文章內容就可以了
按 F12 打開審查元素,然后點擊左側小圖標
鼠標定位到 文章標題 ,找到文章標題定位的位置,如果文章標題 的標簽是h1,為了匹配正確放置頁面還有其他地方有h1,所欲必須向上在找一個class 或者id 名稱來定位。
文章標題規(guī)則:所以這里選擇:.page_title> h1,text作為文章標題規(guī)則 text 位固定格式
文章內容匹配:
鼠標定位到文章內容部分:可以看到文章內容上方的 class 或者id ,必須把所有文章內容都包含的元素內部。這里選擇 class=”TRS_Editor” 選擇器就是 .TRS_Editor
按照采集的格式 :選擇器+html
采集文章內容 .TRS_Editor,html
入庫設置:
規(guī)則寫好后先保存下,然后測試,直到測試正確為止。
html的文件結構大家都是知道的了,總體分為head和body部分
我們要實現(xiàn)變色,在head部分實現(xiàn)格式
<style>
.tablex {border-collapse: collapse;}
.tablex tr {}
.tablex tr td {text-align:center; line-height:30px;}
.tablex tr td:hover { background-color:#f00; color:#fff;}
</style>
然后在body部分,使用table時候,注明class="tablex".這樣的話,就實現(xiàn)了我們所說的效果了。
附上完整代碼:
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=GBK" />
<title>測試鼠標移到到表格單元格背景顏色改變的</title>
<style>
.table1 {border-collapse: collapse;}
.table1 tr {}
.table1 tr td {text-align:center; line-height:30px;}
.table1 tr td:hover { background-color:#006030; color:#006030;}
</style>
</head>
<body>
<table class="table1" width="70%" border="1">
<tr>
<td>測試</td>
<td>測試</td>
<td>測試</td>
<td>測試</td>
</tr>
<tr>
<td>測試</td>
<td>測試</td>
<td>測試</td>
<td>測試</td>
</tr>
<tr>
<td>測試</td>
<td>測試</td>
<td>測試</td>
<td>測試</td>
</tr>
<tr>
<td>測試</td>
<td>測試</td>
<td>測試</td>
<td>測試</td>
</tr>
<tr>
<td>測試</td>
<td>測試</td>
<td>測試</td>
<td>測試</td>
</tr>
</table>
</body>
</html>
在任何一個瀏覽器中運行,效果如下
南大盛聯(lián)20年來一直致力于高端IT培訓--打造高級軟件人才實戰(zhàn)培訓專家,學生對我們的認可是我們一直前進的動力;項目團隊全球招聘,特聘來自海外的老師進行任教,采用100%商業(yè)項目進行實戰(zhàn)培訓,線上線下同步進行。
課程全部緊隨市場需求進行設計,并且動態(tài)進行調整;7天免費試聽,0首付開始學習,學完后進行100%推薦就業(yè),不滿意工作崗位2次推薦。
選定一個平臺,認識一群志同道合的朋友,你的未來人生路必定不一樣。
目前已經(jīng)開設下面這些培訓項目
Java培訓
安卓培訓
JavaWeb培訓
Linux培訓
云服務器布置培訓
HTML5培訓
SEO培訓
視頻剪輯培訓
UI培訓
歡迎您們分享給自己愿意分享的朋友,大家一起來進步;相互轉告,咨詢,學習。
南大盛聯(lián)培訓理念:我懂,我也能讓你懂。
文來源于:程序員成長指北;作者:海闊_天空
原文:https://juejin.cn/post/7173596154297810957
如有侵權,聯(lián)系刪除
如何快速定位線上bug,是多數(shù)開發(fā)者都會遇到的難題
web-see[1] 前端監(jiān)控方案,提供了 前端錄屏+定位源碼 方式,讓bug無處藏身
這是前端監(jiān)控的第二篇,該篇講解如何實現(xiàn)錯誤還原功能,第一篇 從0到1搭建前端監(jiān)控平臺,面試必備的亮點項目總結(已開源)沒有看過的小伙伴,建議先了解下
在監(jiān)控后臺,通過報錯信息列表,可以查看具體報錯的源碼,以及報錯時的錄屏回放
效果演示:
錄屏記錄了用戶的所有操作,紅色的線代表了鼠標的移動軌跡
前端項目發(fā)布上線,代碼一般都會進行壓縮、混淆、甚至加密,當線上代碼報錯時,很難定位到具體的源碼
SourceMap 完美解決了代碼反解的問題,項目在打包時,除了生成最終 XXX.js 文件外,還會額外生成一個 XXX.js.map 的文件
.map 文件里包含了原始代碼及其映射信息,可以利用它反解出報錯信息的源碼
先了解下 SourceMap 的基本內容
例如 app.a2a3ceec.js 代碼如下:
var add=function(x, y){return x+y;};
//# sourceMappingURL=app.a2a3ceec.js.map
復制代碼
其中 sourceMappingURL 用來說明該文件對應的map文件
對應的 app.a2a3ceec.js.map 代碼如下:
{
version : 3, // SourceMap標準版本,最新的為3
file: "js/app.a2a3ceec.js", // 轉換后的文件名
sourceRoot : "", // 轉換前的文件所在目錄,如果與轉換前的文件在同一目錄,該項為空
sources: [ // 轉換前的文件,該項是一個數(shù)組,表示可能存在多個文件合并
"webpack://web-see-demo/./src/App.vue",
"webpack://web-see-demo/./src/main.js"
],
names: [], // 轉換前的所有變量名和屬性名
sourcesContent: [ // 原始文件內容
"const add = (x,y) => {\n return x+y;\n}"
],
// 所有映射點
mappings: "AAAA,IAAM,GAAG,GAAG,UAAC,CAAQ,EAAC,CAAQ;IAC5B,OAAO,CAAC,GAAC,CAAC,CAAC;AACb,CAAC,CAAA"
}
復制代碼
其中 sources 和 sourcesContent 是關鍵字段,下文的還原示例中將用到
代碼還原,這里主要使用 source-map-js[3] 庫,下面介紹下如何使用
示例代碼:
import sourceMap from 'source-map-js';
/**
* findCodeBySourceMap用于獲取map文件對應的源代碼
* @param { string } fileName .map文件名稱
* @param { number } line 發(fā)生錯誤的行號
* @param { number } column 發(fā)生錯誤的列號
* @param { function } 回調函數(shù),返回對應的源碼
*/
const findCodeBySourceMap = async ({ fileName, line, column }, callback) => {
// loadSourceMap 用于獲取服務器上 .map 的文件內容
let sourceData = await loadSourceMap(fileName);
let { sourcesContent, sources } = sourceData;
// SourceMapConsumer實例表示一個已解析的源映射
// 可以通過在生成的源中給它一個文件位置來查詢有關原始文件位置的信息
let consumer = await new sourceMap.SourceMapConsumer(sourceData);
// 輸入錯誤的發(fā)生行和列,可以得到源碼對應原始文件、行和列信息
let result = consumer.originalPositionFor({
line: Number(line),
column: Number(column)
});
// 從sourcesContent得到具體的源碼信息
let code = sourcesContent[sources.indexOf(result.source)];
……
callback(code)
復制代碼
本小節(jié)的代碼倉庫[4]
source-map 的還原流程:
1、從服務器獲取指定.map 的文件內容
2、new 一個 SourceMapConsumer 的實例,表示一個已解析的源映射,給它一個文件位置來查詢有關原始文件位置的信息
3、輸入報錯發(fā)生的行和列,可以得到源碼對應原始文件名、行和列信息
4、從源文件的 sourcesContent 字段中,獲取對應的源碼信息
接下來的重點就變?yōu)椋喝绾潍@取報錯發(fā)生的原始文件名、行和列信息
通過第一篇文章的介紹,我們知道可以通過多種方式來捕獲報錯
比如 error事件、unhandledrejection事件、vue 中通過Vue.config.errorHander、react中通過componentDidCatch
為了消除各瀏覽器的差異,使用 error-stack-parser[5] 庫來提取給定錯誤的原始文件名、行和列信息
示例代碼:
import ErrorStackParser from 'error-stack-parser';
ErrorStackParser.parse(new Error('BOOM'));
// 返回值 StackFrame 堆棧列表
[
StackFrame({functionName: 'foo', args: [], fileName: 'path/to/file.js', lineNumber: 35, columnNumber: 79, isNative: false, isEval: false}),
StackFrame({functionName: 'Bar', fileName: 'https://cdn.somewherefast.com/utils.min.js', lineNumber: 1, columnNumber: 832, isNative: false, isEval: false, isConstructor: true}),
StackFrame(... and so on ...)
]
復制代碼
這里簡單說明下 JS 堆棧列表
堆棧示例:
function c() {
try {
var bar = baz;
throw new Error()
} catch (e) {
console.log(e.stack);
}
}
function b() {
c();
}
function a() {
b();
}
a();
復制代碼
上述代碼中會在執(zhí)行到 c 函數(shù)的時候報錯,調用棧為 a -> b -> c,如下圖所示:
一般我們只需要定位到 c 函數(shù)的堆棧信息,所以使用 error-stack-parser 庫的時候,只取 StackFrame 數(shù)組中的第一個元素
最終代碼:
import ErrorStackParser from 'error-stack-parser';
// 取StackFrame數(shù)組中的第一個元素
let stackFrame = ErrorStackParser.parse(error)[0];
// 獲取對應的原始文件名、行和列信息,并上報
let { fileName, columnNumber, lineNumber } = stackFrame;
復制代碼
下載 web-see-demo[6] 安裝并運行
1)點擊 js錯誤 按鈕,會執(zhí)行 HomeView.vue 文件中的 codeErr 方法
codeErr的源碼為:
codeErr.png
2)Vue.config.errorHander中捕獲到報錯信息為:
length.png
3)使用 ErrorStackParser.parse 解析后的stackFrame為:
stackFrame.png
4)經(jīng)過 consumer.originalPositionFor 還原后的 result 結果為:
result.png
5)最終拿到的源碼:
code.png
sourcemap.png
如上圖所示,定位源碼流程總結:
1、項目中引入監(jiān)控 SDK,打包后將js文件發(fā)布到服務器上
2、將 .map 文件放到指定的地址,統(tǒng)一存儲
3、當線上代碼報錯時,利用 error-stack-parser 獲取具體原始文件名、行和列信息,并上報
4、利用 source-map 從 .map 文件中得到對應的源碼并展示
web-see 監(jiān)控通過 rrweb[7] 提供了前端錄屏的功能
先介紹下在vue中如何使用
錄制示例:
import { record } from 'rrweb';
// events存儲錄屏信息
let events = [];
// record 用于記錄 `DOM` 中的所有變更
rrweb.record({
emit(event, isCheckout) {
// isCheckout 是一個標識,告訴你重新制作了快照
if (isCheckout) {
events.push([]);
}
events.push(event);
},
recordCanvas: true, // 記錄 canvas 內容
checkoutEveryNms: 10 * 1000, // 每10s重新制作快照
checkoutEveryNth: 200, // 每 200 個 event 重新制作快照
});
復制代碼
播放示例:
<template>
<div ref='player'>
</div>
</template>
<script>
import rrwebPlayer from 'rrweb-player';
import 'rrweb-player/dist/style.css';
export default {
mounted() {
// 將記錄的變更按照對應的時間一一重放
new rrwebPlayer(
{
target: this.$refs.player, // 回放所需要的HTML元素
data: { events }
},
{
UNSAFE_replayCanvas: true // 回放 canvas 內容
}
)
}
}
</script>
復制代碼
rrweb 主要由 rrweb 、 rrweb-player 和 rrweb-snapshot 三個庫組成:
1)rrweb:提供了 record 和 replay 兩個方法;record 方法用來記錄頁面上 DOM 的變化,replay 方法支持根據(jù)時間戳去還原 DOM 的變化
2)rrweb-player:基于 svelte 模板實現(xiàn),為 rrweb 提供了回放的 GUI 工具,支持暫停、倍速播放、拖拽時間軸等功能。內部調用了 rrweb 的提供的 replay 等方法
3)rrweb-snapshot:包括 snapshot 和 rebuilding 兩大特性,snapshot 用來序列化 DOM 為增量快照,rebuilding 負責將增量快照還原為 DOM
rrweb 整體流程:
1)rrweb 在錄制時會首先進行首屏 DOM 快照,遍歷整個頁面的 DOM 樹,轉換為 JSON 結構數(shù)據(jù),使用增量快照的處理方式,通過 mutationObserver 獲取 DOM 增量變化,同步轉換為 JSON 數(shù)據(jù)進行存儲
2)整個錄制的過程會生成 unique id,來確定增量數(shù)據(jù)所對應的 DOM 節(jié)點,通過 timestamp 保證回放順序。
3) 回放時,會創(chuàng)建一個 iframe 作為承載事件回放的容器,針對首屏 DOM 快照進行重建,在遍歷 JSON 的同時,根據(jù)序列化后的節(jié)點數(shù)據(jù)構建出實際的 DOM 節(jié)點
4)rrweb 可以監(jiān)聽的用戶行為包括:鼠標移動,鼠標交互,頁面滾動,視窗變化、用戶輸入等,通過添加相應的監(jiān)聽事件來實現(xiàn)
如果一直錄屏,數(shù)據(jù)量是巨大的
實測下來,錄制10s的時長,數(shù)據(jù)大小約為 8M 左右(頁面的不同復雜度、用戶不同操作的頻率都會造成大小不一樣)
數(shù)據(jù)如果不經(jīng)過壓縮,直接傳給后端,面對大量的用戶,需要非常高的帶寬做支持。還好,rrweb官方提供了數(shù)據(jù)壓縮函數(shù)[8]
基于 packFn 的單數(shù)據(jù)壓縮,在錄制時可以作為 packFn 傳入
rrweb.record({
emit(event) {},
packFn: rrweb.pack,
});
復制代碼
回放時,需要傳入 rrweb.unpack 作為 unpackFn 傳入
const replayer = new rrweb.Replayer(events, {
unpackFn: rrweb.unpack,
});
復制代碼
但是官方提供的壓縮方式,是對每個 event 數(shù)據(jù)單獨進行壓縮,壓縮比不高。實測下來,壓縮比在70%左右,比如原來 8M 的數(shù)據(jù),壓縮后為 2.4M 左右
官方更加推薦將多個 event 批量一次性壓縮,這樣壓縮效果更好
web-see 內部使用 pako.js[9]、js-base64[10] 相結合的壓縮方式,實測下來,壓縮比為 85% 以上,原來 8M 的數(shù)據(jù),壓縮后為 1.2M 左右
壓縮代碼示例:
import pako from 'pako';
import { Base64 } from 'js-base64';
// 壓縮
export function zip(data) {
if (!data) return data;
// 判斷數(shù)據(jù)是否需要轉為JSON
const dataJson = typeof data !== 'string' && typeof data !== 'number' ? JSON.stringify(data) : data;
// 使用Base64.encode處理字符編碼,兼容中文
const str = Base64.encode(dataJson);
let binaryString = pako.gzip(str);
let arr = Array.from(binaryString);
let s = '';
arr.forEach((item) => {
s += String.fromCharCode(item);
});
return Base64.btoa(s);
}
復制代碼
解壓代碼示例:
import { Base64 } from 'js-base64';
import pako from 'pako';
// 解壓
export function unzip(b64Data) {
let strData = Base64.atob(b64Data);
let charData = strData.split('').map(function (x) {
return x.charCodeAt(0);
});
let binData = new Uint8Array(charData);
let data = pako.ungzip(binData);
// ↓切片處理數(shù)據(jù),防止內存溢出報錯↓
let str = '';
const chunk = 8 * 1024;
let i;
for (i = 0; i < data.length / chunk; i++) {
str += String.fromCharCode.apply(null, data.slice(i * chunk, (i + 1) * chunk));
}
str += String.fromCharCode.apply(null, data.slice(i * chunk));
// ↑切片處理數(shù)據(jù),防止內存溢出報錯↑
const unzipStr = Base64.decode(str);
let result = '';
// 對象或數(shù)組進行JSON轉換
try {
result = JSON.parse(unzipStr);
} catch (error) {
if (/Unexpected token o in JSON at position 0/.test(error)) {
// 如果沒有轉換成功,代表值為基本數(shù)據(jù),直接賦值
result = unzipStr;
}
}
return result;
}
復制代碼
一般關注的是,頁面報錯的時候用戶做了哪些操作,所以目前只把報錯前10s的錄屏上報到服務端
如何只上報報錯時的錄屏信息呢 ?
1)window上設置 hasError、recordScreenId 變量,hasError用來判斷某段時間代碼是否報錯;recordScreenId 用來記錄此次錄屏的id
2)當頁面發(fā)出報錯需要上報時,判斷是否開啟了錄屏,如果開啟了,將 hasError 設為 true,同時將 window 上的 recordScreenId,存儲到此次上報信息的 data 中
3)rrweb 設置10s重新制作快照的頻率,每次重置錄屏時,判斷 hasError 是否為 true(即這段時間內是否發(fā)生報錯),有的話將這次的錄屏信息上報,并重置錄屏信息和 recordScreenId,作為下次錄屏使用
4)后臺報錯列表,從本次報錯報的data中取出 recordScreenId 來播放錄屏
錄屏的代碼示例:
handleScreen() {
try {
// 存儲錄屏信息
let events = [];
record({
emit(event, isCheckout) {
if (isCheckout) {
// 此段時間內發(fā)生錯誤,上報錄屏信息
if (_support.hasError) {
let recordScreenId = _support.recordScreenId;
// 重置recordScreenId,作為下次使用
_support.recordScreenId = generateUUID();
transportData.send({
type: EVENTTYPES.RECORDSCREEN,
recordScreenId,
time: getTimestamp(),
status: STATUS_CODE.OK,
events: zip(events)
});
events = [];
_support.hasError = false;
} else {
// 不上報,清空錄屏
events = [];
_support.recordScreenId = generateUUID();
}
}
events.push(event);
},
recordCanvas: true,
// 默認每10s重新制作快照
checkoutEveryNms: 1000 * options.recordScreentime
});
復制代碼
按照官方的 canvas 配置,驗證下來,rrweb 還是不支持 canvas 的錄制,比如使用 echarts 畫圖,圖形區(qū)域的錄屏顯示是空白的
官方配置[11] 如下:
Canvas.png
測試demo[12] 如下:
echart.png
錄屏回放,圖形這塊區(qū)域是空白的:
canvas.gif
*請認真填寫需求信息,我們會在24小時內與您取得聯(lián)系。