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

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

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

          性能優(yōu)化之優(yōu)化資源加載

          文鏈接:Optimize resource loading, from web.dev。翻譯時(shí)有刪改。

          在上一篇文章中,我們討論了關(guān)鍵渲染路徑,了解了影響頁(yè)面初始渲染效率的阻塞渲染 CSS 和 阻塞解析 JavaScript。

          頁(yè)面加載時(shí),會(huì)伴隨很多資源的引用。這些資源可能是為頁(yè)面提供外觀和布局的 CSS,也可能是提交頁(yè)面交互效果的 JavaScript。

          本文,我們將深入了解這 2 類(lèi)資源的阻塞原理并具體學(xué)習(xí)一些優(yōu)化手段。

          阻塞渲染

          從之前的學(xué)習(xí),我們知道 CSS 屬于阻塞渲染的資源,在瀏覽器完成下載和解析 CSS 成為 CSSOM 之前,會(huì)停止渲染工作。

          之所以將 CSS 作為阻塞渲染的資源,是為了避免頁(yè)面出現(xiàn)短暫的無(wú)樣式閃現(xiàn)。類(lèi)似下面這樣:

          有一個(gè)術(shù)語(yǔ)專門(mén)用于描述這個(gè)場(chǎng)景,叫 FOUC,全稱是“Flash of Unstyled Content”。

          因?yàn)?CSS 會(huì)作為阻塞渲染的資源處理,F(xiàn)OUC 現(xiàn)象你通常是看不到的,但要理解這個(gè)概念,就能明白瀏覽器為什么要這么做了。

          阻塞解析

          從之前的學(xué)習(xí),我們知道瀏覽器在遇到 <script> 元素時(shí),會(huì)阻塞瀏覽器進(jìn)一步的解析 ,優(yōu)先下載、處理和執(zhí)行 JavaScript,在處于其余部分的 HTML。

          JavaScript 的阻塞解析,也是瀏覽器刻意為之的策略。這是因?yàn)?JavaScript 代碼中可能會(huì)包含對(duì) DOM 結(jié)構(gòu)的訪問(wèn)和修改。

          html復(fù)制代碼<!-- This is a parser-blocking script: -->
          <script src="/script.js"></script>

          當(dāng) <script> 元素沒(méi)有注明是 async/defer 的情況下,瀏覽器會(huì)先終止后續(xù)解析,優(yōu)先下載(可選,適應(yīng)于引用外部資源)、解析、執(zhí)行 JavaScript 文件。直到這一過(guò)程結(jié)束,瀏覽器才繼續(xù)后續(xù)內(nèi)容的解析。

          值得注意的是,JavaScript 的執(zhí)行也有一個(gè)前提,就是當(dāng)前沒(méi)有正在處理的 CSS 資源,這也是刻意為之。

          因?yàn)?CSS 解析時(shí),并不會(huì)阻止瀏覽器解析 JavaScript,如果 JavaScript 中有類(lèi)似 element.getComputedStyle() 代碼調(diào)用,那么必然要等到樣式解析完成才行,否則是不準(zhǔn)確的。因此,JavaScript 的執(zhí)行要等待 CSS 解析徹底完成。

          預(yù)加載掃描器

          預(yù)加載掃描器(preload scanner) 是瀏覽器的一個(gè)優(yōu)化手段,它是主 HTML 解析器(primary HTML parser)之外的另一個(gè) HTML 解析器,叫輔助 HTML 解析器(secondary HTML parser)。

          預(yù)加載掃描器會(huì)在主 HTML 解析器發(fā)現(xiàn)資源之前查找并獲取資源。比如:提前下載 <img> 元素中指定的資源。這個(gè)操作即便在 HTML 解析器被 JavaScript 和 CSS 阻塞也是如此。

          不過(guò)預(yù)加載掃描器也有一些處理盲區(qū)。盲區(qū)之內(nèi)引用的資源無(wú)法被識(shí)別,也就無(wú)法優(yōu)化了。這些處理盲區(qū)包括:

          1. CSS 中加載的圖片資源(通過(guò) background-oimage 屬性)
          2. 通過(guò) JavaScript 代碼或 import() 動(dòng)態(tài)創(chuàng)建的 DOM 節(jié)點(diǎn)
          3. 客戶端渲染 HTML,這類(lèi)典型就是 SPA 應(yīng)用
          4. CSS @import 聲明

          以上場(chǎng)景都有一個(gè)共同特點(diǎn),就是資源加載都是滯后的,自然就無(wú)法被預(yù)加載掃描器知道。

          當(dāng)然,針對(duì)這類(lèi)場(chǎng)景我們還能使用 preload hint 手動(dòng)提示來(lái)支持。不過(guò)這是下一篇的內(nèi)容了,這里先不贅述。

          CSS

          CSS 影響頁(yè)面的外觀和布局效果,它是一種阻塞渲染的資源。本小節(jié),我們就來(lái)看看如果優(yōu)化 CSS,來(lái)改善頁(yè)面加時(shí)間。

          壓縮

          通過(guò)壓縮 CSS 減少文件大小,從而加快下載速度。

          demo:壓縮前

          css復(fù)制代碼/* Unminified CSS: */
          
          /* Heading 1 */
          h1 {
            font-size: 2em;
            color: #000000;
          }
          
          /* Heading 2 */
          h2 {
            font-size: 1.5em;
            color: #000000;
          }

          demo:壓縮后

          css復(fù)制代碼/* Minified CSS: */
          h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}

          壓縮是 CSS 最基本而且有效的一個(gè)優(yōu)化手段,可以提高網(wǎng)站 FCP 甚至 LCP 指標(biāo)。一般前端工程中是配置的打包工具(bundler)都內(nèi)置了這個(gè)功能。

          移除無(wú)用 CSS

          在瀏覽器渲染網(wǎng)頁(yè)內(nèi)容前,需要下載并解析所有樣式。當(dāng)然,這里花費(fèi)的時(shí)間還包括當(dāng)前頁(yè)面未使用的樣式。如果你使用的打包工具將所有 CSS 資源組合到一個(gè)文件中,那么你的用戶可能會(huì)下載比實(shí)際當(dāng)前渲染頁(yè)面所需要的更多 CSS。

          要發(fā)現(xiàn)當(dāng)前頁(yè)面未使用的 CSS,可以使用 Chrome DevTools 中的 Coverage 工具。

          刪除未使用的 CSS 會(huì)帶來(lái) 2 個(gè)好處:

          1. 減少下載時(shí)間
          2. 優(yōu)化渲染樹(shù)構(gòu)造。減少瀏覽器需要處理的 CSS 規(guī)則

          避免 CSS @import聲明

          雖然看起來(lái)很方便,但您應(yīng)該避免在 CSS 中使用 @import 聲明:

          css復(fù)制代碼/* Don't do this: */
          @import url('style.css');

          與 HTML 中 <link> 元素的工作方式類(lèi)似,CSS 中的 @import 聲明能讓你從樣式表中導(dǎo)入外部 CSS 資源。

          這兩種方法之間的主要區(qū)別在于 HTML <link> 元素是 HTML 響應(yīng)的一部分,因此比通過(guò) @import 聲明下載的 CSS 文件能更快比發(fā)現(xiàn)。

          原因在于 @import 聲明必須先下載包含 CSS 文件。這會(huì)產(chǎn)生所謂的請(qǐng)求鏈(request chain),它會(huì)延遲頁(yè)面初始渲染所需的時(shí)間。另一個(gè)缺點(diǎn),是使用 @import 聲明加載的樣式表無(wú)法被預(yù)加載掃描器發(fā)現(xiàn),也會(huì)成為后期發(fā)現(xiàn)的渲染阻塞資源。

          html復(fù)制代碼<!-- Do this instead: -->
          <link rel="stylesheet" href="style.css">

          在大多數(shù)情況下,你可以使用 <link rel="stylesheet"> 元素替換 @import<link> 元素是同時(shí)下載樣式表的,這樣能減少總體加載時(shí)間,這與 @import 聲明連續(xù)下載樣式表的策略也是不一樣的。

          內(nèi)聯(lián)關(guān)鍵 CSS

          所謂“關(guān)鍵 CSS”是指“首屏”頁(yè)面內(nèi)容所需要的樣式。

          下載 CSS 文件需要時(shí)間,這會(huì)增加頁(yè)面的 FCP 指標(biāo)。如果在文檔 <head> 中內(nèi)聯(lián)關(guān)鍵樣式可以消除對(duì) CSS 資源的網(wǎng)絡(luò)請(qǐng)求。剩余的 CSS 可以異步加載,或者附加在 <body> 元素的末尾。

          html復(fù)制代碼<head>
            <title>Page Title</title>
            <!-- ... -->
            <style>h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}</style>
          </head>
          <body>
            <!-- Other page markup... -->
            <link rel="stylesheet" href="non-critical.css">
          </body>

          另一方面,內(nèi)聯(lián)大量 CSS 會(huì)向初始 HTML 響應(yīng)增加更多字節(jié)。而通過(guò) HTML 資源通常無(wú)法緩存很長(zhǎng)時(shí)間(或根本不緩存),因此這個(gè)也需要我們基于自身情況做選擇。

          JavaScript

          JavaScript 驅(qū)動(dòng)了網(wǎng)絡(luò)上的大部分交互,但也付出了代價(jià)。

          傳送太多的 JavaScript 可能會(huì)讓頁(yè)面加載時(shí)間太長(zhǎng)、響應(yīng)緩慢,交互也變慢了,這兩種情況都會(huì)讓用戶抓狂。

          阻塞解析的 JavaScript

          當(dāng)網(wǎng)頁(yè)中使用的 <script> 元素不帶 deferasync 時(shí),當(dāng)前代碼的解析會(huì)阻止瀏覽器后續(xù)工作的進(jìn)行,直到當(dāng)前代碼處理完成。同樣的,內(nèi)聯(lián)腳本也會(huì)阻塞解析,直到腳本代碼處理完成。

          async和 defer

          <script> 的 async 和 defer attribute 可以讓你在加載外部腳本同時(shí)不阻塞 HTML 解析器的工作。不過(guò),async 和 defer 策略上還是有一些不同。

          來(lái)源自 html.spec.whatwg.org/multipage/s…

          使用 async 加載的腳本在下載后立即解析并執(zhí)行;而使用 defer 加載的腳本在 HTML 文檔解析完成時(shí)執(zhí)行——跟 DOMContentLoaded 事件一個(gè)時(shí)機(jī)。

          另外,當(dāng)網(wǎng)頁(yè)中同時(shí)存在多個(gè)腳本時(shí)。 async 腳本可能無(wú)法保證執(zhí)行執(zhí)行,而 defer 腳本則會(huì)始終按照它們?cè)谠创a中出現(xiàn)的順序執(zhí)行。

          值得注意的是:帶有 type="module" 的腳本(包括內(nèi)聯(lián)腳本)效果跟 defer 一樣;而通過(guò)腳本動(dòng)態(tài)注入的 <script> 標(biāo)簽效果類(lèi)似 async

          避免客戶端渲染

          一般來(lái)說(shuō),你應(yīng)該避免使用 JavaScript 來(lái)呈現(xiàn)任何關(guān)鍵內(nèi)容或頁(yè)面的 LCP 元素。這種做法稱為“客戶端渲染”,是單頁(yè)應(yīng)用程序 (SPA) 中廣泛使用的一種技術(shù)。

          通過(guò) JavaScript 渲染的 HTML 標(biāo)簽是無(wú)法被預(yù)加載掃描器觀察到的。這可能會(huì)延遲關(guān)鍵資源的下載,例如 LCP 圖片。瀏覽器只會(huì)在腳本執(zhí)行后才開(kāi)始下載 LCP 圖片,并添加到 DOM 中,這應(yīng)該被避免。

          此外,與直接在服務(wù)器響應(yīng)請(qǐng)求返回相比,使用 JavaScript 渲染標(biāo)簽更有可能產(chǎn)生較長(zhǎng)的任務(wù)。廣泛使用 HTML 客戶端渲染也會(huì)延遲交互,在頁(yè)面 DOM 非常大的情況下尤其如此。

          壓縮

          與 CSS 類(lèi)似,壓縮 JavaScript 可以減少腳本文件大小,加快下載速度,讓瀏覽器更快解析和編譯 JavaScript。

          此外,JavaScript 的壓縮可以比 CSS 等其他資源更好。當(dāng)壓縮 JavaScript 時(shí),它不僅能刪除空格、制表符和注釋等內(nèi)容,而且 JavaScript 中標(biāo)識(shí)符也能被縮短,這個(gè)過(guò)程有時(shí)被稱為丑化(uglification)。

          js復(fù)制代碼// Unuglified JavaScript source code:
          export function injectScript () {
            const scriptElement = document.createElement('script');
            scriptElement.src = '/js/scripts.js';
            scriptElement.type = 'module';
          
            document.body.appendChild(scriptElement);
          }
          js復(fù)制代碼// Uglified JavaScript production code:
          export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}

          觀察可以發(fā)現(xiàn),你可以看到源代碼中變量 scriptElement 被縮短成 t 了。當(dāng)你腳本代碼很多時(shí),這種做法可以節(jié)省的體積相當(dāng)可觀,而且也不會(huì)影響網(wǎng)站的生產(chǎn)環(huán)境功能。

          如果你有在用打包工具處理網(wǎng)站源碼,JavaScript 的生產(chǎn)壓縮默認(rèn)就有。這類(lèi)丑化工具——例如 Terser——也是高度可配置的,你可以調(diào)整丑化算法的激進(jìn)程度來(lái)實(shí)現(xiàn)最大程度的壓縮。然而,任何丑化工具的默認(rèn)設(shè)置通常就足夠使用的了。

          總結(jié)

          本文在上一篇關(guān)鍵渲染路徑的基礎(chǔ)上,進(jìn)一步討論了瀏覽器針對(duì) JavaScript/CSS 采用的不同阻塞策略的原因,繼而給出優(yōu)化 JavaScript、CSS 的不同手段。希望對(duì)各位正在閱讀的朋友日后的工作帶來(lái)一些幫助。

          下一篇里,我們會(huì)繼續(xù)探討另一種在網(wǎng)頁(yè)中進(jìn)行手工優(yōu)化的方式——資源提示(resource hints),敬請(qǐng)期待。

          再見(jiàn)。

          網(wǎng)絡(luò)爬蟲(chóng)開(kāi)發(fā)中,使用強(qiáng)大的庫(kù)是至關(guān)重要的,而requests-html就是其中一顆璀璨的明星。本文將深度探討requests-html的各個(gè)方面,包括基本的HTTP請(qǐng)求、HTML解析、JavaScript渲染、選擇器的使用以及高級(jí)特性的應(yīng)用。

          安裝與基本用法

          首先,需要安裝requests-html

          pip install requests-html

          然后,進(jìn)行簡(jiǎn)單的HTTP請(qǐng)求:

          from requests_html import HTMLSession
          
          session = HTMLSession()
          response = session.get('https://example.com')
          print(response.html.text)

          HTML解析與選擇器

          requests-html內(nèi)置了強(qiáng)大的HTML解析器和類(lèi)似jQuery的選擇器,使得數(shù)據(jù)提取變得非常便捷:

          # 使用選擇器提取標(biāo)題
          titles = response.html.find('h2')
          for title in titles:
              print(title.text)

          JavaScript渲染

          對(duì)于需要JavaScript渲染的頁(yè)面,requests-html也能輕松應(yīng)對(duì):

          # JavaScript渲染
          r = session.get('https://example.com', params={'q': 'python'})
          r.html.render()
          print(r.html.text)

          更高級(jí)的特性

          1 異步JavaScript渲染

          對(duì)于異步加載的JavaScript內(nèi)容,requests-html提供了pyppeteer的支持:

          # 異步JavaScript渲染
          r = session.get('https://example.com')
          r.html.render(sleep=1, keep_page=True)
          print(r.html.text)

          2 自定義Headers和Cookies

          在請(qǐng)求中自定義Headers和Cookies是常見(jiàn)需求,requests-html為此提供了簡(jiǎn)單易用的方法:

          # 自定義Headers和Cookies
          headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/58.0.3029.110 Safari/537.3'}
          cookies = {'example_cookie': 'value'}
          r = session.get('https://example.com', headers=headers, cookies=cookies)
          print(r.html.text)

          實(shí)際應(yīng)用場(chǎng)景

          1 抓取動(dòng)態(tài)頁(yè)面

          通過(guò)requests-html,可以輕松抓取動(dòng)態(tài)頁(yè)面的數(shù)據(jù):

          # 抓取動(dòng)態(tài)頁(yè)面
          r = session.get('https://example.com/dynamic-page')
          r.html.render()
          print(r.html.text)

          2 表單提交

          模擬用戶行為,實(shí)現(xiàn)表單提交:

          # 表單提交
          payload = {'username': 'user', 'password': 'pass'}
          r = session.post('https://example.com/login', data=payload)
          print(r.html.text)

          強(qiáng)大的選擇器和數(shù)據(jù)提取

          requests-html內(nèi)置了類(lèi)似于jQuery的選擇器,讓數(shù)據(jù)提取變得輕松:

          # 使用選擇器提取鏈接
          links = response.html.find('a')
          for link in links:
              print(link.attrs['href'])

          此外,通過(guò)更復(fù)雜的選擇器和過(guò)濾器,可以更精準(zhǔn)地定位和提取所需數(shù)據(jù):

          # 使用更復(fù)雜的選擇器和過(guò)濾器
          articles = response.html.find('article')
          for article in articles:
              title = article.find('h2', first=True).text
              author = article.find('.author', first=True).text
              print(f"Title: {title}, Author: {author}")

          頁(yè)面等待和截圖

          對(duì)于需要等待頁(yè)面加載完成的情況,requests-html提供了wait參數(shù):

          # 等待頁(yè)面加載完成
          r = session.get('https://example.com/dynamic-content')
          r.html.render(wait=2)
          print(r.html.text)

          此外,還可以利用render函數(shù)生成頁(yè)面截圖:

          # 生成頁(yè)面截圖
          r = session.get('https://example.com')
          r.html.render(screenshot='screenshot.png')

          異常處理和錯(cuò)誤頁(yè)面重試

          在爬蟲(chóng)過(guò)程中,異常處理是不可或缺的一部分。requests-html提供了捕獲異常和錯(cuò)誤頁(yè)面重試的選項(xiàng):

          # 異常處理和錯(cuò)誤頁(yè)面重試
          try:
              r = session.get('https://example.com/unstable-page')
              r.html.render(retries=3, wait=2)
              print(r.html.text)
          except Exception as e:
              print(f"Error: {e}")

          性能優(yōu)化和并發(fā)請(qǐng)求

          在爬蟲(chóng)開(kāi)發(fā)中,性能優(yōu)化和并發(fā)請(qǐng)求是至關(guān)重要的。requests-html提供了一些功能和選項(xiàng),能夠更好地處理這些方面的問(wèn)題。

          1. 并發(fā)請(qǐng)求

          并發(fā)請(qǐng)求是同時(shí)向多個(gè)目標(biāo)發(fā)送請(qǐng)求,以提高效率。requests-html使用asyncio庫(kù)支持異步請(qǐng)求,從而實(shí)現(xiàn)并發(fā)。以下是一個(gè)簡(jiǎn)單的例子:

          from requests_html import AsyncHTMLSession
          
          async def fetch(url):
              async with AsyncHTMLSession() as session:
                  response = await session.get(url)
                  return response.html.text
          
          urls = ['https://example.com/page1', 'https://example.com/page2', 'https://example.com/page3']
          
          # 利用asyncio.gather實(shí)現(xiàn)并發(fā)請(qǐng)求
          results = AsyncHTMLSession().run(lambda: [fetch(url) for url in urls])
          
          for result in results:
              print(result)

          在這個(gè)例子中,asyncio.gather被用于同時(shí)運(yùn)行多個(gè)異步請(qǐng)求。這種方式在大量頁(yè)面需要抓取時(shí)可以顯著提高效率。

          2. 鏈接池

          requests-htmlSession對(duì)象內(nèi)置了連接池,它能夠維護(hù)多個(gè)持久化連接,減少請(qǐng)求時(shí)的連接建立開(kāi)銷(xiāo)。這對(duì)于頻繁請(qǐng)求同一域名下的多個(gè)頁(yè)面時(shí)尤為有用。以下是一個(gè)簡(jiǎn)單的使用示例:

          from requests_html import HTMLSession
          
          session = HTMLSession()
          
          # 利用連接池發(fā)送多個(gè)請(qǐng)求
          responses = session.get(['https://example.com/page1', 'https://example.com/page2', 'https://example.com/page3'])
          
          for response in responses:
              print(response.html.text)

          這里,session.get()接受一個(gè)包含多個(gè)URL的列表,使用連接池維護(hù)這些請(qǐng)求的連接。

          3. 緩存

          requests-html允許使用緩存,以避免重復(fù)下載相同的內(nèi)容。這對(duì)于頻繁訪問(wèn)不經(jīng)常更新的網(wǎng)頁(yè)時(shí)很有用。以下是一個(gè)使用緩存的例子:

          from requests_html import HTMLSession
          
          session = HTMLSession()
          
          # 使用緩存
          response = session.get('https://example.com', cached=True)
          print(response.html.text)

          在這個(gè)例子中,cached=True表示啟用緩存。

          總結(jié)

          在本篇博客中,深入探討了requests-html這一Python爬蟲(chóng)庫(kù),揭示了其強(qiáng)大而靈活的功能。通過(guò)詳細(xì)的示例代碼和實(shí)際應(yīng)用場(chǎng)景,展示了如何使用該庫(kù)進(jìn)行HTTP請(qǐng)求、HTML解析、JavaScript渲染以及高級(jí)功能的應(yīng)用。requests-html的異步支持使得并發(fā)請(qǐng)求變得輕而易舉,通過(guò)連接池和緩存的利用,我們能夠更好地優(yōu)化性能,提高爬蟲(chóng)的效率。同時(shí),庫(kù)內(nèi)置的強(qiáng)大選擇器和靈活的數(shù)據(jù)提取方式讓頁(yè)面解析變得更為簡(jiǎn)單。

          總體而言,requests-html為爬蟲(chóng)開(kāi)發(fā)者提供了一個(gè)強(qiáng)大而友好的工具,使得從靜態(tài)網(wǎng)頁(yè)到動(dòng)態(tài)渲染頁(yè)面的抓取都變得更加便捷。通過(guò)學(xué)習(xí)本文,不僅能夠熟練掌握requests-html的基本用法,還能深入理解其高級(jí)功能,為實(shí)際項(xiàng)目的開(kāi)發(fā)提供更全面的解決方案。希望通過(guò)這篇博客,能夠更加自信和高效地運(yùn)用requests-html來(lái)應(yīng)對(duì)各類(lèi)爬蟲(chóng)任務(wù)。

          、BeautifulSoup簡(jiǎn)介

          BeautifulSoup是Python爬蟲(chóng)應(yīng)用解析Html的利器,是Python三方模塊bs4中提供的進(jìn)行HTML解析的類(lèi),可以認(rèn)為是一個(gè)HTML解析工具箱,對(duì)HTML報(bào)文中的標(biāo)簽具有比較好的容錯(cuò)識(shí)別功能。lxml是一款html文本解析器,BeautifulSoup構(gòu)建對(duì)象時(shí)需要指定HTML解析器,推薦使用lxml。

          BeautifulSoup和lxml安裝命令:

          1.pip install -i https://pypi.tuna.tsinghua.edu.cn/simple bs4

          2.pip install -i https://pypi.tuna.tsinghua.edu.cn/simple lxml

          加載BeautifulSoup

          1.from bs4 import BeautifulSoup

          BeatifulSoap解析HTML報(bào)文的常用功能

          1. 通過(guò)BeautifulSoup對(duì)象可以訪問(wèn)標(biāo)簽對(duì)應(yīng)的html元素、并進(jìn)一步訪問(wèn)標(biāo)簽的名字、屬性、html元素標(biāo)簽對(duì)中的內(nèi)容。
            案例from bs4 import BeautifulSoup
          2. import urllib.request
          3. def getURLinf(url):
          4. header = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/44.0.2403.157 Safari/537.36'}
          5. req = urllib.request.Request(url=url,headers=header)
          6. resp = urllib.request.urlopen(req,timeout=5)
          7. html = resp.read().decode()

          8. soup = BeautifulSoup(html,'lxml')
          9. return (soup,req,resp)

          10. soup,req ,resp = getURLinf
          11. print(soup.p)
          12. print(soup.link)
          13. print(soup.title)
          14. print(soup.link.attrs)
          15. print(soup.link['rel'])

          通過(guò)標(biāo)簽的contents屬性,可以訪問(wèn)其下嵌套的所有下級(jí)HTML元素,這些該標(biāo)簽下的子標(biāo)簽對(duì)應(yīng)的HTML元素放到一個(gè)contents 指向的列表中。

          如:print(soup.body.contents)

          可以訪問(wèn)標(biāo)簽對(duì)應(yīng)的父、子、兄弟及祖先標(biāo)簽信息;

          使用strings屬性迭代訪問(wèn)除標(biāo)簽外的所有內(nèi)容;

          可以使用find、find_all、find_parent、find_parents等系列方法查找滿足特定條件的標(biāo)簽;

          使用select通過(guò)css選擇器定位特定標(biāo)簽。


          二、一些解析技巧

          在HTML解析時(shí),如果通過(guò)簡(jiǎn)單的tag、或單個(gè)tag屬性(如id、class)或文本一次搜索或select定位是最簡(jiǎn)單的,而有些情況需要使用組合方法才能處理。

          2.1、通過(guò)標(biāo)簽的多個(gè)屬性組合定位或查找

          經(jīng)常有些要定位的標(biāo)簽有很多,按單個(gè)屬性查找也有很多,得使用多個(gè)屬性查找。如:

          上面的html文本中有多個(gè)id為article_content的div標(biāo)簽,如果使用:

          1. >>> text="""```html
          2. <div id="article_content" class="article_content clearfix">
          3. ......
          4. </div>
          5. <div id="article_content" class="article_view">
          6. ......
          7. </div>
          8. <div id="article_view" class="article_view">
          9. ......
          10. </div>"""
          11. >>> s = BeautifulSoup(text,'lxml')
          12. >>> s.select('div#article_content')
          13. [<div class="article_content clearfix" id="article_content">......</div>,
          14. <div class="article_view" id="article_content">......</div>]
          15. >>>


          就會(huì)返回兩條記錄。這時(shí)候就可以使用多標(biāo)簽屬性定位的如下4種語(yǔ)句:

          1. >>>s.select('div#article_content[class="article_content clearfix"]')
          2. [<div class="article_content clearfix" id="article_content">......</div>]
          3. >>>s.select('div[id="article_content"][class="article_content clearfix"]')
          4. [<div class="article_content clearfix" id="article_content">......</div>]
          5. >>>s.find_all("div",id="article_content",class_='article_content clearfix')
          6. [<div class="article_content clearfix" id="article_content">......</div>]
          7. >>>s.find_all("div","#article_content",class_='article_content clearfix')
          8. [<div class="article_content clearfix" id="article_content">......</div>]


          以上四種方式是等價(jià)的,因?yàn)閕d可以用#來(lái)標(biāo)記,class在查找時(shí)需要和Python關(guān)鍵字class區(qū)分,因此有上述不同方法,注意select的每個(gè)屬性必須用中括號(hào)括起來(lái),不同屬性的中括號(hào)之間不能有空格,如果有空格表示的就不是查找同一標(biāo)簽的屬性,空格后的屬性表示前一個(gè)屬性對(duì)應(yīng)標(biāo)簽的子孫標(biāo)簽的屬性。


          2.2、利用tag標(biāo)簽關(guān)系定位內(nèi)容

          tag標(biāo)簽關(guān)系包括父子、兄弟、祖先等關(guān)系,有時(shí)要查找或定位的內(nèi)容本身不是很好定位,但結(jié)合其他標(biāo)簽關(guān)系(主要是父子、祖先關(guān)系)則可以唯一確認(rèn)。

          案例:

          這是博文中關(guān)于博主個(gè)人信息的部分報(bào)文:

          1. <div class="data-info d-flex item-tiling">
          2. <dl class="text-center" title="1055">
          3. <a href=" " data-report-click='{"mod":"1598321000_001","spm":"1001.2101.3001.4310"}' data-report-query="t=1">
          4. <dt><span class="count">1055</span></dt>
          5. <dd class="font">原創(chuàng)</dd>
          6. </a>
          7. </dl>
          8. <dl class="text-center" data-report-click='{"mod":"1598321000_002","spm":"1001.2101.3001.4311"}' title="22">
          9. <a href=" " target="_blank">
          10. <dt><span class="count">22</span></dt>
          11. <dd class="font">周排名</dd>
          12. </a>
          13. </dl>
          14. </div>


          以上報(bào)文中,如果要取博主的原創(chuàng)文章數(shù)和周排名,原創(chuàng)文章數(shù)和博主周排名的tag標(biāo)簽完全相同,二者都在span標(biāo)簽內(nèi),標(biāo)簽的屬性及值都相同,只是span標(biāo)簽的父標(biāo)簽dt標(biāo)簽的兄弟標(biāo)簽dd標(biāo)簽的string的中文內(nèi)容才能區(qū)分。對(duì)于這種情況,首先要通過(guò)祖先標(biāo)簽<div class="data-info d-flex item-tiling">定位到祖先標(biāo)簽,再在祖先標(biāo)簽內(nèi)通過(guò)中文字符串定位到要訪問(wèn)屬性的兄弟標(biāo)簽的子標(biāo)簽,然后通過(guò)該子標(biāo)簽找到其父標(biāo)簽的父標(biāo)簽,再通過(guò)該父標(biāo)簽的dt子標(biāo)簽的span子標(biāo)簽訪問(wèn)具體取值。

          示例代碼如下:

          1. >>> text="""
          2. <div class="data-info d-flex item-tiling">
          3. <dl class="text-center" title="1055">
          4. <a href=" " data-report-click='{"mod":"1598321000_001","spm":"1001.2101.3001.4310"}' data-report-query="t=1">
          5. <dt><span class="count">1055</span></dt>
          6. <dd class="font">原創(chuàng)</dd>
          7. </a>
          8. </dl>
          9. <dl class="text-center" data-report-click='{"mod":"1598321000_002","spm":"1001.2101.3001.4311"}' title="22">
          10. <a href=" " target="_blank">
          11. <dt><span class="count">22</span></dt>
          12. <dd class="font">周排名</dd>
          13. </a>
          14. </dl>
          15. </div>"""
          16. >>> s = BeautifulSoup(text,'lxml')
          17. >>> subSoup = s.select('[class="data-info d-flex item-tiling"] [class="font"]')
          18. >>> for item in subSoup:
          19. parent = item.parent
          20. if item.string=='原創(chuàng)':
          21. orignalNum = int(parent.select('.count')[0].string)
          22. elif item.string=='周排名':
          23. weekRank = int(parent.select('.count')[0].string)

          24. >>> print(orignalNum,weekRank)
          25. 1055 22
          26. >>>


          注意:上面的select使用的也是標(biāo)簽的屬性來(lái)定位標(biāo)簽,并且兩個(gè)中括號(hào)之間有空格,表明后一個(gè)要查找的標(biāo)簽在前一個(gè)屬性對(duì)應(yīng)標(biāo)簽的子孫標(biāo)簽范圍內(nèi)。

          2.3、分析前去除程序代碼避免干擾

          在解析HTML報(bào)文時(shí),絕大多數(shù)情況是需要分析有用的標(biāo)簽信息,但作為技術(shù)文章,大部分的博文中都有代碼,這些代碼可能會(huì)對(duì)分析進(jìn)行干擾。如本文中的代碼含有一些分析的HTML報(bào)文,如果獲取本文的完整HTML內(nèi)容,這些報(bào)文在非代碼部分也會(huì)出現(xiàn),此時(shí)要排除代碼的影響,可以將代碼先從分析內(nèi)容中去除再來(lái)分析。

          目前大多數(shù)技術(shù)平臺(tái)的博文編輯器都支持對(duì)代碼的標(biāo)識(shí),象markdown等編輯器代碼的標(biāo)簽為code標(biāo)檢,如果有其他編輯器用不同標(biāo)簽的,只有確認(rèn)了標(biāo)簽名,都可以按下面介紹的類(lèi)似方式來(lái)處理。

          處理步驟如下:

          獲取報(bào)文;

          構(gòu)建BeatifulSoap對(duì)象soup;

          通過(guò)soup.code.extract()或soup.code.decompose()方式就從soup對(duì)象中去除了代碼部分,decompose方法與extract方法的區(qū)別就是decompose直接刪除對(duì)應(yīng)對(duì)象數(shù)據(jù)而extract再刪除時(shí)將刪除對(duì)象單獨(dú)返回。

          三、小結(jié)

          本文介紹了使用BeatifulSoap解析HTML報(bào)文的三個(gè)使用技巧,包括通過(guò)多屬性組合查找或定位標(biāo)簽、通過(guò)結(jié)合多個(gè)標(biāo)簽關(guān)系來(lái)定位標(biāo)簽以及去除html報(bào)文中的代碼標(biāo)簽來(lái)避免代碼對(duì)解析的影響。

          寫(xiě)字不易,敬請(qǐng)支持:

          如果閱讀本文于您有所獲,敬請(qǐng)點(diǎn)贊、評(píng)論、收藏,謝謝大家的支持!

          ————————————————

          版權(quán)聲明:本文為轉(zhuǎn)載文章,如有侵權(quán),請(qǐng)聯(lián)系作者刪除。


          主站蜘蛛池模板: 亚州日本乱码一区二区三区| 成人国产精品一区二区网站公司| 日本免费一区二区三区| 亚洲AV无码第一区二区三区| 国产亚洲一区二区三区在线不卡 | 久久精品国产一区二区三| 冲田杏梨高清无一区二区| 日韩欧美一区二区三区免费观看| 亚洲国产一区在线观看| 久久er99热精品一区二区| 人妻少妇精品视频一区二区三区| 无码少妇一区二区性色AV | 国产熟女一区二区三区四区五区| 国产A∨国片精品一区二区| 成人区精品一区二区不卡 | 色婷婷AV一区二区三区浪潮| 日韩精品无码一区二区三区AV| 久久精品国产亚洲一区二区| 日韩人妻一区二区三区免费 | 国产日本一区二区三区| 午夜性色一区二区三区不卡视频| 欧洲精品码一区二区三区免费看 | 99久久人妻精品免费一区| 国精品无码A区一区二区| 在线精品动漫一区二区无广告 | 一区二区手机视频| 日本一区二区在线播放| 精品一区二区三区在线观看l| 精品国产精品久久一区免费式| 一区二区三区在线看| 亚洲国产精品一区二区久| 精品乱人伦一区二区| 亚洲国产成人久久一区二区三区| 风流老熟女一区二区三区| 免费在线观看一区| 精品少妇人妻AV一区二区| 一区二区三区高清在线| 亚洲AV综合色区无码一区爱AV| 亚洲av乱码一区二区三区| 无码人妻一区二区三区免费n鬼沢 无码人妻一区二区三区免费看 | 色噜噜AV亚洲色一区二区|