用 Python 寫網(wǎng)絡爬蟲(第2版)_第1頁
用 Python 寫網(wǎng)絡爬蟲(第2版)_第2頁
用 Python 寫網(wǎng)絡爬蟲(第2版)_第3頁
用 Python 寫網(wǎng)絡爬蟲(第2版)_第4頁
用 Python 寫網(wǎng)絡爬蟲(第2版)_第5頁
已閱讀5頁,還剩163頁未讀, 繼續(xù)免費閱讀

下載本文檔

版權說明:本文檔由用戶提供并上傳,收益歸屬內容提供方,若內容存在侵權,請進行舉報或認領

文檔簡介

用Python寫網(wǎng)絡爬蟲(第2版)目錄\h第1章網(wǎng)絡爬蟲簡介\h1.1網(wǎng)絡爬蟲何時有用\h1.2網(wǎng)絡爬蟲是否合法\h1.3Python3\h1.4背景調研\(zhòng)h1.4.1檢查robots.txt\h1.4.2檢查網(wǎng)站地圖\h1.4.3估算網(wǎng)站大小\h1.4.4識別網(wǎng)站所用技術\h1.4.5尋找網(wǎng)站所有者\h1.5編寫第一個網(wǎng)絡爬蟲\h1.5.1抓取與爬取的對比\h1.5.2下載網(wǎng)頁\h1.5.3網(wǎng)站地圖爬蟲\h1.5.4ID遍歷爬蟲\h1.5.5鏈接爬蟲\h1.5.6使用requests庫\h1.6本章小結\h第2章數(shù)據(jù)抓取\h2.1分析網(wǎng)頁\h2.23種網(wǎng)頁抓取方法\h2.2.1正則表達式\h2.2.2BeautifulSoup\h2.2.3Lxml\h2.3CSS選擇器和瀏覽器控制臺\h2.4XPath選擇器\h2.5LXML和家族樹\h2.6性能對比\h2.7抓取結果\h2.7.1抓取總結\h2.7.2為鏈接爬蟲添加抓取回調\h2.8本章小結\h第3章下載緩存\h3.1何時使用緩存\h3.2為鏈接爬蟲添加緩存支持\h3.3磁盤緩存\h3.3.1實現(xiàn)磁盤緩存\h3.3.2緩存測試\h3.3.3節(jié)省磁盤空間\h3.3.4清理過期數(shù)據(jù)\h3.3.5磁盤緩存缺點\h3.4鍵值對存儲緩存\h3.4.1鍵值對存儲是什么\h3.4.2安裝Redis\h3.4.3Redis概述\h3.4.4Redis緩存實現(xiàn)\h3.4.5壓縮\h3.4.6測試緩存\h3.4.7探索requests-cache\h3.5本章小結\h第4章并發(fā)下載\h4.1100萬個網(wǎng)頁\h4.1.1解析Alexa列表\h4.2串行爬蟲\h4.3多線程爬蟲\h4.4線程和進程如何工作\h4.4.1實現(xiàn)多線程爬蟲\h4.4.2多進程爬蟲\h4.5性能\h4.5.1Python多進程與GIL\h4.6本章小結\h第5章動態(tài)內容\h5.1動態(tài)網(wǎng)頁示例\h5.2對動態(tài)網(wǎng)頁進行逆向工程\h5.2.1邊界情況\h5.3渲染動態(tài)網(wǎng)頁\h5.3.1PyQt還是PySide\h5.3.2執(zhí)行JavaScript\h5.3.3使用WebKit與網(wǎng)站交互\h5.4渲染類\h5.4.1Selenium\h5.5本章小結\h第6章表單交互\h6.1登錄表單\h6.1.1從瀏覽器加載cookie\h6.2支持內容更新的登錄腳本擴展\h6.3使用Selenium實現(xiàn)自動化表單處理\h6.3.1網(wǎng)絡抓取時的“人類化”方法\h6.4本章小結\h第7章驗證碼處理\h7.1注冊賬號\h7.1.1加載驗證碼圖像\h7.2光學字符識別\h7.2.1進一步改善\h7.3處理復雜驗證碼\h7.4使用驗證碼處理服務\h7.4.19kw入門\h7.4.2報告錯誤\h7.4.3與注冊功能集成\h7.5驗證碼與機器學習\h7.6本章小結\h第8章Scrapy\h8.1安裝Scrapy\h8.2啟動項目\h8.2.1定義模型\h8.2.2創(chuàng)建爬蟲\h8.3不同的爬蟲類型\h8.4使用shell命令抓取\h8.4.1檢查結果\h8.4.2中斷與恢復爬蟲\h8.5使用Portia編寫可視化爬蟲\h8.5.1安裝\h8.5.2標注\h8.5.3運行爬蟲\h8.5.4檢查結果\h8.6使用Scrapely實現(xiàn)自動化抓取\h8.7本章小結\h第9章綜合應用\h9.1Google搜索引擎\h9.2Facebook\h9.2.1網(wǎng)站\h9.2.2FacebookAPI\h9.3Gap\h9.4寶馬\h9.5本章小結第1章網(wǎng)絡爬蟲簡介歡迎來到網(wǎng)絡爬蟲的廣闊天地!網(wǎng)絡爬蟲被用于許多領域,收集不太容易以其他格式獲取的數(shù)據(jù)。你可能是正在撰寫新報道的記者,也可能是正在抽取新數(shù)據(jù)集的數(shù)據(jù)科學家。即使你只是臨時的開發(fā)人員,網(wǎng)絡爬蟲也是非常有用的工具,比如當你需要檢查大學網(wǎng)站上最新的家庭作業(yè)并且希望通過郵件發(fā)送給你時。無論你的動機是什么,我們都希望你已經(jīng)準備好開始學習了!在本章中,我們將介紹如下主題:網(wǎng)絡爬蟲領域簡介;解釋合法性質疑;介紹Python3安裝;對目標網(wǎng)站進行背景調研;逐步完善一個高級網(wǎng)絡爬蟲;使用非標準庫協(xié)助抓取網(wǎng)站。1.1網(wǎng)絡爬蟲何時有用假設我有一個鞋店,并且想要及時了解競爭對手的價格。我可以每天訪問他們的網(wǎng)站,與我店鋪中鞋子的價格進行對比。但是,如果我店鋪中的鞋類品種繁多,或是希望能夠更加頻繁地查看價格變化的話,就需要花費大量的時間,甚至難以實現(xiàn)。再舉一個例子,我看中了一雙鞋,想等到它促銷時再購買。我可能需要每天訪問這家鞋店的網(wǎng)站來查看這雙鞋是否降價,也許需要等待幾個月的時間,我才能如愿盼到這雙鞋促銷。上述這兩個重復性的手工流程,都可以利用本書介紹的網(wǎng)絡爬蟲技術實現(xiàn)自動化處理。在理想狀態(tài)下,網(wǎng)絡爬蟲并不是必需品,每個網(wǎng)站都應該提供API,以結構化的格式共享它們的數(shù)據(jù)。然而在現(xiàn)實情況中,雖然一些網(wǎng)站已經(jīng)提供了這種API,但是它們通常會限制可以抓取的數(shù)據(jù),以及訪問這些數(shù)據(jù)的頻率。另外,網(wǎng)站開發(fā)人員可能會變更、移除或限制其后端API??傊?,我們不能僅僅依賴于API去訪問我們所需的在線數(shù)據(jù),而是應該學習一些網(wǎng)絡爬蟲技術的相關知識。1.2網(wǎng)絡爬蟲是否合法盡管在過去20年間已經(jīng)做出了諸多相關裁決,不過網(wǎng)絡爬蟲及其使用時法律所允許的內容仍然處于建設當中。如果被抓取的數(shù)據(jù)用于個人用途,且在合理使用版權法的情況下,通常沒有問題。但是,如果這些數(shù)據(jù)會被重新發(fā)布,并且抓取行為的攻擊性過強導致網(wǎng)站宕機,或者其內容受版權保護,抓取行為違反了其服務條款的話,那么則有一些法律判例可以提及。在FeistPublications,Inc.起訴RuralTelephoneServiceCo.的案件中,美國聯(lián)邦最高法院裁定抓取并轉載真實數(shù)據(jù)(比如,電話清單)是允許的。在澳大利亞,TelstraCorporationLimited起訴PhoneDirectoriesCompanyPtyLtd這一類似案件中,則裁定只有擁有明確作者的數(shù)據(jù),才可以受到版權的保護。而在另一起發(fā)生于美國的美聯(lián)社起訴融文集團的內容抓取案件中,則裁定對美聯(lián)社新聞重新聚合為新產品的行為是侵犯版權的。此外,在歐盟的ofir.dk起訴home.dk一案中,最終裁定定期抓取和深度鏈接是允許的。還有一些案件中,原告控告一些公司抓取強度過大,嘗試通過法律手段停止其抓取行為。在最近的QVC訴訟Resultly的案件中,最終裁定除非抓取行為造成了私人財產損失,否則不能被認定為故意侵害,即使爬蟲活動導致了部分站點的可用性問題。這些案件告訴我們,當抓取的數(shù)據(jù)是現(xiàn)實生活中真實的公共數(shù)據(jù)(比如,營業(yè)地址、電話清單)時,在遵守合理的使用規(guī)則的情況下是允許轉載的。但是,如果是原創(chuàng)數(shù)據(jù)(比如,意見和評論或用戶隱私數(shù)據(jù)),通常就會受到版權限制,而不能轉載。無論如何,當你抓取某個網(wǎng)站的數(shù)據(jù)時,請記住自己是該網(wǎng)站的訪客,應當約束自己的抓取行為,否則他們可能會封禁你的IP,甚至采取更進一步的法律行動。這就要求下載請求的速度需要限定在一個合理值之內,并且還需要設定一個專屬的用戶代理來標識自己的爬蟲。你還應該設法查看網(wǎng)站的服務條款,確保你所獲取的數(shù)據(jù)不是私有或受版權保護的內容。如果你還有疑慮或問題,可以向媒體律師咨詢你所在地區(qū)的相關判例。你可以自行搜索下述法律案件的更多信息。FeistPublicationsInc.起訴RuralTelephoneServiceCo.的案件。TelstraCorporationLimited起訴PhoneDirectoriesCompanyPvtLtd的案件。美聯(lián)社起訴融文集團的案件。ofir.dk起訴home.dk的案件。QVC起訴Resultly的案件。1.3Python3在本書中,我們將完全使用Python3進行開發(fā)。Python軟件基金會已經(jīng)宣布Python2將會被逐步淘汰,并且只支持到2020年;出于該原因,我們和許多其他Python愛好者一樣,已經(jīng)將開發(fā)轉移到對Python3的支持當中,在本書中我們將使用3.6版本。本書代碼將兼容Python3.4+的版本。如果你熟悉PythonVirtualEnvironments或Anaconda的使用,那么你可能已經(jīng)知道如何在一個新環(huán)境中創(chuàng)建Python3了。如果你希望以全局形式安裝Python3,那么我們推薦你搜索自己使用的操作系統(tǒng)的特定文檔。就我而言,我會直接使用VirtualEnvironmentWrapper(https://virtualenvwrapper.readthedocs.io/en/latest),這樣就可以很容易地對不同項目和Python版本使用多個不同的環(huán)境了。使用Conda環(huán)境或虛擬環(huán)境是最為推薦的,這樣你就可以輕松變更基于項目需求的依賴,而不會影響到你正在做的其他工作了。對于初學者來說,我推薦使用Conda,因為其需要的安裝工作更少一些。Conda的介紹文檔(https://conda.io/docs/intro.html)是一個不錯的開始!從此刻開始,所有代碼和命令都假設你已正確安裝Python3并且正在使用Python3.4+的環(huán)境。如果你看到了導入或語法錯誤,請檢查你是否處于正確的環(huán)境當中,查看跟蹤信息中是否存在Python2.7的文件路徑。1.4背景調研在深入討論爬取一個網(wǎng)站之前,我們首先需要對目標站點的規(guī)模和結構進行一定程度的了解。網(wǎng)站自身的robots.txt和Sitemap文件都可以為我們提供一定的幫助,此外還有一些能提供更詳細信息的外部工具,比如Google搜索和WHOIS。1.4.1檢查robots.txt大多數(shù)網(wǎng)站都會定義robots.txt文件,這樣可以讓爬蟲了解爬取該網(wǎng)站時存在哪些限制。這些限制雖然是僅僅作為建議給出,但是良好的網(wǎng)絡公民都應當遵守這些限制。在爬取之前,檢查robots.txt文件這一寶貴資源可以將爬蟲被封禁的可能性降至最低,而且還能發(fā)現(xiàn)和網(wǎng)站結構相關的線索。關于robots.txt協(xié)議的更多信息可以參見。下面的代碼是我們的示例文件robots.txt中的內容,可以訪問/robots.txt獲取。#section1

User-agent:BadCrawler

Disallow:/

#section2

User-agent:*

Crawl-delay:5

Disallow:/trap

#section3

Sitemap:/sitemap.xml

在section1中,robots.txt文件禁止用戶代理為BadCrawler的爬蟲爬取該網(wǎng)站,不過這種寫法可能無法起到應有的作用,因為惡意爬蟲根本不會遵從robots.txt的要求。本章后面的一個例子將會展示如何讓爬蟲自動遵守robots.txt的要求。section2規(guī)定,無論使用哪種用戶代理,都應該在兩次下載請求之間給出5秒的抓取延遲,我們需要遵從該建議以避免服務器過載。這里還有一個/trap鏈接,用于封禁那些爬取了不允許訪問的鏈接的惡意爬蟲。如果你訪問了這個鏈接,服務器就會封禁你的IP一分鐘!一個真實的網(wǎng)站可能會對你的IP封禁更長時間,甚至是永久封禁。不過如果這樣設置的話,我們就無法繼續(xù)這個例子了。section3定義了一個Sitemap文件,我們將在下一節(jié)中了解如何檢查該文件。1.4.2檢查網(wǎng)站地圖網(wǎng)站提供的Sitemap文件(即網(wǎng)站地圖)可以幫助爬蟲定位網(wǎng)站最新的內容,而無須爬取每一個網(wǎng)頁。如果想要了解更多信息,可以從/protocol.html獲取網(wǎng)站地圖標準的定義。許多網(wǎng)站發(fā)布平臺都有自動生成網(wǎng)站地圖的能力。下面是在robots.txt文件中定位到的Sitemap文件的內容。<?xmlversion="1.0"encoding="UTF-8"?>

<urlsetxmlns="/schemas/sitemap/0.9">

<url><loc>/view/Afghanistan-1</loc>

</url>

<url><loc>/view/Aland-Islands-2</loc>

</url>

<url><loc>/view/Albania-3</loc>

</url>

...

</urlset>

網(wǎng)站地圖提供了所有網(wǎng)頁的鏈接,我們會在后面的小節(jié)中使用這些信息,用于創(chuàng)建我們的第一個爬蟲。雖然Sitemap文件提供了一種爬取網(wǎng)站的有效方式,但是我們仍需對其謹慎處理,因為該文件可能存在缺失、過期或不完整的問題。1.4.3估算網(wǎng)站大小目標網(wǎng)站的大小會影響我們如何進行爬取。如果是像我們的示例站點這樣只有幾百個URL的網(wǎng)站,效率并沒有那么重要;但如果是擁有數(shù)百萬個網(wǎng)頁的站點,使用串行下載可能需要持續(xù)數(shù)月才能完成,這時就需要使用第4章中介紹的分布式下載來解決了。估算網(wǎng)站大小的一個簡便方法是檢查Google爬蟲的結果,因為Google很可能已經(jīng)爬取過我們感興趣的網(wǎng)站。我們可以通過Google搜索的site關鍵詞過濾域名結果,從而獲取該信息。我們可以從/advanced_search了解到該接口及其他高級搜索參數(shù)的用法。在域名后面添加URL路徑,可以對結果進行過濾,僅顯示網(wǎng)站的某些部分。同樣,你的結果可能會有所不同;不過,這種附加的過濾條件非常有用,因為在理想情況下,你只希望爬取網(wǎng)站中包含有用數(shù)據(jù)的部分,而不是爬取網(wǎng)站的每個頁面。1.4.4識別網(wǎng)站所用技術構建網(wǎng)站所使用的技術類型也會對我們如何爬取產生影響。有一個十分有用的工具可以檢查網(wǎng)站構建的技術類型——detectem模塊,該模塊需要Python3.5+環(huán)境以及Docker。如果你還沒有安裝Docker,可以遵照/products/overview中你使用的操作系統(tǒng)所對應的說明操作。當Docker安裝好后,你可以運行如下命令。dockerpullscrapinghub/splash

pipinstalldetectem

上述操作將從ScrapingHub拉取最新的Docker鏡像,并通過pip安裝該庫。為了確保不受任何更新或改動的影響,推薦使用Python虛擬環(huán)境(/3/library/venv.html)或Conda環(huán)境(https://conda.io/docs/using/envs.html),并查看項目的ReadMe頁面(/spectresearch/detectem)。為什么使用環(huán)境?假設你的項目使用了早期版本的庫進行開發(fā)(比如detectem),而在最新的版本中,detectem引入了一些向后不兼容的變更,造成你的項目無法正常工作。但是,你正在開發(fā)的其他項目中,可能使用了更新的版本。如果你的項目使用系統(tǒng)中安裝的detectem,那么當更新庫以支持其他項目時,該項目就會無法運行。IanBicking的virtualenv為解決該問題提供了一個巧妙的解決方法,該方法通過復制系統(tǒng)中Python的可執(zhí)行程序及其依賴到一個本地目錄中,創(chuàng)建了一個獨立的Python環(huán)境。這就能夠讓一個項目安裝指定版本的Python庫,而不依賴于外部系統(tǒng)。你還可以在不同的虛擬環(huán)境中使用不同的Python版本。Conda環(huán)境中使用了Anaconda的Python路徑,提供了相似的功能。detectem模塊基于許多擴展模塊,使用一系列請求和響應,來探測網(wǎng)站使用的技術。它使用了Splash,這是由ScrapingHub開發(fā)的一個腳本化瀏覽器。要想運行該模塊,只需使用det命令即可。$det

[('jquery','1.11.0')]

我們可以看到示例網(wǎng)站使用了通用的JavaScript庫,因此其內容很可能嵌入在HTML當中,相對來說應該比較容易抓取。detectem仍然相當年輕,旨在成為Wappalyzer的Python對標版本,Wappalyzer是一個基于Node.js的項目,支持解析不同后端、廣告網(wǎng)絡、JavaScript庫以及服務器設置。你也可以在Docker中運行Wappalyzer。首先需要下載其Docker鏡像,運行如下命令。$dockerpullwappalyzer/cli

然后,你可以從Docker實例中運行腳本。$dockerrunwappalyzer/cli

輸出結果不太容易閱讀,不過當我們將其拷貝到JSON解析器中,可以看到檢測出來的很多庫和技術。{'applications':

[{'categories':['JavascriptFrameworks'],

'confidence':'100',

'icon':'Modernizr.png',

'name':'Modernizr',

'version':''},

{'categories':['WebServers'],

'confidence':'100',

'icon':'Nginx.svg',

'name':'Nginx',

'version':''},

{'categories':['WebFrameworks'],

'confidence':'100',

'icon':'TwitterBootstrap.png',

'name':'TwitterBootstrap',

'version':''},

{'categories':['WebFrameworks'],

'confidence':'100',

'icon':'Web2py.png',

'name':'Web2py',

'version':''},

{'categories':['JavascriptFrameworks'],

'confidence':'100',

'icon':'jQuery.svg',

'name':'jQuery',

'version':''},

{'categories':['JavascriptFrameworks'],

'confidence':'100',

'icon':'jQueryUI.svg',

'name':'jQueryUI',

'version':'1.10.3'},

{'categories':['ProgrammingLanguages'],

'confidence':'100',

'icon':'Python.png',

'name':'Python',

'version':''}],

'originalUrl':'',

'url':''}

從上面可以看出,檢測結果認為Python和web2py框架具有很高的可信度。我們還可以看到網(wǎng)站使用了前端CSS框架TwitterBootstrap。Wappalyzer還檢測到網(wǎng)站使用了Modernizer.js以及用于后端服務器的Nginx。由于網(wǎng)站只使用了JQuery和Modernizer,那么網(wǎng)站不太可能全部頁面都是通過JavaScript加載的。而如果改用AngularJS或React構建該網(wǎng)站的話,此時的網(wǎng)站內容很可能就是動態(tài)加載的了。另外,如果網(wǎng)站使用了ASP.NET,那么在爬取網(wǎng)頁時,就必須要用到會話管理和表單提交了。對于這些更加復雜的情況,我們會在第5章和第6章中進行介紹。1.4.5尋找網(wǎng)站所有者對于一些網(wǎng)站,我們可能會關心其所有者是誰。比如,我們已知網(wǎng)站的所有者會封禁網(wǎng)絡爬蟲,那么我們最好把下載速度控制得更加保守一些。為了找到網(wǎng)站的所有者,我們可以使用WHOIS協(xié)議查詢域名的注冊者是誰。Python中有一個針對該協(xié)議的封裝庫,其文檔地址為/pypi/python-whois,我們可以通過pip進行安裝。pipinstallpython-whois

下面是使用該模塊對這個域名進行WHOIS查詢時返回結果的核心部分。>>>importwhois

>>>print(whois.whois(''))

{

...

"name_servers":[

"NS1.GOOGLE.COM",

"NS2.GOOGLE.COM",

"NS3.GOOGLE.COM",

"NS4.GOOGLE.COM",

"",

"",

"",

""

],

"org":"GoogleInc.",

"emails":[

"abusecomplaints@",

"dns-admin@"

]

}

從結果中可以看出該域名歸屬于Google,實際上也確實如此。該域名是用于GoogleAppEngine服務的。Google經(jīng)常會阻斷網(wǎng)絡爬蟲,盡管實際上其自身就是一個網(wǎng)絡爬蟲業(yè)務。當我們爬取該域名時需要十分小心,因為Google經(jīng)常會阻斷抓取其服務過快的IP;而你,或與你生活或工作在一起的人,可能需要使用Google的服務。我經(jīng)歷過在使用Google服務一段時間后,被要求輸入驗證碼的情況,甚至只是在對Google域名運行了簡單的搜索爬蟲之后。1.5編寫第一個網(wǎng)絡爬蟲為了抓取網(wǎng)站,我們首先需要下載包含有感興趣數(shù)據(jù)的網(wǎng)頁,該過程一般稱為爬取(crawling)。爬取一個網(wǎng)站有很多種方法,而選用哪種方法更加合適,則取決于目標網(wǎng)站的結構。本章中,我們首先會探討如何安全地下載網(wǎng)頁,然后會介紹如下3種爬取網(wǎng)站的常見方法:爬取網(wǎng)站地圖;使用數(shù)據(jù)庫ID遍歷每個網(wǎng)頁;跟蹤網(wǎng)頁鏈接。到目前為止,我們交替使用了抓取和爬取這兩個術語,接下來讓我們先來定義這兩種方法的相似點和不同點。1.5.1抓取與爬取的對比根據(jù)你所關注的信息以及站點內容和結構的不同,你可能需要進行網(wǎng)絡抓取或是網(wǎng)站爬取。那么它們有什么區(qū)別呢?網(wǎng)絡抓取通常針對特定網(wǎng)站,并在這些站點上獲取指定信息。網(wǎng)絡抓取用于訪問這些特定的頁面,如果站點發(fā)生變化或者站點中的信息位置發(fā)生變化的話,則需要進行修改。例如,你可能想要通過網(wǎng)絡抓取查看你喜歡的當?shù)夭蛷d的每日特色菜,為了實現(xiàn)該目的,你需要抓取其網(wǎng)站中日常更新該信息的部分。與之不同的是,網(wǎng)絡爬取通常是以通用的方式構建的,其目標是一系列頂級域名的網(wǎng)站或是整個網(wǎng)絡。爬取可以用來收集更具體的信息,不過更常見的情況是爬取網(wǎng)絡,從許多不同的站點或頁面中獲取小而通用的信息,然后跟蹤鏈接到其他頁面中。除了爬取和抓取外,我們還會在第8章中介紹網(wǎng)絡爬蟲。爬蟲可以用來爬取指定的一系列網(wǎng)站,或是在多個站點甚至整個互聯(lián)網(wǎng)中進行更廣泛的爬取。一般來說,我們會使用特定的術語反映我們的用例。在你開發(fā)網(wǎng)絡爬蟲時,可能會注意到它們在你想要使用的技術、庫和包中的區(qū)別。在這些情況下,你對不同術語的理解,可以幫助你基于所使用的術語選擇適當?shù)陌蚣夹g(例如,是否只用于抓???是否也適用于爬蟲?)。1.5.2下載網(wǎng)頁要想抓取網(wǎng)頁,我們首先需要將其下載下來。下面的示例腳本使用Python的urllib模塊下載URL。importurllib.request

