使用Python編寫多線程爬蟲抓取百度貼吧郵箱與手機號

来源:https://www.cnblogs.com/pythonedu/archive/2018/05/19/9061779.html
-Advertisement-
Play Games

醒來的時候登QQ發現有人找我要一份貼吧爬蟲的源代碼,想起之前練手的時候寫過一個抓取百度貼吧發帖記錄中的郵箱與手機號的爬蟲,於是開源分享給大家學習與參考。 需求分析: 本爬蟲主要是對百度貼吧中各種帖子的內容進行抓取,並且分析帖子內容將其中的手機號和郵箱地址抓取出來。主要流程在代碼註釋中有詳細解釋。 測 ...


醒來的時候登QQ發現有人找我要一份貼吧爬蟲的源代碼,想起之前練手的時候寫過一個抓取百度貼吧發帖記錄中的郵箱與手機號的爬蟲,於是開源分享給大家學習與參考。

需求分析:

本爬蟲主要是對百度貼吧中各種帖子的內容進行抓取,並且分析帖子內容將其中的手機號和郵箱地址抓取出來。主要流程在代碼註釋中有詳細解釋。

測試環境:

代碼在Windows7 64bit,python 2.7 64bit(安裝mysqldb擴展)以及centos 6.5,python 2.7(帶mysqldb擴展)環境下測試通過

 

github:

 

環境準備:

工欲善其事必先利其器,大家可以從截圖看出我的環境是Windows 7 + PyCharm。我的Python環境是Python 2.7 64bit。這是比較適合新手使用的開發環境。然後我再建議大家安裝一個easy_install,聽名字就知道這是一個安裝器,它是用來安裝一些擴展包的,比如說在python中如果我們要操作mysql資料庫的話,python原生是不支持的,我們必須安裝mysqldb包來讓python可以操作mysql資料庫,如果有easy_install的話我們只需要一行命令就可以快速安裝號mysqldb擴展包,他就像php中的composer,centos中的yum,Ubuntu中的apt-get一樣方便。

相關工具可在我的github中找到:cw1997/python-tools,其中easy_install的安裝只需要在python命令行下運行那個py腳本然後稍等片刻即可,他會自動加入Windows的環境變數,在Windows命令行下如果輸入easy_install有回顯說明安裝成功。

環境選擇的細節說明:

至於電腦硬體當然是越快越好,記憶體起碼8G起步,因為爬蟲本身需要大量存儲和解析中間數據,尤其是多線程爬蟲,在碰到抓取帶有分頁的列表和詳情頁,並且抓取數據量很大的情況下使用queue隊列分配抓取任務會非常占記憶體。包括有的時候我們抓取的數據是使用json,如果使用mongodb等nosql資料庫存儲,也會很占記憶體。

網路連接建議使用有線網,因為市面上一些劣質的無線路由器和普通的民用無線網卡線上程開的比較大的情況下會出現間歇性斷網或者數據丟失,掉包等情況,這個我親有體會。

