[原創]手把手教你寫網路爬蟲(7):URL去重

来源:https://www.cnblogs.com/tuohai666/archive/2018/04/26/8949393.html
-Advertisement-
Play Games

手把手教你寫網路爬蟲(7) 作者:拓海 摘要:從零開始寫爬蟲,初學者的速成指南! 封面: 本期我們來聊聊URL去重那些事兒。以前我們曾使用Python的字典來保存抓取過的URL,目的是將重覆抓取的URL去除,避免多次抓取同一網頁。爬蟲會將待抓取的URL放在todo隊列中,從抓取到的網頁中提取到新的U ...


 

手把手教你寫網路爬蟲(7)

作者:拓海

摘要:從零開始寫爬蟲,初學者的速成指南!

封面:

 

本期我們來聊聊URL去重那些事兒。以前我們曾使用Python的字典來保存抓取過的URL,目的是將重覆抓取的URL去除,避免多次抓取同一網頁。爬蟲會將待抓取的URL放在todo隊列中,從抓取到的網頁中提取到新的URL,在它們被放入隊列之前,首先要確定這些新的URL是否被抓取過,如果之前已經抓取過了,就不再放入隊列。

有別於單機系統,在分散式系統中,這些URL應該存放在公共緩存中,才能讓多個爬蟲實例共用,我們繼續使用Redis緩存這些數據。URL既可以存儲在Redis的Set數據結構中,也可以將URL作為Key存儲為Redis的String類型。至於這兩種方案各有什麼優缺點,就留給讀者自己去思考了。

 

直接存儲URL

將URL以字元串的形式直接存儲到記憶體中。保守估計一下URL的平均長度是100位元組,那麼1億個URL所占的記憶體是: 100000000 * 0.0001MB = 10000MB,約等於10G。這也不是不能用,占用的空間再大都能通過擴容來解決。

問題是,如果一個伺服器存不下這麼多URL該怎麼辦呢?其實也簡單,明確每台伺服器的分工,也就是說得到一個URL就知道要交給哪台伺服器存儲,每台伺服器只存儲一類URL,比較簡單的實現方式就是對URL先哈希再取模。雖然能用,但還是有很大優化空間的。

 

存儲消息摘要

MD5是一個消息摘要演算法,它的用途很廣泛,我們這裡用它來壓縮URL。

消息摘要演算法的特點:

  1. 無論輸入的消息有多長,計算出來的消息摘要的長度總是固定的。
  2. 只要輸入的消息不同,對其進行摘要以後產生的摘要消息也必不相同;但相同的輸入必會產生相同的輸出。
  3. 消息摘要是單向、不可逆的。只能進行正向的信息摘要,而無法從摘要中恢復出任何的原始消息。

以上特點說明我們可以通過存儲URL的MD5來實現去重功能,因為不同的URL,MD5不同,相同的URL,MD5相同嘛。

以前我們要存的URL是這樣的:http://news.baidu.com/ns?ct=1&rn=20&ie=utf-8&bs=%E4%BA%AC%E4%B8%9C%E9%87%91%E8%9E%8D&rsv_bp=1&sr=0&cl=2&f=8&prevct=no&tn=news&word=%E4%BA%AC%E4%B8%9C%E9%87%91%E8%9E%8D&rsv_sug3=1&rsv_sug4=6&rsv_sug1=1&rsv_sug=1

對應的MD5值是這樣的:d552b0b40e21d06d73a1a0938635eb1a

怎麼樣?省了不少空間吧?

有人說,拓海你不要騙我,這個演算法的輸入是個無窮集合,而輸出是一個有限集合,必然會存在碰撞的,也就是存在不同的URL算出相同的MD5。這會導致去重時誤判,少抓數據!

好吧,從理論上來說,必然會出現這種情況。可是出現這種情況的概率是多少呢?下麵就算算兩個不同URL產生相同消息摘要的概率。

以下是三種常見的消息摘要演算法,分別是32、64、128位元組,每個位元組是十六進位數字的字元,它們的可能值數量分別是:

md5:   16^32  = 2^128 = 3.4 * 10^38

sha256: 16^64  = 2^256 = 1.2 × 10^77