defdownload(url):

returnurllib.request.urlopen(url).read()

當傳入URL參數(shù)時,該函數(shù)將會下載網(wǎng)頁并返回其HTML。不過,這個代碼片段存在一個問題,即當下載網(wǎng)頁時,我們可能會遇到一些無法控制的錯誤,比如請求的頁面可能不存在。此時,urllib會拋出異常,然后退出腳本。安全起見,下面再給出一個更穩(wěn)建的版本,可以捕獲這些異常。importurllib.request

fromurllib.errorimportURLError,HTTPError,ContentTooShortError

defdownload(url):

print('Downloading:',url)

try:

html=urllib.request.urlopen(url).read()

except(URLError,HTTPError,ContentTooShortError)ase:

print('Downloaderror:',e.reason)

html=None

returnhtml

現(xiàn)在,當出現(xiàn)下載或URL錯誤時,該函數(shù)能夠捕獲到異常,然后返回None。在本書中,我們將假設你在文件中編寫代碼,而不是使用提示符的方式(如上述代碼所示)。當你發(fā)現(xiàn)代碼以Python提示符>>>或IPython提示符In[1]:開始時,你需要將其輸入到正在使用的主文件中,或是保存文件后,在Python解釋器中導入這些函數(shù)和類。1.重試下載下載時遇到的錯誤經(jīng)常是臨時性的,比如服務器過載時返回的503ServiceUnavailable錯誤。對于此類錯誤,我們可以在短暫等待后嘗試重新下載,因為這個服務器問題現(xiàn)在可能已經(jīng)解決。不過,我們不需要對所有錯誤都嘗試重新下載。如果服務器返回的是404NotFound這種錯誤,則說明該網(wǎng)頁目前并不存在,再次嘗試同樣的請求一般也不會出現(xiàn)不同的結果?;ヂ?lián)網(wǎng)工程任務組(InternetEngineeringTaskForce)定義了HTTP錯誤的完整列表,從中可以了解到4xx錯誤發(fā)生在請求存在問題時,而5xx錯誤則發(fā)生在服務端存在問題時。所以,我們只需要確保download函數(shù)在發(fā)生5xx錯誤時重試下載即可。下面是支持重試下載功能的新版本代碼。defdownload(url,num_retries=2):

