聲明:html
本系列文章原創於慕課網,做者秋名山車神,任何人不得以任何形式在不經做者容許的狀況下,進行任何形式的印刷以及銷售,轉載需註明出處及此聲明。前端
本系列文章更新至少每週一更,將涉及Python爬蟲基礎,Requests,Scrapy等主流爬蟲技術。同時會介紹圖片驗證碼,語音驗證碼的識別以及我本身設計的一個高併發可擴展易維護的集羣爬蟲架構。python
對文章有任何問題請在下面留言,我會不按期的回覆你們。面試
人非聖賢,若是文章有錯別字請你們自行區分或指正出來,我將不按期修改錯誤的地方。算法
本系列可否持久更新下去離不開你們的支持與鼓勵,以及對原創版權的尊重。json
做者想說的話瀏覽器
最近一段時間特別的忙,事情也不少。有幾家出版社找着寫書,都讓我給推了,昨天閒着沒事在翻慕課網的手記時,發現了這個系列的文章。看到那麼多人瀏覽,那麼多人評論讓快點更新,我以爲不能讓你們失望,因此我開始更新了。網絡
我想作的事情很很普通,就是但願我所知道的技術可以以一種力所能及的方式帶給你們,我但願慕課網是一個積極進取的社區,每一個人都能毫無保留的對待別人。也但願我作的這件普通的事情,可以幫助到每個想學習python,學習爬蟲的人。謝謝你們的支持。數據結構
大師的嗅覺架構
在上一章中,咱們已經學習瞭如何使用 urllib 來從網絡上獲取信息,這一章咱們來學習若是從這些信息中提取咱們想要的內容。
依然以咱們的慕課網爲例,順便讓咱們回顧一下上一章的代碼。
#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib.request # 建立一個Request對象 req = urllib.request.Request('http://www.imooc.com') # 使用Request對象發送請求 response = urllib.request.urlopen(req) print(response.read())
若是運行上面的代碼,就能夠看到慕課網首頁的靜態代碼了,也就是咱們一般所說的HTML代碼。
那這麼多的數據,咱們怎麼能從中獲取到想要的內容呢?好比咱們想要獲取慕課網首頁實戰推薦裏面全部推薦的實戰課名稱,該怎麼辦呢?
此時咱們就可使用另一個庫 BeautifulSoup
打開咱們的命令行工具,輸入:
pip install beautifulsoup4==4.6.0
若是你那裏下載的速度特別慢,可使用下面這條命令,指定本次使用豆瓣的pip鏡像源來安裝:
pip install beautifulsoup4==4.6.0 -i https://pypi.douban.com/simple
看到以下的信息就表示安裝成功了:
Successfully installed beautifulsoup4-4.6.0
有的小夥伴可能還會看到一些黃色的文字:
You are using pip version 9.0.1, however version 10.0.1 is available.
You should consider upgrading via the 'python -m pip install --upgrade pip' command.
這是說咱們的pip版本過低了,提示咱們可使用 python -m pip install --upgrade pip
這條命令來升級到最新版本,通常不須要升級,若是想要升級的可使用這條命令,升級之後就不會再出這個提示了。
不用讀系列:我並不想把本教程作成一個官方文檔,或者是像其餘書那樣,把官方文檔抄下來。這樣又有什麼意義呢?學了之後仍是不知道怎麼應用,因此我但願每個庫咱們都是經過一個小項目來學習的,用到的每個方法我都會詳細的說明。而用不到的方法,它都用不到了,咱們還學它幹嗎?
你們跟着我一步一步學習怎麼狩獵吧
首先咱們打開慕課網的首頁:https://www.imooc.com
建議使用Chrome瀏覽器
而後找到咱們的實戰推薦,在其中一個推薦的實戰課圖片上,右鍵點擊圖片,選擇檢查。
而後會打開瀏覽器的開發者工具,若是還不知道這個工具怎麼用的,你們能夠去看一下個人免費課程:
接着咱們選擇開發者工具上面的選擇器,使用鼠標左鍵點擊咱們想要獲取的地方:
能夠看到下面的代碼就是咱們想要獲取的內容:
<h3 class="course-card-name">全網最熱Python3入門+進階 更快上手實際開發</h3>
那麼其餘的幾個也是這樣的嗎?重複上面的步驟點擊每一個實戰推薦的名字,看到以下結果:
想要獲取同類型的內容,就尋找他們相同的部分。
那麼上面的實戰推薦中,很顯然他們相同的部分就是 <h3 class="course-card-name"></h3>
,那接下來就直接修改上面的代碼來試着獲取這部分的內容。
#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib.request # 導包 from bs4 import BeautifulSoup # 建立一個Request對象 req = urllib.request.Request('http://www.imooc.com') # 使用Request對象發送請求 response = urllib.request.urlopen(req) soup = BeautifulSoup(response.read()) course_names = soup.find_all('h3', {'class':'course-card-name'}) print(course_names)
from bs4 import BeautifulSoup 這句代碼呢,意思就是從bs4這個模塊裏,導入咱們的 BeautifulSoup 方法,沒什麼其餘的含義。
BeautifulSoup() 方法接收兩個參數,第一個參數是一個str類型的,就是咱們獲取到的HTML代碼。第二個參數也是一個str類型的,表示咱們但願使用哪一個庫來解析HTML的代碼,經常使用的都 html.parser
和 lxml
,其中lxml
效率更高一些。而咱們上面的代碼並無指定第二個參數,因此它會輸出一個警告信息。
UserWarning: No parser was explicitly specified, so I'm using the best available HTML parser for this system ("lxml"). This usually isn't a problem, but if you run this code on another system, or in a different virtual environment, it may use a different parser and behave differently.
The code that caused this warning is on line 5 of the file C:\dream\python\Anaconda3\Scripts\ipython-script.py. To get rid of this warning, change code that looks like this:
BeautifulSoup(YOUR_MARKUP})
to this:
BeautifulSoup(YOUR_MARKUP, "lxml")
markup_type=markup_type))
關鍵在於倒數第二行 BeautifulSoup(YOUR_MARKUP, "lxml")
,這裏提示的很明顯,讓咱們增長第二個參數 lxml
。固然了,這是由於個人電腦裏安裝了 lxml
庫,沒有安裝的小夥伴,這裏提示的多是 html.parser
,無論提示的是什麼,你都按照BeautifulSoup提示你的來進行修改就能夠了~
我這裏按照提示修改之後的那句代碼就成了:
soup = BeautifulSoup(response.read(), "lxml")
這個方法還會給咱們返回一個bs4.BeautifulSoup的對象,咱們可使用一個變量來接收,這個變量能夠是任意名字,這裏咱們用 soup 來表示。
soup.find_all() 這個方法接收兩個參數,表明從 soup 這個對象裏,搜索所有符合條件的內容。第一個參數是str類型的,表示咱們要從哪一個HTML標籤中獲取,這裏咱們是要從 h3 這個標籤裏獲取。第二個dict類型的,接收key和value的鍵值對。表明咱們要獲取的這個h3標籤,有那些屬性能夠來標識它。
<h3 class="course-card-name">全網最熱Python3入門+進階 更快上手實際開發</h3>
能夠看到咱們想獲取的h3標籤,只有一個class屬性,那第二個參數就是:
{"class": "course-card-name"}
該方法返回一個 bs4.element.ResultSet
類型的對象,相似於咱們python的list列表,咱們能夠像操做列表同樣來操做這個對象。一樣使用一個變量去接收它。
最終上面的代碼運行之後,可能看到以下的結果:
[<h3 class="course-card-name">前端進階:響應式開發與經常使用框架</h3>, <h3 class="course-card-name">揭祕一線互聯網企業 前端JavaScript高級面試</h3>, ...省略N條... <h3 class="course-card-name">Python Flask 構建微電影視頻網站</h3>, <h3 class="course-card-name">AWS雲-深度學習&amp;機器學習的AI業務應用</h3>]
提示:因爲慕課網是不斷在更新的,因此當你看到這篇教程的時候,相關的內容可能已經更換了名稱,好比class不叫course-card-name。我但願你可以本身修改上面的代碼,來達到符合你看到教程時,慕課網的樣子。
咱們發現,結果和咱們預想的不太同樣,這是爲何呢?咱們再次回到慕課網,右鍵在網頁空白的部分單擊,而後選擇查看網頁源碼:
而後在彈出來的窗口中,按下 ctrl + f
的組合快捷鍵,接着搜索 course-card-name
。
嗯,我相信你們知道組合鍵的意思就是先按下鍵盤上的
Ctrl
鍵,而後不鬆開這個鍵的狀況下,再按鍵盤上的F
鍵。
看到的搜索結果以下:
出現了76個搜索結果,這是爲何呢?咱們明明只是想獲取實戰推薦裏面的課程名稱,爲何出現了這麼多的搜索結果?
簡單思考一下咱們就能夠知道,咱們剛纔搜索是從 response.read()
裏面搜索的,這表明了整個慕課網首頁的全部內容。而慕課網全部的課程名稱,使用的都是下面的樣式:
<h3 class="course-card-name"></h3>
因此咱們不只獲取了實戰推薦的課程名稱,還獲取了其餘不想要的課程名稱。
爬蟲並非獲取的越多越好,而是精準獲取咱們想要的數據,其餘不想要的數據越少越好。
那麼很顯然,是咱們狩獵的範圍太大了,試着來縮小咱們的狩獵範圍吧。
一樣使用瀏覽器的開發者工具,慢慢的移動咱們的鼠標,直到鼠標指向的內容,恰好包裹咱們想要獲取的那一部分。
如圖所示,最終咱們指向了一個div的標籤,它覆蓋了咱們想要獲取的全部內容,而沒有覆蓋其餘咱們不想獲取的內容。而且這個div也有一個class屬性,經過上面學習到的,來修改一下咱們的代碼,看看是否是能達到咱們的目的。
#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib.request # 導包 from bs4 import BeautifulSoup # 建立一個Request對象 req = urllib.request.Request('http://www.imooc.com') # 使用Request對象發送請求 response = urllib.request.urlopen(req) soup = BeautifulSoup(response.read(), "lxml") # 先獲取咱們剛纔找到的div course_div = soup.find_all('div', {'class':'clearfix types-content'}) # 再從咱們的div裏獲取想要的內容 course_names = course_div.find_all('h3', {'class':'course-card-name'}) # 結果是不是咱們想要的呢? print(course_names)
結果就報錯了~
AttributeError: ResultSet object has no attribute 'find_all'. You're probably treating a list of items like a single item. Did you call find_all() when you meant to call find()?
其實你們看到報錯,不要懼怕,要慢慢的看這個報錯的內容,通常報錯都是從倒數第一行開始看的,也只有倒數第一行每每纔是有用的東西。
那麼上面的這個報錯很顯然,find_all()方法返回的是一個ResultSet對象,這個對象不具備find_all()方法,因此在咱們下面使用 course_div.find_all('h3', {'class':'course-card-name'})
時,就報錯了。
可是不要慌,BeautifulSoup已經給出修改意見了,它說若是咱們想要獲取的是一個單一的內容,能夠嘗試使用find()方法,修改一下咱們的代碼,再次運行。
#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib.request # 導包 from bs4 import BeautifulSoup # 建立一個Request對象 req = urllib.request.Request('http://www.imooc.com') # 使用Request對象發送請求 response = urllib.request.urlopen(req) soup = BeautifulSoup(response.read(), "lxml") # 先獲取咱們剛纔找到的div course_div = soup.find('div', {'class':'clearfix types-content'}) # 再從咱們的div裏獲取想要的內容 course_names = course_div.find_all('h3', {'class':'course-card-name'}) # 此次還會報錯嗎? print(course_names)
結果如咱們所料,沒有報錯,而且輸出了以下內容:
[<h3 class="course-card-name">全網最熱Python3入門+進階 更快上手實際開發</h3>, <h3 class="course-card-name">玩轉數據結構 從入門到進階</h3>, <h3 class="course-card-name">Java企業級電商項目架 構演進之路 Tomcat集羣與Redis分佈式</h3>, <h3 class="course-card-name">React Native技術精講與高質量上線APP開發</h3>, <h3 class="course-card-name"> Vue2.5開發去哪兒網App 從零基礎入門到實戰項目</h3>]
soup.find() 這個方法,惟一和咱們find_all不一樣的點在於,他們返回的對象不一樣。find_all返回的是一個bs4.element.ResultSet列表對象。而find返回的是bs4.element.Tag。而find_all返回的列表中,包含的就是一個個的bs4.element.Tag對象。bs4.element.Tag對象,就具備了find或者是find_all的方法,以便咱們不斷的縮小範圍,最終獲取咱們想要的結果。
你可能認爲咱們本章到此結束了,並無結束。由於咱們的目的還沒達到,我上面說過了,一個好的爬蟲就是除了咱們想要的內容之外,別的什麼都不要有。那咱們輸出的結果裏,顯然還包含了 <h3 class="course-card-name">
這些東西,那怎麼樣把它過濾掉呢?
#!/usr/bin/env python # -*- coding: utf-8 -*- import urllib.request # 導包 from bs4 import BeautifulSoup # 建立一個Request對象 req = urllib.request.Request('http://www.imooc.com') # 使用Request對象發送請求 response = urllib.request.urlopen(req) soup = BeautifulSoup(response.read(), "lxml") # 先獲取咱們剛纔找到的div course_div = soup.find('div', {'class':'clearfix types-content'}) # 再從咱們的div裏獲取想要的內容 course_names = course_div.find_all('h3', {'class':'course-card-name'}) for course_name in course_names: print(course_name.text)
只須要從咱們的ResultSet列表對象中,取出每個Tag對象,而後調用它的text屬性,就能獲取到標籤中的文本內容了~
時間老是過的很快,你們能夠在評論中告訴我,用本節學到的知識作了哪些有意思的事情。你們對本系列課程有任何的建議和疑問,均可以經過下方的留言告訴我,每一個人的留言我都會看的。
同時你們也能夠加入下面兩個Python的交流羣:
慕課網Python討論羣①:221828022
也能夠加入我建的一個Python交流羣:685024920
兩個羣我都有在裏面,你們有任何的問題均可以在裏面 @秋名山車神~
學習一下咱們 liuyubobobo 老師的口頭禪,你們加油~電動叉車
同時也但願你們多多支持 liuyubobobo 老師的實戰課,多多強化本身的算法內功,早日成功一個優秀的 攻城獅。