中文字幕在线观看,亚洲а∨天堂久久精品9966,亚洲成a人片在线观看你懂的,亚洲av成人片无码网站,亚洲国产精品无码久久久五月天

使用爬蟲技術(shù)實(shí)現(xiàn) Web 頁面資源可用性檢測

2018-07-20    來源:編程學(xué)習(xí)網(wǎng)

容器云強(qiáng)勢上線!快速搭建集群,上萬Linux鏡像隨意使用

對(duì)于電商類型和內(nèi)容服務(wù)類型的網(wǎng)站,經(jīng)常會(huì)出現(xiàn)因?yàn)榕渲缅e(cuò)誤造成頁面鏈接無法訪問的情況(404)。

顯然,要確保網(wǎng)站中的所有鏈接都具有可訪問性,通過人工進(jìn)行檢測肯定是不現(xiàn)實(shí)的,常用的做法是使用爬蟲技術(shù)定期對(duì)網(wǎng)站進(jìn)行資源爬取,及時(shí)發(fā)現(xiàn)訪問異常的鏈接。

對(duì)于網(wǎng)絡(luò)爬蟲,當(dāng)前市面上已經(jīng)存在大量的開源項(xiàng)目和技術(shù)討論的文章。不過,感覺大家普遍都將焦點(diǎn)集中在爬取效率方面,例如當(dāng)前就存在大量討論不同并發(fā)機(jī)制哪個(gè)效率更高的文章,而在爬蟲的其它特性方面探討的不多。

個(gè)人認(rèn)為,爬蟲的核心特性除了 快 ,還應(yīng)該包括 全 和 穩(wěn) ,并且從重要性的排序來看, 全 、 穩(wěn) 、 快 應(yīng)該是從高到低的。

全 排在第一位,是因?yàn)檫@是爬蟲的基本功能,若爬取的頁面不全,就會(huì)出現(xiàn)信息遺漏的情況,這種情況肯定是不允許的;而 穩(wěn) 排在第二位,是因?yàn)榕老x通常都是需要長期穩(wěn)定運(yùn)行的,若因?yàn)椴呗蕴幚聿划?dāng)造成爬蟲運(yùn)行過程中偶爾無法正常訪問頁面,肯定也是無法接受的;最后才是 快 ,我們通常需要爬取的頁面鏈接會(huì)非常多,因此效率就很關(guān)鍵,但這也必須建立在 全 和 穩(wěn) 的基礎(chǔ)上。

當(dāng)然,爬蟲本身是一個(gè)很深的技術(shù)領(lǐng)域,我接觸的也只是皮毛。本文只針對(duì)使用爬蟲技術(shù)實(shí)現(xiàn) Web 頁面資源可用性檢測的實(shí)際場景,詳細(xì)剖析下其中涉及到的幾個(gè)技術(shù)點(diǎn),重點(diǎn)解決如下幾個(gè)問題:

  • 全:如何才能爬取網(wǎng)站所有的頁面鏈接?特別是當(dāng)前許多網(wǎng)站的頁面內(nèi)容都是要靠前端渲染生成的,爬蟲要如何支持這種情況?
  • 穩(wěn):很多網(wǎng)站都有訪問頻率限制,若爬蟲策略處理不當(dāng),就常出現(xiàn) 403 和 503 的問題,該種問題要怎么解決?
  • 快:如何在保障爬蟲功能正常的前提下,盡可能地提升爬蟲效率?

爬蟲實(shí)現(xiàn)前端頁面渲染

在早些年,基本上絕大多數(shù)網(wǎng)站都是通過后端渲染的,即在服務(wù)器端組裝形成完整的 HTML 頁面,然后再將完整頁面返回給前端進(jìn)行展現(xiàn)。而近年來,隨著 AJAX 技術(shù)的不斷普及,以及 AngularJS 這類 SPA 框架的廣泛應(yīng)用,前端渲染的頁面越來越多。

不知大家有沒有聽說過,前端渲染相比于后端渲染,是不利于進(jìn)行 SEO 的,因?yàn)閷?duì)爬蟲不友好。究其原因,就是因?yàn)榍岸虽秩镜捻撁媸切枰跒g覽器端執(zhí)行 JavaScript 代碼(即 AJAX 請(qǐng)求)才能獲取后端數(shù)據(jù),然后才能拼裝成完整的 HTML 頁面。