print('Downloading:',url)

try:

html=urllib.request.urlopen(url).read()

except(URLError,HTTPError,ContentTooShortError)ase:

print('Downloaderror:',e.reason)

html=None

ifnum_retries>0:

ifhasattr(e,'code')and500<=e.code<600:

#recursivelyretry5xxHTTPerrors

returndownload(url,num_retries-1)

returnhtml

現(xiàn)在,當download函數(shù)遇到5xx錯誤碼時,將會遞歸調用函數(shù)自身進行重試。此外,該函數(shù)還增加了一個參數(shù),用于設定重試下載的次數(shù),其默認值為兩次。我們在這里限制網(wǎng)頁下載的嘗試次數(shù),是因為服務器錯誤可能暫時還沒有恢復。想要測試該函數(shù),可以嘗試下載http://httpstat.us/500,該網(wǎng)址會始終返回500錯誤碼。>>>download('http://httpstat.us/500')

Downloading:http://httpstat.us/500

Downloaderror:InternalServerError

Downloading:http://httpstat.us/500

Downloaderror:InternalServerError

Downloading:http://httpstat.us/500

Downloaderror:InternalServerError

從上面的返回結果可以看出,download函數(shù)的行為和預期一致,先嘗試下載網(wǎng)頁,在接收到500錯誤后,又進行了兩次重試才放棄。2.設置用戶代理默認情況下,urllib使用Python-urllib/3.x作為用戶代理下載網(wǎng)頁內容,其中3.x是環(huán)境當前所用Python的版本號。如果能使用可辨識的用戶代理則更好,這樣可以避免我們的網(wǎng)絡爬蟲碰到一些問題。此外,也許是因為曾經(jīng)歷過質量不佳的Python網(wǎng)絡爬蟲造成的服務器過載,一些網(wǎng)站還會封禁這個默認的用戶代理。因此,為了使下載網(wǎng)站更加可靠,我們需要控制用戶代理的設定。下面的代碼對download函數(shù)進行了修改,設定了一個默認的用戶代理‘wswp’(即WebScrapingwithPython的首字母縮寫)。defdownload(url,user_agent='wswp',num_retries=2):

