Python爬蟲開發(fā)(二):整站爬蟲與Web挖掘

0×00 介紹

在互聯(lián)網(wǎng)這個復(fù)雜的環(huán)境中,搜索引擎本身的爬蟲袜漩,出于個人目的的爬蟲雹顺,商業(yè)爬蟲肆意橫行绒净,肆意掠奪網(wǎng)上的或者公共或者私人的資源。顯然數(shù)據(jù)的收集并不是為所欲為,有一些協(xié)議或者原則還是需要每一個人注意肉迫。本文主要介紹關(guān)于爬蟲的一些理論和約定協(xié)議,然后相對完整完成一個爬蟲的基本功能互纯。

本人對于Python學(xué)習(xí)創(chuàng)建了一個小小的學(xué)習(xí)圈子,為各位提供了一個平臺殖卑,大家一起來討論學(xué)習(xí)Python。歡迎各位到來Python學(xué)習(xí)群:960410445一起討論視頻分享學(xué)習(xí)伦乔。Python是未來的發(fā)展方向吏祸,正在挑戰(zhàn)我們的分析能力及對世界的認知方式,因此,我們與時俱進贡翘,迎接變化蹈矮,并不斷的成長,掌握Python核心技術(shù)鸣驱,才是掌握真正的價值所在泛鸟。

0×01 協(xié)議

一般情況下網(wǎng)站的根目錄下存在著一個robots.txt的文件,用于告訴爬蟲那些文件夾或者哪些文件是網(wǎng)站的擁有者或者管理員不希望被搜索引擎和爬蟲瀏覽的踊东,或者是不希望被非人類的東西查看的北滥。但是不僅僅如此,在這個文件中闸翅,有時候還會指明sitemap的位置再芋,爬蟲可以直接尋找sitemap而不用費力去爬取網(wǎng)站,制作自己的sitemap坚冀。那么最好我們看個具體的例子吧济赎,這樣更有助于理解robots協(xié)議:

