上一篇演示瞭如何使用requests模塊向網站發送http請求,獲取到網頁的HTML數據。這篇來演示如何使用BeautifulSoup模塊來從HTML文本中提取咱們想要的數據。html
update on 2016-12-28:以前忘記給BeautifulSoup的官網了,今天補上,順便再補點BeautifulSoup的用法。html5
update on 2017-08-16:不少網友留言說Unsplash網站改版了,不少內容是動態加載的。因此建議動態加載的內容使用PhantomJS而不是Request庫進行請求,若是使用PhantomJS請看個人下一篇博客,若是是定位html文檔使用的class等名字更改的話,建議你們根據更改後的內容進行定位,學爬蟲重要的是爬取數據的邏輯,邏輯掌握了網站怎麼變都不重要啦。web
個人運行環境以下:chrome
系統版本
Windows10。瀏覽器
Python版本
Python3.5,推薦使用Anaconda 這個科學計算版本,主要是由於它自帶一個包管理工具,能夠解決有些包安裝錯誤的問題。去Anaconda官網,選擇Python3.5版本,而後下載安裝。網絡
IDE
我使用的是PyCharm,是專門爲Python開發的IDE。這是JetBrians的產品,點我下載。函數
BeautifulSoup 有多個版本,咱們使用BeautifulSoup4。詳細使用看BeautifuSoup4官方文檔。
使用管理員權限打開cmd命令窗口,在窗口中輸入下面的命令便可安裝:
conda install beautifulsoup4
工具
直接使用Python3.5 沒有使用Anaconda版本的童鞋使用下面命令安裝:
pip install beautifulsoup4
測試
而後咱們安裝lxml,這是一個解析器,BeautifulSoup可使用它來解析HTML,而後提取內容。網站
Anaconda 使用下面命令安裝lxml:
conda install lxml
使用Python3.5 的童鞋們直接使用pip安裝會報錯(因此才推薦使用Anaconda版本),安裝教程看這裏。
若是不安裝lxml,則BeautifulSoup會使用Python內置的解析器對文檔進行解析。之因此使用lxml,是由於它速度快。
文檔解析器對照表以下:
解析器 | 使用方法 | 優點 | 劣勢 |
---|---|---|---|
Python標準庫 | BeautifulSoup(markup,"html.parser") | 1. Python的內置標準庫 2. 執行速度適 3. 中文檔容錯能力強 |
Python 2.7.3 or 3.2.2)前 的版本中文檔容錯能力差 |
lxml HTML 解析器 | BeautifulSoup(markup,"lxml") | 1. 速度快 2. 文檔容錯能力強 |
須要安裝C語言庫 |
lxml XML 解析器 | BeautifulSoup(markup,["lxml-xml"]) BeautifulSoup(markup,"xml") |
1. 速度快 2. 惟一支持XML的解析器 |
須要安裝C語言庫 |
html5lib | BeautifulSoup(markup,"html5lib") | 1. 最好的容錯性 2. 以瀏覽器的方式解析文檔 3. 生成HTML5格式的文檔 |
速度慢,不依賴外部擴展 |
網上找到的幾個官方文檔:BeautifulSoup4.4.0中文官方文檔,BeautifulSoup4.2.0中文官方文檔。不一樣版本的用法差很少,幾個經常使用的語法都同樣。
首先來看BeautifulSoup的對象種類,在使用的過程當中就會了解你獲取到的東西接下來應該如何操做。
Beautiful Soup將複雜HTML文檔轉換成一個複雜的樹形結構,每一個節點都是Python對象。全部對象能夠概括爲4種類型: Tag , NavigableString , BeautifulSoup , Comment 。下面咱們分別看看這四種類型都是什麼東西。
這個就跟HTML或者XML(還能解析XML?是的,能!)中的標籤是同樣同樣的。咱們使用find()方法返回的類型就是這個(插一句:使用find-all()返回的是多個該對象的集合,是能夠用for循環遍歷的。)。返回標籤以後,還能夠對提取標籤中的信息。
tag.name
tag['attribute']
咱們用一個例子來了解這個類型:
from bs4 import BeautifulSoup html_doc = """ <html><head><title>The Dormouse's story</title></head> <body> <p class="title"><b>The Dormouse's story</b></p> <p class="story">Once upon a time there were three little sisters; and their names were <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>, <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>; and they lived at the bottom of a well.</p> <p class="story">...</p> """ soup = BeautifulSoup(html_doc, 'lxml') #聲明BeautifulSoup對象 find = soup.find('p') #使用find方法查到第一個p標籤 print("find's return type is ", type(find)) #輸出返回值類型 print("find's content is", find) #輸出find獲取的值 print("find's Tag Name is ", find.name) #輸出標籤的名字 print("find's Attribute(class) is ", find['class']) #輸出標籤的class屬性值
NavigableString就是標籤中的文本內容(不包含標籤)。獲取方式以下:
tag.string
仍是以上面那個例子,加上下面這行,而後執行:
print('NavigableString is:', find.string)
BeautifulSoup對象表示一個文檔的所有內容。支持遍歷文檔樹和搜索文檔樹。
這個對象其實就是HTML和XML中的註釋。
markup = "<b><!--Hey, buddy. Want to buy a used parser?--></b>" soup = BeautifulSoup(markup) comment = soup.b.string type(comment) # <class 'bs4.element.Comment'>
有些時候,咱們並不想獲取HTML中的註釋內容,因此用這個類型來判斷是不是註釋。
if type(SomeString) == bs4.element.Comment: print('該字符是註釋') else: print('該字符不是註釋')
可使用子節點、父節點、 及標籤名的方式遍歷:
soup.head #查找head標籤 soup.p #查找第一個p標籤 #對標籤的直接子節點進行循環 for child in title_tag.children: print(child) soup.parent #父節點 # 全部父節點 for parent in link.parents: if parent is None: print(parent) else: print(parent.name) # 兄弟節點 sibling_soup.b.next_sibling #後面的兄弟節點 sibling_soup.c.previous_sibling #前面的兄弟節點 #全部兄弟節點 for sibling in soup.a.next_siblings: print(repr(sibling)) for sibling in soup.find(id="link3").previous_siblings: print(repr(sibling))
最經常使用的固然是find()和find_all()啦,固然還有其餘的。好比find_parent() 和 find_parents()、 find_next_sibling() 和 find_next_siblings() 、find_all_next() 和 find_next()、find_all_previous() 和 find_previous() 等等。
咱們就看幾個經常使用的,其他的若是用到就去看官方文檔哦。
find_all( name , attrs , recursive , string , **kwargs )
soup.find_all("title") # [<title>The Dormouse's story</title>] # soup.find_all("p", "title") # [<p class="title"><b>The Dormouse's story</b></p>] # soup.find_all("a") # [<a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>, # <a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>, # <a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>] # soup.find_all(id="link2") # [<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>] # import re soup.find(string=re.compile("sisters")) # u'Once upon a time there were three little sisters; and their names were\n'
name 參數:能夠查找全部名字爲 name 的tag。
attr 參數:就是tag裏的屬性。
string 參數:搜索文檔中字符串的內容。
recursive 參數: 調用tag的 find_all() 方法時,Beautiful Soup會檢索當前tag的全部子孫節點。若是隻想搜索tag的直接子節點,可使用參數 recursive=False 。
find( name , attrs , recursive , string , **kwargs )
soup.find('title') # <title>The Dormouse's story</title> # soup.find("head").find("title") # <title>The Dormouse's story</title>
基本功已經練完,開始實戰!
繼續上一篇的網站Unsplash,咱們在首頁選中圖片,查看html代碼。發現全部的圖片都在a標籤裏,而且class都是cV68d,以下圖。
經過仔細觀察,發現圖片的連接在style中的background-image中有個url。這個url就包含了圖片的地址,url後面跟了一堆參數,能夠看到其中有&w=XXX&h=XXX,這個是寬度和高度參數。咱們把高度和寬度的參數去掉,就能獲取到大圖。下面,咱們先獲取到全部的含有圖片的a標籤,而後在循環獲取a標籤中的style內容。
其實在圖片的右下方有一個下載按鈕,按鈕的標籤中有一個下載連接,可是該連接並不能直接請求到圖片,須要跳轉幾回,經過獲取表頭裏的Location才能獲取到真正的圖片地址。後續我再以這個角度獲取圖片寫一篇博文,我們現根據能直接獲取到的url稍作處理來獲取圖片。小夥伴兒們也可能會發現其餘的方式來獲取圖片的url,都是能夠的,盡情的嘗試吧!
import requests #導入requests 模塊 from bs4 import BeautifulSoup #導入BeautifulSoup 模塊 headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/54.0.2840.99 Safari/537.36'} #給請求指定一個請求頭來模擬chrome瀏覽器 web_url = 'https://unsplash.com'r = requests.get(web_url, headers=headers) #像目標url地址發送get請求,返回一個response對象 all_a = BeautifulSoup(r.text, 'lxml').find_all('a', class_='cV68d') #獲取網頁中的class爲cV68d的全部a標籤 for a in all_a: print(a['style']) #循環獲取a標籤中的style
這裏的find_all('a', class_='cV68d') 是找到全部class爲cV68d的a標籤,返回的是一個list,因此能夠用for循環獲取每一個a標籤。
還有,get請求使用了headers參數,這個是用來模擬瀏覽器的。如何知道‘User-Agent’是什麼呢?
在你的Chrome瀏覽器中,按F12,而後刷新網頁,看下圖就能夠找到啦。
OK,咱們來執行如下上面的代碼,結果以下:
接下來的任務是在一行的文本中取到圖片的url。仔細看每一行的字符串,兩個雙引號之間的內容就是圖片的url了,因此咱們Python的切片功能來截取這中間的內容。
改寫for循環中的內容:
for a in all_a: img_str = a['style'] #a標籤中完整的style字符串 print(img_str[img_str.index('"')+1 : img_str.index('"',img_str[img_str.index('"')+1)]) #使用Python的切片功能截取雙引號之間的內容
獲取到url後還要把寬度和高度的參數去掉。
for a in all_a: img_str = a['style'] #a標籤中完整的style字符串 print('a標籤的style內容是:', img_str) first_pos = img_str.index('"') + 1 second_pos = img_str.index('"',first_pos) img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取雙引號之間的內容 width_pos = img_url.index('&w=') height_pos = img_url.index('&q=') width_height_str = img_url[width_pos : height_pos] print('高度和寬度數據字符串是:', width_height_str) img_url_final = img_url.replace(width_height_str, '') print('截取後的圖片的url是:', img_url_final)
有了這些圖片的url,就能夠經過繼續發請求的方式獲取圖片啦。接下來咱們先來封裝一下發請求的代碼。
先建立一個類:
class BeautifulPicture(): def __init__(self): #類的初始化操做 self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/55.0.2883.87 Safari/537.36'} #給請求指定一個請求頭來模擬chrome瀏覽器 self.web_url = 'https://unsplash.com' #要訪問的網頁地址 self.folder_path = 'D:\BeautifulPicture' #設置圖片要存放的文件目錄
而後封裝request請求:
def request(self, url): #返回網頁的response r = requests.get(url) # 像目標url地址發送get請求,返回一個response對象 return r
咱們在文件目錄下保存圖片的話,要先建立文件目錄。因此再添加一個建立目錄的方法:
要先引入os庫哦。
import os
而後是方法定義:
def mkdir(self, path): ##這個函數建立文件夾 path = path.strip() isExists = os.path.exists(path) if not isExists: print('建立名字叫作', path, '的文件夾') os.makedirs(path) print('建立成功!') else: print(path, '文件夾已經存在了,再也不建立')
再而後是保存圖片啦。
def save_img(self, url, name): ##保存圖片 print('開始保存圖片...') img = self.request(url) time.sleep(5) file_name = name + '.jpg' print('開始保存文件') f = open(file_name, 'ab') f.write(img.content) print(file_name,'文件保存成功!') f.close()
工具方法都已經準備完畢,開始咱們的邏輯部分:
def get_pic(self): print('開始網頁get請求') r = self.request(self.web_url) print('開始獲取全部a標籤') all_a = BeautifulSoup(r.text, 'lxml').find_all('a', class_='cV68d') #獲取網頁中的class爲cV68d的全部a標籤 print('開始建立文件夾') self.mkdir(self.folder_path) #建立文件夾 print('開始切換文件夾') os.chdir(self.folder_path) #切換路徑至上面建立的文件夾 i = 1 #後面用來給圖片命名 for a in all_a: img_str = a['style'] #a標籤中完整的style字符串 print('a標籤的style內容是:', img_str) first_pos = img_str.index('"') + 1 second_pos = img_str.index('"',first_pos) img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取雙引號之間的內容 width_pos = img_url.index('&w=') height_pos = img_url.index('&q=') width_height_str = img_url[width_pos : height_pos] print('高度和寬度數據字符串是:', width_height_str) img_url_final = img_url.replace(width_height_str, '') print('截取後的圖片的url是:', img_url_final) self.save_img(img_url_final, str(i)) i += 1
最後就是執行啦:
beauty = BeautifulPicture() #建立一個類的實例 beauty.get_pic() #執行類中的方法
最後來一個完整的代碼,對中間的一些部分進行了封裝和改動,並添加了每部分的註釋,一看就明白了。有哪塊有疑惑的能夠留言~~
import requests #導入requests 模塊 from bs4 import BeautifulSoup #導入BeautifulSoup 模塊 import os #導入os模塊 class BeautifulPicture(): def __init__(self): #類的初始化操做 self.headers = {'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.1 (KHTML, like Gecko) Chrome/22.0.1207.1 Safari/537.1'} #給請求指定一個請求頭來模擬chrome瀏覽器 self.web_url = 'https://unsplash.com' #要訪問的網頁地址 self.folder_path = 'D:\BeautifulPicture' #設置圖片要存放的文件目錄 def get_pic(self): print('開始網頁get請求') r = self.request(self.web_url) print('開始獲取全部a標籤') all_a = BeautifulSoup(r.text, 'lxml').find_all('a', class_='cV68d') #獲取網頁中的class爲cV68d的全部a標籤 print('開始建立文件夾') self.mkdir(self.folder_path) #建立文件夾 print('開始切換文件夾') os.chdir(self.folder_path) #切換路徑至上面建立的文件夾 for a in all_a: #循環每一個標籤,獲取標籤中圖片的url而且進行網絡請求,最後保存圖片 img_str = a['style'] #a標籤中完整的style字符串 print('a標籤的style內容是:', img_str) first_pos = img_str.index('"') + 1 second_pos = img_str.index('"',first_pos) img_url = img_str[first_pos: second_pos] #使用Python的切片功能截取雙引號之間的內容 #獲取高度和寬度的字符在字符串中的位置 width_pos = img_url.index('&w=') height_pos = img_url.index('&q=') width_height_str = img_url[width_pos : height_pos] #使用切片功能截取高度和寬度參數,後面用來將該參數替換掉 print('高度和寬度數據字符串是:', width_height_str) img_url_final = img_url.replace(width_height_str, '') #把高度和寬度的字符串替換成空字符 print('截取後的圖片的url是:', img_url_final) #截取url中參數前面、網址後面的字符串爲圖片名 name_start_pos = img_url.index('photo') name_end_pos = img_url.index('?') img_name = img_url[name_start_pos : name_end_pos] self.save_img(img_url_final, img_name) #調用save_img方法來保存圖片 def save_img(self, url, name): ##保存圖片 print('開始請求圖片地址,過程會有點長...') img = self.request(url) file_name = name + '.jpg' print('開始保存圖片') f = open(file_name, 'ab') f.write(img.content) print(file_name,'圖片保存成功!') f.close() def request(self, url): #返回網頁的response r = requests.get(url, headers=self.headers) # 像目標url地址發送get請求,返回一個response對象。有沒有headers參數均可以。 return r def mkdir(self, path): ##這個函數建立文件夾 path = path.strip() isExists = os.path.exists(path) if not isExists: print('建立名字叫作', path, '的文件夾') os.makedirs(path) print('建立成功!') else: print(path, '文件夾已經存在了,再也不建立') beauty = BeautifulPicture() #建立類的實例 beauty.get_pic() #執行類中的方法
執行的過程當中可能會有點慢,這是由於圖片自己比較大!若是僅僅是爲了測試爬蟲,則能夠不把圖片的寬度和高度替換掉,圖片就沒那麼大啦,運行過程會快不少。
夥伴兒們是否是發現,咱們只獲取到了10張圖片,並無把網站全部照片都下載下來。
這是由於我們爬取的網站是下拉刷新的,下拉一次,刷新10張照片。那麼,該如何爬取這種下拉刷新的網頁呢?請看下一篇嘍。