print('Downloading:',url)

request=urllib.request.Request(url)

request.add_header('User-agent',user_agent)

try:

html=urllib.request.urlopen(request).read()

except(URLError,HTTPError,ContentTooShortError)ase:

print('Downloaderror:',e.reason)

html=None

ifnum_retries>0:

ifhasattr(e,'code')and500<=e.code<600:

#recursivelyretry5xxHTTPerrors

returndownload(url,num_retries-1)

returnhtml

現(xiàn)在,如果你再次嘗試訪問,就能夠看到一個合法的HTML了。我們的下載函數(shù)可以在后續(xù)代碼中得到復用,該函數(shù)能夠捕獲異常、在可能的情況下重試網(wǎng)站以及設置用戶代理。1.5.3網(wǎng)站地圖爬蟲在第一個簡單的爬蟲中,我們將使用示例網(wǎng)站robots.txt文件中發(fā)現(xiàn)的網(wǎng)站地圖來下載所有網(wǎng)頁。為了解析網(wǎng)站地圖,我們將會使用一個簡單的正則表達式,從<loc>標簽中提取出URL。我們需要更新代碼以處理編碼轉換,因為我們目前的download函數(shù)只是簡單地返回了字節(jié)。而在下一章中,我們將會介紹一種更加穩(wěn)健的解析方法——CSS選擇器。下面是該示例爬蟲的代碼。importre