至於操作系統和python當然肯定是選擇64位。如果你使用的是32位的操作系統,那麼無法使用大記憶體。如果你使用的是32位的python,可能在小規模抓取數據的時候感覺不出有什麼問題,但是當數據量變大的時候,比如說某個列表,隊列,字典裡面存儲了大量數據,導致python的記憶體占用超過2g的時候會報記憶體溢出錯誤。原因在我曾經segmentfault上提過的問題中依雲的回答有解釋(java - python只要占用記憶體達到1.9G之後httplib模塊就開始報記憶體溢出錯誤 - SegmentFault

如果你準備使用mysql存儲數據,建議使用mysql5.5以後的版本,因為mysql5.5版本支持json數據類型,這樣的話可以拋棄mongodb了。(有人說mysql會比mongodb穩定一點,這個我不確定。)

至於現在python都已經出了3.x版本了,為什麼我這裡還使用的是python2.7?我個人選擇2.7版本的原因是自己當初很早以前買的python核心編程這本書是第二版的,仍然以2.7為示例版本。並且目前網上仍然有大量的教程資料是以2.7為版本講解,2.7在某些方面與3.x還是有很大差別,如果我們沒有學過2.7,可能對於一些細微的語法差別不是很懂會導致我們理解上出現偏差,或者看不懂demo代碼。而且現在還是有部分依賴包只相容2.7版本。我的建議是如果你是準備急著學python然後去公司工作,並且公司沒有老代碼需要維護,那麼可以考慮直接上手3.x,如果你有比較充裕的時間,並且沒有很系統的大牛帶,只能依靠網上零零散散的博客文章來學習,那麼還是先學2.7在學3.x,畢竟學會了2.7之後3.x上手也很快。

多線程爬蟲涉及到的知識點:

其實對於任何軟體項目而言,我們凡是想知道編寫這個項目需要什麼知識點,我們都可以觀察一下這個項目的主要入口文件都導入了哪些包。

 

現在來看一下我們這個項目,作為一個剛接觸python的人,可能有一些包幾乎都沒有用過,那麼我們在本小節就來簡單的說說這些包起什麼作用,要掌握他們分別會涉及到什麼知識點,這些知識點的關鍵詞是什麼。這篇文章並不會花費長篇大論來從基礎講起,因此我們要學會善用百度,搜索這些知識點的關鍵詞來自學。下麵就來一一分析一下這些知識點。

 

HTTP協議:

我們的爬蟲抓取數據本質上就是不停的發起http請求,獲取http響應,將其存入我們的電腦中。瞭解http協議有助於我們在抓取數據的時候對一些能夠加速抓取速度的參數能夠精準的控制,比如說keep-alive等。

threading模塊(多線程):

我們平時編寫的程式都是單線程程式,我們寫的代碼都在主線程裡面運行,這個主線程又運行在python進程中。關於線程和進程的解釋可以參考阮一峰的博客:進程與線程的一個簡單解釋 - 阮一峰的網路日誌

在python中實現多線程是通過一個名字叫做threading的模塊來實現。之前還有thread模塊,但是threading對於線程的控制更強,因此我們後來都改用threading來實現多線程編程了。

關於threading多線程的一些用法,我覺得這篇文章不錯:[python] 專題八.多線程編程之thread和threading 大家可以參考參考。

簡單來說,使用threading模塊編寫多線程程式,就是先自己定義一個類,然後這個類要繼承threading.Thread,並且把每個線程要做的工作代碼寫到一個類的run方法中,當然如果線程本身在創建的時候如果要做一些初始化工作,那麼就要在他的__init__方法中編寫好初始化工作所要執行的代碼,這個方法就像php,java中的構造方法一樣。

這裡還要額外講的一點就是線程安全這個概念。通常情況下我們單線程情況下每個時刻只有一個線程在對資源(文件,變數)操作,所以不可能會出現衝突。但是當多線程的情況下,可能會出現同一個時刻兩個線程在操作同一個資源,導致資源損壞,所以我們需要一種機制來解決這種衝突帶來的破壞,通常有加鎖等操作,比如說mysql資料庫的innodb表引擎有行級鎖等,文件操作有讀取鎖等等,這些都是他們的程式底層幫我們完成了。所以我們通常只要知道那些操作,或者那些程式對於線程安全問題做了處理,然後就可以在多線程編程中去使用它們了。而這種考慮到線程安全問題的程式一般就叫做“線程安全版本”,比如說php就有TS版本,這個TS就是Thread Safety線程安全的意思。下麵我們要講到的Queue模塊就是一種線程安全的隊列數據結構,所以我們可以放心的在多線程編程中使用它。

最後我們就要來講講至關重要的線程阻塞這個概念了。當我們詳細學習完threading模塊之後,大概就知道如何創建和啟動線程了。但是如果我們把線程創建好了,然後調用了start方法,那麼我們會發現好像整個程式立馬就結束了,這是怎麼回事呢?其實這是因為我們在主線程中只有負責啟動子線程的代碼,也就意味著主線程只有啟動子線程的功能,至於子線程執行的那些代碼,他們本質上只是寫在類裡面的一個方法,並沒在主線程裡面真正去執行他,所以主線程啟動完子線程之後他的本職工作就已經全部完成了,已經光榮退場了。既然主線程都退場了,那麼python進程就跟著結束了,那麼其他線程也就沒有記憶體空間繼續執行了。所以我們應該是要讓主線程大哥等到所有的子線程小弟全部執行完畢再光榮退場,那麼線上程對象中有什麼方法能夠把主線程卡住呢?thread.sleep嘛?這確實是個辦法,但是究竟應該讓主線程sleep多久呢?我們並不能準確知道執行完一個任務要多久時間,肯定不能用這個辦法。所以我們這個時候應該上網查詢一下有什麼辦法能夠讓子線程“卡住”主線程呢?“卡住”這個詞好像太粗鄙了,其實說專業一點,應該叫做“阻塞”,所以我們可以查詢“python 子線程阻塞主線程”,如果我們會正確使用搜索引擎的話,應該會查到一個方法叫做join(),沒錯,這個join()方法就是子線程用於阻塞主線程的方法,當子線程還未執行完畢的時候,主線程運行到含有join()方法的這一行就會卡在那裡,直到所有線程都執行完畢才會執行join()方法後面的代碼。

Queue模塊(隊列):

假設有一個這樣的場景,我們需要抓取一個人的博客,我們知道這個人的博客有兩個頁面,一個list.php頁面顯示的是此博客的所有文章鏈接,還有一個view.php頁面顯示的是一篇文章的具體內容。

如果我們要把這個人的博客裡面所有文章內容抓取下來,編寫單線程爬蟲的思路是:先用正則表達式把這個list.php頁面的所有鏈接a標簽的href屬性抓取下來,存入一個名字叫做article_list的數組(在python中不叫數組,叫做list,中文名列表),然後再用一個for迴圈遍歷這個article_list數組,用各種抓取網頁內容的函數把內容抓取下來然後存入資料庫。

如果我們要編寫一個多線程爬蟲來完成這個任務的話,就假設我們的程式用10個線程把,那麼我們就要想辦法把之前抓取的article_list平均分成10份,分別把每一份分配給其中一個子線程。

但是問題來了,如果我們的article_list數組長度不是10的倍數,也就是文章數量並不是10的整數倍,那麼最後一個線程就會比別的線程少分配到一些任務,那麼它將會更快的結束。

如果僅僅是抓取這種只有幾千字的博客文章這看似沒什麼問題,但是如果我們一個任務(不一定是抓取網頁的任務,有可能是數學計算,或者圖形渲染等等耗時任務)的運行時間很長,那麼這將造成極大地資源和時間浪費。我們多線程的目的就是儘可能的利用一切計算資源並且計算時間,所以我們要想辦法讓任務能夠更加科學合理的分配。

並且我還要考慮一種情況,就是文章數量很大的情況下,我們要既能快速抓取到文章內容,又能儘快的看到我們已經抓取到的內容,這種需求在很多CMS採集站上經常會體現出來。

比如說我們現在要抓取的目標博客,有幾千萬篇文章,通常這種情況下博客都會做分頁處理,那麼我們如果按照上面的傳統思路先抓取完list.php的所有頁面起碼就要幾個小時甚至幾天,老闆如果希望你能夠儘快顯示出抓取內容,並且儘快將已經抓取到的內容展現到我們的CMS採集站上,那麼我們就要實現一邊抓取list.php並且把已經抓取到的數據丟入一個article_list數組,一邊用另一個線程從article_list數組中提取已經抓取到的文章URL地址,然後這個線程再去對應的URL地址中用正則表達式取到博客文章內容。如何實現這個功能呢?

我們就需要同時開啟兩類線程,一類線程專門負責抓取list.php中的url然後丟入article_list數組,另外一類線程專門負責從article_list中提取出url然後從對應的view.php頁面中抓取出對應的博客內容。

但是我們是否還記得前面提到過線程安全這個概念?前一類線程一邊往article_list數組中寫入數據,另外那一類的線程從article_list中讀取數據並且刪除已經讀取完畢的數據。但是python中list並不是線程安全版本的數據結構,因此這樣操作會導致不可預料的錯誤。所以我們可以嘗試使用一個更加方便且線程安全的數據結構,這就是我們的子標題中所提到的Queue隊列數據結構。

同樣Queue也有一個join()方法,這個join()方法其實和上一個小節所講到的threading中join()方法差不多,只不過在Queue中,join()的阻塞條件是當隊列不為空空的時候才阻塞,否則繼續執行join()後面的代碼。在這個爬蟲中我便使用了這種方法來阻塞主線程而不是直接通過線程的join方式來阻塞主線程,這樣的好處是可以不用寫一個死迴圈來判斷當前任務隊列中是否還有未執行完的任務,讓程式運行更加高效,也讓代碼更加優雅。

還有一個細節就是在python2.7中隊列模塊的名字是Queue,而在python3.x中已經改名為queue,就是首字母大小寫的區別,大家如果是複製網上的代碼,要記得這個小區別。

getopt模塊:

如果大家學過c語言的話,對這個模塊應該會很熟悉,他就是一個負責從命令行中的命令裡面提取出附帶參數的模塊。比如說我們通常在命令行中操作mysql資料庫,就是輸入mysql -h127.0.0.1 -uroot -p,其中mysql後面的“-h127.0.0.1 -uroot -p”就是可以獲取的參數部分。

我們平時在編寫爬蟲的時候,有一些參數是需要用戶自己手動輸入的,比如說mysql的主機IP,用戶名密碼等等。為了讓我們的程式更加友好通用,有一些配置項是不需要硬編碼在代碼裡面,而是在執行他的時候我們動態傳入,結合getopt模塊我們就可以實現這個功能。

hashlib(哈希):

哈希本質上就是一類數學演算法的集合,這種數學演算法有個特性就是你給定一個參數,他能夠輸出另外一個結果,雖然這個結果很短,但是他可以近似認為是獨一無二的。比如說我們平時聽過的md5,sha-1等等,他們都屬於哈希演算法。他們可以把一些文件,文字經過一系列的數學運算之後變成短短不到一百位的一段數字英文混合的字元串。

python中的hashlib模塊就為我們封裝好了這些數學運算函數,我們只需要簡單的調用它就可以完成哈希運算。

為什麼在我這個爬蟲中用到了這個包呢?因為在一些介面請求中,伺服器需要帶上一些校驗碼,保證介面請求的數據沒有被篡改或者丟失,這些校驗碼一般都是hash演算法,所以我們需要用到這個模塊來完成這種運算。

json:

很多時候我們抓取到的數據不是html,而是一些json數據,json本質上只是一段含有鍵值對的字元串,如果我們需要提取出其中特定的字元串,那麼我們需要json這個模塊來將這個json字元串轉換為dict類型方便我們操作。

re(正則表達式):

有的時候我們抓取到了一些網頁內容,但是我們需要將網頁中的一些特定格式的內容提取出來,比如說電子郵箱的格式一般都是前面幾位英文數字字母加一個@符號加的功能變數名稱,而要像電腦語言描述這種格式,我們可以使用一種叫做正則表達式的表達式來表達出這種格式,並且讓電腦自動從一大段字元串中將符合這種特定格式的文字匹配出來。

sys:

這個模塊主要用於處理一些系統方面的事情,在這個爬蟲中我用他來解決輸出編碼問題。

time:

稍微學過一點英語的人都能夠猜出來這個模塊用於處理時間,在這個爬蟲中我用它來獲取當前時間戳,然後通過在主線程末尾用當前時間戳減去程式開始運行時的時間戳,得到程式的運行時間。

 

 

如圖所示,開50個線程抓取100頁(每頁30個帖子,相當於抓取了3000個帖子)貼吧帖子內容並且從中提取出手機郵箱這個步驟共耗時330秒。

 

urllib和urllib2:

這兩個模塊都是用於處理一些http請求,以及url格式化方面的事情。我的爬蟲http請求部分的核心代碼就是使用這個模塊完成的。

MySQLdb:

這是一個第三方模塊,用於在python中操作mysql資料庫。

這裡我們要註意一個細節問題:mysqldb模塊並不是線程安全版本,意味著我們不能在多線程中共用同一個mysql連接句柄。所以大家可以在我的代碼中看到,我在每個線程的構造函數中都傳入了一個新的mysql連接句柄。因此每個子線程只會用自己獨立的mysql連接句柄。

cmd_color_printers:

這也是一個第三方模塊,網上能夠找到相關代碼,這個模塊主要用於向命令行中輸出彩色字元串。比如說我們通常爬蟲出現錯誤,要輸出紅色的字體會比較顯眼,就要使用到這個模塊。

自動化爬蟲的錯誤處理:

 

如果大家在網路質量不是很好的環境下使用該爬蟲,會發現有的時候會報如圖所示的異常,這是我為了偷懶並沒有寫各種異常處理的邏輯。

 

通常情況下我們如果要編寫高度自動化的爬蟲,那麼就需要預料到我們的爬蟲可能會遇到的所有異常情況,針對這些異常情況做處理。

比如說如圖所示的錯誤,我們就應該把當時正在處理的任務重新塞入任務隊列,否則我們就會出現遺漏信息的情況。這也是爬蟲編寫的一個複雜點。

總結:

其實多線程爬蟲的編寫也不複雜,多看示例代碼,多自己動手嘗試,多去社區,論壇交流,很多經典的書上對多線程編程也有非常詳細的解釋。這篇文章本質上主要還是一篇科普文章,內容講解的都不是很深入,大家還需要課外自己多結合網上各種資料自己學習。如果對代碼中的邏輯有所不明白可以在評論區下提問,有空我都會耐心解答。

python學習交流群:125240963

轉載至:https://zhuanlan.zhihu.com/p/25039408

 


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

-Advertisement-
Play Games
更多相關文章
  • 使用google-gson類庫解析json文件 使用JsonParser解析器來解析字元串和輸入流,變成json對象 代碼如下: ...
  • 引言: 都說,滴水穿石非一日之功。然而有些人即使奮鬥一輩子也比不上別人一年,別人學習一年比不得你學習一個月。其中緣由,有些人看了大半輩子還沒看明白。 即使Python這麼火,為何你學習一年的Python還找不到工作? 我認為有以下四點非常關鍵: 1,功利心強: 急需賺錢之人,所以才會著重強調“賺錢” ...
  • 糾結於爬取百度圖片,竟然花費了一天的時間才讓程式順利跑起來。其中踩坑無數。而且還發現公司電腦實在是比較差勁。。。 ...
  • 在此之前,我花了兩個晚上去找思路 感想 1.其實程式開發都是一樣,每一個大程式都是成百上千的模塊組成,一個大功能你寫不出來,那麼一個登陸驗證就很輕鬆的寫出來 2.你只是因為沒有接觸過是如何實現這種功能的,所以你就不知道如何下筆 3.不會寫不要氣餒,多在網上搜相關的代碼看看別人是怎麼寫的 4.寫功能的 ...
  • :) 標題是開玩笑的,千萬別認真。 隨著AI的飛速發展,有志於此行的碼農也是急劇的增加,帶來的就是大家對演算法、數學的興趣也格外升高。 本文的來歷是這樣,今天某老同事在朋友圈發了一張屏拍,求公式。 看了一下還是難度不大,上半部分基本是兩個半圓,下半部分是兩個旋轉了的反餘弦函數。 不過我的數學也比較渣, ...
  • 什麼是網路爬蟲 網路爬蟲(又被稱為網頁蜘蛛,網路機器人,在FOAF社區中間,更經常的稱為網頁追逐者),是一種按照一定的規則,自動地抓取萬維網信息的程式或者腳本。另外一些不常使用的名字還有螞蟻、自動索引、模擬程式或者蠕蟲。 環境:Python3.6+Windows 開發工具:你喜歡用哪個就用哪個,你開 ...
  • 使用dom4j處理xml操作xml數據 示例代碼: 結果: 解析: 使用DocumentHelper.parseText();將字元串轉換成document對象直接使用document.asXML()就可以這document轉換成字元串對象 ...
  • python的垃圾回收機制是以引用計數為主,加上標記-清除,分代收集等輔助方式組成的,如果想打開gc功能,需要 import gc 模塊 ,然後 gc.enable() 就打開了這個功能,關閉是 gc.disable() . 查看一個對象的引用計數: sys.getrefcount() 總是會比實際 ...
一周排行
    -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.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...