針對(duì)這類情況,當(dāng)前也已經(jīng)有很多解決方案,最常用的就是借助 PhantomJS、 puppeteer 這類 Headless 瀏覽器工具,相當(dāng)于在爬蟲中內(nèi)置一個(gè)瀏覽器內(nèi)核,對(duì)抓取的頁面先渲染(執(zhí)行 Javascript 腳本),然后再對(duì)頁面內(nèi)容進(jìn)行抓取。

不過,要使用這類技術(shù),通常都是需要使用 Javascript 來開發(fā)爬蟲工具,對(duì)于我這種寫慣了 Python 的人來說的確有些痛苦。

直到某一天, kennethreitz 大神發(fā)布了開源項(xiàng)目 requests-html ,看到項(xiàng)目介紹中的那句 Full JavaScript support! 時(shí)不禁熱淚盈眶,就是它了!該項(xiàng)目在 GitHub 上發(fā)布后不到三天,star 數(shù)就達(dá)到 5000 以上,足見其影響力。

requests-html 為啥會(huì)這么火?

寫過 Python 的人,基本上都會(huì)使用 requests 這么一個(gè) HTTP 庫,說它是最好的 HTTP 庫一點(diǎn)也不夸張(不限編程語言),對(duì)于其介紹語 HTTP Requests for Humans 也當(dāng)之無愧。也是因?yàn)檫@個(gè)原因, Locust 和 HttpRunner 都是基于 requests 來進(jìn)行開發(fā)的。

而 requests-html ,則是 kennethreitz 在 requests 的基礎(chǔ)上開發(fā)的另一個(gè)開源項(xiàng)目,除了可以復(fù)用 requests 的全部功能外,還實(shí)現(xiàn)了對(duì) HTML 頁面的解析,即支持對(duì) Javascript 的執(zhí)行,以及通過 CSS 和 XPath 對(duì) HTML 頁面元素進(jìn)行提取的功能,這些都是編寫爬蟲工具非常需要的功能。

在實(shí)現(xiàn) Javascript 執(zhí)行方面, requests-html 也并沒有自己造輪子,而是借助了 pyppeteer 這個(gè)開源項(xiàng)目。還記得前面提到的 puppeteer 項(xiàng)目么,這是 GoogleChrome 官方實(shí)現(xiàn)的 Node API ;而 pyppeteer 這個(gè)項(xiàng)目,則相當(dāng)于是使用 Python 語言對(duì) puppeteer 的非官方實(shí)現(xiàn),基本具有 puppeteer 的所有功能。

理清了以上關(guān)系后,相信大家對(duì) requests-html 也就有了更好的理解。

在使用方面, requests-html 也十分簡單,用法與 requests 基本相同,只是多了 render 功能。

from requests_html import HTMLSession

session = HTMLSession()
r = session.get('http://python-requests.org')
r.html.render()

在執(zhí)行 render() 之后,返回的就是經(jīng)過渲染后的頁面內(nèi)容。

爬蟲實(shí)現(xiàn)訪問頻率控制

為了防止流量攻擊,很多網(wǎng)站都有訪問頻率限制,即限制單個(gè) IP 在一定時(shí)間段內(nèi)的訪問次數(shù)。若超過這個(gè)設(shè)定的限制,服務(wù)器端就會(huì)拒絕訪問請(qǐng)求,即響應(yīng)狀態(tài)碼為 403(Forbidden)。

這用來應(yīng)對(duì)外部的流量攻擊或者爬蟲是可以的,但在這個(gè)限定策略下,公司內(nèi)部的爬蟲測試工具同樣也無法正常使用了。針對(duì)這個(gè)問題,常用的做法就是在應(yīng)用系統(tǒng)中開設(shè)白名單,將公司內(nèi)部的爬蟲測試服務(wù)器 IP 加到白名單中,然后針對(duì)白名單中的 IP 不做限制,或者提升限額。但這同樣可能會(huì)出現(xiàn)問題。因?yàn)閼?yīng)用服務(wù)器的性能不是無限的,假如爬蟲的訪問頻率超過了應(yīng)用服務(wù)器的處理極限,那么就會(huì)造成應(yīng)用服務(wù)器不可用的情況,即響應(yīng)狀態(tài)碼為 503(Service Unavailable Error)。

基于以上原因,爬蟲的訪問頻率應(yīng)該是要與項(xiàng)目組的開發(fā)和運(yùn)維進(jìn)行統(tǒng)一評(píng)估后確定的;而對(duì)于爬蟲工具而言,實(shí)現(xiàn)對(duì)訪問頻率的控制也就很有必要了。

那要怎樣實(shí)現(xiàn)訪問頻率的控制呢?