defdownload(url,user_agent='wswp',num_retries=2,charset='utf-8'):

print('Downloading:',url)

request=urllib.request.Request(url)

request.add_header('User-agent',user_agent)

try:

resp=urllib.request.urlopen(request)

cs=resp.headers.get_content_charset()

ifnotcs:

cs=charset

html=resp.read().decode(cs)

except(URLError,HTTPError,ContentTooShortError)ase:

print('Downloaderror:',e.reason)

html=None

ifnum_retries>0:

ifhasattr(e,'code')and500<=e.code<600:

#recursivelyretry5xxHTTPerrors

returndownload(url,num_retries-1)

returnhtml

defcrawl_sitemap(url):

#downloadthesitemapfile

sitemap=download(url)

#extractthesitemaplinks

links=re.findall('<loc>(.*?)</loc>',sitemap)

#downloadeachlink

forlinkinlinks:

html=download(link)

#scrapehtmlhere

#...

現(xiàn)在,運行網(wǎng)站地圖爬蟲,從示例網(wǎng)站中下載所有國家或地區(qū)頁面。>>>crawl_sitemap('/sitemap.xml')

Downloading:/sitemap.xml

Downloading:/view/Afghanistan-1

Downloading:/view/Aland-Islands-2

Downloading:/view/Albania-3

...

正如上面代碼中的download方法所示,我們必須更新字符編碼才能利用正則表達式處理網(wǎng)站響應。Python的read方法返回字節(jié),而正則表達式期望的則是字符串。我們的代碼依賴于網(wǎng)站維護者在響應頭中包含適當?shù)淖址幋a。如果沒有返回字符編碼頭部,我們將會把它設置為默認值UTF-8,并抱有最大的希望。當然,如果返回頭中的編碼不正確,或是編碼沒有設置并且也不是UTF-8的話,則會拋出錯誤。還有一些更復雜的方式用于猜測編碼(參見/pypi/chardet),該方法非常容易實現(xiàn)。到目前為止,網(wǎng)站地圖爬蟲已經(jīng)符合預期。不過正如前文所述,我們無法依靠Sitemap文件提供每個網(wǎng)頁的鏈接。下一節(jié)中,我們將會介紹另一個簡單的爬蟲,該爬蟲不再依賴于Sitemap文件。如果你在任何時候不想再繼續(xù)爬取,可以按下Ctrl+C或cmd+C退出Python解釋器或執(zhí)行的程序。1.5.4ID遍歷爬蟲本節(jié)中,我們將利用網(wǎng)站結構的弱點,更加輕松地訪問所有內容。下面是一些示例國家(或地區(qū))的URL。/view/Afghanistan-1/view/Australia-2/view/Brazil-3可以看出,這些URL只在URL路徑的最后一部分有所區(qū)別,包括國家(或地區(qū))名(作為頁面別名)和ID。在URL中包含頁面別名是非常普遍的做法,可以對搜索引擎優(yōu)化起到幫助作用。一般情況下,Web服務器會忽略這個字符串,只使用ID來匹配數(shù)據(jù)庫中的相關記錄。下面我們將其移除,查看/view/1,測試示例網(wǎng)站中的鏈接是否仍然可用。測試結果如圖1.1所示。圖1.1從圖1.1中可以看出,網(wǎng)頁依然可以加載成功,也就是說該方法是有用的?,F(xiàn)在,我們就可以忽略頁面別名,只利用數(shù)據(jù)庫ID來下載所有國家(或地區(qū))的頁面了。下面是使用了該技巧的代碼片段。importitertools

