Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
文鏈接:Optimize resource loading, from web.dev。翻譯時有刪改。
在上一篇文章中,我們討論了關鍵渲染路徑,了解了影響頁面初始渲染效率的阻塞渲染 CSS 和 阻塞解析 JavaScript。
頁面加載時,會伴隨很多資源的引用。這些資源可能是為頁面提供外觀和布局的 CSS,也可能是提交頁面交互效果的 JavaScript。
本文,我們將深入了解這 2 類資源的阻塞原理并具體學習一些優化手段。
從之前的學習,我們知道 CSS 屬于阻塞渲染的資源,在瀏覽器完成下載和解析 CSS 成為 CSSOM 之前,會停止渲染工作。
之所以將 CSS 作為阻塞渲染的資源,是為了避免頁面出現短暫的無樣式閃現。類似下面這樣:
有一個術語專門用于描述這個場景,叫 FOUC,全稱是“Flash of Unstyled Content”。
因為 CSS 會作為阻塞渲染的資源處理,FOUC 現象你通常是看不到的,但要理解這個概念,就能明白瀏覽器為什么要這么做了。
從之前的學習,我們知道瀏覽器在遇到 <script> 元素時,會阻塞瀏覽器進一步的解析 ,優先下載、處理和執行 JavaScript,在處于其余部分的 HTML。
JavaScript 的阻塞解析,也是瀏覽器刻意為之的策略。這是因為 JavaScript 代碼中可能會包含對 DOM 結構的訪問和修改。
html復制代碼<!-- This is a parser-blocking script: -->
<script src="/script.js"></script>
當 <script> 元素沒有注明是 async/defer 的情況下,瀏覽器會先終止后續解析,優先下載(可選,適應于引用外部資源)、解析、執行 JavaScript 文件。直到這一過程結束,瀏覽器才繼續后續內容的解析。
值得注意的是,JavaScript 的執行也有一個前提,就是當前沒有正在處理的 CSS 資源,這也是刻意為之。
因為 CSS 解析時,并不會阻止瀏覽器解析 JavaScript,如果 JavaScript 中有類似 element.getComputedStyle() 代碼調用,那么必然要等到樣式解析完成才行,否則是不準確的。因此,JavaScript 的執行要等待 CSS 解析徹底完成。
預加載掃描器(preload scanner) 是瀏覽器的一個優化手段,它是主 HTML 解析器(primary HTML parser)之外的另一個 HTML 解析器,叫輔助 HTML 解析器(secondary HTML parser)。
預加載掃描器會在主 HTML 解析器發現資源之前查找并獲取資源。比如:提前下載 <img> 元素中指定的資源。這個操作即便在 HTML 解析器被 JavaScript 和 CSS 阻塞也是如此。
不過預加載掃描器也有一些處理盲區。盲區之內引用的資源無法被識別,也就無法優化了。這些處理盲區包括:
以上場景都有一個共同特點,就是資源加載都是滯后的,自然就無法被預加載掃描器知道。
當然,針對這類場景我們還能使用 preload hint 手動提示來支持。不過這是下一篇的內容了,這里先不贅述。
CSS 影響頁面的外觀和布局效果,它是一種阻塞渲染的資源。本小節,我們就來看看如果優化 CSS,來改善頁面加時間。
通過壓縮 CSS 減少文件大小,從而加快下載速度。
demo:壓縮前
css復制代碼/* Unminified CSS: */
/* Heading 1 */
h1 {
font-size: 2em;
color: #000000;
}
/* Heading 2 */
h2 {
font-size: 1.5em;
color: #000000;
}
demo:壓縮后
css復制代碼/* Minified CSS: */
h1,h2{color:#000}h1{font-size:2em}h2{font-size:1.5em}
壓縮是 CSS 最基本而且有效的一個優化手段,可以提高網站 FCP 甚至 LCP 指標。一般前端工程中是配置的打包工具(bundler)都內置了這個功能。
在瀏覽器渲染網頁內容前,需要下載并解析所有樣式。當然,這里花費的時間還包括當前頁面未使用的樣式。如果你使用的打包工具將所有 CSS 資源組合到一個文件中,那么你的用戶可能會下載比實際當前渲染頁面所需要的更多 CSS。
要發現當前頁面未使用的 CSS,可以使用 Chrome DevTools 中的 Coverage 工具。
刪除未使用的 CSS 會帶來 2 個好處:
雖然看起來很方便,但您應該避免在 CSS 中使用 @import 聲明:
css復制代碼/* Don't do this: */
@import url('style.css');
與 HTML 中 <link> 元素的工作方式類似,CSS 中的 @import 聲明能讓你從樣式表中導入外部 CSS 資源。
這兩種方法之間的主要區別在于 HTML <link> 元素是 HTML 響應的一部分,因此比通過 @import 聲明下載的 CSS 文件能更快比發現。
原因在于 @import 聲明必須先下載包含 CSS 文件。這會產生所謂的請求鏈(request chain),它會延遲頁面初始渲染所需的時間。另一個缺點,是使用 @import 聲明加載的樣式表無法被預加載掃描器發現,也會成為后期發現的渲染阻塞資源。
html復制代碼<!-- Do this instead: -->
<link rel="stylesheet" href="style.css">
在大多數情況下,你可以使用 <link rel="stylesheet"> 元素替換 @import。<link> 元素是同時下載樣式表的,這樣能減少總體加載時間,這與 @import 聲明連續下載樣式表的策略也是不一樣的。
所謂“關鍵 CSS”是指“首屏”頁面內容所需要的樣式。
下載 CSS 文件需要時間,這會增加頁面的 FCP 指標。如果在文檔 <head> 中內聯關鍵樣式可以消除對 CSS 資源的網絡請求。剩余的 CSS 可以異步加載,或者附加在 <body> 元素的末尾。
html復制代碼<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>
另一方面,內聯大量 CSS 會向初始 HTML 響應增加更多字節。而通過 HTML 資源通常無法緩存很長時間(或根本不緩存),因此這個也需要我們基于自身情況做選擇。
JavaScript 驅動了網絡上的大部分交互,但也付出了代價。
傳送太多的 JavaScript 可能會讓頁面加載時間太長、響應緩慢,交互也變慢了,這兩種情況都會讓用戶抓狂。
當網頁中使用的 <script> 元素不帶 defer 或 async 時,當前代碼的解析會阻止瀏覽器后續工作的進行,直到當前代碼處理完成。同樣的,內聯腳本也會阻塞解析,直到腳本代碼處理完成。
<script> 的 async 和 defer attribute 可以讓你在加載外部腳本同時不阻塞 HTML 解析器的工作。不過,async 和 defer 策略上還是有一些不同。
來源自 html.spec.whatwg.org/multipage/s…
使用 async 加載的腳本在下載后立即解析并執行;而使用 defer 加載的腳本在 HTML 文檔解析完成時執行——跟 DOMContentLoaded 事件一個時機。
另外,當網頁中同時存在多個腳本時。 async 腳本可能無法保證執行執行,而 defer 腳本則會始終按照它們在源代碼中出現的順序執行。
值得注意的是:帶有 type="module" 的腳本(包括內聯腳本)效果跟 defer 一樣;而通過腳本動態注入的 <script> 標簽效果類似 async。
一般來說,你應該避免使用 JavaScript 來呈現任何關鍵內容或頁面的 LCP 元素。這種做法稱為“客戶端渲染”,是單頁應用程序 (SPA) 中廣泛使用的一種技術。
通過 JavaScript 渲染的 HTML 標簽是無法被預加載掃描器觀察到的。這可能會延遲關鍵資源的下載,例如 LCP 圖片。瀏覽器只會在腳本執行后才開始下載 LCP 圖片,并添加到 DOM 中,這應該被避免。
此外,與直接在服務器響應請求返回相比,使用 JavaScript 渲染標簽更有可能產生較長的任務。廣泛使用 HTML 客戶端渲染也會延遲交互,在頁面 DOM 非常大的情況下尤其如此。
與 CSS 類似,壓縮 JavaScript 可以減少腳本文件大小,加快下載速度,讓瀏覽器更快解析和編譯 JavaScript。
此外,JavaScript 的壓縮可以比 CSS 等其他資源更好。當壓縮 JavaScript 時,它不僅能刪除空格、制表符和注釋等內容,而且 JavaScript 中標識符也能被縮短,這個過程有時被稱為丑化(uglification)。
js復制代碼// 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復制代碼// Uglified JavaScript production code:
export function injectScript(){const t=document.createElement("script");t.src="/js/scripts.js",t.type="module",document.body.appendChild(t)}
觀察可以發現,你可以看到源代碼中變量 scriptElement 被縮短成 t 了。當你腳本代碼很多時,這種做法可以節省的體積相當可觀,而且也不會影響網站的生產環境功能。
如果你有在用打包工具處理網站源碼,JavaScript 的生產壓縮默認就有。這類丑化工具——例如 Terser——也是高度可配置的,你可以調整丑化算法的激進程度來實現最大程度的壓縮。然而,任何丑化工具的默認設置通常就足夠使用的了。
本文在上一篇關鍵渲染路徑的基礎上,進一步討論了瀏覽器針對 JavaScript/CSS 采用的不同阻塞策略的原因,繼而給出優化 JavaScript、CSS 的不同手段。希望對各位正在閱讀的朋友日后的工作帶來一些幫助。
下一篇里,我們會繼續探討另一種在網頁中進行手工優化的方式——資源提示(resource hints),敬請期待。
再見。
網絡爬蟲開發中,使用強大的庫是至關重要的,而requests-html就是其中一顆璀璨的明星。本文將深度探討requests-html的各個方面,包括基本的HTTP請求、HTML解析、JavaScript渲染、選擇器的使用以及高級特性的應用。
首先,需要安裝requests-html:
pip install requests-html
然后,進行簡單的HTTP請求:
from requests_html import HTMLSession
session = HTMLSession()
response = session.get('https://example.com')
print(response.html.text)
requests-html內置了強大的HTML解析器和類似jQuery的選擇器,使得數據提取變得非常便捷:
# 使用選擇器提取標題
titles = response.html.find('h2')
for title in titles:
print(title.text)
對于需要JavaScript渲染的頁面,requests-html也能輕松應對:
# JavaScript渲染
r = session.get('https://example.com', params={'q': 'python'})
r.html.render()
print(r.html.text)
1 異步JavaScript渲染
對于異步加載的JavaScript內容,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
在請求中自定義Headers和Cookies是常見需求,requests-html為此提供了簡單易用的方法:
# 自定義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)
1 抓取動態頁面
通過requests-html,可以輕松抓取動態頁面的數據:
# 抓取動態頁面
r = session.get('https://example.com/dynamic-page')
r.html.render()
print(r.html.text)
2 表單提交
模擬用戶行為,實現表單提交:
# 表單提交
payload = {'username': 'user', 'password': 'pass'}
r = session.post('https://example.com/login', data=payload)
print(r.html.text)
requests-html內置了類似于jQuery的選擇器,讓數據提取變得輕松:
# 使用選擇器提取鏈接
links = response.html.find('a')
for link in links:
print(link.attrs['href'])
此外,通過更復雜的選擇器和過濾器,可以更精準地定位和提取所需數據:
# 使用更復雜的選擇器和過濾器
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}")
對于需要等待頁面加載完成的情況,requests-html提供了wait參數:
# 等待頁面加載完成
r = session.get('https://example.com/dynamic-content')
r.html.render(wait=2)
print(r.html.text)
此外,還可以利用render函數生成頁面截圖:
# 生成頁面截圖
r = session.get('https://example.com')
r.html.render(screenshot='screenshot.png')
在爬蟲過程中,異常處理是不可或缺的一部分。requests-html提供了捕獲異常和錯誤頁面重試的選項:
# 異常處理和錯誤頁面重試
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}")
在爬蟲開發中,性能優化和并發請求是至關重要的。requests-html提供了一些功能和選項,能夠更好地處理這些方面的問題。
1. 并發請求
并發請求是同時向多個目標發送請求,以提高效率。requests-html使用asyncio庫支持異步請求,從而實現并發。以下是一個簡單的例子:
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實現并發請求
results = AsyncHTMLSession().run(lambda: [fetch(url) for url in urls])
for result in results:
print(result)
在這個例子中,asyncio.gather被用于同時運行多個異步請求。這種方式在大量頁面需要抓取時可以顯著提高效率。
2. 鏈接池
requests-html的Session對象內置了連接池,它能夠維護多個持久化連接,減少請求時的連接建立開銷。這對于頻繁請求同一域名下的多個頁面時尤為有用。以下是一個簡單的使用示例:
from requests_html import HTMLSession
session = HTMLSession()
# 利用連接池發送多個請求
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()接受一個包含多個URL的列表,使用連接池維護這些請求的連接。
3. 緩存
requests-html允許使用緩存,以避免重復下載相同的內容。這對于頻繁訪問不經常更新的網頁時很有用。以下是一個使用緩存的例子:
from requests_html import HTMLSession
session = HTMLSession()
# 使用緩存
response = session.get('https://example.com', cached=True)
print(response.html.text)
在這個例子中,cached=True表示啟用緩存。
在本篇博客中,深入探討了requests-html這一Python爬蟲庫,揭示了其強大而靈活的功能。通過詳細的示例代碼和實際應用場景,展示了如何使用該庫進行HTTP請求、HTML解析、JavaScript渲染以及高級功能的應用。requests-html的異步支持使得并發請求變得輕而易舉,通過連接池和緩存的利用,我們能夠更好地優化性能,提高爬蟲的效率。同時,庫內置的強大選擇器和靈活的數據提取方式讓頁面解析變得更為簡單。
總體而言,requests-html為爬蟲開發者提供了一個強大而友好的工具,使得從靜態網頁到動態渲染頁面的抓取都變得更加便捷。通過學習本文,不僅能夠熟練掌握requests-html的基本用法,還能深入理解其高級功能,為實際項目的開發提供更全面的解決方案。希望通過這篇博客,能夠更加自信和高效地運用requests-html來應對各類爬蟲任務。
、BeautifulSoup簡介
BeautifulSoup是Python爬蟲應用解析Html的利器,是Python三方模塊bs4中提供的進行HTML解析的類,可以認為是一個HTML解析工具箱,對HTML報文中的標簽具有比較好的容錯識別功能。lxml是一款html文本解析器,BeautifulSoup構建對象時需要指定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報文的常用功能:
通過標簽的contents屬性,可以訪問其下嵌套的所有下級HTML元素,這些該標簽下的子標簽對應的HTML元素放到一個contents 指向的列表中。
如:print(soup.body.contents)
可以訪問標簽對應的父、子、兄弟及祖先標簽信息;
使用strings屬性迭代訪問除標簽外的所有內容;
可以使用find、find_all、find_parent、find_parents等系列方法查找滿足特定條件的標簽;
使用select通過css選擇器定位特定標簽。
二、一些解析技巧
在HTML解析時,如果通過簡單的tag、或單個tag屬性(如id、class)或文本一次搜索或select定位是最簡單的,而有些情況需要使用組合方法才能處理。
2.1、通過標簽的多個屬性組合定位或查找
經常有些要定位的標簽有很多,按單個屬性查找也有很多,得使用多個屬性查找。如:
上面的html文本中有多個id為article_content的div標簽,如果使用:
就會返回兩條記錄。這時候就可以使用多標簽屬性定位的如下4種語句:
以上四種方式是等價的,因為id可以用#來標記,class在查找時需要和Python關鍵字class區分,因此有上述不同方法,注意select的每個屬性必須用中括號括起來,不同屬性的中括號之間不能有空格,如果有空格表示的就不是查找同一標簽的屬性,空格后的屬性表示前一個屬性對應標簽的子孫標簽的屬性。
2.2、利用tag標簽關系定位內容
tag標簽關系包括父子、兄弟、祖先等關系,有時要查找或定位的內容本身不是很好定位,但結合其他標簽關系(主要是父子、祖先關系)則可以唯一確認。
案例:
這是博文中關于博主個人信息的部分報文:
以上報文中,如果要取博主的原創文章數和周排名,原創文章數和博主周排名的tag標簽完全相同,二者都在span標簽內,標簽的屬性及值都相同,只是span標簽的父標簽dt標簽的兄弟標簽dd標簽的string的中文內容才能區分。對于這種情況,首先要通過祖先標簽<div class="data-info d-flex item-tiling">定位到祖先標簽,再在祖先標簽內通過中文字符串定位到要訪問屬性的兄弟標簽的子標簽,然后通過該子標簽找到其父標簽的父標簽,再通過該父標簽的dt子標簽的span子標簽訪問具體取值。
示例代碼如下:
注意:上面的select使用的也是標簽的屬性來定位標簽,并且兩個中括號之間有空格,表明后一個要查找的標簽在前一個屬性對應標簽的子孫標簽范圍內。
2.3、分析前去除程序代碼避免干擾
在解析HTML報文時,絕大多數情況是需要分析有用的標簽信息,但作為技術文章,大部分的博文中都有代碼,這些代碼可能會對分析進行干擾。如本文中的代碼含有一些分析的HTML報文,如果獲取本文的完整HTML內容,這些報文在非代碼部分也會出現,此時要排除代碼的影響,可以將代碼先從分析內容中去除再來分析。
目前大多數技術平臺的博文編輯器都支持對代碼的標識,象markdown等編輯器代碼的標簽為code標檢,如果有其他編輯器用不同標簽的,只有確認了標簽名,都可以按下面介紹的類似方式來處理。
處理步驟如下:
獲取報文;
構建BeatifulSoap對象soup;
通過soup.code.extract()或soup.code.decompose()方式就從soup對象中去除了代碼部分,decompose方法與extract方法的區別就是decompose直接刪除對應對象數據而extract再刪除時將刪除對象單獨返回。
三、小結
本文介紹了使用BeatifulSoap解析HTML報文的三個使用技巧,包括通過多屬性組合查找或定位標簽、通過結合多個標簽關系來定位標簽以及去除html報文中的代碼標簽來避免代碼對解析的影響。
寫字不易,敬請支持:
如果閱讀本文于您有所獲,敬請點贊、評論、收藏,謝謝大家的支持!
————————————————
版權聲明:本文為轉載文章,如有侵權,請聯系作者刪除。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。