1. 程式人生 > >精通Python爬蟲-03-狩獵大師

精通Python爬蟲-03-狩獵大師

效率 child 機器 virtual 做成 昨天 comm 彈出 bs4

聲明:

本系列文章原創於慕課網,作者秋名山車神,任何人不得以任何形式在不經作者允許的情況下,進行任何形式的印刷以及銷售,轉載需註明出處及此聲明。

本系列文章更新至少每周一更,將涉及Python爬蟲基礎,Requests,Scrapy等主流爬蟲技術。同時會介紹圖片驗證碼,語音驗證碼的識別以及我自己設計的一個高並發可擴展易維護的集群爬蟲架構。

對文章有任何問題請在下面留言,我會不定期的回復大家。

人非聖賢,如果文章有錯別字請大家自行區分或指正出來,我將不定期修改錯誤的地方。

本系列能否持久更新下去離不開大家的支持與鼓勵,以及對原創版權的尊重。

作者想說的話

最近一段時間特別的忙,事情也很多。有幾家出版社找著寫書,都讓我給推了,昨天閑著沒事在翻慕課網的手記時,發現了這個系列的文章。看到那麽多人瀏覽,那麽多人評論讓快點更新,我覺得不能讓大家失望,所以我開始更新了。

我想做的事情很很普通,就是希望我所知道的技術能夠以一種力所能及的方式帶給大家,我希望慕課網是一個積極進取的社區,每個人都能毫無保留的對待別人。也希望我做的這件普通的事情,能夠幫助到每一個想學習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

安裝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.parserlxml,其中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;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 老師的實戰課,多多強化自己的算法內功,早日成功一個優秀的 攻城獅。

精通Python爬蟲-03-狩獵大師