defcrawl_site(url):

forpageinitertools.count(1):

pg_url='{}{}'.format(url,page)

html=download(pg_url)

ifhtmlisNone:

break

#success-canscrapetheresult

現(xiàn)在,我們可以使用該函數(shù)傳入基礎URL。>>>crawl_site('/view/-')

Downloading:/view/-1

Downloading:/view/-2

Downloading:/view/-3

Downloading:/view/-4

[...]

在這段代碼中,我們對ID進行遍歷,直到出現(xiàn)下載錯誤時停止,我們假設此時抓取已到達最后一個國家(或地區(qū))的頁面。不過,這種實現(xiàn)方式存在一個缺陷,那就是某些記錄可能已被刪除,數(shù)據(jù)庫ID之間并不是連續(xù)的。此時,只要訪問到某個間隔點,爬蟲就會立即退出。下面是這段代碼的改進版本,在該版本中連續(xù)發(fā)生多次下載錯誤后才會退出程序。defcrawl_site(url,max_errors=5):

forpageinitertools.count(1):

pg_url='{}{}'.format(url,page)

html=download(pg_url)

ifhtmlisNone:

num_errors+=1

ifnum_errors==max_errors:

#maxerrorsreached,exitloop

break

else:

num_errors=0

#success-canscrapetheresult

上面代碼中實現(xiàn)的爬蟲需要連續(xù)5次下載錯誤才會停止遍歷,這樣就很大程度上降低了遇到記錄被刪除或隱藏時過早停止遍歷的風險。在爬取網(wǎng)站時,遍歷ID是一個很便捷的方法,但是和網(wǎng)站地圖爬蟲一樣,這種方法也無法保證始終可用。比如,一些網(wǎng)站會檢查頁面別名是否在URL中,如果不是,則會返回404NotFound錯誤。而另一些網(wǎng)站則會使用非連續(xù)大數(shù)作為ID,或是不使用數(shù)值作為ID,此時遍歷就難以發(fā)揮其作用了。例如,Amazon使用ISBN作為可用圖書的ID,這種編碼包含至少10位數(shù)字。使用ID對ISBN進行遍歷需要測試數(shù)十億次可能的組合,因此這種方法肯定不是抓取該站內容最高效的方法。正如你一直關注的那樣,你可能已經(jīng)注意到一些TOOMANYREQUESTS下載錯誤信息。現(xiàn)在無須擔心它,我們將會在1.5.5節(jié)的“高級功能”部分中介紹更多處理該類型錯誤的方法。1.5.5鏈接爬蟲到目前為止,我們已經(jīng)利用示例網(wǎng)站的結構特點實現(xiàn)了兩個簡單爬蟲,用于下載所有已發(fā)布的國家(或地區(qū))頁面。只要這兩種技術可用,就應當使用它們進行爬取,因為這兩種方法將需要下載的網(wǎng)頁數(shù)量降至最低。不過,對于另一些網(wǎng)站,我們需要讓爬蟲表現(xiàn)得更像普通用戶,跟蹤鏈接,訪問感興趣的內容。通過跟蹤每個鏈接的方式,我們可以很容易地下載整個網(wǎng)站的頁面。但是,這種方法可能會下載很多并不需要的網(wǎng)頁。例如,我們想要從一個在線論壇中抓取用戶賬號詳情頁,那么此時我們只需要下載賬號頁,而不需要下載討論貼的頁面。本章使用的鏈接爬蟲將使用正則表達式來確定應當下載哪些頁面。下面是這段代碼的初始版本。importre

deflink_crawler(start_url,link_regex):

"""CrawlfromthegivenstartURLfollowinglinksmatchedby

link_regex

"""

crawl_queue=[start_url]

whilecrawl_queue:

url=crawl_queue.pop()

html=download(url)

ifhtmlisnotNone:

continue

#filterforlinksmatchingourregularexpression

forlinkinget_links(html):

ifre.match(link_regex,link):

crawl_queue.append(link)

defget_links(html):

"""Returnalistoflinksfromhtml

"""

#aregularexpressiontoextractalllinksfromthewebpage

webpage_regex=pile("""<a[^>]+href=["'](.*?)["']""",

re.IGNORECASE)

#listofalllinksfromthewebpage

returnwebpage_regex.findall(html)

要運行這段代碼,只需要調用link_crawler函數(shù),并傳入兩個參數(shù):要爬取的網(wǎng)站URL以及用于匹配你想跟蹤的鏈接的正則表達式。對于示例網(wǎng)站來說,我們想要爬取的是國家(或地區(qū))列表索引頁和國家(或地區(qū))頁面。我們查看站點可以得知索引頁鏈接遵循如下格式:/index/1/index/2國家(或地區(qū))頁遵循如下格式:/view/Afghanistan-1/view/Aland-Islands-2因此,我們可以用/(index|view)/這個簡單的正則表達式來匹配這兩類網(wǎng)頁。當爬蟲使用這些輸入?yún)?shù)運行時會發(fā)生什么呢?你會得到如下所示的下載錯誤。>>>link_crawler('','/(index|view)/')

Downloading:

Downloading:/index/1

Traceback(mostrecentcalllast):

...

ValueError:unknownurltype:/index/1

正則表達式是從字符串中抽取信息的非常好的工具,因此我推薦每名程序員都應當“學會如何閱讀和編寫一些正則表達式”。即便如此,它們往往會非常脆弱,容易失效。我們將在本書后續(xù)部分介紹更先進的抽取鏈接和識別頁面的方式??梢钥闯觯瑔栴}出在下載/index/1時,該鏈接只有網(wǎng)頁的路徑部分,而沒有協(xié)議和服務器部分,也就是說這是一個相對鏈接。由于瀏覽器知道你正在瀏覽哪個網(wǎng)頁,并且能夠采取必要的步驟處理這些鏈接,因此在瀏覽器瀏覽時,相對鏈接是能夠正常工作的。但是,urllib并沒有上下文。為了讓urllib能夠定位網(wǎng)頁,我們需要將鏈接轉換為絕對鏈接的形式,以便包含定位網(wǎng)頁的所有細節(jié)。如你所愿,Python的urllib中有一個模塊可以用來實現(xiàn)該功能,該模塊名為parse。下面是link_crawler的改進版本,使用了urljoin方法來創(chuàng)建絕對路徑。fromurllib.parseimporturljoin

