eb前端技術由html、css和 javascript三大部分構成,是一個龐大而復雜的技術體系,其復雜程度不低于任何一門后端語言。而我們在學習它的時候往往是先從某一個點切入,然后不斷地接觸和學習新的知識點,因此對于初學者很難理清楚整個體系的脈絡結構。本文將對Web前端知識體系進行簡單的梳理,對應的每個知識點點到為止,不作詳細介紹。目的是幫助大家審查自己的知識結構是否完善,如有遺漏或不正確的地方,希望共勉。
HTML 篇
1、BOM
BOM 是 Browser Object Model
的縮寫,即瀏覽器對象模型,當一個瀏覽器頁面初始化時,會在內存創建一個全局的對象,用以描述當前窗口的屬性和狀態,這個全局對象被稱為瀏覽器對象模型,即BOM。BOM的核心對象就是window,window
對象也是BOM的頂級對象,其中包含了瀏覽器的 6個核心模塊:
document -
即文檔對象,渲染引擎在解析HTML代碼時,會為每一個元素生成對應的DOM對象,由于元素之間有層級關系,因此整個HTML代碼解析完以后,會生成一個由不同節點組成的樹形結構,俗稱DOM樹,document
用于描述DOM樹的狀態和屬性,并提供了很多操作DOM的API。
frames - HTML 子框架,即在瀏覽器里嵌入另一個窗口,父框架和子框架擁有獨立的作用域和上下文。
history - 以棧(FIFO)的形式保存著頁面被訪問的歷史記錄,頁面前進即入棧,頁面返回即出棧。
location - 提供了當前窗口中加載的文檔相關信息以及一些導航功能。
navigator - 用來描述瀏覽器本身,包括瀏覽器的名稱、版本、語言、系統平臺、用戶特性字符串等信息。
screen - 提供了瀏覽器顯示屏幕的相關屬性,比如顯示屏幕的寬度和高度,可用寬度和高度。
2、DOM 系統
DOM 是 Document Object Model 的縮寫,即 文檔對象模型,是所有瀏覽器公共遵守的標準,DOM
將HTML和XML文檔映射成一個由不同節點組成的樹型結構,俗稱DOM樹。其核心對象是document,用于描述DOM樹的狀態和屬性,并提供對應的DOM操作API。隨著歷史的發展,DOM
被劃分為1級、2級、3級,共3個級別:
1級DOM - 在1998年10月份成為W3C的提議,由DOM核心與DOM
HTML兩個模塊組成。DOM核心能映射以XML為基礎的文檔結構,允許獲取和操作文檔的任意部分。DOM
HTML通過添加HTML專用的對象與函數對DOM核心進行了擴展。
2級DOM - 鑒于1級DOM僅以映射文檔結構為目標,DOM
2級面向更為寬廣。通過對原有DOM的擴展,2級DOM通過對象接口增加了對鼠標和用戶界面事件(DHTML長期支持鼠標與用戶界面事件)、范圍、遍歷(重復執行DOM文檔)和層疊樣式表(CSS)的支持。同時也對DOM
1的核心進行了擴展,從而可支持XML命名空間。
3級DOM -
通過引入統一方式載入和保存文檔和文檔驗證方法對DOM進行進一步擴展,DOM3包含一個名為“DOM載入與保存”的新模塊,DOM核心擴展后可支持XML1.0的所有內容,包括XML
Infoset、 XPath、和XML Base。
瀏覽器對不同級別DOM的支持情況如下所示:
從圖中可以看出,移動端常用的 webkit 內核瀏覽器目前只支持DOM2,而不支持DOM3 。
新手福利獲取方式:
1.在你手機的右上角有【關注】選項,或點擊我的頭像,點擊關注!(關注我)
2.關注后,手機客戶端點擊我的主頁面,右上角有私信,請私信發我:html
其實作為一個開發者,有一個學習的氛圍跟一個交流圈子特別重要這里請私信我“html”不管你是小白還是大牛歡迎入住大家一起交流成長。小編會在里面不定期分享干貨源碼,包括我精心整理的一份零基礎教程。歡迎各位感興趣的的小伙伴。
學習思路:
3、事件系統
事件是用戶與頁面交互的基礎,到目前為止,DOM事件從PC端的 鼠標事件(mouse) 發展到了 移動端的 觸摸事件(touch) 和
手勢事件(guesture),touch事件描述了手指在屏幕操作的每一個細節,guesture 則是描述多手指操作時更為復雜的情況,總結如下:
第一根手指放下,觸發 touchstart,除此之外什么都不會發生
手指滑動時,觸發touchmove
第二根手指放下,觸發 gesturestart
觸發第二根手指的 touchstart
立即觸發 gesturechange
任意手指移動,持續觸發 gesturechange
第二根手指彈起時,觸發 gestureend,以后將不會再觸發 gesturechange
觸發第二根手指的 touchend
觸發touchstart (多根手指在屏幕上,提起一根,會刷新一次全局touch) _ ___
彈起第一根手指,觸發 touchend
更多關于手勢事件的介紹請參考:
gesture事件處理復雜手勢
DOM2.0 模型將事件處理流程分為三個階段,即 事件捕獲階段 、 事件處理階段 、 事件冒泡階段, 如圖所示:
事件捕獲 :當用戶觸發點擊事件后,頂層對象document 就會發出一個事件流,從最外層的DOM節點向目標元素節點傳遞,最終到達目標元素。
事件處理 :當到達目標元素之后,執行目標元素綁定的處理函數。如果沒有綁定監聽函數,則不做任何處理。
事件冒泡 :事件流從目標元素開始,向最外層DOM節點傳遞,途中如果有節點綁定了事件處理函數,這些函數就會被執行。
利用事件冒泡原理可以實現 事件委托
,所謂事件委托,就是在父元素上添加事件監聽器,用以監聽和處理子元素的事件,避免重復為子元素綁定相同的事件。當目標元素的事件被觸發以后,這個事件就從目標元素開始,向最外層元素傳遞,最終冒泡到父元素上,父元素再通過event.target
獲取到這個目標元素,這樣做的好處是,父元素只需綁定一個事件監聽,就可以對所有子元素的事件進行處理了,從而減少了不必要的事件綁定,對頁面性能有一定的提升。
4、HTML解析過程
瀏覽器加載 html 文件以后,渲染引擎會從上往下,一步步來解析HTML標簽,大致過程如下:
用戶輸入網址,瀏覽器向服務器發出請求,服務器返回html文件;
渲染引擎開始解析 html 標簽,并將標簽轉化為DOM節點,生成 DOM樹;
如果head 標簽中引用了外部css文件,則發出css文件請求,服務器返回該文件,該過程會阻塞后面的解析;
如果引用了外部 js 文件,則發出 js 文件請求,服務器返回后立即執行該腳本,這個過程也會阻塞html的解析;
引擎開始解析 body 里面的內容,如果標簽里引用了css 樣式,就需要解析剛才下載好的css文件,然后用css來設置標簽的樣式屬性,并生成渲染樹;
如果 body 中的 img 標簽引用了圖片資源,則立即向服務器發出請求,此時引擎不會等待圖片下載完畢,而是繼續解析后面的標簽;
服務器返回圖片文件,由于圖片需要占用一定的空間,會影響到后面元素的排版,因此引擎需要重新渲染這部分內容;
如果此時 js 腳本中運行了 style.display="none",布局被改變,引擎也需要重新渲染這部分代碼;
直到 html 結束標簽為止,頁面解析完畢。
5、重繪 和 回流
當渲染樹中的一部分(或全部)因為元素的規模尺寸,布局,隱藏等改變而需要重新構建。這就稱為回流。比如上面的img文件加載完成后就會引起回流,每個頁面至少需要一次回流,就是在頁面第一次加載的時候。
當渲染樹中的一些元素需要更新屬性,而這些屬性只是影響元素的外觀,風格,而不會影響布局的,比如 background-color。則就叫稱為重繪。
從上面可以看出,回流必將引起重繪,而重繪不一定會引起回流。會引起重繪和回流的操作如下:
添加、刪除元素(回流+重繪)
隱藏元素,display:none(回流+重繪),visibility:hidden(只重繪,不回流)
移動元素,比如改變top,left的值,或者移動元素到另外一個父元素中。(重繪+回流)
對style的操作(對不同的屬性操作,影響不一樣)
還有一種是用戶的操作,比如改變瀏覽器大小,改變瀏覽器的字體大小等(回流+重繪)
另外,transform
操作不會引起重繪和回流,是一種高效率的渲染。這是因為transform屬于合成屬性,對合成屬性進行transition/animation
動畫時將會創建一個合成層,這使得動畫元素在一個獨立的層中進行渲染,當元素的內容沒有發生改變,就沒必要進行重繪,瀏覽器會通過重新復合來創建動畫幀。
6、本地存儲
本地存儲最原始的方式就是 cookie,cookie 是存放在本地瀏覽器的一段文本,數據以鍵值對的形式保存,可以設置過期時間。 但是 cookie
不適合大量數據的存儲,因為每請求一次頁面,cookie 都會發送給服務器,這使得 cookie
速度很慢而且效率也不高。因此cookie的大小被限制為4k左右(不同瀏覽器可能不同,分HOST),如下所示:
Firefox和Safari允許cookie多達4097個字節,包括名(name)、值(value) 和 等號。
Opera允許cookie多達4096個字節,包括:名(name)、值(value) 和 等號。
Internet Explorer允許cookie多達4095個字節,包括:名(name)、值(value) 和 等號。
在所有瀏覽器中,任何cookie大小超過限制都被忽略,且永遠不會被設置。
html5 提供了兩種在客戶端存儲數據的新方法:localStorage 和 sessionStorage, 它們都是以key/value
的形式來存儲數據,前者是永久存儲,后者的存儲期限僅限于瀏覽器會話(session),即當瀏覽器窗口關閉后,sessionStorage中的數據被清除。
localStorage的存儲空間大約5M左右(不同瀏覽器可能不同,分
HOST),這個相當于一個5M大小的前端數據庫,相比于cookie,可以節約帶寬,但localStorage在瀏覽器隱私模式下是不可讀取的,當存儲數據超過了localStorage
的存儲空間后會拋出異常。
此外,H5還提供了逆天的websql和
indexedDB,允許前端以關系型數據庫的方式來存儲本地數據,相對來說,這個功能目前應用的場景比較少,此處不作介紹。
7、瀏覽器緩存機制
瀏覽器緩存機制是指通過 HTTP 協議頭里的 Cache-Control (或 Expires) 和 Last-Modified (或 Etag)
等字段來控制文件緩存的機制。
Cache-Control 用于控制文件在本地緩存有效時長。最常見的,比如服務器回包:Cache-Control:max-age=600
表示文件在本地應該緩存,且有效時長是600秒 (從發出請求算起)。在接下來600秒內,如果有請求這個資源,瀏覽器不會發出 HTTP
請求,而是直接使用本地緩存的文件。
Last-Modified 是標識文件在服務器上的最新更新時間。下次請求時,如果文件緩存過期,瀏覽器通過 If-Modified-Since
字段帶上這個時間,發送給服務器,由服務器比較時間戳來判斷文件是否有修改。如果沒有修改,服務器返回304告訴瀏覽器繼續使用緩存;如果有修改,則返回200,同時返回最新的文件。
Cache-Control 通常與 Last-Modified 一起使用。一個用于控制緩存有效時間,一個在緩存失效后,向服務查詢是否有更新。
Cache-Control 還有一個同功能的字段:Expires。Expires 的值一個絕對的時間點,如:Expires: Thu, 10 Nov
2015 08:45:11 GMT,表示在這個時間點之前,緩存都是有效的。
Expires 是 HTTP1.0 標準中的字段,Cache-Control 是 HTTP1.1
標準中新加的字段,功能一樣,都是控制緩存的有效時間。當這兩個字段同時出現時,Cache-Control 是高優化級的。
Etag 也是和 Last-Modified 一樣,對文件進行標識的字段。不同的是,Etag
的取值是一個對文件進行標識的特征字串。在向服務器查詢文件是否有更新時,瀏覽器通過 If-None-Match
字段把特征字串發送給服務器,由服務器和文件最新特征字串進行匹配,來判斷文件是否有更新。沒有更新回包304,有更新回包200。Etag 和
Last-Modified 可根據需求使用一個或兩個同時使用。兩個同時使用時,只要滿足基中一個條件,就認為文件沒有更新。
另外有兩種特殊的情況:
手動刷新頁面(F5),瀏覽器會直接認為緩存已經過期(可能緩存還沒有過期),在請求中加上字段:Cache-Control:max-age=0,發包向服務器查詢是否有文件是否有更新。
強制刷新頁面(Ctrl+F5),瀏覽器會直接忽略本地的緩存(有緩存也會認為本地沒有緩存),在請求中加上字段:Cache-Control:no-cache
(或 Pragma:no-cache),發包向服務重新拉取文件。
8、History
用戶訪問網頁的歷史記錄通常會被保存在一個類似于棧的對象中,即history對象,點擊返回就出棧,跳下一頁就入棧。 它提供了以下方法來操作頁面的前進和后退:
window.history.back( ) 返回到上一個頁面
window.history.forward( ) 進入到下一個頁面
window.history.go( [delta] ) 跳轉到指定頁面
HTML5 對History Api 進行了增強,新增了兩個Api 和一個事件,分別是pushState、replaceState 和
onpopstate:
pushState是往history對象里添加一個新的歷史記錄,即壓棧。
replaceState 是替換history對象中的當前歷史記錄。
當點擊瀏覽器后退按鈕或 js調用history.back 都會觸發 onpopstate 事件。
與其類似的還有一個事件:onhashchange,onhashchange是老API,瀏覽器支持度高,本來是用來監聽hash變化的,但可以被利用來做客戶端前進和后退事件的監聽,而onpopstate是專門用來監聽瀏覽器前進后退的,不僅可以支持hash,非hash的同源
url 也支持。
9、HTML5離線緩存
HTML5離線緩存又叫Application
Cache,是從瀏覽器的緩存中分出來的一塊緩存區,如果要在這個緩存中保存數據,可以使用一個描述文件(manifest file),列出要下載和緩存的資源。
manifest 文件是簡單的文本文件,它告知瀏覽器被緩存的內容(以及不緩存的內容)。manifest 文件可分為三個部分:
- CACHE MANIFEST - 在此標題下列出的文件將在首次下載后進行緩存
- NETWORK - 在此標題下列出的文件需要與服務器的連接,且不會被緩存
- FALLBACK - 在此標題下列出的文件規定當頁面無法訪問時的回退頁面(比如 404 頁面)
離線緩存為應用帶來三個優勢:
離線瀏覽 - 用戶可在應用離線時使用它們
速度 - 已緩存資源加載得更快
減少服務器負載 - 瀏覽器將只從服務器下載更新過或更改過的資源。
10、Web語義化 和 SEO
Web語義化是指使用語義恰當的標簽,使頁面有良好的結構,頁面元素有含義,能夠讓人和搜索引擎都容易理解。
SEO是指在了解搜索引擎自然排名機制的基礎之上,對網站進行內部及外部的調整優化,改進網站在搜索引擎中關鍵詞的自然排名,獲得更多的展現量,吸引更多目標客戶點擊訪問網站,從而達到互聯網營銷及品牌建設的目標。
搜索引擎通過爬蟲技術獲取的頁面就是由一堆 html 標簽組成的代碼,人可以通過可視化的方式來判斷頁面上哪些內容是重點,而機器做不到。
但搜索引擎會根據標簽的含義來判斷內容的權重,因此,在合適的位置使用恰當的標簽,使整個頁面的語義明確,結構清晰,搜索引擎才能正確識別頁面中的重要內容,并予以較高的權值。比如h1~h6這幾個標簽在SEO中的權值非常高,用它們作頁面的標題就是一個簡單的SEO優化。
我開始一個項目,并開始計劃如何布局主頁時,我的大腦復現出浮動和定位。有些人可能會使用 Bootstrap 或其他框架。 那是因為這是2016年,我們一直在用這些方法來做布局。 但假設我們乘坐時光機來到2018年,所有主流瀏覽器都支持CSS Grid 布局模塊。此時我們的頁面布局模式已經完全改變,CSS的功能最終強大到能輕松實現我們的設計目標,這是一個web開發人員最美好的時代。現在,讓我們使用超贊的工具——Grid布局來創建一個主頁。
設計
下面是我們將要實現的頁面
在我們開始編碼之前,我們需要進入網格的思維模式。 第一步是觀察我們的設計稿,并將其劃分為主要的網格組件。 以下是我為此設計做的劃分:
你會發現整個頁面分為7個頂級網格區域。 我之所以說“頂級”是因為我們可以在其內部繼續嵌套網格,這正是我們將要對hero部分所做的事:
HTML
這是HTML的基本結構。 稍后我會顯示整個完成的文件,但現在我已經省去了大部分的細節。 這里要注意的重要部分是作為 body 的直接后代的7個元素:top-bar、main-header、hero、 blog-posts、 news、 side-bar 以及 main-footer。 body將成為我們的網格容器(grid container),它的孩子將成為網格項(grid items)。
正如剛剛提到的,我們也將設置 hero 作為網格容器。 它有兩個孩子,將作為網格項:message和 award。
<body> <header class="top-bar"> <!-- social links and contact info --> </header> <header class="main-header"> <!-- logo and main navigation --> </header> <section class="hero"> <div class="message"> <!-- circular element --> </div> <div class="award"> <!-- award image and quote --> </div> </section> <section class="blog-posts"> <!-- blog posts and excerpts --> </section> <section class="news"> <!-- news headlines and excerpts --> </section> <aside class="side-bar"> <!-- critter of the month info --> </aside> <footer class="main-footer"> <!-- footer menu and copyright --> </footer> </body>
CSS
Okey,我們按照這種方式講解,教程中我們不會展示所有使用到的CSS,在文章的最后我會展示最終完整的文件。現在我們只關注吸引我們的網格部分以及任何與它直接相關的樣式即可。
我們首先在body上定義主網格容器:
body{ display: grid; grid-template-columns: 12% auto 400px 12%; grid-template-rows: auto auto 950px auto auto auto; }
我們剛剛創建了一個4列6行的網格,第一列和最后一列將作為主內容兩側的填充。 我把第三列設置為400px,因為這是我們將要放置side-bar元素的地方,我們希望這是一個固定的寬度。 hero 元素(第三行)的固定高度為950px。
現在我們使用grid-template-areas來定義某個網格區域會跑到哪里。 這是非常有趣的部分:
body{ display: grid; grid-template-columns: 12% auto 400px 12%; grid-template-rows: auto auto 950px auto auto auto; grid-template-areas: "top-bar top-bar top-bar top-bar" "main-header main-header main-header main-header" "hero hero hero hero" ". blog-posts side-bar ." ". news side-bar ." "main-footer main-footer main-footer main-footer"; }
grid-template-areas讓我們能把元素放在任何想要放置的地方,并且對于元素的布局該屬性給我們提供一個不錯的可視化。 值得注意的是,這里使用的值(top-bar,main-header,hero等)不是指那些元素的類名,而是指我們用grid-area屬性給它們起的名字,下一步我們將對它們命名。
當網格區域名稱重復時,該元素將跨越這些列/行。 例如,top-bar 橫跨四列,side-bar橫跨四行和五行。 .號代表空單元格。如果你回頭看看上面的完整設計,您會看到這個定義如何與我們的網格模式相匹配。
假設我們已經應用了我們所有的樣式,但還沒有為網格項分配網格區域名稱,到目前為止我們的頁面看起來還不太好:
在將網格區域名稱分配給網格項之前,網格將根據它們的源順序自動將我們的元素放置在網格中。 顯然這不是我們想要的。 為了使我們的布局按預期工作,我們需要定義我們的網格區域。所以我們繼續往下走:
.top-bar{ grid-area: top-bar; } .main-header{ grid-area: main-header; } .hero{ grid-area: hero; } .blog-posts{ grid-area: blog-posts; } .news{ grid-area: news; } .side-bar{ grid-area: side-bar; } .main-footer{ grid-area: main-footer; }
需要注意的是這些名稱可以隨意設置。 為了方便,我選擇了讓它們與類名相匹配。
現在,我們已經為網格項分配了網格區域名稱,它們將在被放置在網格中合適的位置。 這一步帶來的變化很大:
除了 hero 部分中的網格項外,所有內容都完全按照需要正確放置,我們差不多要完成了。
但是在我們修復 hero 部分之前,我想解釋一下一些難以理解的地方:主要內容兩邊的填充區域的設置。 作為提醒,我們再次把剛剛的設置搬過來,用如下方式調整列:
body{ grid-template-columns: 12% auto 400px 12%; }
設置為12%的兩列用于填充主要內容兩邊的空白,但是它們僅用于第四行和第五行。 回想一下,我們告訴我們的top-bar、main-header、hero和main-footer元素跨越所有列,包括這兩個“填充”列。 我們為什么這樣做? 因為我們希望這些元素的背景色橫跨越整個視窗寬度,且任何一側都沒有空白。 我們只想在 blog-post/news和sidebar元素周圍留出空白(第四行和第五行)。
為了讓元素水平覆蓋整個寬度,同時讓元素里面的內容保存一定的padding,我們需要顯示地在這些元素上設置padding:
.top-bar{ padding: 4px 12%; } .main-header{ padding: 12px 12%; } .hero{ padding: 55px 12% 0 12%; } .main-footer{ padding: 25px 12%; }
我們給元素設置左右 padding 為12%,這和grid-template-areas定義中的第一列和最后一列的寬度是一樣的。 現在,需要填充整個寬度的元素最終呈現的結果是,背景橫跨水平寬度,但其內容在兩側都預留出12%的空白。 很贊!
好了,讓我們來修復 hero 部分。 這也將是一個網格容器,因此我們把它定義為一個網格,就像剛剛做過的那樣:
.hero{ display: grid; grid-template-columns: auto 1fr auto; grid-template-rows: auto auto auto; grid-template-areas: ". . award" "message . . " ". . . "; }
這是一個3×3的網格,除了中間的列,其它都設置為 auto。 我們給中間一列大小設為1fr,因為我們希望在第一列和最后一列用東西填充后,剩下的空間完全需要完全填滿。
hero中只有兩個元素:message和award。 我們要message占據第二行的第一列,我們要award占據第一行的第三列。所以我們的完整網格定義應該如下所示:
.hero{ display: grid; grid-template-columns: auto 1fr auto; grid-template-rows: auto auto auto; grid-template-areas: ". . award" "message . . " ". . . "; }
下面我們所要做的就是命名我們的元素:
.message{ grid-area: message; } .award{ grid-area: award; }
就這樣,message和award卡入到位,我們的頁面完成:
引入響應式
CSS Grid 使用媒體查詢讓重新排列整個布局變得非常簡單。你所做的就是重新放置你的網格項。現在回到我們的設計,簡單起見,我們只對兩個寬度臨界值做響應式處理,1600px 和 1050px。我們需要對一些元素(padding、margin等)進行一些小的樣式調整,但是我不會把所有的樣式調整都全部展示在這里。后面我會放出完整的代碼,現在我們只需要關注關注網格相關的東西即可。
1600px 這個臨界點的處理比較簡單,當瀏覽器寬度到底1600px時我們將減少網站外部填充的地方。 之所以選擇1600px,是到了這個寬度后12%填充看起來不太合適。為了解決這個問題,我們需要做的是在body上改變grid-template-columns的值,將第一列和最后一列減少到2%。 我們還需要調整其他元素的填充以匹配:
@media (max-width: 1600px) { body{ grid-template-columns: 2% auto 400px 2%; } .top-bar{ padding: 4px 2%; } .main-header{ padding: 12px 2%; } .hero{ padding: 55px 2% 0 2%; } .main-footer{ padding: 25px 2%; } }
對于下一個臨界值,我們對網格項重新排列,使它們排列在一個列中。 再次回頭看看我們原來的代碼是如何對body進行設置的:
body{ display: grid; grid-template-columns: 12% auto 400px 12%; grid-template-rows: auto auto 950px auto auto auto; grid-template-areas: "top-bar top-bar top-bar top-bar" "main-header main-header main-header main-header" "hero hero hero hero" ". blog-posts side-bar ." ". news side-bar ." "main-footer main-footer main-footer main-footer"; }
下面是重新設置的媒體查詢:
@media (max-width: 1050px) { body{ grid-template-columns: 3% auto 3%; grid-template-rows: auto auto auto auto auto auto auto; grid-template-areas: "top-bar top-bar top-bar" "main-header main-header main-header" "hero hero hero" ". blog-posts ." ". news ." ". side-bar ." "main-footer main-footer main-footer"; } }
我們在這里做了一些重要的改變:將列數從四個減少到三個,將第一列和最后一列的值改為3%(3%在較窄的寬度上優于2%),添加了 附加行,將所有行的長度改為auto,并將side-bar移動到自己的行。 現在我們的頁面元素很適合在較窄的寬度下展示:
譯者:若愚老師
https://zhuanlan.zhihu.com/p/33031255
在《Python進階記錄之urllib模塊》中,我們介紹了Python內置的HTTP請求庫urllib模塊的基本用法,需要重點掌握使用urllib的request模塊進行簡單的get、post請求。今天我們講一下Python內置的HTML解析庫HTMLParser模塊,并結合之前的re模塊和urllib模塊實現爬取指定新聞頁提取新聞文本內容的小需求。
我們使用urllib模塊進行HTTP請求獲取到的是整個網頁的HTML,但是我們往往只需要其中一部分對我們有用的內容。這時我們就可以使用HTMLParser模塊來幫助我們處理HTML。
HTMLParser是Python內置的專門用來解析HTML的模塊。利用HTMLParser,我們可以分析出一段HTML里面的標簽、數據等,是一種處理HTML的簡便途徑。我們先來看一個官方的例子。
HTMLParser模塊官方例子
從上述代碼中可以看出,HTMLParser模塊來自html.parser,導包時要格外注意。使用HTMLParser時,我們需要定義一個繼承自HTMLParser的子類,并根據需要重寫HTMLParser父類中的成員方法。例子中使用的各方法作用如下:
handle_starttag(tag, attrs):識別HTML的開始標簽,例如<html>、<title>、<body>、<div>等。
handle_endtag(tag):識別HTML的結束標簽,例如</html>、</body>、</div>、</p>等。
handle_data(data):識別HTML標簽內容,例如“<p>Test</p>”中的Test。
handle_startendtag(tag, attrs):識別沒有結束標簽的HTML標簽,例如<img />等。
handle_comment(data):識別HTML中的注釋內容,一般是“<!-- 注釋 -->”中的注釋內容。
HTMLParser采用的是一種事件驅動的模式,HTMLParser找到一個特定的標記時,它會去調用一個用戶定義的函數,以此來通知程序處理。
我們可以利用這些方法來實現HTML解析相關的功能。其中參數tag表示的是HTML標簽,attrs是一個列表,列表元素為一個個“(屬性,值)”形式的元組。HTMLParser會自動將tag和attrs都轉為小寫,解析時調用feed( )方法,把待解析的HTML字符串傳入即可。
現在有以下網頁,我們需要獲取出里面的新聞文本內容。
待請求網頁
首先就是獲取該網頁的HTML。經過上節內容的學習,我們很容易想到利用urllib庫請求獲取這個網頁的HTML。
獲取網頁HTML
代碼很簡單,使用urlopen( )方法,傳入url即可。此時,我們已經得到了整個網頁的HTML,但是我們要獲取的是新聞內容,顯然此時的HTML中有太多我們不需要的東西。
觀察整個網頁HTML,我們發現新聞內容是包含在一個div中的。
新聞內容相關HTML
我們可以使用正則表達式re模塊將包含新聞內容的這個div提取出來。
提取新聞內容相關的HTML
新聞內容的div格式主要是:<div class="article-content">...</div>。由于該div下嵌套了其他div,如果我們直接使用r'<div class=\"article-content\">(.*?)</div>'進行正則提取,會發現在下一個</div>處就截斷了。為了正確提取所有新聞內容相關的HTML,我們需要在</div>前加一個</p>,以保證是在新聞內容結束的</div>處截斷。
此時,我們已經得到了新聞內容相關的HTML。現在還剩最后一部,就是把HTML標簽去掉,保留新聞文本內容。這一步,我們就可以利用HTMLParser來實現了。
HTMLParser提取新聞內容
我們定義一個繼承自HTMLParser的子類,然后重寫handle_data(data)方法獲取當前HTML中的文本內容即可。由于我們定義的私有變量__text是通過一個列表來逐條接收新聞內容的,所以我們在類中定義一個獲取私有變量__text的方法,并將列表轉換成字符串。至此,我們已經獲取到了新聞的文本內容。
然而,當前文本內容一整段在一起,看起來并不美觀,與實際分段的新聞文本內容也有差別。我們可以使用HTMLParser來美化新聞內容。我們知道,網頁上的新聞內容是通過“\n”、“\t”、“<br/>”等特殊符號或標簽進行間隔和分段的。因此,我們只需要在解析時替換掉這些特殊符號和標簽即可。
替換特殊符號和標簽
重寫handle_data(data)方法,識別文本的同時替換掉“\t”、“\r”、“\n”等特殊字符;重寫handle_starttag(tag, attrs)方法,識別出<p>、<br>進行替換;重寫handle_startendtag(tag, attrs)方法,識別出<br/>進行替換。再次運行程序,可以看到,新聞內容進行了分段,看起來就美觀多了。
上述過程實現了一個非常簡單的爬蟲,爬取新聞網頁,提取新聞內容。當然,由于我們目前還沒介紹其他第三方庫,實現起來還不夠靈活,例如獲取新聞內容的div我們現在只能通過正則表達式實現,但實際上,如果使用lxml(etree、xpath)、BeautifulSoup等第三方庫會更簡單實用。
以上內容介紹了Python內置的HTML解析庫HTMLParser模塊,需要重點掌握HTMLParser類常用方法的作用,能夠重寫這些方法進行自定義解析。感謝大家的支持與關注,歡迎一起學習交流~
*請認真填寫需求信息,我們會在24小時內與您取得聯系。