標籤:爬蟲 程式設計語言 Regex python
今天第一次寫爬蟲,感覺非常有趣!,中途也遇到了許多問題,所以寫篇部落格~
目標:爬取豆瓣編程類書籍中9分以上的
剛接觸爬蟲,說下我的認識(不一定準確^_^)
我們知道網頁的呈現也是用程式設計語言寫出來的,有源碼,每個網頁我們都可以查看它的源碼,我的瀏覽器快速鍵是Ctrl+U,
一般點擊右鍵就可以看見查看源碼。因為要爬取豆瓣的資料,那看看豆瓣圖書頁面的部分源碼
它所對應的資料是這樣的
那麼我們知道了,網頁上所能看見的每個資料在源碼上都能找到,有的點擊會跳轉也是因為源碼上連結著其他地方。
所以我們直接分析源碼即可。
思路
其實很簡單,我們想要爬取編程類9分以上的書籍。那麼從編程類的首頁開始爬取資料。
用Regex來過濾資料,取出9分以上的資料,關於Regex如果不熟悉可參考我的上篇文章^_^
然後我們在每個網頁的最後可以發現跳轉到下一個網頁的連結,通過Regex過濾出這個連結即可。
儲存這個連結作為下一次爬取的url。
我設定的結束條件是爬取當前頁的9分以上書籍為0時跳出迴圈,為了簡單起見。
看代碼:
#!/usr/bin/env python#coding:UTF-8import urllibimport re#得到一個網頁的源碼def gethtml(url): #返回類似檔案描述副,可進行讀操作,read返回str page = urllib.urlopen(url) html = page.read() return html#根據正則得到我們想要的資料def getdata(obj): patt = r'(<dl>.+?>(9\.\d).+?</dl>)' #編譯正則模組 pattern = re.compile(patt, re.DOTALL) #findall查詢此頁面所有合格資料,返回list m = pattern.findall(obj) Len = len(m) print 'len: %d' % Len if m is not None: for i in m: print i[1], print else: print 'not found' #如果此頁面的9分資料為0,則認為沒有9分資料 if Len == 0: return False else: return True#得到下一個頁面def getnextpage(obj): #我們要爬去的每個頁面url前面都一樣,所以只換後面的資料即可 nextpage = 'http://www.douban.com/tag/%E7%BC%96%E7%A8%8B/book?start=' #根據正則匹配後面的資料,然後串連到一起 patt = '<span class="break">...</span>.+start=(\d+)' pattern = re.compile(patt, re.DOTALL) #下一個頁面只有一個符合資料,所以用search m = pattern.search(obj) if m is not None: nextpage += m.group(1) else: print 'not found' return nextpageif __name__ == '__main__': #起始頁面 html = gethtml("http://www.douban.com/tag/%E7%BC%96%E7%A8%8B/book") while 1: if getdata(html) == False: break ret = getnextpage(html) html = gethtml(ret)
注釋寫的比較詳細。運行結果我輸出了每個頁面的書籍分數,len是此頁面9分以上書籍的數量
運行結果:部分資料
遇到的問題:貌似都是Regex的問題 - - 。
1.
網頁源碼中每個書籍是這樣的
可以看見我們要分析的資料中間包含中文,過濾中文首先必須要在開頭加上
#coding:UTF-8,‘ . ‘符號在正則中可以過濾任何字元,\w只能是字母和數字
其次這段資料中包含了許多分行符號號,在Regex中\s只能過濾空白字元,不能過濾斷行符號。
我們需要在
#編譯正則模組 pattern = re.compile(patt, re.DOTALL)
裡面設則,DOTALL的意思就是忽略斷行符號符號
2.findall的問題
findall是正則匹配中一次匹配多個資料的函數
它返回的是一個list。
先來看看遇到的問題吧
在getdata中,我的需求是擷取每本書的整個標籤,也就是上面的圖片,但是我還想順便過濾出分數,就是把分數也作為一個分組可以查看。
類似m = search.( ),m.group(0),m.group(1)。
但是findall返回的是list,裡面的資料是str。
和小夥伴討論後又仔細了書,結論:
當Regex只有一個子組的時候(()是一個子組),findall( )返回子組匹配的字串組成的列表,如果運算式有多個子組,返回的是一個元組的列表
元組中的每個元素都是一個子組的匹配內容,像這樣的元組構成了返回列表中的元素
這也是為什麼代碼中我用的是m[1],因為返回的是一個元組唄!
順便吐槽下python核心編程這本書,我目前發現的印刷錯誤至少十幾處了!
而且findall( )這部分除了簡單的介紹外沒有給任何例子,結尾還說了句 “這些內容初次聽到可能感到費解,但如果你看看各種例子,就會明白了”
它沒給例子...
3.貪婪匹配
很重要,當時我只是簡單的看了看,沒太在意,結果實戰就出錯了
原本我的getdata正則是這樣寫的
def getdata(obj): patt = r'(<dl>.+>(9\.\d).+</dl>)'
怎麼匹配也出不來
於是發現了貪婪匹配的問題
Regex預設是貪心匹配的,簡單來說,如果Regex用到萬用字元(*, +, ?)等,它在從左至右匹配的時候會盡量匹配
最長的字串。
看個例子:
#!/usr/bin/env python#coding:UTF-8import res = 'helloworld800-333-3333'patt = '.+(\d+-\d+-\d+)'m = re.search(patt, s)print m.group(1)
我們想得出結果800-333-3333這個號碼
結果卻是
因為patt前面的‘.+‘預設是貪婪匹配的,它會匹配它能匹配盡量多的字元,所以我們後面的\d+只能匹配了一個0而已
解決辦法是用非貪婪操作符號 ‘ ? ‘
那麼‘.+‘就不會盡量讀取多的字元
patt = '.+?(\d+-\d+-\d+)'
可以運行下試試,結果為800-333-3333
Regex問題的卻比較多,一個學長建議我用BeautifulSoup來解析,會方便很多,感興趣可以試試。
簡單的入門,見笑了 ^_^
python實現一個簡單的爬蟲