-----------------------以下時freebuf的robots.txt-------------User-agent: *Disallow:/*?*

Disallow: /trackback

Disallow: /wp-*/Disallow:*/comment-page-*Disallow:/*?replytocom=*

Disallow: */trackbackDisallow:/?randomDisallow:*/feedDisallow:/*.css$

Disallow: /*.js$

Sitemap: http://www.freebuf.com/sitemap.txt

大家可以看到,這里指明了適用的User-agent頭记某,指明了Disallow的目錄司训,也指明了sitemap,然后我們在看一下sitemap中是什么:

我們大致可以發(fā)現(xiàn)這些都是整個網(wǎng)站允許公開的內(nèi)容液南,如果這個爬蟲作者是對freebuf的文章感興趣的話壳猜,大可不必從頭到尾設(shè)計爬蟲算法拿下整個網(wǎng)站的sitemap,這樣直接瀏覽sitemap節(jié)省了大量的時間滑凉。?

0×02 原則

如果協(xié)議不存在的話统扳,我們?nèi)匀徊荒転樗麨椋暇W(wǎng)隨意搜索一下源于爬蟲協(xié)議的官司譬涡,國內(nèi)外都有闪幽。爬蟲的協(xié)議規(guī)則建立在如下的基礎(chǔ)上:

1.????搜索技術(shù)應(yīng)該服務(wù)于人類,尊重信息提供者的意愿涡匀,并維護其隱私權(quán)盯腌;

2.????網(wǎng)站也有義務(wù)保護其使用者的個人信息和隱私不被侵犯。

簡單來說陨瘩,就是構(gòu)建的爬蟲以信息收集為目的是沒錯的腕够,但是不能侵犯別人的隱私,比如你掃描并且進入了網(wǎng)站的robots中的disallow字段舌劳,你就可能涉及侵犯別人隱私的問題帚湘。當然作為一般人來講,我們使用爬蟲技術(shù)無非是學(xué)習(xí)甚淡,或者是搜集想要的信息大诸,并沒有想那么多的侵權(quán),或者是商業(yè)的問題。

0×03 確立目標與分析過程

這里我提供一個爬蟲誕生要經(jīng)歷的一般過程:

1.?確立需求在资柔,sitemap中挑選出需要挖掘的頁面焙贷;

2.?依次分析挑選出的頁面;

3.?存儲分析結(jié)果贿堰。

但是有時候問題就是辙芍,我們的目標網(wǎng)站沒有提供sitemap,那么這就得麻煩我們自己去獲取自己定制的sitemap羹与。

目標:制作一個網(wǎng)站的sitemap:

分析:我們要完成這個過程故硅,但是不存在現(xiàn)成sitemap,筆者建議大家把這個網(wǎng)站想象成一個圖的結(jié)構(gòu)纵搁,網(wǎng)站的url之間縱橫交錯吃衅,我們可以通過主頁面進行深度優(yōu)先或者廣度優(yōu)先搜索從而遍歷整個網(wǎng)站拿到sitemap。顯然我們發(fā)現(xiàn)诡渴,我們首先要做的第一步就是完整的獲取整個頁面的url捐晶。

但是我們首先得想清楚一個問題:我們獲取到的url是應(yīng)該是要限制域名的,如果爬蟲從目標網(wǎng)站跳走了妄辩,也就意味著將無限陷入整個網(wǎng)絡(luò)進行挖掘惑灵。這么龐大的數(shù)據(jù)量,我想不是一般人的電腦硬盤可以承受的吧眼耀!

那么我們的第一步就是編寫代碼去獲取整個頁面的url英支。其實這個例子在上一篇文章中已經(jīng)講到過。

0×04 動手

我們的第一個小目標就是獲取當前頁面所有的url:

首先必須說明的是哮伟,這個腳本并不具有普遍性(只針對freebuf.com)干花,并且效率低下,可以優(yōu)化的地方很多:只是為了方便楞黄,簡單實現(xiàn)了功能池凄,有興趣的朋友可以任意重構(gòu)達到高效優(yōu)雅的目的

importurllibfrombs4importBeautifulSoupimportredefget_all_url(url):urls = []???web = urllib.urlopen(url)soup =BeautifulSoup(web.read())#通過正則過濾合理的url(針對與freebuf.com來講)tags_a =soup.findAll(name='a',attrs={'href':re.compile("^https?://")})try:fortag_aintags_a:???????????urls.append(tag_a['href'])#return urlsexcept:passreturnurls#得到所有freebuf.com下的urldefget_local_urls(url):local_urls = []???urls = get_all_url(url)for_urlinurls:???????ret = _urlif'freebuf.com'inret.replace('//','').split('/')[0]:???????????local_urls.append(_url)returnlocal_urls#得到所有的不是freebuf.com域名的urldefget_remote_urls(url):remote_urls = []???urls = get_all_url(url)for_urlinurls:???????ret = _urlif"freebuf.com"notinret.replace('//','').split('/')[0]:???????????remote_urls.append(_url)returnremote_urlsdef__main__():url ='http://freebuf.com/'rurls = get_remote_urls(url)print"--------------------remote urls-----------------------"forretinrurls:printretprint"---------------------localurls-----------------------"lurls = get_local_urls(url)forretinlurls:printretif__name__ =='__main__':__main__()

這樣我們就得到了該頁面的url,本域名和其他域名下:

當然圖中所示的結(jié)果為部分截圖鬼廓,至少證明了我們上面的代碼比較好的解決的url獲取的問題肿仑,由于這里我們的判斷規(guī)則簡單,還有很多情況沒有考慮到碎税,所以建議大家如果有時間尤慰,可以把上面代碼重構(gòu)成更佳普適,更加完整的腳本雷蹂,再投入真正的實際使用伟端。

0×05 sitemap爬蟲

所謂的sitemap,我們在上面的例子中又講到sitemap記錄了整個網(wǎng)站的網(wǎng)站擁有者允許你爬取內(nèi)容的鏈接匪煌。但是很多情況下责蝠,sitemap沒有寫出党巾,那么問題就來了,我們面對一個陌生的網(wǎng)站如何進行爬人健(在不存在sitemap的情況下).

當然我們首先要獲取sitemap昧港,沒有現(xiàn)成的我們就自己動手獲取整個網(wǎng)站的sitemap。

再開始之前我們需要整理一下思路:我們可以把整站當成一個錯綜復(fù)雜的圖結(jié)構(gòu)支子,有一些算法基礎(chǔ)的讀者都會知道圖的簡單遍歷方法:dfs和bfs(深度優(yōu)先和廣度優(yōu)先)。如果這里讀者有問題的話建議先去學(xué)習(xí)一下這兩種算法达舒。大體的算法結(jié)構(gòu)我們清楚了值朋,但是在實現(xiàn)中我們顯然需要特殊處理url,需要可以區(qū)分當前目標站點域名下的網(wǎng)站和其他域名的網(wǎng)站巩搏,除此之外昨登,在href的值中經(jīng)常會出現(xiàn)相對url,這里也要特別處理贯底。

importurllibfrombs4importBeautifulSoupimporturlparseimporttimeimporturllib2?url ="http://xxxx.xx/"domain ="xxxx.xx"deep =0tmp =""sites = set()visited = set()#local = set()defget_local_pages(url,domain):globaldeepglobalsitesglobaltmp???repeat_time =0pages = set()#防止url讀取卡住whileTrue:try:print"Ready to Open the web!"time.sleep(1)print"Opening the web", url???????????web = urllib2.urlopen(url=url,timeout=3)print"Success to Open the web"breakexcept:print"Open Url Failed !!! Repeat"time.sleep(1)???????????repeat_time = repeat_time+1ifrepeat_time ==5:returnprint"Readint the web ..."soup = BeautifulSoup(web.read())print"..."tags = soup.findAll(name='a')fortagintags:#避免參數(shù)傳遞異常try:???????????ret = tag['href']except:print"Maybe not the attr : href"continueo = urlparse.urlparse(ret)"""

???????#Debug I/O

???????for _ret in o:

???????????if _ret == "":

??????????????? pass

???????????else:

??????????????? print _ret

???????"""#處理相對路徑urlifo[0]is""ando[1]is"":print"Fix? Page: "+ret???????????url_obj = urlparse.urlparse(web.geturl())???????????ret = url_obj[0] +"://"+ url_obj[1] + url_obj[2] + ret#保持url的干凈ret = ret[:8] + ret[8:].replace('//','/')???????????o = urlparse.urlparse(ret)#這里不是太完善丰辣,但是可以應(yīng)付一般情況if'../'ino[2]:??????????????? paths = o[2].split('/')fori inrange(len(paths)):ifpaths[i] =='..':??????????????????????? paths[i] =''ifpaths[i-1]:??????????????????????????? paths[i-1] =''tmp_path =''forpathinpaths:ifpath =='':continuetmp_path = tmp_path +'/'+path??????????????? ret =ret.replace(o[2],ret_path)print"FixedPage: "+ ret#協(xié)議處理if'http'notino[0]:print"Bad? Page:"+ ret.encode('ascii')continue#url合理性檢驗ifo[0]is""ando[1]isnot"":print"Bad? Page: "+retcontinue#域名檢驗ifdomainnotino[1]:print"Bad? Page: "+retcontinue#整理,輸出newpage = retifnewpagenotinsites:print"Add New Page: "+ newpage???????????pages.add(newpage)returnpages#dfs算法遍歷全站defdfs(pages):#無法獲取新的url說明便利完成禽捆,即可結(jié)束dfsifpagesisset():returnglobalurlglobaldomainglobalsitesglobalvisited???sites = set.union(sites,pages)forpageinpages:ifpagenotinvisited:print"Visiting",page???????????visited.add(page)???????????url = page???????????pages = get_local_pages(url, domain)???????????dfs(pages)print"sucess"pages = get_local_pages(url, domain)dfs(pages)foriinsites:printi

在這個腳本中笙什,我們采用了dfs(深度優(yōu)先算法),關(guān)于算法的問題我們不做深入討論胚想,實際上在完成過程中我們關(guān)鍵要完成的是對href的處理琐凭,在腳本代碼中我們也可以看到,url的處理占了大部分代碼浊服,但是必須說明的是:很遺憾我們并沒有把所有的情況都解決清楚统屈,但是經(jīng)過測試,上面的代碼可以應(yīng)付很多種情況牙躺。所以大家如果有需要可以隨意修改使用愁憔。

測試:用上面的腳本在網(wǎng)絡(luò)狀況一般的情況下,對我的個人博客(大概100個頁面)進行掃描孽拷,大概用時2分半吨掌,這是單機爬蟲。

0×06 Web元素處理

在接下來的這個例子中乓搬,我們不再關(guān)注url的處理思犁,我們暫且把目光對準web單個頁面的信息處理。按照爬蟲編寫的一般流程进肯,在本例子中激蹲,爭取每一步都是完整的可操作的。

確立目標:獲取freebuf的文章并且生成docx文檔江掩。

過程:偵察目標網(wǎng)頁的結(jié)構(gòu)学辱,針對特定結(jié)構(gòu)設(shè)計方案

腳本實現(xiàn):bs4模塊和docx模塊的使用

預(yù)備知識:

1.Bs4的基本api的使用乘瓤,關(guān)于beautifulSoup的基本使用方法,我這里需要介紹在下面的腳本中我使用到的方法:

Soup= BeautifulSoup(data)#構(gòu)建一個解析器Tags= Soup.findAll(name,attr)

我們重點要講findAll方法的兩個參數(shù):name和attr

Name:? 指的是標簽名策泣,傳入一個標簽名的名稱就可以返回所有固定名稱的標簽名

Attr:???? 是一個字典存儲需要查找的標簽參數(shù)衙傀,返回對應(yīng)的標簽

Tag.children表示獲取tag標簽的所有子標簽Tag.string表示獲取tag標簽內(nèi)的所有字符串,不用一層一層索引下去尋找字符串Tag.attrs[key]表示獲取tag標簽內(nèi)參數(shù)的鍵值對鍵為key的值Tag.img表示獲取tag標簽的標簽名為img的自標簽(一個)

在本例子的使用中萨咕,筆者通過標簽名+id+class來定位標簽统抬,雖然這樣的方法不是絕對的正確,但是一般情況下危队,準確率還是比較高的聪建。

2.docx的使用:在使用這個模塊的時候,要記清楚如果:

pip install python-docx

easy_install python-docx

兩種方式安裝都不能正常使用的話茫陆,就需要下載tar包自己手動安裝

Docx模塊是一個可以直接操作生成docx文檔的python模塊金麸,使用方法極盡簡單:

Demo = Document() #在內(nèi)存中建立一個doc文檔

Demo.add_paragraph(data) #在doc文檔中添加一個段落

Demo.add_picture(“pic.png”) #doc文檔中添加一個圖片

Demo.save(‘demo.docx’) #存儲docx文檔

當然這個模塊還可以操作段落的字體啊格式啊各種強大的功能,大家可以從官方網(wǎng)站找到對應(yīng)的文檔簿盅。鑒于我們以學(xué)習(xí)為目的挥下,我就不作深入的排版介紹了

開始:

觀察html結(jié)構(gòu):

我們大致觀察一下結(jié)構(gòu),定位到文章的具體內(nèi)容需要找到標簽桨醋,然后再遍歷標簽的子標簽即可遍歷到所有的段落棚瘟,配圖資料

這樣定位到圖片,那么我們怎么樣來尋找

代碼

fromdocximportDocumentfrombs4importBeautifulSoupimporturllib?url ="http://freebuf.com/news/94263.html"data = urllib.urlopen(url)?document = Document()?soup = BeautifulSoup(data)article = soup.find(name ="div",attrs={'id':'contenttxt'}).childrenforeinarticle:try:ife.img:???????????pic_name =''printe.img.attrs['src']if'gif'ine.img.attrs['src']:??????????????? pic_name ='temp.gif'elif'png'ine.img.attrs['src']:??????????????? pic_name ='temp.png'elif'jpg'ine.img.attrs['src']:??????????????? pic_name ='temp.jpg'else:??????????????? pic_name ='temp.jpeg'urllib.urlretrieve(e.img.attrs['src'], filename=pic_name)???????????document.add_picture(pic_name)except:passife.string:printe.string.encode('gbk','ignore')???????document.add_paragraph(e.string)???????document.save("freebuf_article.docx")print"success create a document"

當然上面的代碼讨盒,讀者看起來是非常粗糙的解取,其實沒關(guān)系,我們的目的達到了返顺,在使用中學(xué)習(xí)禀苦,這也是python的精神∷烊担可能大家不熟悉beautifulsoup的使用振乏,這里需要大家自行去讀一下beautifulsoup官方的doc,筆者不知道有沒有中文版秉扑,但是讀英文也不是那么麻煩慧邮,基本寫的還是非常簡明易懂。

那么我們可以看一下輸出的結(jié)構(gòu)吧舟陆!

這是文件夾目錄下的文件汉规,打開生成的docx文檔以后:

這里我們發(fā)現(xiàn)排版仍然是有缺陷腐芍,但是所有的內(nèi)容贸毕,圖片都按照原來的順序成功存儲在了.docx文件中盟迟。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市踱承,隨后出現(xiàn)的幾起案子倡缠,更是在濱河造成了極大的恐慌哨免,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,544評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件昙沦,死亡現(xiàn)場離奇詭異琢唾,居然都是意外死亡,警方通過查閱死者的電腦和手機盾饮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,430評論 3 392
  • 文/潘曉璐 我一進店門采桃,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人丘损,你說我怎么就攤上這事芍碧。” “怎么了号俐?”我有些...
    開封第一講書人閱讀 162,764評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長定庵。 經(jīng)常有香客問我吏饿,道長,這世上最難降的妖魔是什么蔬浙? 我笑而不...
    開封第一講書人閱讀 58,193評論 1 292
  • 正文 為了忘掉前任猪落,我火速辦了婚禮,結(jié)果婚禮上畴博,老公的妹妹穿的比我還像新娘笨忌。我一直安慰自己,他們只是感情好俱病,可當我...
    茶點故事閱讀 67,216評論 6 388
  • 文/花漫 我一把揭開白布官疲。 她就那樣靜靜地躺著,像睡著了一般亮隙。 火紅的嫁衣襯著肌膚如雪途凫。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,182評論 1 299
  • 那天溢吻,我揣著相機與錄音维费,去河邊找鬼。 笑死促王,一個胖子當著我的面吹牛犀盟,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播蝇狼,決...
    沈念sama閱讀 40,063評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼阅畴,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了题翰?” 一聲冷哼從身側(cè)響起恶阴,我...
    開封第一講書人閱讀 38,917評論 0 274
  • 序言:老撾萬榮一對情侶失蹤诈胜,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后冯事,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體焦匈,經(jīng)...
    沈念sama閱讀 45,329評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,543評論 2 332
  • 正文 我和宋清朗相戀三年昵仅,在試婚紗的時候發(fā)現(xiàn)自己被綠了缓熟。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,722評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡摔笤,死狀恐怖够滑,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情吕世,我是刑警寧澤彰触,帶...
    沈念sama閱讀 35,425評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站命辖,受9級特大地震影響况毅,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜尔艇,卻給世界環(huán)境...
    茶點故事閱讀 41,019評論 3 326
  • 文/蒙蒙 一尔许、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧终娃,春花似錦味廊、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,671評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至窍荧,卻和暖如春衙熔,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搅荞。 一陣腳步聲響...
    開封第一講書人閱讀 32,825評論 1 269
  • 我被黑心中介騙來泰國打工红氯, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人咕痛。 一個月前我還...
    沈念sama閱讀 47,729評論 2 368
  • 正文 我出身青樓痢甘,卻偏偏與公主長得像,于是被迫代替她去往敵國和親茉贡。 傳聞我的和親對象是個殘疾皇子塞栅,可洞房花燭夜當晚...
    茶點故事閱讀 44,614評論 2 353

推薦閱讀更多精彩內(nèi)容