0×00 介紹
本文我們就兩個方面來討論如何改進我們的爬蟲:數(shù)據(jù)存儲和多線程办素,當(dāng)然我承認這是為我們以后要討論的一些東西做鋪墊。
本人對于Python學(xué)習(xí)創(chuàng)建了一個小小的學(xué)習(xí)圈子沮焕,為各位提供了一個平臺,大家一起來討論學(xué)習(xí)Python叫搁。歡迎各位到來Python學(xué)習(xí)群:960410445一起討論視頻分享學(xué)習(xí)。Python是未來的發(fā)展方向瓢剿,正在挑戰(zhàn)我們的分析能力及對世界的認知方式,因此悠轩,我們與時俱進间狂,迎接變化,并不斷的成長哗蜈,掌握Python核心技術(shù)前标,才是掌握真正的價值所在坠韩。
目的:通常我們需要對爬蟲捕捉的數(shù)據(jù)進行分析距潘,處理,再次利用或者格式化只搁,顯然我們不能只是把爬蟲捕捉到的數(shù)據(jù)在內(nèi)存中處理音比,然后打印在屏幕上。在本章氢惋,我將介紹幾種主流的數(shù)據(jù)存儲方法洞翩。爬蟲處理數(shù)據(jù)的能力往往是決定爬蟲價值的決定性因素,同時一個穩(wěn)定的存儲數(shù)據(jù)的方法也絕對是一個爬蟲的價值體現(xiàn)焰望。
另外骚亿,采用多開線程的爬蟲,創(chuàng)造多個并行線程協(xié)調(diào)工作也絕對是提高爬蟲效率熊赖,降低失敗率的好辦法来屠。
0×01 引導(dǎo)
我們就接下來要講的部分做一個簡單的引導(dǎo),關(guān)于數(shù)據(jù)存儲方式:
1震鹉、?存儲索引或者直接下載數(shù)據(jù)
2俱笛、CSV
3、MySQL
關(guān)于線程:
如果讀者并不會python的線程處理传趾,可以參考這篇文章迎膜。
分為函數(shù)式和類包裝,這兩個方法進行線程處理浆兰。
0×02 數(shù)據(jù)存儲:存儲索引或者直接下載數(shù)據(jù)
關(guān)于這一點我覺得沒有必要做深入的解釋磕仅,因為這一點我們在前幾篇文章中或多或少都有接觸:比如制作sitemap:這里存儲了整個網(wǎng)站你需要的鏈接,比如抓取freebuff文章生成.docx文檔的這一節(jié)簸呈,這些其實都屬于本節(jié)所說的數(shù)據(jù)存儲方式榕订。那么就本節(jié)而言,我再介紹一個例子蝶棋,爬取一個freebuf商品列表區(qū)域所有的圖片(聽起來還是挺有趣的吧P读痢?)
步驟1:了解網(wǎng)站結(jié)構(gòu)
步驟2:編寫腳本
步驟3:測試
首先我們需要了解一下我們的目標(為了避免廣告嫌疑玩裙,這里還是以freebuf作為目標吧)
審查元素發(fā)現(xiàn)下面的div標簽包含了單個的商品信息兼贸,
Div(class=nall-news)->div(class=col-sm6col-md-lg-4mall-product-list)->div(class=photo)->a->img
這樣我們就輕松加愉快地找到了img所在的地方段直,那么根據(jù)這些,我們可以指定簡單的方案:獲取商品的所在的標簽溶诞,然后由于商品標簽的一致性鸯檬,我們可以一層一層索引下去找到圖片的位置,當(dāng)然有個不保險的辦法就是螺垢,獲取的直接獲取img喧务,(幸運的是,在這個例子中只存在一個img標簽)枉圃,我們測試從簡功茴,節(jié)約時間,那么一兩分鐘我們就寫出了自己的腳本:
importurllibfrombs4importBeautifulSoupimportreurl ='http://shop.freebuf.com/'print"prepare&reading to read theweb"data = urllib.urlopen(url).read()printdataprint"parsing ... ... ... "soup = BeautifulSoup(data)#<div class="col-sm-6 col-md-4col-lg-4 mall-product-list">itemlist =soup.findAll(name='div',attrs={'class':'col-sm-6 col-md-4 col-lg-4mall-product-list'})foriteminitemlist:printitem.img
這樣我們就在自己的debug I/O看到了打印出的九個img標簽:
然后我們用以前學(xué)到的技能孽亲,就足夠把這些圖片dump下來了坎穿,
完善腳本!
importurllibfrombs4importBeautifulSoupimportreurl ='http://shop.freebuf.com/'print"prepare&reading to read theweb"data = urllib.urlopen(url).read()printdataprint"parsing ... ... ... "soup = BeautifulSoup(data)#<div class="col-sm-6 col-md-4col-lg-4 mall-product-list">itemlist = soup.findAll(name='div',attrs={'class':'col-sm-6col-md-4 col-lg-4 mall-product-list'})foriteminitemlist:"""
為了適配圖片的格式返劲,我們這里這樣處理玲昧。
不過不是絕對的,某些時候這樣做就不合適:
"""???print item.img['src'][-4:]"""
urlretrieve這個方法是我們以前接觸過的篮绿,用于下載圖片孵延,還可以下載整個頁面:
"""urllib.urlretrieve(url=item.img['src'],filename=item.img['alt']+item.img['src'][-4:])
然后我們可以看一下成果,這樣做的好處就是避免下來一大堆無關(guān)的圖片亲配,(有些時候我們下載整站尘应,然后提取圖片會發(fā)現(xiàn)各種圖片混在一起了,那樣確實煩得很):
效果可以說是還不錯吧弃榨,當(dāng)然我懶并沒有把圖片建立文件夾存起來菩收。
0×03 數(shù)據(jù)存儲:CSV
CSV(comma-separated values),是目前比較流行的一種文件存儲格式。被Excel和很多的應(yīng)用程序支持鲸睛。CSV文件存儲的例子如下:
Fruit,cost
Apple,1.00
Banana,0.30
Pear,1.25
看起來就是表格的壓縮版娜饵,其實真的沒有什么奇怪的,這個很簡單的對吧官辈?當(dāng)然箱舞,大家都能想到這種方法存儲表格再好不過了。不過筆者在這里建議:如果你只有一個table要處理拳亿,復(fù)制粘貼應(yīng)該是比這樣快晴股,如果一堆table要處理,或者是要從各種數(shù)據(jù)中挑選出表格肺魁,然后組合成一張新表电湘,這樣無疑可以加快你的速度。
那么我們就舉一個例子來介紹一個下我們下一個例子。一定是一個有趣的體驗:
作為上一個例子的拓展:我們腰身成一個.csv文件寂呛,存儲每個商品的名稱和需要的金幣數(shù)怎诫。
我們觀察一下具體的金幣位置,商品信息都在哪里贷痪?筆者相信大家已經(jīng)看到了幻妓,那么接下來我們得先整理一下獲取info的辦法:
->div(class=info)
僅僅一步我們就可以得到信息位置。
Div(class=info)->h4->商品信息
Div(class=info)->p->strong->商品價格
那么我們這就很簡單了劫拢,對不對肉津?
在使用csv模塊時,打開一個文件然后把文件描述符傳入csv的writer然后寫入row舱沧,但是由于我們這里存在中文妹沙,要注意要utf-8處理一下,否則報錯或者是中文沒法正常顯示:
importurllibfrombs4importBeautifulSoupimportcsv?csvFile = open('items.csv','w+')?url ='http://shop.freebuf.com/'print"prepare&reading to read theweb"data = urllib.urlopen(url).read()printdataprint"parsing ... ... ... "soup = BeautifulSoup(data)#<div class="col-sm-6 col-md-4col-lg-4 mall-product-list">itemlist =soup.findAll(name='div',attrs={'class':'col-sm-6 col-md-4 col-lg-4mall-product-list'})?writer = csv.writer(csvFile)writer.writerow(('name','price'))foriteminitemlist:#name#print item.find(name="h4").stringname = item.find(name='h4').string#price#print item.find(name='strong').stringprice = item.find(name='strong').string???writer.writerow((name.encode('utf-8','ignore'),price.encode('utf-8','ignore')))?csvFile.close()print"success to create the csvfile"
那么最后我們得到了.csv文件狗唉,這個文件可以被excel打開的初烘,在我的linux中涡真,已經(jīng)被識別成了csv:
但是這里要注意中文編碼問題分俯。具體的解決辦法限于篇幅我就不介紹了。
0×04 MySQL
我們在這一部分改造上面的例子哆料,把MySQL整合在爬蟲中缸剪,至于MySQL的安裝配置我就不再解釋了,我們簡要梳理一下MySQL的py使用過程:
0东亦、提前建立好數(shù)據(jù)庫scraping杏节,建立好表items
1、引入pymysql庫
2典阵、通過數(shù)據(jù)庫參數(shù)建立一個鏈接conn,
3奋渔、Cur.conn.cursor()
4、Cur.execute(query)
5壮啊、如果需要的話還需要使用Cur.commit()
6嫉鲸、Cur.close()
7、Conn.close()
數(shù)據(jù)庫建立:CREATE DATABASE `scraping` DEFAULTCHARACTER SET utf8 COLLATE utf8_general_ci;
創(chuàng)建表:CREATE TABLE item(namevarchar(255),price int(11));
插入:INSERT INTO item (name,price) VALUES(‘test’,4);
刪除:DELETE FROM item WHRER name=’test’
稍微修改一下上面的代碼就可以簡單適配MySQL了歹啼。
importurllibfrombs4importBeautifulSoupimportpymysql?conn =pymysql.connect(host="127.0.0.1",user='root',passwd='toor',db='mysql')cur = conn.cursor()cur.execute('USE scraping')url ='http://shop.freebuf.com/'print"prepare&reading to read theweb"data = urllib.urlopen(url).read()printdataprint"parsing ... ... ... "soup = BeautifulSoup(data)#<div class="col-sm-6 col-md-4col-lg-4 mall-product-list">itemlist =soup.findAll(name='div',attrs={'class':'col-sm-6 col-md-4 col-lg-4mall-product-list'})foriteminitemlist:#name#print item.find(name="h4").stringname = item.find(name='h4').string#price#print item.find(name='strong').stringprice = item.find(name='strong').string???query ='insert item(name,price) value('+"\'"+name.encode('utf-8','ignore') +"\',"+price.encode('utf-8','ignore') +');'cur.execute(query)???conn.commit()print"success to update thedatabase"print"preparing to read the data fromdatabase!"query ="Select * from item where1;"cur.execute(query)printcur.fetchall()?cur.close()conn.close()
當(dāng)然上面的代碼只是展示最簡單的用法而已玄渗,我們還需要弄清楚的是編碼問題,數(shù)據(jù)庫還需要配置狸眼,要知道畢竟數(shù)據(jù)庫的使用如果處理不好的話藤树,也是一個不大不小的問題。
0×06 嵌入式數(shù)據(jù)庫BerkeleyDB(BDB)
關(guān)于介紹我不想太官方拓萌,那么簡單來說岁钓,筆者對BDB的認識如下:
BDB是oracle的一個輕量型嵌入式數(shù)據(jù)庫,只支持key-value數(shù)據(jù)形式存儲,介于內(nèi)存數(shù)據(jù)庫和硬盤數(shù)據(jù)庫之間屡限,是單寫入多讀取的相當(dāng)好的解決方案降宅。
BDB的python接口,bsddb模塊囚霸,可以把BDB的數(shù)據(jù)庫讀寫操作作為一個數(shù)組來進行腰根。查閱python2手冊可以找到這個模塊,非常易于使用拓型。
筆者在這里建議额嘿,使用BDB來存儲url。筆者可以提出一個比較可行的方案劣挫,一個url的md5值作為key册养,url的值作為value,讀寫直接操作數(shù)據(jù)庫压固,簡化url管理球拦。
0×05 多線程
我們回顧sitemap爬蟲的時候,我們發(fā)現(xiàn)帐我,爬取一個相對比較小的網(wǎng)站(一百頁左右)的時候大概用了2分40秒(加上為了避免頻繁請求設(shè)置的延時)坎炼,顯然對于我們來說這是不夠的,我們當(dāng)然有很多辦法來加速拦键,但是筆者在這里并不建議修改源代碼中的url請求等待時間谣光。我們僅僅多開線程執(zhí)行就可以達到我們預(yù)期的效果,這是基本所有類似程序都會采用的方法芬为,但是實際的使用的時候萄金,可能會有各種問題:
1、同步問題
2媚朦、線程池管理
……
高效穩(wěn)定的線程管理是編寫多線程程序或者腳本的基礎(chǔ)氧敢。
預(yù)備知識:
Python的線程模塊Threading模塊,【鏈接】
Python自制線程池:線程池是解決并發(fā)問題的有力武器询张,在多線程爬蟲設(shè)計中孙乖,我們?nèi)匀豢梢允褂眠@樣的方法改裝一下
理論:
我們采用多線程爬蟲的時候,要清楚這個過程:每個爬蟲爬到的數(shù)據(jù)都要匯總到一起瑞侮,然后處理的圆,然后再分配新任務(wù)到空閑的爬蟲。然后根據(jù)這樣的過程我們可以想到這樣的過程好像和master-slave模式類似半火,如果大家沒有接觸過這個東西也沒關(guān)系越妈,簡單來說,就是包工頭和工人的關(guān)系钮糖,包工頭負責(zé)整個小項目的統(tǒng)籌和任務(wù)派發(fā)梅掠,工人負責(zé)埋頭苦干酌住。
然后根據(jù)這樣的需求,我們初步設(shè)計一下這個多線程爬蟲系統(tǒng)應(yīng)該是怎么樣的酪我。
在開始之前,我們首先需要明白一個manager的最基礎(chǔ)職能:
1且叁、維護任務(wù)隊列
2都哭、派發(fā)任務(wù)
3、處理子線程返回的數(shù)據(jù)
這樣我們可以初步設(shè)想一下逞带,再主線程的循環(huán)中進行所有的操作欺矫。那么,我們就意識到了這個manager的重要性了展氓。那么就按照我們現(xiàn)在的想法穆趴,我們來整理以下這個多線程爬蟲的設(shè)計思路:
按著這個思路,筆者實現(xiàn)了兩套多線程爬蟲遇汞,一套是簡易未妹,不穩(wěn)定的版本,一套是相對穩(wěn)定的版本空入。為什么不是直接看第二套比較完整的呢络它?顯然第一個版本簡易不穩(wěn)定,但是易于大家理解架構(gòu)执庐,第二個版本相對穩(wěn)定酪耕,但是讀起來可能有點痛苦。
具體的代碼在Github轨淌。
因為單個腳本太長了300+行。
第二個版本看尼,為了項目管理方便也為了貼合我自己的習(xí)慣递鹉,我就使用了vs13作為開發(fā)和管理工具,然后代碼現(xiàn)在托管在github上藏斩,項目目錄如下:
參見Github鏈接躏结,如果讀者喜歡這個項目可以隨意fork或者賞star,筆者將會更有力氣維護這個多線程爬蟲狰域。當(dāng)然這是一個未完成的項目媳拴,但是現(xiàn)在功能基本是完整的,可以實現(xiàn)自己自定義線程數(shù)穩(wěn)定爬取固定域名下的網(wǎng)站sitemap兆览,如果需要爬取內(nèi)容的話屈溉,需要使用者自己去定義worker的分析部分,url處理我已經(jīng)替大家基本寫完了抬探,這個項目的目的實際就是作為一個scraper platform存在子巾,因此取名scraplat帆赢,但是筆者真的水平有限,趕著這篇文章之前完成了基本功能线梗,在今后的一段時間內(nèi)這個項目還會不斷完善椰于,實現(xiàn)動態(tài)網(wǎng)頁爬取,自動化網(wǎng)頁測試等高級接口仪搔。如果對爬蟲技術(shù)感興趣的讀者可以長期關(guān)注以下這個項目瘾婿,也歡迎大家在留言區(qū)寫下自己想要實現(xiàn)的功能。
0×06 總結(jié)與下章預(yù)告
本章我們討論了數(shù)據(jù)存儲和多線程爬蟲的實現(xiàn)烤咧,如果大家明白原理以后憋他,就可以自己設(shè)計出自己的多線程爬蟲甚至是分布式爬蟲。到現(xiàn)在位置髓削,我們手中的爬蟲才算是像模像樣竹挡。
但是這還是不夠,直到現(xiàn)在為止立膛,我們只能處理靜態(tài)的網(wǎng)頁揪罕,如果想要處理動態(tài)加載的網(wǎng)頁,(不知道有沒有好事的讀者曾經(jīng)試過爬取淘寶商品頁面宝泵,試過的朋友會發(fā)現(xiàn)傳統(tǒng)的方案是沒有辦法處理淘寶商品頁面的)好啰。還有我們有時候希望我們的爬蟲能真正的進入互聯(lián)網(wǎng),自由爬行儿奶,這些都是我們渴望解決的問題框往。