整合營(yíng)銷服務(wù)商

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

          免費(fèi)咨詢熱線:

          「2022」打算跳槽漲薪,必問面試題及答案-VUE篇

          「2022」打算跳槽漲薪,必問面試題及答案-VUE篇

          、為什么選擇VUE,解決了什么問題?

          vue.js 正如官網(wǎng)所說的,是一套構(gòu)建用戶界面的漸進(jìn)式框架。與其它重量級(jí)框架不同的是,vue 被設(shè)計(jì)為可以自底向上逐層應(yīng)用。vue 的核心庫(kù)只關(guān)注視圖層,不僅易于上手,還便于與第三方庫(kù)或既有項(xiàng)目整合。另外一方面,當(dāng)與現(xiàn)代化工具鏈以及各種支持類庫(kù)結(jié)合使用時(shí),vue 也完全能夠?yàn)閺?fù)雜的單頁應(yīng)用提供驅(qū)動(dòng)。

          vue.js 有聲明式,響應(yīng)式的數(shù)據(jù)綁定,組件化開發(fā),并且還使用虛擬 DOM 等技術(shù),統(tǒng)一編程規(guī)范和模塊等,將項(xiàng)目功能模塊化更方便組織和構(gòu)建復(fù)雜應(yīng)用,便于項(xiàng)目的擴(kuò)展和維護(hù)。 vue 框架維護(hù)及時(shí),且 Vue 3 將在 2022 年 2 月 7 日 成為新的默認(rèn)版本。

          2、如果加入 keep-alive,第一次進(jìn)入組件會(huì)執(zhí)行哪些生命周期函數(shù)?

          會(huì)執(zhí)行的鉤子函數(shù)以及它們的順序分別為:

          beforeCreat、created、beforeMount、mounted、activated

          3、key 的作用和工作原理。

          key 的作用主要是為了高效地更新虛擬 DOM,其原理是 vue 中在 patch 過程中,通過 key 可以精準(zhǔn)判斷兩個(gè)節(jié)點(diǎn)是否是同一個(gè),從而避免頻繁更新不同元素,使得整個(gè) patch 過程更加高效,減少 DOM 操作量,提高性能。

          另外,若不設(shè)置 key 還可能在列表更新時(shí),引發(fā)一些隱蔽的 bug 。vue 在使用相同標(biāo)簽名元素的過濾或切換時(shí),也會(huì)使用到 key 屬性,其目的也是為了讓 vue 可以區(qū)分它們,否則 vue 只會(huì)替換其內(nèi)部屬性而不會(huì)觸發(fā)過濾效果。

          4、v-if 和 v-for 的優(yōu)先級(jí)哪個(gè)高?

          v-for 的優(yōu)先級(jí)更高。

          如果 v-if 和 v-for 同時(shí)出現(xiàn),每次渲染都會(huì)先執(zhí)行循環(huán),再判斷條件,無論如何循環(huán)都不可避免,浪費(fèi)了性能。

          情景一:每次遍歷時(shí),都需要執(zhí)行 v-if 解析一次,浪費(fèi)性能。

          <ul>
            <li
              v-for="user in users"
              v-if="shouldShowUsers"
              :key="user.id"
            >
              {{ user.name }}
            </li>
          </ul>

          要避免出現(xiàn)這種情況,則在外層嵌套 template ,在這一層進(jìn)行 v-if 判斷,然后在內(nèi)部進(jìn)行 v-for 循環(huán)。可以改為:

          <ul>
            <template v-if="shouldShowUsers">
             <li
               v-for="user in users"
              :key="user.id"
            >
              {{ user.name }}
            </li>
           </template>
          </ul>

          情景二:v-if 和 v-for 同時(shí)出現(xiàn)在一個(gè)標(biāo)簽,過濾一個(gè)列表中的項(xiàng)目,比如:

          <ul>
            <li
              v-for="user in users"
              v-if="user.isActive"
              :key="user.id"
            >
              {{ user.name }}
            </li>
          </ul>

          在這種情況下,請(qǐng)將 users 替換為一個(gè)計(jì)算屬性,讓其返回過濾后的列表。

          <ul>
            <li
              v-for="user in activeUsers"
              :key="user.id"
            >
              {{ user.name }}
            </li>
          </ul>
          computed: {
            activeUsers: function () {
              return this.users.filter(function (user) {
                return user.isActive
              })
            }
          }

          5、談?wù)剬?duì) vue 組件化的理解。

          5.1、組件化的定義

          組件是獨(dú)立和可復(fù)用的代碼組織單元,組件系統(tǒng)是 vue 核心特性之一,它使開發(fā)者使用小型、獨(dú)立和通常可復(fù)用的組件構(gòu)建大型應(yīng)用。

          也可以通俗介紹,把一些用戶在程序中一些獨(dú)立的功能和模塊單獨(dú)提取出來,然后切分為更小的塊,這些塊有獨(dú)立的邏輯,有更好的復(fù)用性。

          組件按照分類有:頁面組件(導(dǎo)航)、業(yè)務(wù)組件(登錄)、通用組件(輸入框)。

          5.2、組件化特點(diǎn)

          vue 的組件是基于配置的,通常編寫的組件是組件配置而非組件,框架后續(xù)會(huì)生成其構(gòu)造函數(shù),它們基于 VueComponent 這個(gè)類擴(kuò)展于 vue 。

          常見的組件化技術(shù)有:prop 屬性、自定義事件、插槽等,這些主要用于組件之間的通信等。

          組件之間遵循單向數(shù)據(jù)流原則。

          5.3、組件化的優(yōu)點(diǎn)

          組件化的開發(fā)能大幅提高開發(fā)效率、測(cè)試性和復(fù)用性等。

          合理的劃分組件能夠大幅提升應(yīng)用性能,組件應(yīng)該是高內(nèi)聚,低耦合的。

          6、為什么 data 在組件內(nèi)必須是函數(shù),而 vue 的根實(shí)例則沒有此限制?

          vue 組件可能存在多個(gè)實(shí)例,如果使用對(duì)象形式定義 data ,則會(huì)導(dǎo)致它們公用一個(gè) data 對(duì)象,那么狀態(tài)變更將會(huì)影響所有組件實(shí)例,這是不合理的。

          如果采用函數(shù)的形式,在實(shí)例化組件時(shí),data 會(huì)被當(dāng)做工廠函數(shù)返回一個(gè)全新的 data 對(duì)象,有效規(guī)避多實(shí)例之間狀態(tài)污染問題。

          所以在組件中的 data 必須是函數(shù),不能使用對(duì)象形式。那為什么 vue 根實(shí)例沒有限制呢?

          在 vue 中根實(shí)例只能有一個(gè),所以不需要擔(dān)心多實(shí)例的問題,所以根實(shí)例中的 data 可以是函數(shù)也可以是對(duì)象。

          7、你了解哪些 vue 性能優(yōu)化的方法?

          我所了解的 vue 性能優(yōu)化方法分別有:

          1>、路由懶加載

          Vue.use(VueRouter)
          // 傳統(tǒng)寫法
          import Home from '@/views/login/index.vue'
          
          //路由懶加載
          const  Login=()=> import('@/views/login/index.vue') 
          
          const router=new VueRouter({
           routes: [
            { path: '/login', component: Login },
            { path: '/home', component: Home },
            ]
          export default router

          使用路由懶加載,項(xiàng)目打包的時(shí)候體積會(huì)大幅減小,訪問項(xiàng)目時(shí),這些組件也會(huì)按需進(jìn)行加載,大大提升了項(xiàng)目性能。

          2>、keep-alive 緩存頁面

          <template>
           <keep-alive>
            <router-view /> 
           </keep-alive>  
          </template>

          使用 keep-alive 之后會(huì)緩存頁面,第一次加載之后,關(guān)閉再次打開,頁面不會(huì)重新渲染。keep-alive 的屬性:

          • include:字符串或正則表達(dá)式。如果只緩存?zhèn)€別頁面,可以使用 include 屬性,只緩存匹配組件。
          • exclude:字符串或正則表達(dá)式。如果個(gè)別頁面不需要緩存時(shí),可以使用 exclude 屬性,任何匹配的組件都不會(huì)緩存。

          3>、v-for遍歷避免同時(shí)使用 v-if

          <ul>
            <li
              v-for="user in activeUsers"
              :key="user.id"
            >
              {{ user.name }}
            </li>
          </ul>
          computed: {
            activeUsers: function () {
              return this.users.filter(function (user) {
                return user.isActive
              })
            }
          }

          4>、長(zhǎng)列表性能優(yōu)化

          如果列表是純粹的數(shù)據(jù)展示,不會(huì)有任何的改變,就不需要做響應(yīng)式。

          export default{
           data(){
            return {
             users:[]
            }
           },
           created(){
            const user=await axios("/api/user")
            this.users=Object.freeze(user)
           }
          }

          Object.freeze() 方法可以凍結(jié)一個(gè)對(duì)象,對(duì)象被凍結(jié)之后不能被修改,可以讓性能大幅度提升。

          如果是大數(shù)據(jù)長(zhǎng)列表,可采用虛擬滾動(dòng),只渲染少部分區(qū)域的內(nèi)容。可采用三方 vue-virtual-scroll。

          5>、事件的銷毀

          vue組件銷毀時(shí),會(huì)自動(dòng)解綁它的全部指令及事件監(jiān)聽器,但是僅限于組件本身的事件。

          created(){
           this.timer=setInterval( this.refresh, 2000 )
          },
          beforeDestory(){
           clearInterval( this.timer )
          }

          6>、圖片懶加載

          對(duì)于圖片過多的頁面,為了加快頁面的加載速度,所以很多時(shí)候,需要把未出現(xiàn)在可視區(qū)域的圖片暫不進(jìn)行加載,滾動(dòng)到可視區(qū)域之后再開始加載。

          可以使用三方的 vue-lazyload 庫(kù)。

          <img v-lazy="/src/img/01.jpg" />

          7>、第三方插件按需引用

          使用三方庫(kù)時(shí),可以按需引入避免體積太大。比如 element-ui :

          import { Button  } from "element-ui"

          8>、無狀態(tài)的組件標(biāo)記為函數(shù)式組件

          <template functional>
           <div>組件內(nèi)容</div>  
          </template>

          通過 functional 將組件標(biāo)記為函數(shù)式組件,因?yàn)楹瘮?shù)式組件沒有實(shí)例,所以運(yùn)行時(shí)耗費(fèi)資源較少。

          另外還有 v-show 復(fù)用 DOM、子組件分割、SSR 等。

          8、computed 與 methods 、watch 的區(qū)別?

          computed VS methods

          computed:{
           yyds(){
            log("computed show")
            return "計(jì)算屬性"
           }
          },
          methods:{
           show(){
            log("method show")
            return "計(jì)算屬性"
           }
          }

          computed 是計(jì)算屬性,methods 內(nèi)都是方法,所以調(diào)用不同分別為:

          <div>yyds</div>
          <div>show()</div>

          computed 是有緩存的,而 methods 沒有緩存,所以 computed 性能比 methods 的好。

          computed VS watch

          computed 是計(jì)算某一個(gè)屬性的改變,如果某一個(gè)值改變了,計(jì)算屬性會(huì)監(jiān)測(cè)到,然后進(jìn)行返回值。

          watch 是監(jiān)聽某一個(gè)數(shù)據(jù)或路由,改變了才會(huì)響應(yīng),只有改變了才會(huì)執(zhí)行操作。

          9、你怎么理解 vue 中的 diff 算法?

          1.diff算法是虛擬DOM技術(shù)的必然產(chǎn)物:通過新舊虛擬DOM作對(duì)比(即diff),將變化的地方更新在真實(shí)DOM上;

          另外,也需要diff高效的執(zhí)行對(duì)比過程,從而降低時(shí)間復(fù)雜度為O(n)。(what)

          2.vue2.x中為了降低Watcher粒度,每個(gè)組件只有一個(gè)Watcher與之對(duì)應(yīng),只有引入diff才能精確找到發(fā)生變化的地方。(why)

          3.vue中diff執(zhí)行的時(shí)刻是組件實(shí)例執(zhí)行其更新函數(shù)時(shí),它會(huì)比對(duì)上一次渲染結(jié)果oldVnode和新的渲染結(jié)果newVnode,此過程稱為patch。(where)

          4.diff過程整體遵循深度優(yōu)先、同層比較的策略;兩個(gè)節(jié)點(diǎn)之間比較會(huì)根據(jù)它們是否擁有子節(jié)點(diǎn)或者文本節(jié)點(diǎn)做不同操作;(How)

          比較兩組子節(jié)點(diǎn)是算法的重點(diǎn),首先假設(shè)頭尾節(jié)點(diǎn)可能相同做4次比對(duì)嘗試,如果沒有找到相同節(jié)點(diǎn)才按照通用方式遍歷查找,查找結(jié)束再按情況處理剩下的節(jié)點(diǎn);

          借助key通常可以非常精確找到相同節(jié)點(diǎn),因此整個(gè)patch過程非常高效。

          10、props 和 data 的優(yōu)先級(jí)誰高?

          vue組件內(nèi)數(shù)據(jù)相關(guān)的屬性它們的樣式優(yōu)先級(jí)從高到底分別為:

          props > methods > data > computed > watch

          11、vue 組件之間的通信

          vue 組件之間的關(guān)系有:父子關(guān)系、兄弟關(guān)系、隔代關(guān)系。

          所以 vue 組件之間的通信可分為:父子組件之間通信,兄弟組件之間通信和跨層組件之間通信。

          1>、父?jìng)髯?/p>

          可使用的方法有:

          • 通過 props 傳值
          • 通過 refs 傳值
          • 通過 children 傳值

          2>、子傳父

          可使用的方法:

          • $emit 自定義事件
          • provide 和 inject

          3>、兄弟組件之間

          • 利用中央事件總線 bus 的 $emit 和 $on 。
          • 笨辦法,通過父組件共同傳值

          4>、跨層組件

          • provide 和 inject

          5>、沒有關(guān)系的組件之間通信

          • 可以使用 vuex 進(jìn)行數(shù)據(jù)管理

          端事件循環(huán)是什么

          前端事件循環(huán)指的是 JavaScript 在瀏覽器中運(yùn)行時(shí)的一種機(jī)制,它用于處理異步事件。事件循環(huán)會(huì)不斷檢查是否有事件發(fā)生,并調(diào)用相應(yīng)的回調(diào)函數(shù)來處理這些事件。

          事件循環(huán)的工作流程如下:

          1. 初始化:瀏覽器會(huì)初始化事件循環(huán),并創(chuàng)建一個(gè)事件隊(duì)列。
          2. 事件發(fā)生:當(dāng)用戶操作頁面、網(wǎng)絡(luò)請(qǐng)求完成或計(jì)時(shí)器超時(shí)等情況下,都會(huì)產(chǎn)生事件并添加到事件隊(duì)列中。
          3. 事件處理:事件循環(huán)會(huì)從事件隊(duì)列中取出事件,并調(diào)用事件的回調(diào)函數(shù)來處理事件。
          4. 重復(fù):事件循環(huán)會(huì)不斷重復(fù)步驟 2 和 3,直到事件隊(duì)列為空。

          事件循環(huán)的優(yōu)點(diǎn):

          • 高效:事件循環(huán)可以高效地處理大量的異步事件。
          • 可擴(kuò)展:事件循環(huán)可以擴(kuò)展到支持新的事件類型。
          • 跨平臺(tái):事件循環(huán)可以在不同的瀏覽器和操作系統(tǒng)上工作。

          事件循環(huán)的注意點(diǎn):

          • 事件循環(huán)是單線程的:這意味著所有事件都將在同一個(gè)線程中處理。
          • 事件循環(huán)不會(huì)阻塞:即使事件循環(huán)正在處理事件,應(yīng)用程序也可以執(zhí)行其他操作。
          • 用戶需要確保事件回調(diào)函數(shù)是線程安全的

          瀏覽器事件循環(huán)與 Node.js 事件循環(huán)

          Node.js 和瀏覽器都使用事件循環(huán)來處理異步事件。但是,兩者之間存在一些關(guān)鍵差異:

          1. 事件隊(duì)列

          • 瀏覽器:只有一個(gè)事件隊(duì)列,用于處理所有類型的事件,包括宏任務(wù)和微任務(wù)。
          • Node.js:有兩個(gè)事件隊(duì)列,一個(gè)用于宏任務(wù),另一個(gè)用于微任務(wù)。

          2. 任務(wù)執(zhí)行順序

          • 瀏覽器:宏任務(wù)和微任務(wù)交替執(zhí)行。在執(zhí)行完一個(gè)宏任務(wù)后,瀏覽器會(huì)檢查微任務(wù)隊(duì)列,并執(zhí)行所有待處理的微任務(wù)。
          • Node.js:宏任務(wù)和微任務(wù)是分離的。在事件循環(huán)的每個(gè)階段,Node.js 會(huì)先執(zhí)行所有待處理的宏任務(wù),然后再執(zhí)行所有待處理的微任務(wù)。

          3. 事件循環(huán)的階段

          • 瀏覽器:只有一個(gè)階段,用于處理所有類型的事件。
          • Node.js:有多個(gè)階段,每個(gè)階段用于處理特定類型的事件。例如,I/O 操作會(huì)在 I/O 階段處理,計(jì)時(shí)器會(huì)在計(jì)時(shí)器階段處理。

          4. timers

          • 瀏覽器:計(jì)時(shí)器是在瀏覽器主線程中執(zhí)行的。
          • Node.js:計(jì)時(shí)器是在單獨(dú)的線程中執(zhí)行的。

          5. nextTick

          • 瀏覽器:nextTick 會(huì)將回調(diào)函數(shù)排隊(duì)到微任務(wù)隊(duì)列中,在下一次事件循環(huán)迭代中執(zhí)行。
          • Node.js:nextTick 會(huì)將回調(diào)函數(shù)排隊(duì)到宏任務(wù)隊(duì)列中,在下一次事件循環(huán)的下一個(gè)階段執(zhí)行。

          Node.js 與 libuv

          Node.js 是一個(gè)基于 JavaScript 的運(yùn)行時(shí)環(huán)境,用于構(gòu)建服務(wù)器端應(yīng)用程序。libuv 是一個(gè)開源的 C 庫(kù),提供了一組異步 I/O 和事件處理 API。

          Node.js 與 libuv 的關(guān)系:

          • Node.js 是建立在 libuv 之上的。libuv 為 Node.js 提供了底層的異步 I/O 和事件處理功能。
          • Node.js 提供了一組 JavaScript API,用于使用 libuv 的功能。
          • libuv 可以被其他應(yīng)用程序和庫(kù)使用,而不僅僅是 Node.js。

          libuv 的主要功能:

          • 異步 I/O:libuv 提供了一組異步 I/O API,用于執(zhí)行網(wǎng)絡(luò)操作、文件操作和其他類型的 I/O 操作。
          • 事件處理:libuv 提供了一組事件處理 API,用于處理異步事件。
          • 線程池:libuv 提供了一個(gè)線程池,用于執(zhí)行 CPU 密集型任務(wù)。
          • 計(jì)時(shí)器:libuv 提供了一組計(jì)時(shí)器 API,用于執(zhí)行定時(shí)任務(wù)。

          Node.js 使用 libuv 的優(yōu)勢(shì):

          • 高效:libuv 提供了高效的異步 I/O 和事件處理功能,這使得 Node.js 應(yīng)用程序可以非常高效地處理大量的并發(fā)連接。
          • 可擴(kuò)展:libuv 可以擴(kuò)展到支持新的事件類型和新的 I/O 操作。
          • 跨平臺(tái):libuv 是跨平臺(tái)的,這意味著 Node.js 應(yīng)用程序可以在不同的操作系統(tǒng)上運(yùn)行。

          事件循環(huán)是 libuv 的核心,也是理解 libuv 的關(guān)鍵。事件循環(huán)是一種處理事件的機(jī)制,它會(huì)不斷檢查是否有事件發(fā)生,并調(diào)用相應(yīng)的回調(diào)函數(shù)來處理這些事件。

          libuv 的事件循環(huán)是基于 epoll 的,epoll 是 Linux 內(nèi)核中的一種高效事件通知機(jī)制。epoll 可以監(jiān)視大量的文件描述符,并通知應(yīng)用程序哪些文件描述符上有事件發(fā)生。

          libuv 的事件循環(huán)主要包含以下幾個(gè)部分:

          • 事件隊(duì)列:用于存儲(chǔ)待處理的事件。事件隊(duì)列是一個(gè)鏈表,每個(gè)事件節(jié)點(diǎn)包含以下信息:事件類型事件源事件回調(diào)函數(shù)用戶數(shù)據(jù)
          • 事件源:用于生成事件。事件源可以是 I/O 操作、定時(shí)器或信號(hào)。
          • 事件回調(diào)函數(shù):用于處理事件。事件回調(diào)函數(shù)可以執(zhí)行任何操作,例如讀取數(shù)據(jù)、寫入數(shù)據(jù)或執(zhí)行其他任務(wù)。

          事件循環(huán)的工作流程如下:

          1. 初始化事件循環(huán):libuv 會(huì)調(diào)用 uv_loop_init() 函數(shù)來初始化事件循環(huán)。
          2. 注冊(cè)事件源:libuv 會(huì)調(diào)用 uv_poll()、uv_timer_init() 或 uv_signal_init() 等函數(shù)來注冊(cè)事件源到事件循環(huán)。
          3. 啟動(dòng)事件循環(huán):libuv 會(huì)調(diào)用 uv_run() 函數(shù)來啟動(dòng)事件循環(huán)。
          4. 等待事件發(fā)生:libuv 會(huì)調(diào)用 epoll_wait() 函數(shù)來等待事件發(fā)生。
          5. 處理事件:如果有事件發(fā)生,libuv 會(huì)從事件隊(duì)列中取出事件節(jié)點(diǎn)。libuv 會(huì)調(diào)用事件節(jié)點(diǎn)的回調(diào)函數(shù)來處理事件。事件回調(diào)函數(shù)可以執(zhí)行任何操作,例如讀取數(shù)據(jù)、寫入數(shù)據(jù)或執(zhí)行其他任務(wù)。
          6. 重復(fù)步驟 4 和 5:事件循環(huán)會(huì)一直重復(fù)步驟 4 和 5,直到事件循環(huán)被停止。

          libuv 提供了以下幾種事件類型:

          • I/O 事件:用于監(jiān)視 I/O 操作的完成,例如讀寫操作。
          • 定時(shí)器事件:用于定時(shí)執(zhí)行某個(gè)函數(shù)。
          • 信號(hào)事件:用于處理操作系統(tǒng)信號(hào)。

          libuv 的事件循環(huán)具有以下優(yōu)點(diǎn):

          • 高效:epoll 可以高效地監(jiān)視大量的文件描述符。
          • 可擴(kuò)展:libuv 支持多種事件類型,可以擴(kuò)展到新的事件類型。
          • 跨平臺(tái):libuv 支持多種操作系統(tǒng),可以跨平臺(tái)使用。

          以下是一些 libuv 事件循環(huán)的示例代碼:

          static void uv__poll(uv_loop_t *loop) {
            int timeout=loop->time < 0 ? -1 : loop->time;
            int nfds=epoll_wait(loop->epoll_fd, loop->watchers, ARRAY_SIZE(loop->watchers), timeout);
          
            if (nfds < 0) {
              if (errno !=EAGAIN && errno !=EINTR) {
                uv__set_error(loop, errno);
              }
              return;
            }
          
            for (i=0; i < nfds; i++) {
              uv_watcher_t *w=loop->watchers[i].data;
          
              if (w->events & UV_READABLE) {
                w->cb(w, UV_READABLE, 0);
              }
          
              if (w->events & UV_WRITABLE) {
                w->cb(w, UV_WRITABLE, 0);
              }
            }
          }
          
          

          這段代碼是 libuv 的 uv__poll() 函數(shù),用于處理 I/O 事件。

          libuv 的事件循環(huán)是一種高效、可擴(kuò)展和跨平臺(tái)的事件處理機(jī)制。理解 libuv 的事件循環(huán)對(duì)于理解 libuv 的工作原理至關(guān)重要。

          eautiful Soup 簡(jiǎn)介

          Beautiful Soup 是一個(gè)可以從 HTML 或 XML 文件中提取數(shù)據(jù)的 Python 庫(kù),它提供了一些簡(jiǎn)單的操作方式來幫助你處理文檔導(dǎo)航,查找,修改文檔等繁瑣的工作。因?yàn)槭褂煤?jiǎn)單,所以 Beautiful Soup 會(huì)幫你節(jié)省不少的工作時(shí)間。

          Beautiful Soup 安裝

          你可以使用如下命令安裝 Beautiful Soup。二選一即可。

          $ easy_install beautifulsoup4
          
          $ pip install beautifulsoup4

          Beautiful Soup 不僅支持 Python 標(biāo)準(zhǔn)庫(kù)中的 HTML 解析器,還支持很多第三方的解析器,比如 lxml,html5lib 等。初始化 Beautiful Soup 對(duì)象時(shí)如果不指定解析器,那么 Beautiful Soup 將會(huì)選擇最合適的解析器(前提是你的機(jī)器安裝了該解析器)來解析文檔,當(dāng)然你也可以手動(dòng)指定解析器。這里推薦大家使用 lxml 解析器,功能強(qiáng)大,方便快捷,而且該解析器是唯一支持 XML 的解析器。

          你可以使用如下命令來安裝 lxml 解析器。二選一即可。

          $ easy_install lxml
          
          $ pip install lxml

          Beautiful Soup 小試牛刀

          Beautiful Soup 使用來起來非常簡(jiǎn)單,你只需要傳入一個(gè)文件操作符或者一段文本即可得到一個(gè)構(gòu)建完成的文檔對(duì)象,有了該對(duì)象之后,就可以對(duì)該文檔做一些我們想做的操作了。而傳入的文本大都是通過爬蟲爬取過來的,所以 Beautiful Soup 和 requests 庫(kù)結(jié)合使用體驗(yàn)更佳。

          # demo 1
          from bs4 import BeautifulSoup
          # soup=BeautifulSoup(open("index.html"))
          soup=BeautifulSoup("<html><head><title>index</title></head><body>content</body></html>", "lxml") # 指定解析器
          print(soup.head)
          
          # 輸出結(jié)果
          <head><title>index</title></head>

          Beautiful Soup 將復(fù)雜的 HTML 文檔轉(zhuǎn)換成一個(gè)復(fù)雜的樹形結(jié)構(gòu),每個(gè)節(jié)點(diǎn)都是 Python 對(duì)象,所有對(duì)象可以歸納為 4 種: Tag,NavigableString,BeautifulSoup,Comment。

          Tag 就是 HTML 的一個(gè)標(biāo)簽,比如 div,p 標(biāo)簽等,也是我們用的最多的一個(gè)對(duì)象。

          NavigableString 指標(biāo)簽內(nèi)部的文字,直譯就是可遍歷的字符串。

          BeautifulSoup 指一個(gè)文檔的全部?jī)?nèi)容,可以當(dāng)成一個(gè) Tag 來處理。

          Comment 是一個(gè)特殊的 NavigableString,其輸出內(nèi)容不包括注視內(nèi)容。

          為了故事的順利發(fā)展,我們先定義一串 HTML 文本,下文的所有例子都是基于這段文本的。

          html_doc="""
          <html><head><title>index</title></head>
          <body>
          <p class="title"><b>首頁</b></p>
          <p class="main">我常用的網(wǎng)站
          <a href="https://www.google.com" class="website" id="google">Google</a>
          <a href="https://www.baidu.com" class="website" id="baidu">Baidu</a>
          <a href="https://cn.bing.com" class="website" id="bing">Bing</a>
          </p>
          <div><!--這是注釋內(nèi)容--></div>
          <p class="content1">...</p>
          <p class="content2">...</p>
          </body>
          """

          子節(jié)點(diǎn)

          Tag 有兩個(gè)很重要的屬性,name 和 attributes。期中 name 就是標(biāo)簽的名字,attributes 是標(biāo)簽屬性。標(biāo)簽的名字和屬性是可以被修改的,注意,這種修改會(huì)直接改變 BeautifulSoup 對(duì)象。

          # demo 2
          soup=BeautifulSoup(html_doc, "lxml");
          p_tag=soup.p
          print(p_tag.name)
          print(p_tag["class"])
          print(p_tag.attrs)
          
          p_tag.name="myTag" # attrs 同樣可被修改,操作同字典
          print(p_tag)
          
          #輸出結(jié)果
          p
          ['title']
          {'class': ['title']}
          <myTag class="title"><b>首頁</b></myTag>

          由以上例子我么可以看出,可以直接通過點(diǎn)屬性的方法來獲取 Tag,但是這種方法只能獲取第一個(gè)標(biāo)簽。同時(shí)我們可以多次調(diào)用點(diǎn)屬性這個(gè)方法,來獲取更深層次的標(biāo)簽。

          # demo 3
          soup=BeautifulSoup(html_doc, "lxml");
          print(soup.p.b)
          
          #輸出結(jié)果
          <b>首頁</b>

          如果想獲得所有的某個(gè)名字的標(biāo)簽,則可以使用 find_all(tag_name) 函數(shù)。

          # demo 4
          soup=BeautifulSoup(html_doc, "lxml");
          a_tags=soup.find_all("a")
          print(a_tags)
          
          #輸出結(jié)果
          [<a class="website" href="https://www.google.com" id="google">Google</a>, <a class="website" href="https://www.baidu.com" id="baidu">Baidu</a>, <a class="website" href="https://cn.bing.com" id="bing">Bing</a>]

          我們可以使用 .contents 將 tag 以列表方式輸出,即將 tag 的子節(jié)點(diǎn)格式化為列表,這很有用,意味著可以通過下標(biāo)進(jìn)行訪問指定節(jié)點(diǎn)。同時(shí)我們還可以通過 .children 生成器對(duì)節(jié)點(diǎn)的子節(jié)點(diǎn)進(jìn)行遍歷。

          # demo 5
          soup=BeautifulSoup(html_doc, "lxml");
          head_tag=soup.head
          print(head_tag)
          print(head_tag.contents)
          
          for child in head_tag.children:
          	print("child is : ", child)
          
          #輸出結(jié)果
          <head><title>index</title></head>
          [<title>index</title>]
          child is :  <title>index</title>

          .children 只可以獲取 tag 的直接節(jié)點(diǎn),而獲取不到子孫節(jié)點(diǎn),.descendants 可以滿足你。

          # demo 6
          soup=BeautifulSoup(html_doc, "lxml");
          head_tag=soup.head
          for child in head_tag.descendants:
          	print("child is : ", child)
          
          # 輸出結(jié)果
          child is :  <title>index</title>
          child is :  index

          父節(jié)點(diǎn)

          通過 .parent 屬性獲取標(biāo)簽的父親節(jié)點(diǎn)。 title 的父標(biāo)簽是 head,html 的父標(biāo)簽是 BeautifulSoup 對(duì)象,而 BeautifulSoup 對(duì)象的父標(biāo)簽是 None。

          # demo 7
          soup=BeautifulSoup(html_doc, "lxml");
          title_tag=soup.title
          
          print(title_tag.parent)
          print(type(soup.html.parent))
          print(soup.parent)
          
          # 輸出結(jié)果
          <head><title>index</title></head>
          <class 'bs4.BeautifulSoup'>
          None

          同時(shí),我們可以通過 parents 得到指定標(biāo)簽的所有父親標(biāo)簽。

          # demo 8
          soup=BeautifulSoup(html_doc, "lxml");
          a_tag=soup.a
          
          for parent in a_tag.parents:
              print(parent.name)
          
          # 輸出結(jié)果
          p
          body
          html
          [document]

          兄弟節(jié)點(diǎn)

          通過 .next_sibling 和 .previous_sibling 來獲取下一個(gè)標(biāo)簽和上一個(gè)標(biāo)簽。

          # demo 9
          soup=BeautifulSoup(html_doc, "lxml");
          div_tag=soup.div
          
          print(div_tag.next_sibling)
          print(div_tag.next_sibling.next_sibling)
          
          # 輸出結(jié)果
          
          <p class="content1">...</p>

          你可能會(huì)納悶,調(diào)用了兩次 next_sibling 怎么只有一個(gè)輸出呢,這方法是不是有 bug 啊。事實(shí)上是 div 的第一個(gè) next_sibling 是div 和 p 之間的換行符。這個(gè)規(guī)則對(duì)于 previous_sibling 同樣適用。

          另外,我們可以通過 .next_siblings 和 .previous_siblings 屬性可以對(duì)當(dāng)前節(jié)點(diǎn)的兄弟節(jié)點(diǎn)迭代輸出。在該例子中,我們?cè)诿看屋敵銮凹恿饲熬Y,這樣就可以更直觀的看到 dib 的第一個(gè) previous_sibling 是換行符了。

          # demo 10
          soup=BeautifulSoup(html_doc, "lxml");
          div_tag=soup.div
          
          for pre_tag in div_tag.previous_siblings:
          	print("pre_tag is : ", pre_tag)
          
          # 輸出結(jié)果
          pre_tag is :  
          
          pre_tag is :  <p class="main">我常用的網(wǎng)站
          <a class="website" href="https://www.google.com" id="google">Google</a>
          <a class="website" href="https://www.baidu.com" id="baidu">Baidu</a>
          <a class="website" href="https://cn.bing.com" id="bing">Bing</a>
          </p>
          pre_tag is :  
          
          pre_tag is :  <p class="title"><b>首頁</b></p>
          pre_tag is :  

          前進(jìn)和后退

          通過 .next_element 和 .previous_element 獲取指定標(biāo)簽的前一個(gè)或者后一個(gè)被解析的對(duì)象,注意這個(gè)和兄弟節(jié)點(diǎn)是有所不同的,兄弟節(jié)點(diǎn)是指有相同父親節(jié)點(diǎn)的子節(jié)點(diǎn),而這個(gè)前一個(gè)或者后一個(gè)是按照文檔的解析順序來計(jì)算的。

          比如在我們的文本 html_doc 中,head 的兄弟節(jié)點(diǎn)是 body(不考慮換行符),因?yàn)樗麄兙哂泄餐母腹?jié)點(diǎn) html,但是 head 的下一個(gè)節(jié)點(diǎn)是 title。即soup.head.next_sibling=title soup.head.next_element=title

          # demo 11
          soup=BeautifulSoup(html_doc, "lxml");
          
          head_tag=soup.head
          print(head_tag.next_element)
          
          title_tag=soup.title
          print(title_tag.next_element)
          
          # 輸出結(jié)果
          <title>index</title>
          index

          同時(shí)這里還需要注意的是 title 下一個(gè)解析的標(biāo)簽不是 body,而是 title 標(biāo)簽內(nèi)的內(nèi)容,因?yàn)?html 的解析順序是打開 title 標(biāo)簽,然后解析內(nèi)容,最后關(guān)閉 title 標(biāo)簽。

          另外,我們同樣可以通過 .next_elements 和 .previous_elements 來迭代文檔樹。由遺下例子我們可以看出,換行符同樣會(huì)占用解析順序,與迭代兄弟節(jié)點(diǎn)效果一致。

          # demo 12
          soup=BeautifulSoup(html_doc, "lxml");
          div_tag=soup.div
          for next_element in div_tag.next_elements:
          	print("next_element is : ", next_element)
          
          # 輸出結(jié)果
          next_element is :  這是注釋內(nèi)容
          next_element is :  
          
          next_element is :  <p class="content1">...</p>
          next_element is :  ...
          next_element is :  
          
          next_element is :  <p class="content2">...</p>
          next_element is :  ...
          next_element is :  
          
          next_element is :  

          Beautiful Soup 總結(jié)

          本章節(jié)介紹了 Beautiful Soup 的使用場(chǎng)景以及操作文檔樹節(jié)點(diǎn)的基本操作,看似很多東西其實(shí)是有規(guī)律可循的,比如函數(shù)的命名,兄弟節(jié)點(diǎn)或者下一個(gè)節(jié)點(diǎn)的迭代函數(shù)都是獲取單個(gè)節(jié)點(diǎn)函數(shù)的復(fù)數(shù)形式。

          同時(shí)由于 HTML 或者 XML 這種循環(huán)嵌套的復(fù)雜文檔結(jié)構(gòu),致使操作起來甚是麻煩,掌握了本文對(duì)節(jié)點(diǎn)的基本操作,將有助于提高你寫爬蟲程序的效率。


          主站蜘蛛池模板: 亚洲av不卡一区二区三区| 亚洲一区AV无码少妇电影☆| 亚洲国产精品一区二区第四页| 国产一区二区三区在线看片 | 男人免费视频一区二区在线观看 | 国产一区二区免费视频| 精品女同一区二区三区免费播放 | 国产精品亚洲综合一区| 一区国严二区亚洲三区| 日本福利一区二区| 精品国产区一区二区三区在线观看| 亚洲视频在线一区| 亚洲AV美女一区二区三区| 精品人无码一区二区三区| 亚洲一区精品无码| 日韩精品一区二区三区中文字幕 | 无码日韩人妻AV一区免费l | 国产午夜精品一区二区三区嫩草| 亚洲国产成人久久一区二区三区| 日韩在线不卡免费视频一区| 清纯唯美经典一区二区| 国产精品一区三区| 国产精品一区在线观看你懂的| 一区二区在线视频观看| 日韩少妇无码一区二区三区| 中文字幕一区二区三区免费视频| 区三区激情福利综合中文字幕在线一区亚洲视频1 | 精品视频一区二区三区免费| 成人国内精品久久久久一区 | 一区二区三区国产| 日本亚洲成高清一区二区三区| 无码精品一区二区三区在线| 无码少妇一区二区三区| 亚洲AV日韩AV天堂一区二区三区| 中文字幕无线码一区二区| 亚洲欧洲无码一区二区三区| 国产精品自拍一区| 99精品国产一区二区三区不卡| 久久久久人妻一区精品性色av| 亚洲一区精品视频在线| 无码精品一区二区三区|