我們可以先回到爬蟲本身的實(shí)現(xiàn)機(jī)制。對(duì)于爬蟲來說,不管采用什么實(shí)現(xiàn)形式,應(yīng)該都可以概括為生產(chǎn)者和消費(fèi)者模型,即:

  • 消費(fèi)者:爬取新的頁面
  • 生產(chǎn)者:對(duì)爬取的頁面進(jìn)行解析,得到需要爬取的頁面鏈接

對(duì)于這種模型,最簡單的做法是使用一個(gè) FIFO 的隊(duì)列,用于存儲(chǔ)未爬取的鏈接隊(duì)列(unvisited_urls_queue)。不管是采用何種并發(fā)機(jī)制,這個(gè)隊(duì)列都可以在各個(gè) worker 中共享。對(duì)于每一個(gè) worker 來說,都可以按照如下做法:

  • 從 unvisited_urls_queue 隊(duì)首中取出一個(gè)鏈接進(jìn)行訪問;
  • 解析出頁面中的鏈接,遍歷所有的鏈接,找出未訪問過的鏈接;
  • 將未訪問過的鏈接加入到 unvisited_urls_queue 隊(duì)尾
  • 直到 unvisited_urls_queue 為空時(shí)終止任務(wù)

然后回到我們的問題,要限制訪問頻率,即單位時(shí)間內(nèi)請(qǐng)求的鏈接數(shù)目。顯然,worker 之間相互獨(dú)立,要在執(zhí)行端層面協(xié)同實(shí)現(xiàn)整體的頻率控制并不容易。但從上面的步驟中可以看出,unvisited_urls_queue 被所有 worker 共享,并且作為源頭供給的角色。那么只要我們可以實(shí)現(xiàn)對(duì) unvisited_urls_queue 補(bǔ)充的數(shù)量控制,就實(shí)現(xiàn)了爬蟲整體的訪問頻率控制。

以上思路是正確的,但在具體實(shí)現(xiàn)的時(shí)候會(huì)存在幾個(gè)問題:

  • 需要一個(gè)用于存儲(chǔ)已經(jīng)訪問鏈接的集合(visited_urls_set),該集合需要在各個(gè) worker 中實(shí)現(xiàn)共享;
  • 需要一個(gè)全局的計(jì)數(shù)器,統(tǒng)計(jì)到達(dá)設(shè)定時(shí)間間隔(rps即1秒,rpm即1分鐘)時(shí)已訪問的總鏈接數(shù);

并且在當(dāng)前的實(shí)際場景中,最佳的并發(fā)機(jī)制是選擇多進(jìn)程(下文會(huì)詳細(xì)說明原因),每個(gè) worker 在不同的進(jìn)程中,那要實(shí)現(xiàn)對(duì)集合的共享就不大容易了。同時(shí),如果每個(gè) worker 都要負(fù)責(zé)對(duì)總請(qǐng)求數(shù)進(jìn)行判斷,即將訪問頻率的控制邏輯放到 worker 中實(shí)現(xiàn),那對(duì)于 worker 來說會(huì)是一個(gè)負(fù)擔(dān),邏輯上也會(huì)比較復(fù)雜。

因此比較好的方式是,除了未訪問鏈接隊(duì)列(unvisited_urls_queue),另外再新增一個(gè)爬取結(jié)果的存儲(chǔ)隊(duì)列(fetched_urls_queue),這兩個(gè)隊(duì)列都在各個(gè) worker 中共享。那么,接下來邏輯就變得簡單了:

  • 在各個(gè) worker 中,只需要從 unvisited_urls_queue 中取數(shù)據(jù),解析出結(jié)果后統(tǒng)統(tǒng)存儲(chǔ)到 fetched_urls_queue,無需關(guān)注訪問頻率的問題;
  • 在主進(jìn)程中,不斷地從 fetched_urls_queue 取數(shù)據(jù),將未訪問過的鏈接添加到 unvisited_urls_queue,在添加之前進(jìn)行訪問頻率控制。

具體的控制方法也很簡單,假設(shè)我們是要實(shí)現(xiàn) RPS 的控制,那么就可以使用如下方式(只截取關(guān)鍵片段):

start_timer = time.time()
requests_queued = 0