sha512: 16^128 = 2^512 = 1.3 × 10^154

你可能會說,我是數字盲,我不知道這個數大不大。好吧,我為你找到了兩個直觀的參照物:

 

IPv6編碼地址數:2^128(約3.4×10^38)

IPv6是IETF設計的用於替代現行版本IP協議(IPv4)的下一代IP協議,號稱可以為全世界的每一粒沙子編上一個網址。

 

可觀測宇宙中的原子總數:10^80

上圖是哈勃望遠鏡對準天球上一個特定的區域(相當於整個天球面積的1/12700000)進行長時間的圖像拍攝,最後在這個區域裡面找到了約有10000個星系。這樣可以合理的推測,目前我們能用天文望遠鏡觀測到的宇宙範圍內有1.27x10^11個星系。

一個星系的恆星數(行星忽略不計了)目前普遍接受的一個數量級是4x10^11個。

像太陽這樣的恆星的質量是1.96x10^30kg。

這就可以算出宇宙的總質量為9.96x10^55kg。

一個氫原子的質量是1.66x10^-24g。

用宇宙質量除以一個氫原子的質量就得出了目前可觀測宇宙中的近似原子個數是10^80個。

可見,不同URL產生相同消息摘要的可能性非常小,簡直像大海撈針。。。不是,簡直像宇宙中撈原子一樣難,所以就放心使用吧。

消息摘要實現了對URL的壓縮,但壓縮後的大小還是和原來在一個數量級,空間效率並沒有質的提升。有沒有辦法只用幾個bit來唯一標識一個URL呢?有!布隆過濾器就是專門解決這類問題的。

 

布隆過濾器

Bloom Filter是一種空間效率很高的隨機數據結構,它利用bit數組很簡潔地表示一個集合,並能判斷一個元素是否屬於這個集合。Bloom Filter的這種高效是有一定代價的:在判斷一個元素是否屬於某個集合時,有可能會把不屬於這個集合的元素誤認為屬於這個集合(false positive)。因此,Bloom Filter不適合那些“零錯誤”的應用場合。而在能容忍低錯誤率的應用場合下,Bloom Filter通過極少的錯誤換取了存儲空間的極大節省。

它的原理很簡單,首先需要準bit數組(所有位初始化為0)和k個獨立hash函數。將hash函數對應的值的位數組置1,查找時如果發現所有hash函數對應位都是1說明存在,否則不存在。很明顯這個過程並不保證查找的結果是100%正確的。

如何根據輸入元素個數n,確定bit數組m的大小及hash函數個數呢?當hash函數個數k=(ln2)*(m/n)時錯誤率最小。在錯誤率不大於E的情況下,m至少要等於n*lg(1/E)才能表示任意n個元素的集合。但m還應該更大些,因為還要保證bit數組裡至少一半為0,則m應該>=nlg(1/E)*lge ,大概就是nlg(1/E)的1.44倍。假設錯誤率為0.01,則此時m應是n的13倍。這樣k大概是8個。

Google的Guava基礎庫里有布隆過濾器的實現,非常的簡潔和有深度,我們一起來學習一下這段java代碼。

 1 public <T> boolean put(T object, Funnel<? super T> funnel, int numHashFunctions, BitArray bits) {
 2     long bitSize = bits.bitSize();
 3     long hash64 = Hashing.murmur3_128().hashObject(object, funnel).asLong();
 4     int hash1 = (int) hash64;
 5     int hash2 = (int) (hash64 >>> 32);
 6 
 7     boolean bitsChanged = false;
 8     for (int i = 1; i <= numHashFunctions; i++) {
 9         int combinedHash = hash1 + (i * hash2);
10         // Flip all the bits if it's negative (guaranteed positive number)
11         if (combinedHash < 0) {
12             combinedHash = ~combinedHash;
13         }
14         bitsChanged |= bits.set(combinedHash % bitSize);
15     }
16     return bitsChanged;
17 }

 

01 函數的功能是把一條數據hash後保存到BitArray中,如果BitArray有變化則返回true,否則返回false。參數是數據、hash函數個數、BitArray地址。

03 使用murmur3 hash出一個long型的值。為什麼是一個,不應該是numHashFunctions個嗎?請往下看。

