爬虫总结(一)-- 爬虫基础 python实现
爬蟲在平時也經常用,但一直沒有系統的總結過,其實它涉及了許多的知識點。這一系列會理一遍這些知識點,不求詳盡,只希望以點帶面構建一個爬蟲的知識框架。這一篇是概念性解釋以及入門級爬蟲介紹(以爬取網易新聞為例)。
爬蟲基礎
什么是爬蟲
爬蟲說白了其實就是獲取資源的程序。制作爬蟲的總體分三步:爬-取-存。首先要獲取整個網頁的所有內容,然后再取出其中對你有用的部分,最后再保存有用的部分。
爬蟲類型
- 網絡爬蟲
網絡爬蟲,是一種按照一定的規則,自動的?抓取萬維網信息的程序或者腳本。網絡爬蟲是搜索引擎系統中十分重要的組成部分,爬取的網頁信息用于建立索引從而為搜索引擎提供支持,它決定著整個引擎系統的內容是否豐富,信息是否即時,其性能的優劣直接影響著搜索引擎的效果。 - 傳統爬蟲
從一個或若干初始網頁的URL開始,獲得初始網頁的URL,在抓取網頁過程中,不斷從當前頁面上抽取新的URL放入隊列,直到滿足系統的一定停止條件。
工作原理
- 根據一定的網頁分析算法過濾與主題無關的鏈接,保留有用鏈接并將其放入等待抓取的URL隊列
- 根據一定的搜索策略從隊列中選擇下一步要抓取的網頁URL,重復上述過程,直到達到指定條件才結束爬取
- 對所有抓取的網頁進行一定的分析、過濾,并建立索引,以便之后的查詢和檢索。
爬取策略
廣度優先
完成當前層次的搜索后才進行下一層次的搜索。一般的使用策略,一般通過隊列來實現。
最佳優先
會有評估算法,凡是被算法評估為有用的網頁,先來爬取。
深度優先
實際應用很少。可能會導致trapped問題。通過棧來實現。
URL( Uniform Resource Locator: 統一資源定位符)
互聯網上資源均有其唯一的地址,由三部分組成。
- 模式/協議
- 文件所在IP地址及端口號
- 主機上的資源位置
- 例子:http://www.example.com/index.html
Web Server/Socket如何建立連接和傳輸數據的
web server 的工作過程其實和打電話的過程差不多(買電話–>注冊號碼–>監聽–>排隊接聽–>讀寫–>關閉),經典的三步握手(有人在嗎?我在呢,你呢?我也在)在排隊接聽時進行。下面一張圖足以解釋一切。
Crawler端需要一個socket接口,向服務器端發起connect請求,完成連接后就可以和服務器交流了,操作完畢會關閉socket接口。服務器端更復雜一點,也需要一個socket接口,并且這個socket接口需要綁定一個地址(bind()),這就相當于有一個固定的電話號碼,這樣其他人撥打這個號碼就可以找到這個服務器。綁定之后服務器的socket就開始監聽(listen())有沒有用戶請求,如果有,就接收請求(accept()),和用戶建立連接,然后就可以交流。
HTML DOM
- DOM 將 HTML 文檔表達為樹結構
- 定義了訪問和操作 HTML 文檔的標準
Cookie
- 由服務器端生成,發送給 User-Agent(一般是瀏覽器),瀏覽器會將 Cookie 的 key/value 保存到某個目錄下的文本文件哪,下次訪問同一網站時就發送該 Cookie 給服務器。
HTTP
- GET 直接以鏈接形式訪問,鏈接中包含了所有的參數
- PUT 把提交的數據放到 HTTP 包的包體中 eg. import urllib import urllib2 url='http://www.zhihu.com/#signin' user_agent='MOZILLA/5.0' values={'username':'252618408@qq.com','password':'xxx'} headers={'User-Agent':user_agent} data=urllib.urlencode(values) # urlencode 是 urllib 獨有的方法 request=urllib2.Request(url,data,headers) # write a letter response=urllib2.urlopen(request) # send the letter and get the reply page=response.read() # read the reply
urllib 僅可以接受 URL,這意味著你不可以偽裝你的 User Agent 字符串等,但 urllib 提供了 urlencode 方法用來GET查詢字符串等產生,而 urllib2 沒有。
因此 urllib, urllib2經常一起使用。
Headers 設置
- User-Agent: 部分服務器或 Proxy 會通過該值來判斷是否是瀏覽器發出的請求
- Content-Type: 使用 REST 接口時,服務器會檢查該值,用來確定 HTTP Body 中的內容該怎樣解析
- application/xml: 在 XMl RPC, 如 RESTful/SOAP 調用時使用
- application/json: 在 JSON RPC 調用時使用
- application/x-www-form-urlencoded: 瀏覽器提交 Web 表單時使用
爬蟲難點
爬蟲的兩部分,一是下載 Web 頁面,有許多問題需要考慮。如何最大程度地利用本地帶寬,如何調度針對不同站點的 Web 請求以減輕對方服務器的負擔等。一個高性能的 Web Crawler 系統里,DNS 查詢也會成為急需優化的瓶頸,另外,還有一些“行規”需要遵循(例如robots.txt)。
而獲取了網頁之后的分析過程也是非常復雜的,Internet 上的東西千奇百怪,各種錯誤百出的 HTML 頁面都有,要想全部分析清楚幾乎是不可能的事;另外,隨著 AJAX 的流行,如何獲取由 Javascript 動態生成的內容成了一大難題;除此之外,Internet 上還有有各種有意或無意出現的 Spider Trap ,如果盲目的跟蹤超鏈接的話,就會陷入 Trap 中萬劫不復了,例如這個網站,據說是之前 Google 宣稱 Internet 上的 Unique URL 數目已經達到了 1 trillion 個,因此這個人 is proud to announce the second trillion 。
最簡單的爬蟲
requests 庫
import requests url = "http://shuang0420.github.io/" r = requests.get(url)urllib2 庫
| 1234567891011 | import urllib2# request source fileurl = "http://shuang0420.github.io/"request = urllib2.Request(url) # write a letterresponse = urllib2.urlopen(request) # send the letter and get the replypage = response.read() # read the reply# save source filewebFile = open('webPage.html', 'wb')webFile.write(page)webFile.close() |
這是一個簡單的爬蟲,打開 webPage.html 是這樣的顯示,沒有css.
實例:爬取網易新聞
爬取網易新聞 [代碼示例]
– 使用 urllib2 的 requests包來爬取頁面
– 使用正則表達式和 bs4 分析一級頁面,使用 Xpath 來分析二級頁面
– 將得到的標題和鏈接,保存為本地文件
分析初始頁面
我們的初始頁面是?http://news.163.com/rank
查看源代碼
我們想要的是分類標題和URL,需要解析 DOM 文檔樹,這里使用了 BeautifulSoup 里的方法。
| 123456789101112 | def Nav_Info(myPage):# 二級導航的標題和頁面pageInfo = re.findall(r'<div class="subNav">.*?<div class="area areabg1">', myPage, re.S)[0].replace('<div class="subNav">', '').replace('<div class="area areabg1">', '')soup = BeautifulSoup(pageInfo, "lxml")tags = soup('a')topics = []for tag in tags:# 只要 科技、財經、體育 的新聞# if (tag.string=='科技' or tag.string=='財經' or tag.string=='體育'):topics.append((tag.string, tag.get('href', None)))return topics |
然而,Beautiful Soup對文檔的解析速度不會比它所依賴的解析器更快,如果對計算時間要求很高或者計算機的時間比程序員的時間更值錢,那么就應該直接使用 lxml。換句話說,還有提高Beautiful Soup效率的辦法,使用lxml作為解析器。Beautiful Soup用lxml做解析器比用html5lib或Python內置解析器速度快很多。bs4 的默認解析器是 html.parser,使用lxml的代碼如下:
BeautifulSoup(markup, "lxml")分析二級頁面
查看源代碼
我們要爬取的是之間的新聞標題和鏈接,同樣需要解析文檔樹,可以通過以下代碼實現,這里用了 lxml 解析器,效率更高。
| 123456 | def News_Info(newPage):# xpath 使用路徑表達式來選取文檔中的節點或節點集dom = etree.HTML(newPage)news_titles = dom.xpath('//tr/td/a/text()')news_urls = dom.xpath('//tr/td/a/@href')return zip(news_titles, news_urls) |
完整代碼
潛在問題
我們的任務是爬取1萬個網頁,按上面這個程序,耗費時間長,我們可以考慮開啟多個線程(池)去一起爬取,或者用分布式架構去并發的爬取網頁。
種子URL和后續解析到的URL都放在一個列表里,我們應該設計一個更合理的數據結構來存放這些待爬取的URL才是,比如隊列或者優先隊列。
對各個網站的url,我們一視同仁,事實上,我們應當區別對待。大站好站優先原則應當予以考慮。
每次發起請求,我們都是根據url發起請求,而這個過程中會牽涉到DNS解析,將url轉換成ip地址。一個網站通常由成千上萬的URL,因此,我們可以考慮將這些網站域名的IP地址進行緩存,避免每次都發起DNS請求,費時費力。
解析到網頁中的urls后,我們沒有做任何去重處理,全部放入待爬取的列表中。事實上,可能有很多鏈接是重復的,我們做了很多重復勞動。
爬蟲被封禁問題
優化方案
并行爬取問題
關于并行爬取,首先我們想到的是多線程或者線程池方式,一個爬蟲程序內部開啟多個線程。同一臺機器開啟多個爬蟲程序,這樣,我們就有N多爬取線程在同時工作,大大提高了效率。
當然,如果我們要爬取的任務特別多,一臺機器、一個網點肯定是不夠的,我們必須考慮分布式爬蟲。分布式架構,考慮的問題有很多,我們需要一個scheduler來分配任務并排序,各個爬蟲之間還需要通信合作,共同完成任務,不要重復爬取相同的網頁。分配任務時我們還需要考慮負載均衡以做到公平。(可以通過Hash,比如根據網站域名進行hash)
負載均衡分派完任務之后,千萬不要以為萬事大吉了,萬一哪臺機器掛了呢?原先指派給掛掉的哪臺機器的任務指派給誰?又或者哪天要增加幾臺機器,任務有該如何進行重新分配呢?所以我們還要 task table 來紀錄狀態。
待爬取網頁隊列
如何對待待抓取隊列,跟操作系統如何調度進程是類似的場景。
不同網站,重要程度不同,因此,可以設計一個優先級隊列來存放待爬起的網頁鏈接。如此一來,每次抓取時,我們都優先爬取重要的網頁。
當然,你也可以效仿操作系統的進程調度策略之多級反饋隊列調度算法。
DNS緩存
為了避免每次都發起DNS查詢,我們可以將DNS進行緩存。DNS緩存當然是設計一個hash表來存儲已有的域名及其IP。
網頁去重
說到網頁去重,第一個想到的是垃圾郵件過濾。垃圾郵件過濾一個經典的解決方案是Bloom Filter(布隆過濾器)。布隆過濾器原理簡單來說就是:建立一個大的位數組,然后用多個Hash函數對同一個url進行hash得到多個數字,然后將位數組中這些數字對應的位置為1。下次再來一個url時,同樣是用多個Hash函數進行hash,得到多個數字,我們只需要判斷位數組中這些數字對應的為是全為1,如果全為1,那么說明這個url已經出現過。如此,便完成了url去重的問題。當然,這種方法會有誤差,只要誤差在我們的容忍范圍之類,比如1萬個網頁,我只爬取到了9999個,并不會有太大的實際影響。
一種很不錯的方法來自url相似度計算,簡單介紹下。
考慮到url本身的結構,對其相似度的計算就可以抽象為對其關鍵特征相似度的計算。比如可以把站點抽象為一維特征,目錄深度抽象為一維特征,一級目錄、二級目錄、尾部頁面的名字也都可以抽象為一維特征。比如下面兩個url:
url1:?http://www.spongeliu.com/go/happy/1234.html
url2:?http://www.spongeliu.com/snoopy/tree/abcd.html
特征:
- 站點特征:如果兩個url站點一樣,則特征取值1,否則取值0;
- 目錄深度特征:特征取值分別是兩個url的目錄深度是否一致;
- 一級目錄特征:在這維特征的取值上,可以采用多種方法,比如如果一級目錄名字相同則特征取1,否則取0;或者根據目錄名字的編輯距離算出一個特征值;或者根據目錄名字的pattern,如是否數字、是否字母、是否字母數字穿插等。這取決于具體需求,這里示例僅僅根據目錄名是否相同取1和0;
- 尾頁面特征:這維特征的取值同一級目錄,可以判斷后綴是否相同、是否數字頁、是否機器生成的隨機字符串或者根據編輯長度來取值,具體也依賴于需求。這里示例僅僅判斷最后一級目錄的特征是否一致(比如是否都由數字組成、是否都有字母組成等)。
這樣,對于這兩個url就獲得了4個維度的特征,分別是:1 1 0 0 。有了這兩個特征組合,就可以根據具體需求判斷是否相似了。我們定義一下每個特征的重要程度,給出一個公式:
similarity = feather1 * x1 + feather2*x2 + feather3*x3 + feather4*x4其中x表示對應特征的重要程度,比如我認為站點和目錄都不重要,最后尾頁面的特征才是最重要的,那么x1,x2,x3都可以取值為0,x4取值為1,這樣根據similarity就能得出是否相似了。或者認為站點的重要性占10%,目錄深度占50%,尾頁面的特征占40%,那么系數分別取值為0.1\0.5\0\0.4即可。
其實這樣找出需要的特征,可以把這個問題簡化成一個機器學習的問題,只需要人為判斷出一批url是否相似,用svm訓練一下就可以達到機器判斷的目的。
除了上面這種兩個url相似度的判斷,也可以將每一條url都抽象成一組特征,然后計算出一個url的得分,設置一個分數差的閾值,就可以達到從一大堆url中找出相似的url的目的。
數據存儲的問題
數據存儲同樣是個很有技術含量的問題。用關系數據庫存取還是用NoSQL,抑或是自己設計特定的文件格式進行存儲,都大有文章可做。
進程間通信
分布式爬蟲,就必然離不開進程間的通信。我們可以以規定的數據格式進行數據交互,完成進程間通信。
反爬蟲機制問題
針對反爬蟲機制,我們可以通過輪換IP地址、輪換Cookie、修改用戶代理(User Agent)、限制速度、避免重復性爬行模式等方法解決。
參考鏈接:
網絡爬蟲基本原理(一)
http://www.chinahadoop.cn/course/596/learn#lesson/11986
https://www.bittiger.io/blog/post/5pDTFcDwkmCvvmKys
https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html
原文地址: http://www.shuang0420.com/2016/06/11/%E7%88%AC%E8%99%AB%E6%80%BB%E7%BB%93%EF%BC%88%E4%B8%80%EF%BC%89/
總結
以上是生活随笔為你收集整理的爬虫总结(一)-- 爬虫基础 python实现的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: caffe实战笔记
- 下一篇: websocket python爬虫_p