整合營銷服務(wù)商

          電腦端+手機端+微信端=數(shù)據(jù)同步管理

          免費咨詢熱線:

          HtmlParse:一款超輕量級的HTML文件解析和爬取工具

          tmlParse 是一款基于windwos平臺的HTML文檔解析工具,可快速構(gòu)建DOM樹,從而輕松實現(xiàn)網(wǎng)頁元素的爬取工作。DOM樹就是一個HTML文檔的節(jié)點樹,每個節(jié)點由:標簽(Tag)、屬性(Attribute)、文本(Text)三個值來描述。

          所謂的HTML文檔解析,指的就是如何構(gòu)建一顆DOM樹,只有成功構(gòu)建出DOM樹,才有可能進行后續(xù)的數(shù)據(jù)爬取和分析工作。顯然,構(gòu)建DOM樹是比較復雜的過程,因為不是每一個HTML文檔都會嚴格按照規(guī)范來書寫,因此解析過程需要具有一定容錯能力。此外,解析效率也是一個需要考慮的因素,也就是說最好通過一次文檔掃描即可建立起DOM樹,而不是反復掃描。

          下面是HtmlParse介紹。

          工具特點

          1、綠色純天然,無任何第三方依賴庫,文件大小不到150K; 2、解析速度快,具有一定的HTML語法容錯能力,可快速將HMTL文檔解析為DOM樹; 3、基于命令行參數(shù),可通過不同參數(shù)獲取指定TAG的屬性值和文本內(nèi)容,從而實現(xiàn)網(wǎng)頁爬取功能; 4、可將爬取數(shù)據(jù)輸出為json格式,方便第三方程序進一步分析和使用; 5、可爬取script腳本到指定的js文件中;

          下載地址:http://softlee.cn/HtmlParse.zip

          使用方法

          HtmlParse HtmlPathFile -tag TagName [-attr] [Attribute] [-o] [JsonPathFile]

          解析指定的HTML文檔,并將文檔中指定的標簽及屬性輸出到指定文件中。

          HtmlPathFile:必選參數(shù),要解析的HTML文檔路徑名,如果文件路徑中有空格,可使用雙引號將文件路徑包含;

          -tag:必選參數(shù),用于指定要抓取的HTML標簽名稱; -attr:可選參數(shù),用于指定標簽的屬性值,如果不指定,則返回該標簽的所有屬性值; -o:可選參數(shù),用于指定抓取內(nèi)容輸出的文件,可將抓取的內(nèi)容保存為json格式的文件。 如果該參數(shù)不指定,則進行控制臺輸出。 如果抓取的是script、style則會保存為js格式文件。

          如果要抓取doctype,可使用-tag doctype,將整個doctype內(nèi)容獲取。此時將會忽略-attr指定的任何屬性值。

          舉例說明

          1、爬取網(wǎng)頁中所有超鏈接

          HtmlParse c:/sina.html -tag a -attr href -o c:/sina.json

          解析C盤下的sina.html文檔,并提取該文檔中的所有超鏈接到sina.json文件中。其中**-tag a -attr href,用于指定獲取超鏈接標簽ahref**屬性。

          2、爬取網(wǎng)頁中所有圖片鏈接

          HtmlParse c:/sina.html -tag img -attr src -o c:/sina.json

          解析C盤下的sina.html文檔,并提取該文檔中的所有圖片鏈接到sina.json文件中。

          3、爬取網(wǎng)頁中所有腳本

          HtmlParse c:/sina.html -tag script -o c:/sina.js

          解析C盤下的sina.html文檔,并提取該文檔中的所有腳本函數(shù)到sina.js文件中。

          輸出內(nèi)容

          如果通過-o參數(shù)指定輸出文件,則會生成一個json格式的文檔。 TagName為爬取的標簽名稱,比如超鏈接的a,其值是一個json數(shù)組,數(shù)組中的每個內(nèi)容為Json對象,每個Json對象,有屬性和文本構(gòu)成。如果-attr 指定了要爬取的屬性,則AttrName為指定的屬性名稱,比如href或src。text為該標簽的文本內(nèi)容,有些標簽不存在文本內(nèi)容,比如img、meta等,則該值為空。json格式如下:

          {
            "TagName":
            {
               {"AttrName":"AttrValue1", "text":"text1"}
               {"AttrName":"AttrValue1", "text":"text2"}
            }
          }

          下面是一個sina網(wǎng)頁的所有超鏈接json

          {
           "a": [{
            "href": "javascript:;",
            "text": "設(shè)為首頁"
           }, {
            "href": "javascript:;",
            "text": "我的菜單"
           }, {
            "href": "https://sina.cn/",
            "text": "手機新浪網(wǎng)"
           }, {
            "href": "",
            "text": "移動客戶端"
           }, {
            "href": "https://c.weibo.cn/client/guide/download",
            "text": "新浪微博"
           }, {
            "href": "https://so.sina.cn/palmnews/web-sinanews-app-download.d.html",
            "text": "新浪新聞"
           }, {
            "href": "https://finance.sina.com.cn/mobile/comfinanceweb.shtml",
            "text": "新浪財經(jīng)"
           }, {
            "href": "https://m.sina.com.cn/m/sinasports.shtml",
            "text": "新浪體育"
           }, {
            "href": "https://tousu.sina.com.cn/about_app/index?frompage=heimaopc",
            "text": "黑貓投訴"
           }, {
            "href": "http://blog.sina.com.cn/lm/z/app/",
            "text": "新浪博客"
           }, {
            "href": "https://games.sina.com.cn/o/kb/12392.shtml",
            "text": "新浪游戲"
           }, {
            "href": "https://zhongce.sina.com.cn/about/app",
            "text": "新浪眾測"
           }, {
            "href": "https://mail.sina.com.cn/client/mobile/index.php?suda-key=mail_app&suda-value=login",
            "text": "新浪郵箱客戶端"
           }, {
            "href": "javascript:;",
            "text": "關(guān)閉置頂"
           }, {

          來源:https://www.cnblogs.com/softlee/p/16374079.html


          們上一章分析了html解析開始標簽的處理,我們本章來看一下結(jié)束標簽的處理。

          以下就是匹配結(jié)束標簽代碼:

           // End tag:
          const endTagMatch = html.match(endTag) // 匹配 </ 
          if (endTagMatch) {
            // 開始位置
            const curIndex = index
            // 減去對應(yīng)的長度
            advance(endTagMatch[0].length)
            // 解析結(jié)束標簽
            parseEndTag(endTagMatch[1], curIndex, index)
            continue
          }

          我們先簡單說一下 parseEndTag 的分析,為了減少篇幅我們就只帖出關(guān)鍵代碼了

          1、匹配 “stack” 數(shù)組里面相同標簽名稱的對象

           // 轉(zhuǎn)換為小寫
          lowerCasedTagName = tagName.toLowerCase()
          // 匹配相同的tag標簽
          for (pos = stack.length - 1; pos >= 0; pos--) {
            if (stack[pos].lowerCasedTag === lowerCasedTagName) {
              break
            }
          }

          2、調(diào)用 “end” 函數(shù)

           if (options.end) {
             options.end(stack[i].tag, start, end)
           }

          那么我們來看一下 “end” 函數(shù)的代碼:

           end (tag, start, end) {
              // 取出 stack 數(shù)組的最后一項
                const element = stack[stack.length - 1]
                // 刪除數(shù)組的最后一項
                stack.length -= 1
                // 當前標簽的附件標簽
                currentParent = stack[stack.length - 1]
                if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
                  // 標簽結(jié)束位置
                  element.end = end
                }
                closeElement(element)
              }

          可看到其實主要的工作是在closeElement 函數(shù),我們簡單總結(jié)一下該函數(shù)的功能,先看代碼:

          function closeElement (element) {
              trimEndingWhitespace(element)
              c
              // tree management
              if (!stack.length && element !== root) {
                // allow root elements with v-if, v-else-if and v-else
                if (root.if && (element.elseif || element.else)) {
                  if (process.env.NODE_ENV !== 'production') {
                    checkRootConstraints(element)
                  }
                  addIfCondition(root, {
                    exp: element.elseif,
                    block: element
                  })
                } else if (process.env.NODE_ENV !== 'production') {
                  warnOnce(
                    `Component template should contain exactly one root element. ` +
                    `If you are using v-if on multiple elements, ` +
                    `use v-else-if to chain them instead.`,
                    { start: element.start }
                  )
                }
              }
              if (currentParent && !element.forbidden) {
                if (element.elseif || element.else) {
                  processIfConditions(element, currentParent)
                } else {
                  if (element.slotScope) {
                    // scoped slot
                    // keep it in the children list so that v-else(-if) conditions can
                    // find it as the prev node.
                    const name = element.slotTarget || '"default"'
                    ;(currentParent.scopedSlots || (currentParent.scopedSlots = {}))[name] = element
                  }
                  currentParent.children.push(element)
                  element.parent = currentParent
                }
              }
          
              // final children cleanup
              // filter out scoped slots
              element.children = element.children.filter(c => !(c: any).slotScope)
              // remove trailing whitespace node again
              trimEndingWhitespace(element)
          
              // check pre state
              if (element.pre) {
                inVPre = false
              }
              if (platformIsPreTag(element.tag)) {
                inPre = false
              }
              // apply post-transforms
              for (let i = 0; i < postTransforms.length; i++) {
                postTransforms[i](element, options)
              }
            }

          接下來我們拆分一下該函數(shù)。

          1、移除子節(jié)點是空白節(jié)點的標簽(node.type === 3 && node.text === '')

          代碼如下:

            trimEndingWhitespace(element)

          2、處理該節(jié)點的屬性以及指令

          代碼如下:

           if (!inVPre && !element.processed) {
                element = processElement(element, options)
              }

          processElement 函數(shù)有如下處理:

          1、處理v-bind指令

          2、處理 ref 屬性

          3、處理 slot 插槽一個 slot 標簽

          4、處理component 組件

          5、處理標簽屬性(attr)

          6、是否存在 style和 :style沖突

          7、是否存在 class和 :class沖突

          class

          我們來看看以上代碼的作用:

          允許根元素具有v-if、v-else-if和v-else。

          checkRootConstraints(element)

          checkRootConstraints 函數(shù),我們已經(jīng)看過很多遍了,驗證不能是 “slot” 和 “template” 標簽,并且不是存在 v-for 指令,這個大家都知道,只能有一個根元素。

           if (element.elseif || element.else) {
             processIfConditions(element, currentParent)
           }

          如果 “elseif”指令存在或者“else” 指令存在,就需要 驗證是否存在 "v-if" 指令是存在存在,因為他們是對應(yīng)的。

           element.children = element.children.filter(c => !(c: any).slotScope)

          處理子元素的作用域。

           if (platformIsPreTag(element.tag)) {
                inPre = false
              }

          是否為平臺的 pre 標簽。

          for (let i = 0; i < postTransforms.length; i++) {
                postTransforms[i](element, options)
              }

          最后調(diào)用 postTransforms 函數(shù),如果存在的話,

          這個我也沒搞明白怎么用的,如果有人知道歡迎指點迷津啊,感激不盡。



          于parseHTML函數(shù)代碼實在過于龐大,我這里就不一次性貼出源代碼了,大家可以前往(https://github.com/vuejs/vue/blob/dev/src/compiler/parser/html-parser.js)查看源代碼。

          我們來總結(jié)一下該函數(shù)的主要功能:

          1、匹配標簽的 "<" 字符

          匹配的標簽名稱不能是:script、style、textarea

          有如下情況:

          1、注釋標簽 /^<!\--/

          2、條件注釋 /^<!\[/

          3、html文檔頭部 /^<!DOCTYPE [^>]+>/i

          4、標簽結(jié)束 /^<\/ 開頭

          5、標簽開始 /^</ 開頭

          然后開始匹配標簽的屬性包括w3的標準屬性(id、class)或者自定義的任何屬性,以及vue的指令(v-、:、@)等,直到匹配到 "/>" 標簽的結(jié)尾。然后把已匹配的從字符串中刪除,一直 while 循環(huán)匹配。

          解析開始標簽函數(shù)代碼:

          function parseStartTag () {
             // 標簽的開始 如<div
              const start = html.match(startTagOpen)
              if (start) {
                const match = {
                  tagName: start[1], // 標簽名稱
                  attrs: [], // 標簽屬性
                  start: index // 開始位置
                }
                 // 減去已匹配的長度
                advance(start[0].length)
                let end, attr
                while (!(end = html.match(startTagClose)) && (attr = html.match(dynamicArgAttribute) || html.match(attribute))) {
                  attr.start = index
                  v
                  advance(attr[0].length)  
                  attr.end = index
                  match.attrs.push(attr) // 把匹配到的屬性添加到attrs數(shù)組
                }
                if (end) { // 標簽的結(jié)束符 ">"
                  match.unarySlash = end[1]
                  advance(end[0].length)  // 減去已匹配的長度
                  match.end = index  // 結(jié)束位置
                  return match
                }
              }
            }

          處理過后結(jié)構(gòu)如下:

          接下來就是處理組合屬性,調(diào)用 “handleStartTag” 函數(shù)

           function handleStartTag (match) {
              const tagName = match.tagName // 標簽名稱
              const unarySlash = match.unarySlash // 一元標簽
              if (expectHTML) {
                if (lastTag === 'p' && isNonPhrasingTag(tagName)) {
                  // 解析標簽結(jié)束
                  parseEndTag(lastTag)
                }
                if (canBeLeftOpenTag(tagName) && lastTag === tagName) {
                  parseEndTag(tagName)
                }
              }
             // 是否為一元標簽
              const unary = isUnaryTag(tagName) || !!unarySlash
              const l = match.attrs.length
              // 標簽屬性集合
              const attrs = new Array(l)
              for (let i = 0; i < l; i++) {
                const args = match.attrs[i]
                const value = args[3] || args[4] || args[5] || ''
                const shouldDecodeNewlines = tagName === 'a' && args[1] === 'href' ? options.shouldDecodeNewlinesForHref : options.shouldDecodeNewlines
                attrs[i] = {
                  name: args[1], // 屬性名稱
                  value: decodeAttr(value, shouldDecodeNewlines) // 屬性值
                }
                if (process.env.NODE_ENV !== 'production' && options.outputSourceRange) {
                  // 開始位置
                  attrs[i].start = args.start + args[0].match(/^\s*/).length
                  // 結(jié)束位置
                  attrs[i].end = args.end
                }
              }
          
              if (!unary) {
                stack.push({ tag: tagName, lowerCasedTag: tagName.toLowerCase(), attrs: attrs, start: match.start, end: match.end })
                lastTag = tagName
              }
          		// 調(diào)用start函數(shù)
              if (options.start) {
                options.start(tagName, attrs, unary, match.start, match.end)
              }
            }

          我們簡單說一下最后調(diào)用的start函數(shù)的作用:

          1、判斷是否為svg標簽,并處理svg在ie下的兼容性問題

          2、遍歷標簽屬性,驗證其名稱是否有效

          3、標簽名是否為 style 或者 script ,如果在服務(wù)端會提示warn警告

          4、檢查屬性是否存在 v-for、v-if、v-once指令

          5、如果是更元素就驗證其合法性,不能是 slot 和 template 標簽,不能存在 v-for指令

          以上就是界面html模板的開始標簽的分析,接下來我們來分析如何匹配結(jié)束標簽。

          請看:Vue源碼全面解析三十 parseHTML函數(shù)(解析html(二)結(jié)束標簽)

          如有錯誤,歡迎指正,謝謝。


          主站蜘蛛池模板: 麻豆果冻传媒2021精品传媒一区下载| 手机看片福利一区二区三区| 亚洲一区二区三区在线观看精品中文 | 少妇特黄A一区二区三区| 在线观看免费视频一区| 亚洲AV无码一区东京热久久| 无码av免费毛片一区二区| 香蕉视频一区二区| 国产吧一区在线视频| 国产福利精品一区二区| 波多野结衣一区二区免费视频| 久久精品午夜一区二区福利| 天堂Aⅴ无码一区二区三区| 爆乳无码AV一区二区三区| 久久精品无码一区二区无码| 国产一区二区三区在线2021| 日本一区二区高清不卡| 无码日韩AV一区二区三区| 99偷拍视频精品一区二区| 91精品一区二区| 中文字幕精品一区二区精品| 亚洲熟妇av一区二区三区| 一区二区三区四区无限乱码| 能在线观看的一区二区三区| 成人免费视频一区二区三区 | 视频在线观看一区| 成人H动漫精品一区二区 | 日本一区二区三区精品国产 | 文中字幕一区二区三区视频播放| 在线精品自拍亚洲第一区| 国产成人久久一区二区三区 | 色噜噜狠狠一区二区三区| 亚洲性日韩精品一区二区三区| 风间由美在线亚洲一区| 欧美人妻一区黄a片| 国产精品无码一区二区三区在| 国产精品亚洲一区二区三区在线观看 | 日韩人妻无码一区二区三区99| 亚洲AV无码一区二区乱孑伦AS| 亚洲一区二区三区无码国产| 乱码人妻一区二区三区|