deflink_crawler(start_url,link_regex):

"""CrawlfromthegivenstartURLfollowinglinksmatchedby

link_regex

"""

crawl_queue=[start_url]

whilecrawl_queue:

url=crawl_queue.pop()

html=download(url)

ifnothtml:

continue

forlinkinget_links(html):

ifre.match(link_regex,link):

abs_link=urljoin(start_url,link)

crawl_queue.append(abs_link)

當你運行這段代碼時,會看到雖然下載了匹配的網(wǎng)頁,但是同樣的地點總是會被不斷下載到。產生該行為的原因是這些地點相互之間存在鏈接。比如,澳大利亞鏈接到了南極洲,而南極洲又鏈接回了澳大利亞,此時爬蟲就會繼續(xù)將這些URL放入隊列,永遠不會到達隊列尾部。要想避免重復爬取相同的鏈接,我們需要記錄哪些鏈接已經(jīng)被爬取過。下面是修改后的link_crawler函數(shù),具備了存儲已發(fā)現(xiàn)URL的功能,可以避免重復下載。deflink_crawler(start_url,link_regex):

crawl_queue=[start_url]

#keeptrackwhichURL'shaveseenbefore

seen=set(crawl_queue)

whilecrawl_queue:

url=crawl_queue.pop()

html=download(url)

ifnothtml:

continue

forlinkinget_links(html):

#checkiflinkmatchesexpectedregex

ifre.match(link_regex,link):

abs_link=urljoin(start_url,link)

#checkifhavealreadyseenthislink

ifabs_linknotinseen:

seen.add(abs_link)

crawl_queue.append(abs_link)

當運行該腳本時,它會爬取所有地點,并且能夠如期停止。最終,我們得到了一個可用的鏈接爬蟲!高級功能現(xiàn)在,讓我們?yōu)殒溄优老x添加一些功能,使其在爬取其他網(wǎng)站時更加有用。1.解析robots.txt首先,我們需要解析robots.txt文件,以避免下載禁止爬取的URL。使用Python的urllib庫中的robotparser模塊,就可以輕松完成這項工作,如下面的代碼所示。>>>fromurllibimportrobotparser

>>>rp=robotparser.RobotFileParser()

>>>rp.set_url('/robots.txt')

>>>rp.read()

>>>url=''

>>>user_agent='BadCrawler'

>>>rp.can_fetch(user_agent,url)

False

>>>user_agent='GoodCrawler'

>>>rp.can_fetch(user_agent,url)

True

robotparser模塊首先加載robots.txt文件,然后通過can_fetch()函數(shù)確定指定的用戶代理是否允許訪問網(wǎng)頁。在本例中,當用戶代理設置為'BadCrawler'時,robotparser模塊的返回結果表明無法獲取網(wǎng)頁,正如我們在示例網(wǎng)站的robots.txt文件中看到的定義一樣。為了將robotparser集成到鏈接爬蟲中,我們首先需要創(chuàng)建一個新函數(shù)用于返回robotparser對象。defget_robots_parser(robots_url):

"Returntherobotsparserobjectusingtherobots_url"

rp=robotparser.RobotFileParser()

rp.set_url(robots_url)

rp.read()

returnrp

我們需要可靠地設置robots_url,此時我們可以通過向函數(shù)傳遞額外的關鍵詞參數(shù)的方法實現(xiàn)這一目標。我們還可以設置一個默認值,防止用戶沒有傳遞該變量。假設從網(wǎng)站根目錄開始爬取,那么我們可以簡單地將robots.txt添加到URL的結尾處。此外,我們還需要定義user_agent。deflink_crawler(start_url,link_regex,robots_url=None,

user_agent='wswp'):

...

ifnotrobots_url:

robots_url='{}/robots.txt'.format(start_url)

rp=get_robots_parser(robots_url)

最后,我們在crawl循環(huán)中添加解析器檢查。...

whilecrawl_queue:

url=crawl_queue.pop()

#checkurlpassesrobots.txtrestrictions

ifrp.can_fetch(user_agent,url):

html=download(url,user_agent=user_agent)

...

else:

print('Blockedbyrobots.txt:',url)

我們可以通過使用壞的用戶代理字符串來測試我們這個高級鏈接爬蟲以及robotparser的使用。>>>link_crawler('','/(index|view)/',

user_agent='BadCrawler')

Blockedbyrobots.txt:

2.支持代理有時我們需要使用代理訪問某個網(wǎng)站。比如,Hulu在美國以外的很多國家被屏蔽,YouTube上的一些視頻也是。使用urllib支持代理并沒有想象中那么容易。我們將在后面的小節(jié)介紹一個對用戶更友好的PythonHTTP模塊——requests,該模塊同樣也能夠處理代理。下面是使用urllib支持代理的代碼。proxy=':1234'#examplestring

proxy_support=urllib.request.ProxyHandler({'http':proxy})

opener=urllib.request.build_opener(proxy_support)

urllib.request.install_opener(opener)

#nowrequestsviaurllib.requestwillbehandledviaproxy

下面是集成了該功能的新版本download函數(shù)。defdownload(url,user_agent='wswp',num_retries=2,charset='utf-8',

proxy=None):

print('Downloading:',url)

request=urllib.request.Request(url)

request.add_header('User-agent',user_agent)

try:

ifproxy:

proxy_support=urllib.request.ProxyHandler({'http':proxy})

opener=urllib.request.build_opener(proxy_support)

urllib.request.install_opener(opener)

resp=urllib.request.urlopen(request)

cs=resp.headers.get_content_charset()

ifnotcs:

cs=charset

html=resp.read().decode(cs)

except(URLError,HTTPError,ContentTooShortError)ase:

print('Downloaderror:',e.reason)

html=None

ifnum_retries>0:

ifhasattr(e,'code')and500<=e.code<600:

#recursivelyretry5xxHTTPerrors

returndownload(url,num_retries-1)

returnhtml

目前在默認情況下(Python3.5),urllib模塊不支持https代理。該問題可能會在Python未來的版本中發(fā)現(xiàn)變化,因此請查閱最新的文檔。此外,你還可以使用文檔推薦的訣竅(/recipes/456195),或繼續(xù)閱讀來學習如何使用requests庫。3.下載限速如果我們爬取網(wǎng)站的速度過快,就會面臨被封禁或是造成服務器過載的風險。為了降低這些風險,我們可以在兩次下載之間添加一組延時,從而對爬蟲限速。下面是實現(xiàn)了該功能的類的代碼。fromurllib.parseimporturlparse

importtime

classThrottle:

"""Addadelaybetweendownloadstothesamedomain

"""

def__init__(self,delay):

#amountofdelaybetweendownloadsforeachdomain

self.delay=delay

#timestampofwhenadomainwaslastaccessed

self.domains={}