04 05 把hash64切成兩半,變成hash1和hash2。

08 09 重點來了,numHashFunctions個hash函數原來是這麼來的:hash1+(i*hash2)。Excuse me? 這種操作太隨意了吧?不用擔心,請看《Less Hashing, Same Performance: Building a Better Bloom Filter》,裡面論述了這種操作不會影響布隆過濾器的性能:A standard technique from the hashing literature is to use two hash functions h1(x) and h2(x) to simulate additional hash functions of the form gi(x) = h1(x) + ih2(x). We demonstrate that this technique can be usefully applied to Bloom filters and related data structures. Specifically, only two hash functions are necessary to effectively implement a Bloom filter without any loss in the asymptotic false positive probability.這個優化非常有用,畢竟hash的代價還是很大的。

11 12 是負的就取反(這裡的操作都很粗暴)。

14 設置BitArray里對應的bit,下麵進入set()里看看。

 1 boolean set(long index) {
 2     if (!get(index)) {
 3         data[(int) (index >>> 6)] |= (1L << index);
 4         bitCount++;
 5         return true;
 6     }
 7     return false;
 8 }
 9   
10 boolean get(long index) {
11     return (data[(int) (index >>> 6)] & (1L << index)) != 0;
12 }

 

02 先get()一下,看看是不是已經置為1。

03 index右移6位就是除以64,說明data是long型的數組,除以64就定位到了bit所在的數組下標。1L左移index位,定位到了bit在long中的位置。

 

下一步

以上就是URL去重的一點思路,希望對大家有幫助。下期打算為大家介紹下字元編解碼,以及亂碼的完美解決方案。再見!

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 在使用handsontable的時候,本身的下拉列表無法滿足業務需求,需要使用key value類型的dropdown. 找了半天終於找到了一個可以滿足需求的 "參考方案" 此方案完美的解決了我的問題。 但是使用過程中需要註意兩點 1.此插件是基於chosen.jquery.js的一個jquery插 ...
  • 一、 前言 狀態欄就是手機屏幕最頂部的區域,包括了:信號、運營商、電量等信息。通常APP都有屬於自己的色調風格,為了達到整體視覺美觀,通常會設置狀態欄和標題欄的色調設置成一致。 圖例: 二、狀態欄狀態類型 三、狀態欄變色 1.效果如圖: 2.根據色調設置狀態欄文字顏色,文字顏色只提供兩種值:ligh ...
  • 獲取元素位置可以用 offset 或 getBoundingClientRect,使用 offset 因為相容性不好,比較麻煩,offset獲取位置會形成“回溯”。而 getBoundingClientRect 方法則 相容性較好,基本所有的瀏覽器都支持了,且使用起來更容易和簡單。 ...
  • 在最近移動端項目中用到了vux,感覺用著還習慣,當把vux使用到PC端的時候出現了IE瀏覽器出現,這樣的錯誤信息: CSS3114: @font-face 未能完成 OpenType 嵌入許可權檢查。許可權必須是可安裝的。 文件: UwCtGsNCf5NCQ0N.... 然後在IE瀏覽器頁面中的字體圖標 ...
  • 本文內容: header nav article footer section aside datalist 音頻標簽: audio 視頻標簽: video 插入媒體標簽: embed 新增input屬性 首發日期:2018-04-25 header 功能:header標簽定義頁面的頁眉信息。【主要... ...
  • ng zorro Carousel 走馬燈的左右方向控制項實現 ng zorro框架的走馬燈本身還沒有左右方向控制項的實現,作者只是在文檔中(0.6x)中曝出幾個方法介面,如圖: 實現: 在根component中引入NzCarouselComponent 組件 在需要引用carousel的父組件中引入N ...
  • package.json webpack.config.js 的簡單配置 ...
  • 先說下自己開發的實例。 最近在使用 Spring Cloud Config 做分散式配置中心(基於 SVN/Git), 當所有服務啟動後,SVN/Git 中的配置文件更改後,客戶端服務讀取的還是舊的配置,並不能實時讀取(配置信息會緩存在客戶端) ,Spring Boot 提供了一種方式進行更新(通過 ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...