while True:
    try:
        url = self.fetched_urls_queue.get(timeout=5)
    except queue.Empty:
        break

    # visited url will not be crawled twice
    if url in self.visited_urls_set:
        continue

    # limit rps or rpm
    if requests_queued >= self.requests_limit:
        runtime_secs = time.time() - start_timer
        if runtime_secs < self.interval_limit:
            sleep_secs = self.interval_limit - runtime_secs
            # exceed rps limit, sleep
            time.sleep(sleep_secs)

        start_timer = time.time()
        requests_queued = 0

    self.unvisited_urls_queue.put(url)
    self.visited_urls_set.add(url)
    requests_queued += 1

提升爬蟲效率

對(duì)于提升爬蟲效率這部分,當(dāng)前已經(jīng)有大量的討論了,重點(diǎn)都是集中在不同的并發(fā)機(jī)制上面,包括多進(jìn)程、多線程、asyncio等。

不過,他們的并發(fā)測試結(jié)果對(duì)于本文中討論的爬蟲場景并不適用。因?yàn)樵诒疚牡呐老x場景中,實(shí)現(xiàn)前端頁面渲染是最核心的一項(xiàng)功能特性,而要實(shí)現(xiàn)前端頁面渲染,底層都是需要使用瀏覽器內(nèi)核的,相當(dāng)于每個(gè) worker 在運(yùn)行時(shí)都會(huì)跑一個(gè) Chromium 實(shí)例。

眾所周知,Chromium 對(duì)于 CPU 和內(nèi)存的開銷都是比較大的,因此為了避免機(jī)器資源出現(xiàn)瓶頸,使用多進(jìn)程機(jī)制(multiprocessing)充分調(diào)用多處理器的硬件資源無疑是最佳的選擇。

另一個(gè)需要注意也是比較被大家忽略的點(diǎn),就是在頁面鏈接的請(qǐng)求方法上。

請(qǐng)求頁面鏈接,不都是使用 GET 方法么?

的確,使用 GET 請(qǐng)求肯定是可行的,但問題在于,GET 請(qǐng)求時(shí)會(huì)加載頁面中的所有資源信息,這本身會(huì)是比較耗時(shí)的,特別是遇到鏈接為比較大的圖片或者附件的時(shí)候。這無疑會(huì)耗費(fèi)很多無謂的時(shí)間,畢竟我們的目的只是為了檢測鏈接資源是否可訪問而已。

比較好的的做法是對(duì)網(wǎng)站的鏈接進(jìn)行分類:

  • 資源型鏈接,包括圖片、CSS、JS、文件、視頻、附件等,這類鏈接只需檢測可訪問性;
  • 外站鏈接,這類鏈接只需檢測該鏈接本身的可訪問性,無需進(jìn)一步檢測該鏈接加載后頁面中包含的鏈接;
  • 本站頁面鏈接,這類鏈接除了需要檢測該鏈接本身的可訪問性,還需要進(jìn)一步檢測該鏈接加載后頁面中包含的鏈接的可訪問性;

在如上分類中,除了第三類是必須要使用 GET 方法獲取頁面并加載完整內(nèi)容(render),前兩類完全可以使用 HEAD 方法進(jìn)行代替。一方面,HEAD 方法只會(huì)獲取狀態(tài)碼和 headers 而不獲取 body,比 GET 方法高效很多;另一方面,前兩類鏈接也無需進(jìn)行頁面渲染,省去了調(diào)用 Chromium 進(jìn)行解析的步驟,執(zhí)行效率的提高也會(huì)非常明顯。

總結(jié)

本文針對(duì)如何使用爬蟲技術(shù)實(shí)現(xiàn) Web 頁面資源可用性檢測進(jìn)行了講解,重點(diǎn)圍繞爬蟲如何實(shí)現(xiàn) 全 、 穩(wěn) 、 快 三個(gè)核心特性進(jìn)行了展開。對(duì)于爬蟲技術(shù)的更多內(nèi)容,后續(xù)有機(jī)會(huì)我們?cè)龠M(jìn)一步進(jìn)行探討。

 

來自:http://debugtalk.com/post/requests-crawler/

 

標(biāo)簽: Google seo 代碼 電商 服務(wù)器 服務(wù)器端 腳本 網(wǎng)絡(luò) 應(yīng)用服務(wù)器

版權(quán)申明:本站文章部分自網(wǎng)絡(luò),如有侵權(quán),請(qǐng)聯(lián)系:west999com@outlook.com
特別注意:本站所有轉(zhuǎn)載文章言論不代表本站觀點(diǎn)!
本站所提供的圖片等素材,版權(quán)歸原作者所有,如需使用,請(qǐng)與原作者聯(lián)系。

上一篇:try-catch-finally,被你忽略掉的執(zhí)行順序

下一篇:JavaScript 的 this 原理