defwait(self,url):

domain=urlparse(url).netloc

last_accessed=self.domains.get(domain)

ifself.delay>0andlast_accessedisnotNone:

sleep_secs=self.delay-(time.time()-last_accessed)

ifsleep_secs>0:

#domainhasbeenaccessedrecently

#soneedtosleep

time.sleep(sleep_secs)

#updatethelastaccessedtime

self.domains[domain]=time.time()

Throttle類記錄了每個域名上次訪問的時間,如果當前時間距離上次訪問時間小于指定延時,則執(zhí)行睡眠操作。我們可以在每次下載之前調用throttle對爬蟲進行限速。throttle=Throttle(delay)

...

throttle.wait(url)

html=download(url,user_agent=user_agent,num_retries=num_retries,

proxy=proxy,charset=charset)

4.避免爬蟲陷阱目前,我們的爬蟲會跟蹤所有之前沒有訪問過的鏈接。但是,一些網(wǎng)站會動態(tài)生成頁面內容,這樣就會出現(xiàn)無限多的網(wǎng)頁。比如,網(wǎng)站有一個在線日歷功能,提供了可以訪問下個月和下一年的鏈接,那么下個月的頁面中同樣會包含訪問再下個月的鏈接,這樣就會一直持續(xù)請求到部件設定的最大時間(可能會是很久之后的時間)。該站點可能還會在簡單的分頁導航中提供相同的功能,本質上是分頁請求不斷訪問空的搜索結果頁,直至達到最大頁數(shù)。這種情況被稱為爬蟲陷阱。想要避免陷入爬蟲陷阱,一個簡單的方法是記錄到達當前網(wǎng)頁經(jīng)過了多少個鏈接,也就是深度。當?shù)竭_最大深度時,爬蟲就不再向隊列中添加該網(wǎng)頁中的鏈接了。要實現(xiàn)最大深度的功能,我們需要修改seen變量。該變量原先只記錄訪問過的網(wǎng)頁鏈接,現(xiàn)在修改為一個字典,增加了已發(fā)現(xiàn)鏈接的深度記錄。deflink_crawler(...,max_depth=4):

seen={}

...

ifrp.can_fetch(user_agent,url):

depth=seen.get(url,0)

ifdepth==max_depth:

print('Skipping%sduetodepth'%url)

continue

...

forlinkinget_links(html):

ifre.match(link_regex,link):

abs_link=urljoin(start_url,link)

ifabs_linknotinseen:

seen[abs_link]=depth+1

crawl_queue.append(abs_link)

有了該功能之后,我們就有信心爬蟲最終一定能夠完成了。如果想要禁用該功能,只需將max_depth設為一個負數(shù)即可,此時當前深度永遠不會與之相等。5.最終版本這個高級鏈接爬蟲的完整源代碼可以在異步社區(qū)中下載得到,其文件名為advanced_link_crawler.py。為了方便按照本書操作,可以派生該代碼庫,并使用它對比及測試你自己的代碼。要測試該鏈接爬蟲,我們可以將用戶代理設置為BadCrawler,也就是本章前文所述的被robots.txt屏蔽了的那個用戶代理。從下面的運行結果中可以看出,爬蟲確實被屏蔽了,代碼啟動后馬上就會結束。>>>start_url='/index'

>>>link_regex='/(index|view)'

>>>link_crawler(start_url,link_regex,user_agent='BadCrawler')

Blockedbyrobots.txt:/

```

現(xiàn)在,讓我們使用默認的用戶代理,并將最大深度設置為`1`,這樣只有主頁上的鏈接才會被下載。

```

>>>link_crawler(start_url,link_regex,max_depth=1)

Downloading://index

Downloading:/index/1

Downloading:/view/Antigua-and-Barbuda-10

Downloading:/view/Antarctica-9

Downloading:/view/Anguilla-8

Downloading:/view/Angola-7

Downloading:/view/Andorra-6

Downloading:/view/American-Samoa-5

Downloading:/view/Algeria-4

Downloading:/view/Albania-3

Downloading:/view/Aland-Islands-2

Downloading:/view/Afghanistan-1

和預期一樣,爬蟲在下載完國家(或地區(qū))列表的第一頁之后就停止了。1.5.6使用requests庫盡管我們只使用urllib就已經(jīng)實現(xiàn)了一個相對高級的解析器,不過目前Python編寫的主流爬蟲一般都會使用requests庫來管理復雜的HTTP請求。該項目起初只是以“人類可讀”的方式協(xié)助封裝urllib功能的小庫,不過現(xiàn)如今已經(jīng)發(fā)展成為擁有數(shù)百名貢獻者的龐大項目??捎玫囊恍┕δ馨▋戎玫木幋a處理、對SSL和安全的重要更新以及對POST請求、JSON、cookie和代理的簡單處理。本書在大部分情況下,都將使用requests庫,因為它足夠簡單并且易于使用,而且它事實上也是大多數(shù)網(wǎng)絡爬蟲項目的標準。想要安裝requests,只需使用pip即可。pipinstallrequests

如果你想了解其所有功能的進一步介紹,可以閱讀它的文檔,地址為,此外也可以瀏覽其源代碼,地址為/kennethreitz/requests。為了對比使用這兩種庫的區(qū)別,我還創(chuàng)建了一個使用requests的高級鏈接爬蟲。你可以在從異步社區(qū)中下載的源碼文件中找到并查看該代碼,其文件名為advanced_link_crawler_using_requests.py。在主要的download函數(shù)中,展示了其關鍵區(qū)別。requests版本如下所示。defdownload(url,user_agent='wswp',num_retries=2,proxies=None):

print('Downloading:',url)

headers={'User-Agent':user_agent}

try:

resp=requests.get(url,headers=headers,proxies=proxies)

html=resp.text

ifresp.status_code>=400:

print('Downloaderror:',resp.text)

html=None

ifnum_retriesand500<=resp.status_code<600:

#recursivelyretry5xxHTTPerrors

returndownload(url,num_retries-1)

exceptrequests.exceptions.RequestExceptionase:

溫馨提示

  • 1. 本站所有資源如無特殊說明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請聯(lián)系上傳者。文件的所有權益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁內容里面會有圖紙預覽,若沒有圖紙預覽就沒有圖紙。
  • 4. 未經(jīng)權益所有人同意不得將文件中的內容挪作商業(yè)或盈利用途。
  • 5. 人人文庫網(wǎng)僅提供信息存儲空間,僅對用戶上傳內容的表現(xiàn)方式做保護處理,對用戶上傳分享的文檔內容本身不做任何修改或編輯,并不能對任何下載內容負責。
  • 6. 下載文件中如有侵權或不適當內容,請與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準確性、安全性和完整性, 同時也不承擔用戶因使用這些下載資源對自己和他人造成任何形式的傷害或損失。

最新文檔

評論

0/150

提交評論