整合營銷服務商

          電腦端+手機端+微信端=數據同步管理

          免費咨詢熱線:

          JavaScript 各種遍歷方式詳解

          了方便例子講解,現有數組和字面量對象如下

          var demoArr = ['Javascript', 'Gulp', 'CSS3', 'Grunt', 'jQuery', 'angular'];
          var demoObj = {
            aaa: 'Javascript',
            bbb: 'Gulp',
            ccc: 'CSS3',
            ddd: 'Grunt',
            eee: 'jQuery',
            fff: 'angular'
          };
          

          for

          可以直接看示例,用得太多了,很簡單

          (function () {
            for (var i = 0, len = demoArr.length; i < len; i++) {
              if (i == 2) {
                // return;   // 函數執行被終止
                // break;    // 循環被終止
                continue; // 循環被跳過
              };
              console.log('demo1Arr[' + i + ']:' + demo1Arr[i]);
            }
          })();
          

          關于for循環,有以下幾點需要注意

          • for循環中的 i 在循環結束之后任然存在于作用域中,為了避免影響作用域中的其他變量,使用函數自執行的方式將其隔離起來()();
          • 避免使用 for(var i=0; i<demo1Arr.length; i++){} 的方式,這樣的數組長度每次都被計算,效率低于上面的方式。也可以將變量聲明放在for的前面來執行,提高閱讀性
          var i = 0, len = demo1Arr.length;
          for(; i<len; i++) {};
          
          • 跳出循環的方式有如下幾種 return 函數執行被終止 break 循環被終止 continue 循環被跳過

          for in

          for(var item in arr|obj){} 可以用于遍歷數組和對象

          • 遍歷數組時,item表示索引值, arr表示當前索引值對應的元素 arr[item]
          • 遍歷對象時,item表示key值,arr表示key值對應的value值 obj[item]
          (function () {
            for (var i in demoArr) {
              if (i == 2) {
                return; // 函數執行被終止
                // break;  // 循環被終止
                // continue;  // 循環被跳過
              };
              console.log('demoArr[' + i + ']:' + demoArr[i]);
            }
            console.log('-------------');
          })();
          
          

          for in 本質上遍歷的是對象,之所以能遍歷數組,是因為數組也是一個對象。

          var arr = ['react', 'vue', 'angular'];
          
          // 等價于
          
          var arr = {
            0: 'react',
            1: 'vue',
            2: 'angular'
          }
          

          關于for in,有以下幾點需要注意:

          • 在 for 循環與 for in 循環中,i 值都會在循環結束之后保留下來。因此使用函數自執行的方式避免。
          • 使用 return,break,continue 跳出循環都與 for 循環一致,不過關于 return 需要注意,在函數體中,return 表示函數執行終止,就算是循環外面的代碼,也不再繼續往下執行。而 break 僅僅只是終止循環,后面的代碼會繼續執行。
          function res() {
            var demoArr = ['Javascript', 'Gulp', 'CSS3', 'Grunt', 'jQuery', 'angular'];
          
            for (var item in demoArr) {
              if (item == 2) {
                return;
              };
              console.log(item, demoArr[item]);
            }
            console.log('desc', 'function res'); //不會執行
          }
          

          因為 for in 的目的是為了遍歷對象,因此在遍歷時,會同時搜索該對象構造函數上的屬性以及原型上的屬性,因此 for in 循環相對來說消耗會更大一點。因此,如果有其他更好的選擇,則盡量避免考慮使用 for in 循環來遍歷數據。

          forEach

          demoArr.forEach(function(arg) {})

          參數arg表示數組每一項的元素,實例如下

          demoArr.forEach(function (val, index) {
            if (e == 'CSS3') {
              return;  // 循環被跳過
              // break;   // 報錯
              // continue;// 報錯
            };
            console.log(val, index);
          })
          
          

          具體有以下需要注意的地方

          • 回調函數中有2個參數,分別表示值和索引,這一點與 jQuery 中的$.each相反
          • forEach無法遍歷對象
          • forEach無法在IE中使用,firefox和chrome實現了該方法
          • forEach無法使用 break,continue 跳出循環,使用 return 時,效果和在 for 循環中使用 continue 一致

          ES5中新增的幾個數組方法,forEach, map, filter, reduce等,可以理解為依次對數組的每一個子項進行一個處理(回調函數中的操作),他們是對簡單循環的更高一層封裝,因此與單純的循環在本質上有一些不同,所以才會導致 return, continue, break 的不同。

          最重要的一點,可以添加第二參數,為一個數組,而且回調函數中的this會指向這個數組。而如果沒有第二參數,則this會指向window。

          var newArr = [];
          demoArr.forEach(function(val, index) {
            this.push(val); // 這里的this指向newArr
          }, newArr)
          

          雖然在原生中 forEach 循環的局限性很多,但是了解他的必要性在于,很多第三方庫會擴展他的方法,使其能夠應用在很多地方,比如 angular 的工具方法中,也有 forEach 方法,其使用與原生的基本沒有差別,只是沒有了局限性,可以在IE下使用,也可以遍歷對象

          var result = [];
          angular.forEach(demoArr, function(val, index) {
            this.push(val);
          }, result);
          

          do/while

          函數具體的實現方式如下,不過有一點值得注意的是,當使用 continue時,如果你將 i++ 放在了后面,那么 i++ 的值將一直不會改變,最后陷入死循環。因此使用do/while一定要小心謹慎一點。

          // 直接使用while
          (function () {
            var i = 0,
              len = demoArr.length;
            while (i < len) {
              if (i == 2) {
                // return; // 函數執行被終止
                // break;  // 循環被終止
                // continue;  // 循環將被跳過,因為后邊的代碼無法執行,i的值沒有改變,因此循環會一直卡在這里,慎用??!
              };
              console.log('demoArr[' + i + ']:' + demoArr[i]);
              i++;
            }
            console.log('------------------------');
          })();
          
          // do while
          (function () {
            var i = 0,
              len = demo3Arr.length;
            do {
              if (i == 2) {
                break; // 循環被終止
              };
              console.log('demo2Arr[' + i + ']:' + demo3Arr[i]);
              i++;
            } while (i < len);
          })();
          

          不建議使用do/while的方式來遍歷數組

          $.each

          $.each(demoArr|demoObj, function(e, ele))
          可以用來遍歷數組和對象,其中e表示索引值或者key值,ele表示value值

          $.each(demoArr, function(e, ele) {
            console.log(e, ele);
          })
          

          輸出為

          0 "Javascript"
          1 "Gulp"
          2 "CSS3"
          3 "Grunt"
          4 "jQuery"
          5 "angular"
          

          這里有很多需要注意的地方

          • 使用return 或者return true為跳過一次循環,繼續執行后面的循環
          • 使用return false為終止循環的執行,但是并不終止函數執行
          • 無法使用break與continue來跳過循環
          • 循環中this值輸出類似如下
          console.log(this);
          //String {0: "C", 1: "S", 2: "S", 3: "3", length: 4, [[PrimitiveValue]]: "CSS3"}
          
          console.log(this == ele);
          // true
          
          • 關于上面的this值,遍歷一下
          $.each(this, function(e, ele) {
            console.log(e, ele);
          })
          
          // 0 c
          // 1 s
          // 2 s
          // 4 3
          

          為什么 length 和 [[PrimitiveValue]]沒有遍歷出來?突然靈光一動,在《javascript高級編程》中找到了答案,大概意思就是javascript的內部屬性中,將對象數據屬性中的 Enumerable 設置為了false

          // 查看length的內部屬性
          console.log(Object.getOwnPropertyDescriptor(this, 'length'));
          // Object {value: 4, writable: false, enumerable: false, configurable: false}
          

          (this)` 與this有所不同,不過遍歷結果卻是一樣,你可以在測試代碼中打印出來看看

          $(selecter).each

          專門用來遍歷DOMList

          $('.list li').each(function (i, ele) {
            console.log(i, ele);
            // console.log(this == ele); // true
            $(this).html(i);
            if ($(this).attr('data-item') == 'do') {
              $(this).html('data-item: do');
            };
          })
          
          • i: 序列值 ele: 只當前被遍歷的DOM元素
          • this 當前被遍歷的DOM元素,不能調用jQuery方法
          • (ele) 當前被遍歷元素的jquery對象,可以調用jquery的方法進行dom操作

          使用for in 遍歷 DOMList

          因為domList并非數組,而是一個對象,只是因為其key值為0,1,2... 而感覺與數組類似,但是直接遍歷的結果如下

          var domList = document.getElementsByClassName('its');
          for(var item in domList) {
            console.log(item, ':' + domList[item]);
          }
          // 0: <li></li>
          // 1: <li></li>
          //    ...
          // length: 5
          // item: function item() {}
          // namedItem: function namedItem() {}
          

          因此我們在使用for in 遍歷domList時,需要將domList轉換為數組

          var res = [].slice.call(domList);
          for(var item in res) {}
          

          類似這樣的對象還有函數的屬性 arguments 對象,當然字符串也是可以遍歷的,但是因為字符串其他屬性的 enumerable 被設置成了false,因此遍歷出來的結果跟數組是一樣的,也就不用擔心這個問題了.

          for of

          for of 用于遍歷可迭代對象「Iterator」。在 JS 中,數組 Array,字符串 String, Map,Set 等,都是可迭代對象。

          對象中包含 Symbol.iterator 屬性的,都被稱為可迭代對象。

          var arr = [1, 2, 3];
          arr[Symbol.iterator]
          // ? values() { [native code] }
          

          簡單案例。

          const iterable = ['react', 'vue', 'angular'];
           
          for (const value of iterable) {
            console.log(value);
          }
          
          • for of 僅僅針對可迭代對象
          • 跳出循環的方式與 for 循環保持一致

          小補充

          如果你發現有些人寫函數這樣搞,不要驚慌,也不要覺得他高大上鳥不起

          +function(ROOT, Struct, undefined) {
            ... 
          }(window, function() {
              function Person() {}
          })

          ()(), !function() {}() +function() {}() 三種函數自執行的方式

          學習是一個艱苦的過程,當然如果能把技術學成,最后也一定可以獲得高薪工作。掌握一個好的學習方法,跟對一個學習的人非常重要。今后要是大家有啥問題,可以隨時來問我,能幫助別人學習解決問題,對于自己也是一個提升的過程。自己整理了一份2020最全面前端學習資料,從最基礎的HTML+CSS+JS到HTML5的項目實戰的學習資料都有整理web前端學習干貨,各種框架都有整理,送給每一位前端小伙伴,想要獲取的可以關注我的頭條號并在后臺私信我:前端,即可免費獲取



          在前面

          今年國慶假期終于可以憋在家里了不用出門了,不用出去看后腦了,真的是一種享受。這么好的光陰怎么浪費,睡覺、吃飯、打豆豆這怎么可能(耍多了也煩),完全不符合我們程序員的作風,趕緊起來把文章寫完。

          這篇文章比較基礎,在國慶期間的業余時間寫的,這幾天又完善了下,力求把更多的前端所涉及到的關于文件上傳的各種場景和應用都涵蓋了,若有疏漏和問題還請留言斧正和補充。

          自測讀不讀

          以下是本文所涉及到的知識點,break or continue ?

          • 文件上傳原理
          • 最原始的文件上傳
          • 使用 koa2 作為服務端寫一個文件上傳接口
          • 單文件上傳和上傳進度
          • 多文件上傳和上傳進度
          • 拖拽上傳
          • 剪貼板上傳
          • 大文件上傳之分片上傳
          • 大文件上傳之斷點續傳
          • node 端文件上傳

          原理概述

          原理很簡單,就是根據 http 協議的規范和定義,完成請求消息體的封裝和消息體的解析,然后將二進制內容保存到文件。

          我們都知道如果要上傳一個文件,需要把 form 標簽的enctype設置為multipart/form-data,同時method必須為post方法。

          那么multipart/form-data表示什么呢?

          multipart互聯網上的混合資源,就是資源由多種元素組成,form-data表示可以使用HTML Forms 和 POST 方法上傳文件,具體的定義可以參考RFC 7578。

          multipart/form-data 結構

          看下 http 請求的消息體



          • 請求頭:

          Content-Type: multipart/form-data; boundary=----WebKitFormBoundaryDCntfiXcSkPhS4PN 表示本次請求要上傳文件,其中boundary表示分隔符,如果要上傳多個表單項,就要使用boundary分割,每個表單項由———XXX開始,以———XXX結尾。

          • 消息體- Form Data 部分

          每一個表單項又由Content-Type和Content-Disposition組成。

          Content-Disposition: form-data 為固定值,表示一個表單元素,name 表示表單元素的 名稱,回車換行后面就是name的值,如果是上傳文件就是文件的二進制內容。

          Content-Type:表示當前的內容的 MIME 類型,是圖片還是文本還是二進制數據。

          解析

          客戶端發送請求到服務器后,服務器會收到請求的消息體,然后對消息體進行解析,解析出哪是普通表單哪些是附件。

          可能大家馬上能想到通過正則或者字符串處理分割出內容,不過這樣是行不通的,二進制buffer轉化為string,對字符串進行截取后,其索引和字符串是不一致的,所以結果就不會正確,除非上傳的就是字符串。

          不過一般情況下不需要自行解析,目前已經有很成熟的三方庫可以使用。

          至于如何解析,這個也會占用很大篇幅,后面的文章在詳細說。

          最原始的文件上傳

          使用 form 表單上傳文件

          在 ie時代,如果實現一個無刷新的文件上傳那可是費老勁了,大部分都是用 iframe 來實現局部刷新或者使用 flash 插件來搞定,在那個時代 ie 就是最好用的瀏覽器(別無選擇)。

          DEMO



          這種方式上傳文件,不需要 js ,而且沒有兼容問題,所有瀏覽器都支持,就是體驗很差,導致頁面刷新,頁面其他數據丟失。

          HTML

           <form method="post" action="http://localhost:8100" enctype="multipart/form-data">
          
                  選擇文件:
                      <input type="file" name="f1"/> input 必須設置 name 屬性,否則數據無法發送<br/>
          <br/>
                      標題:<input type="text" name="title"/><br/><br/><br/>
          
                  <button type="submit" id="btn-0">上 傳</button>
          
          </form>
          
          復制代碼

          文件上傳接口

          服務端文件的保存基于現有的庫koa-body結合 koa2實現服務端文件的保存和數據的返回。

          在項目開發中,文件上傳本身和業務無關,代碼基本上都可通用。

          在這里我們使用koa-body庫來實現解析和文件的保存。

          koa-body 會自動保存文件到系統臨時目錄下,也可以指定保存的文件路徑。



          然后在后續中間件內得到已保存的文件的信息,再做二次處理。

          • ctx.request.files.f1 得到文件信息,f1為input file 標簽的 name
          • 獲得文件的擴展名,重命名文件

          NODE

          /**
           * 服務入口
           */
          var http = require('http');
          var koaStatic = require('koa-static');
          var path = require('path');
          var koaBody = require('koa-body');//文件保存庫
          var fs = require('fs');
          var Koa = require('koa2');
          
          var app = new Koa();
          var port = process.env.PORT || '8100';
          
          var uploadHost= `http://localhost:${port}/uploads/`;
          
          app.use(koaBody({
              formidable: {
                  //設置文件的默認保存目錄,不設置則保存在系統臨時目錄下  os
                  uploadDir: path.resolve(__dirname, '../static/uploads')
              },
              multipart: true // 開啟文件上傳,默認是關閉
          }));
          
          //開啟靜態文件訪問
          app.use(koaStatic(
              path.resolve(__dirname, '../static') 
          ));
          
          //文件二次處理,修改名稱
          app.use((ctx) => {
              var file = ctx.request.files.f1;//得道文件對象
              var path = file.path;
              var fname = file.name;//原文件名稱
              var nextPath = path+fname;
              if(file.size>0 && path){
                  //得到擴展名
                  var extArr = fname.split('.');
                  var ext = extArr[extArr.length-1];
                  var nextPath = path+'.'+ext;
                  //重命名文件
                  fs.renameSync(path, nextPath);
              }
              //以 json 形式輸出上傳文件地址
              ctx.body = `{
                  "fileUrl":"${uploadHost}${nextPath.slice(nextPath.lastIndexOf('/')+1)}"
              }`;
          });
          
          /**
           * http server
           */
          var server = http.createServer(app.callback());
          server.listen(port);
          console.log('demo1 server start ......   ');
          復制代碼

          CODE

          https://github.com/Bigerfe/fe-learn-code/

          x1 工具準備

          工欲善其事必先利其器,爬取語料的根基便是基于python。

          我們基于python3進行開發,主要使用以下幾個模塊:requests、lxml、json。

          簡單介紹一個各模塊的功能

          01|requests

          requests是一個Python第三方庫,處理URL資源特別方便。它的官方文檔上寫著大大口號:HTTP for Humans(為人類使用HTTP而生)。相比python自帶的urllib使用體驗,筆者認為requests的使用體驗比urllib高了一個數量級。

          我們簡單的比較一下:

          urllib:

           1import urllib2
           2import urllib
           3
           4URL_GET = "https://api.douban.com/v2/event/list"
           5#構建請求參數
           6params = urllib.urlencode({'loc':'108288','day_type':'weekend','type':'exhibition'})
           7
           8#發送請求
           9response = urllib2.urlopen('?'.join([URL_GET,'%s'])%params)
          10#Response Headers
          11print(response.info())
          12#Response Code
          13print(response.getcode())
          14#Response Body
          15print(response.read())
          復制代碼
          

          requests:

           1import requests
           2
           3URL_GET = "https://api.douban.com/v2/event/list"
           4#構建請求參數
           5params = {'loc':'108288','day_type':'weekend','type':'exhibition'}
           6
           7#發送請求
           8response = requests.get(URL_GET,params=params)
           9#Response Headers
          10print(response.headers)
          11#Response Code
          12print(response.status_code)
          13#Response Body
          14print(response.text)復制代碼
          

          我們可以發現,這兩種庫還是有一些區別的:

          1. 參數的構建:urllib需要對參數進行urlencode編碼處理,比較麻煩;requests無需額外編碼處理,十分簡潔。

          2. 請求發送:urllib需要額外對url參數進行構造,變為符合要求的形式;requests則簡明很多,直接get對應鏈接與參數。

          3. 連接方式:看一下返回數據的頭信息的“connection”,使用urllib庫時,"connection":"close",說明每次請求結束關掉socket通道,而使用requests庫使用了urllib3,多次請求重復使用一個socket,"connection":"keep-alive",說明多次請求使用一個連接,消耗更少的資源

          4. 編碼方式:requests庫的編碼方式Accept-Encoding更全,在此不做舉例

          綜上所訴,使用requests更為簡明、易懂,極大的方便我們開發。

          02|lxml

          BeautifulSoup是一個庫,而XPath是一種技術,python中最常用的XPath庫是lxml。

          當我們拿到requests返回的頁面后,我們怎么拿到想要的數據呢?這個時候祭出lxml這強大的HTML/XML解析工具。python從不缺解析庫,那么我們為什么要在眾多庫里選擇lxml呢?我們選擇另一款出名的HTML解析庫BeautifulSoup來進行對比。

          我們簡單的比較一下:

          BeautifulSoup:

          1from bs4 import BeautifulSoup #導入庫
          2# 假設html是需要被解析的html
          3
          4#將html傳入BeautifulSoup 的構造方法,得到一個文檔的對象
          5soup = BeautifulSoup(html,'html.parser',from_encoding='utf-8')
          6#查找所有的h4標簽 
          7links = soup.find_all("h4")
          復制代碼
          

          lxml:

          1from lxml import etree
          2# 假設html是需要被解析的html
          3
          4#將html傳入etree 的構造方法,得到一個文檔的對象
          5root = etree.HTML(html)
          6#查找所有的h4標簽 
          7links = root.xpath("http://h4")
          復制代碼
          

          我們可以發現,這兩種庫還是有一些區別的:

          1. 解析html: BeautifulSoup的解析方式和JQ的寫法類似,API非常人性化,支持css選擇器;lxml的語法有一定的學習成本

          2. 性能:BeautifulSoup是基于DOM的,會載入整個文檔,解析整個DOM樹,因此時間和內存開銷都會大很多;而lxml只會局部遍歷,另外lxml是用c寫的,而BeautifulSoup是用python寫的,明顯的性能上lxml>>BeautifulSoup。

          綜上所訴,使用BeautifulSoup更為簡明、易用,lxml雖然有一定學習成本,但總體也很簡明易懂,最重要的是它基于C編寫,速度快很多,對于筆者這種強迫癥,自然而然就選lxml啦。

          03|json

          python自帶json庫,對于基礎的json的處理,自帶庫完全足夠。但是如果你想更偷懶,可以使用第三方json庫,常見的有demjson、simplejson。

          這兩種庫,無論是import模塊速度,還是編碼、解碼速度,都是simplejson更勝一籌,再加上兼容性 simplejson 更好。所以大家如果想使用方庫,可以使用simplejson。

          0x2 確定語料源

          將武器準備好之后,接下來就需要確定爬取方向。

          以電競類語料為例,現在我們要爬電競類相關語料。大家熟悉的電競平臺有企鵝電競、企鵝電競和企鵝電競(斜眼),所以我們以企鵝電競上直播的游戲作為數據源進行爬取。

          我們登陸企鵝電競官網,進入游戲列表頁,可以發現頁面上有很多游戲,通過人工去寫這些游戲名收益明顯不高,于是我們就開始我們爬蟲的第一步:游戲列表爬取。


           1import requests
           2from lxml import etree
           3
           4# 更新游戲列表
           5def _updateGameList():
           6 # 發送HTTP請求時的HEAD信息,用于偽裝為瀏覽器
           7 heads = { 
           8 'Connection': 'Keep-Alive',
           9 'Accept': 'text/html, application/xhtml+xml, */*',
          10 'Accept-Language': 'en-US,en;q=0.8,zh-Hans-CN;q=0.5,zh-Hans;q=0.3',
          11 'Accept-Encoding': 'gzip, deflate',
          12 'User-Agent': 'Mozilla/6.1 (Windows NT 6.3; WOW64; Trident/7.0; rv:11.0) like Gecko'
          13 }
          14 # 需要爬取的游戲列表頁
          15 url = 'https://egame.qq.com/gamelist'
          16
          17 # 不壓縮html,最大鏈接時間為10妙
          18 res = requests.get(url, headers=heads, verify=False, timeout=10)
          19 # 為防止出錯,編碼utf-8
          20 res.encoding = 'utf-8'
          21 # 將html構建為Xpath模式
          22 root = etree.HTML(res.content)
          23 # 使用Xpath語法,獲取游戲名
          24 gameList = root.xpath("http://ul[@class='livelist-mod']//li//p//text()")
          25 # 輸出爬到的游戲名
          26 print(gameList)
          復制代碼
          

          當我們拿到這幾十個游戲名后,下一步就是對這幾十款游戲進行語料爬取,這時候問題就來了,我們要從哪個網站來爬這幾十個游戲的攻略呢,taptap?多玩?17173?在對這幾個網站進行分析后,發現這些網站僅有一些熱門游戲的文章語料,一些冷門或者低熱度的游戲,例如“靈魂籌碼”、“奇跡:覺醒”、“死神來了”等,很難在這些網站上找到大量文章語料,如圖所示:

          我們可以發現,“ 奇跡:覺醒”、“靈魂籌碼”的文章語料特別少,數量上不符合我們的要求。 那么有沒有一個比較通用的資源站,它擁有著無比豐富的文章語料,可以滿足我們的需求。

          其實靜下心來想想,這個資源站我們天天都有用到,那就是百度。我們在百度新聞搜索相關游戲,拿到搜索結果列表,這些列表的鏈接的網頁內容幾乎都與搜索結果強相關,這樣我們數據源不夠豐富的問題便輕松解決了。但是此時出現了一個新的問題,并且是一個比較難解決的問題——如何抓取到任意網頁的文章內容?

          因為不同的網站都有不同的頁面結構,我們無法與預知將會爬到哪個網站的數據,并且我們也不可能針對每一個網站都去寫一套爬蟲,那樣的工作量簡直難以想象!但是我們也不能簡單粗暴的將頁面中的所有文字都爬下來,用那樣的語料來進行訓練無疑是噩夢!

          經過與各個網站斗智斗勇、查詢資料與思索之后,終于找到一條比較通用的方案,下面為大家講一講筆者的思路。

          0x3 任意網站的文章語料爬取

          01|提取方法

          1)基于Dom樹正文提取

          2)基于網頁分割找正文塊

          3)基于標記窗的正文提取

          4)基于數據挖掘或機器學習

          5)基于行塊分布函數正文提取

          02|提取原理

          大家看到這幾種是不是都有點疑惑了,它們到底是怎么提取的呢?讓筆者慢慢道來。

          1)基于Dom樹的正文提?。?/strong>

          這一種方法主要是通過比較規范的HTML建立Dom樹,然后地柜遍歷Dom,比較并識別各種非正文信息,包括廣告、鏈接和非重要節點信息,將非正文信息抽離之后,余下來的自然就是正文信息。

          但是這種方法有兩個問題

          ① 特別依賴于HTML的良好結構,如果我們爬取到一個不按W3c規范的編寫的網頁時,這種方法便不是很適用。

          ② 樹的建立和遍歷時間復雜度、空間復雜度都較高,樹的遍歷方法也因HTML標簽會有不同的差異。

          2) 基于網頁分割找正文塊 :

          這一種方法是利用HTML標簽中的分割線以及一些視覺信息(如文字顏色、字體大小、文字信息等)。

          這種方法存在一個問題:

          ① 不同的網站HTML風格迥異,分割沒有辦法統一,無法保證通用性。

          3) 基于標記窗的正文提?。?/strong>

          先科普一個概念——標記窗,我們將兩個標簽以及其內部包含的文本合在一起成為一個標記窗(比如 <h1>我是h1</h1> 中的“我是h1”就是標記窗內容),取出標記窗的文字。

          這種方法先取文章標題、HTML中所有的標記窗,在對其進行分詞。然后計算標題的序列與標記窗文本序列的詞語距離L,如果L小于一個閾值,則認為此標記窗內的文本是正文。

          這種方法雖然看上去挺好,但其實也是存在問題的:

          ① 需要對頁面中的所有文本進行分詞,效率不高。

          ② 詞語距離的閾值難以確定,不同的文章擁有不同的閾值。

          4)基于數據挖掘或機器學習

          使用大數據進行訓練,讓機器提取主文本。

          這種方法肯定是極好的,但是它需要先有html與正文數據,然后進行訓練。我們在此不進行探討。

          5)基于行塊分布函數正文提取

          對于任意一個網頁,它的正文和標簽總是雜糅在一起。此方法的核心有亮點:① 正文區的密度;② 行塊的長度;一個網頁的正文區域肯定是文字信息分布最密集的區域之一,這個區域可能最大(評論信息長、正文較短),所以同時引進行塊長度進行判斷。

          實現思路:

          ① 我們先將HTML去標簽,只留所有正文,同時留下標簽取出后的所有空白位置信息,我們稱其為Ctext;

          ② 對每一個Ctext取周圍k行(k<5),合起來稱為Cblock;

          ③ 對Cblock去掉所有空白符,其文字總長度稱為Clen;

          ④ 以Ctext為橫坐標軸,以各行的Clen為縱軸,建立坐標系。

          以這個網頁為例: http://www.gov.cn/ldhd/2009-11/08/content_1459564.htm 該網頁的正文區域為145行至182行。


          由上圖可知,正確的文本區域全都是分布函數圖上含有最值且連續的一個區域,這個區域往往含有一個驟升點和一個驟降點。因此,網頁正文抽取問題轉化為了求行塊分布函數上的驟升點和驟降點兩個邊界點,這兩個邊界點所含的區域包含了當前網頁的行塊長度最大值并且是連續的。

          經過大量實驗,證明此方法對于中文網頁的正文提取有較高的準確度,此算法的優點在于,行塊函數不依賴與HTML代碼,與HTML標簽無關,實現簡單,準確率較高。

          主要邏輯代碼如下:

           1# 假設content為已經拿到的html
           2
           3# Ctext取周圍k行(k<5),定為3
           4blocksWidth = 3
           5# 每一個Cblock的長度
           6Ctext_len = []
           7# Ctext
           8lines = content.split('n')
           9# 去空格
          10for i in range(len(lines)):
          11 if lines[i] == ' ' or lines[i] == 'n':
          12 lines[i] = ''
          13# 計算縱坐標,每一個Ctext的長度
          14for i in range(0, len(lines) - blocksWidth):
          15 wordsNum = 0
          16 for j in range(i, i + blocksWidth):
          17 lines[j] = lines[j].replace("\s", "")
          18 wordsNum += len(lines[j])
          19 Ctext_len.append(wordsNum)
          20# 開始標識
          21start = -1
          22# 結束標識
          23end = -1
          24# 是否開始標識
          25boolstart = False
          26# 是否結束標識
          27boolend = False
          28# 行塊的長度閾值
          29max_text_len = 88
          30# 文章主內容
          31main_text = []
          32# 沒有分割出Ctext
          33if len(Ctext_len) < 3:
          34 return '沒有正文'
          35for i in range(len(Ctext_len) - 3):
          36 # 如果高于這個閾值
          37 if(Ctext_len[i] > max_text_len and (not boolstart)):
          38 # Cblock下面3個都不為0,認為是正文
          39 if (Ctext_len[i + 1] != 0 or Ctext_len[i + 2] != 0 or Ctext_len[i + 3] != 0):
          40 boolstart = True
          41 start = i
          42 continue
          43 if (boolstart):
          44 # Cblock下面3個中有0,則結束
          45 if (Ctext_len[i] == 0 or Ctext_len[i + 1] == 0):
          46 end = i
          47 boolend = True
          48 tmp = []
          49
          50 # 判斷下面還有沒有正文
          51 if(boolend):
          52 for ii in range(start, end + 1):
          53 if(len(lines[ii]) < 5):
          54 continue
          55 tmp.append(lines[ii] + "n")
          56 str = "".join(list(tmp))
          57 # 去掉版權信息
          58 if ("Copyright" in str or "版權所有" in str):
          59 continue
          60 main_text.append(str)
          61 boolstart = boolend = False
          62# 返回主內容
          63result = "".join(list(main_text))
          復制代碼
          

          0x4 結語

          至此我們就可以獲取任意內容的文章語料了,但這僅僅是開始,獲取到了這些語料后我們還需要在一次進行清洗、分詞、詞性標注等,才能獲得真正可以使用的語料。


          主站蜘蛛池模板: 精品无码人妻一区二区三区18| 亚洲国产欧美日韩精品一区二区三区| 亚洲视频一区二区| 日韩一区二区三区在线精品| 97久久精品无码一区二区| av无码人妻一区二区三区牛牛| 波多野结衣一区二区免费视频| 一区二区三区在线免费看| 亚洲色无码一区二区三区| 一区二区不卡视频在线观看| 精品国产一区二区三区香蕉事| 亚洲天堂一区二区三区四区| 亚洲AV无码一区二区三区DV| 日韩精品无码一区二区视频| 日本高清成本人视频一区| 动漫精品一区二区三区3d| 在线电影一区二区| eeuss鲁片一区二区三区| 韩国福利一区二区美女视频| 亚洲码一区二区三区| 无码国产精品一区二区免费16| 黑人大战亚洲人精品一区| 中文无码AV一区二区三区| 好爽毛片一区二区三区四 | 国产A∨国片精品一区二区| 视频在线观看一区| 久久久99精品一区二区| 久久无码人妻精品一区二区三区| 亚洲国产成人久久一区WWW| 国产激情无码一区二区三区 | 国产av熟女一区二区三区| 不卡无码人妻一区三区音频 | 亚洲Av无码国产一区二区| 无码人妻精品一区二区三区久久久 | 亚洲av午夜精品一区二区三区| 人妻无码一区二区视频| 久久99久久无码毛片一区二区| AV无码精品一区二区三区宅噜噜 | 精品一区二区久久| 在线播放一区二区| 无码一区18禁3D|