天天看點

Python學習筆記 | 爬蟲基礎之 bs4 資料解析子產品及爬蟲執行個體

bs4(Beautiful Soup 4.4.0) 是一個可以從HTML或XML檔案中提取資料的Python庫,其中的 BeautifulSoup類,可以用來擷取html頁面的内容,配合requests庫可以建構簡單的爬蟲程式。

bs4是第三方庫,需要手動安裝。

Python學習筆記 | 爬蟲基礎之 bs4 資料解析子產品及爬蟲執行個體

一、bs4子產品的基本使用方法

首先建構一個BeautifulSoup執行個體對象,傳入Web頁面的 html 文檔内容(字元串格式)或 本地html 檔案的句柄,傳入所使用的解析器,然後就可以使用BeautifulSoup執行個體對象的屬性和方法擷取 html 文檔中的相應資料了。

傳入的 html 文檔内容可以使用requests子產品從網絡中擷取,也可以打開本地 html 檔案進行讀取,或者直接将本地 html 檔案的句柄傳給BeautifulSoup執行個體對象。

示例代碼:

import requests
from bs4 import BeautifulSoup

# 使用requests子產品擷取Web頁面的文檔内容,建立BeautifulSoup執行個體對象
url = 'https://www.baidu.com'
resp = requests.get(url)
resp.encoding = 'utf-8'
html_doc = resp.text
soup1 = BeautifulSoup(html_doc, 'html.parser') 

# 打開并讀取本地html檔案内容,建立BeautifulSoup執行個體對象
with open('test.html', encoding='utf-8') as fin:
    html_doc = fin.read()
soup2 = BeautifulSoup(html_doc, 'html.parser')

# 直接傳入本地html檔案句柄,建立BeautifulSoup執行個體對象
soup3 = BeautifulSoup(open('test.html', encoding='utf-8'), 'html.parser')

print('soup1的網頁标題是:', soup1.title.text)
print('soup2的網頁标題是:', soup2.title.text)
print('soup3的網頁标題是:', soup3.title.text)

運作結果:
soup1的網頁标題是: 百度一下,你就知道
soup2的網頁标題是: 測試網頁
soup3的網頁标題是: 測試網頁           

【注】上面示例代碼中的html.parser,是建立BeautifulSoup執行個體對象時所使用的解析器。html.parser是Python自帶的内置标準庫,可以直接使用,另外還有第三方解析器,使用時需要手動安裝。

二、解析器

Beautiful Soup 能夠從 html 文檔中解析出各種有用資料,例如網頁标題、文本、連結等,依賴于強大的解析器。它支援Python标準庫中的HTML解析器,還支援一些第三方的解析器,詳細情況如下表:

解析器 調用方法 優勢 劣勢
Python标準庫 "html.parser"
  • Python的内置标準庫
  • 執行速度适中
  • 文檔容錯能力強
  • Python3.2.2之前版本的中文檔容錯能力差
lxml HTML 解析器 "lxml"
  • 速度快
  • 文檔容錯能力強
  • 需要安裝C語言庫
lxml XML 解析器 "lxml-xml"
  • 速度快
  • 唯一支援XML的解析器
  • 需要安裝C語言庫
html5lib
  • 最好的容錯性
  • 以浏覽器的方式解析文檔
  • 生成HTML5格式的文檔
  • 最好的容錯性
  • 以浏覽器的方式解析文檔
  • 生成HTML5格式的文檔

【注】以上解析器中,隻有Python标準庫不需要手動安裝,可以直接使用"html.parser"的形式調用,其它解析器都是第三方庫,需要手動安裝,安裝方法與其它第三方子產品的安裝方法相同。Python内置的"html.parser"解析器,在簡單爬蟲中最為常用。

三、常用屬性和方法

為了友善示範和了解,我們首先建立一個html文檔,然後針對該文檔進行資料解析示範。

測試html文檔(test.html)代碼如下:

<!DOCTYPE html>
<html>
   <head>
      <meta charset="utf-8">
      <title>測試網頁</title>
   </head>
   <body>
      <h1>标題1<a href="test.html">連結</a> </h1>
      <h2>标題2</h2>
      <h3>标題3</h3>
      <h4>标題4</h4>
			<h5></h5>
      <div id="content" class="default">
         <p>段落</p>
         <a href="https://www.baidu.com" id="link1">百度</a><br />
         <a href="https://www.163.com" id="link2">網易</a><br />
         <a href="https://www.iqiyi.com" id="link3">愛奇藝</a><br />
         <img src="https://img10.360buyimg.com/img/jfs/t1/192028/25/33459/5661/63fc2af2F1f6ae1b6/d0e4fdc2f126cbf5.png" alt="京東">
      </div>
   </body>
</html>           

1、text屬性

text屬性傳回标簽内或html文檔的全部文本内容,内容為空則傳回空字元

示例代碼:

from bs4 import BeautifulSoup

# 打開并讀取上面建立的html檔案内容,建立BeautifulSoup執行個體對象
with open('test.html', encoding='utf-8') as fin:
    html_doc = fin.read()
soup = BeautifulSoup(html_doc, 'html.parser')

# 傳回h1标簽中的全部文本内容
print(soup.h1.text)

print('='*30)

# 傳回整個html文檔的全部文本内容
print(soup.text)

運作結果:
标題1連結 
==============================




測試網頁


标題1連結 
标題2
标題3
标題4

段落
百度
網易
愛奇藝




           

2、string屬性

string與text屬性都是傳回文本内容,但又有很大的不同。string屬性傳回的是标簽内的文本内容,如果标簽内的文本為空或者有多處文本,則傳回None。

部分示例代碼:

# 列印輸出h1、h2和h5标簽内的文本内容
print(soup.h1.string)
print(soup.h2.string)
print(soup.h5.string)

運作結果:
None
标題2
None           

3、Tag标簽對象屬性

Tag标簽對象對應 html 文檔中的标簽,可以傳回 html 标簽的相應内容

部分示例代碼:

# 輸出h1标簽的内容
print(soup.h1)
# 輸出h1标簽的名稱
print(soup.h1.name)
# 輸出h1标簽的文本内容
print(soup.h1.text)
# 輸出a标簽内的連結位址,隻會輸出html文檔内的第一個a标簽的内容
print(soup.a['href'])
# 輸出div标簽内的a标簽的連結位址,第一個div裡面的第一個a标簽
print(soup.div.a['href'])

運作結果:
<h1>标題1<a href="test.html">連結</a> </h1>
h1
标題1連結 
test.html
https://www.baidu.com           

4、get_text()方法

get_text()方法的結果與text屬性相同,即擷取标簽或整個文檔的文本内容

部分示例代碼:

# 輸出網頁标題文本内容
print(soup.title.getText())

運作結果:
測試網頁           

5、find()方法

find()方法是根據标簽及标簽中的屬性值進行查找,通常用于爬蟲中進行精确定位。

示例代碼:

from bs4 import BeautifulSoup

# 打開并讀取本地html檔案内容,建立BeautifulSoup執行個體對象
with open('test.html', encoding='utf-8') as fin:
    html_doc = fin.read()
soup = BeautifulSoup(html_doc, 'html.parser')

# 查找h1标簽
data = soup.find('h1')
print(data.text)

# 根據a标簽的id屬性查找
data = soup.find('a', id='link3')
print(data.text)

# 根據div标簽的class屬性查找,由于class是python的關鍵字,是以使用class_進行查找
data = soup.find('div', class_='default')
print(data.text)

運作結果:
标題1連結 
愛奇藝

段落
百度
網易
愛奇藝           

6、find_all()方法

find_all()與find()方法的功能和使用方法基本相同,隻是find_all()查找的是所有指定标簽,傳回一個結果集,可以通過循環進行周遊。find_all()方法也是實作批量爬取的總要手段。

示例代碼:

from bs4 import BeautifulSoup

# 打開并讀取本地html檔案内容,建立BeautifulSoup執行個體對象
with open('test.html', encoding='utf-8') as fin:
    html_doc = fin.read()
soup = BeautifulSoup(html_doc, 'html.parser')

# 查找所有a标簽,循環輸出a标簽的連結位址和文本内容
links = soup.find_all('a')
for link in links:
    print(link['href'], link.getText())

print('='*30)

# 先定位到div區塊,再在該區塊内查找,精确定位
div_node = soup.find('div', id='content')
img = div_node.find('img')
print(img['src'])

運作結果:
test.html 連結
https://www.baidu.com 百度
https://www.163.com 網易
https://www.iqiyi.com 愛奇藝
==============================
https://img10.360buyimg.com/img/jfs/t1/192028/25/33459/5661/63fc2af2F1f6ae1b6/d0e4fdc2f126cbf5.png           

四、簡單爬蟲執行個體

(一)目标

連結位址:https://www.qidian.com/all/chanId21-subCateId8/

爬取上面連結位址的Web頁面中的所有小說的标題以及對應的連結位址,輸出到螢幕上

(二)網頁内容分析

使用浏覽器打開連結位址,在任意一個小說标題上點滑鼠右鍵→檢查,分屏中的代碼會自動定位到該小說标題位置。經過觀察分析可以确定,每個小說标題的最外層都由一對 li 标簽包裹,再向裡有一對class屬性為'book-mid-info'的 div 标簽,我們使用find_all()方法查找這個div标簽,就可以擷取所有的資料。如下圖:

Python學習筆記 | 爬蟲基礎之 bs4 資料解析子產品及爬蟲執行個體

(三)執行個體代碼

import requests
from bs4 import BeautifulSoup

url = 'https://www.qidian.com/all/chanId21-subCateId8/'
r = requests.get(url)
if r.status_code != 200:
    raise Exception()
html_doc = r.text

soup = BeautifulSoup(html_doc, 'html.parser')
div_nodes = soup.find_all('div', class_='book-mid-info')
for div_node in div_nodes:
    link = div_node.find('a')
    print(link.get_text(), 'https:' + link['href'])

運作結果:
道詭異仙 https://book.qidian.com/info/1031794030/
萬古神帝 https://book.qidian.com/info/3546912/
亂世書 https://book.qidian.com/info/1036010291/
女俠且慢 https://book.qidian.com/info/1034840014/
萬相之王 https://book.qidian.com/info/1027368101/
諸界第一因 https://book.qidian.com/info/1029701421/
當不成贅婿就隻好命格成聖 https://book.qidian.com/info/1034730904/
開局簽到荒古聖體 https://book.qidian.com/info/1021378513/
保護我方族長 https://book.qidian.com/info/1024121691/
帝霸 https://book.qidian.com/info/3258971/
最初進化 https://book.qidian.com/info/1017021237/
白骨大聖 https://book.qidian.com/info/1020884972/
獵命人 https://book.qidian.com/info/1032636821/
人族鎮守使 https://book.qidian.com/info/1026225232/
我在聊天群模拟長生路 https://book.qidian.com/info/1035776910/
修煉從簡化功法開始 https://book.qidian.com/info/1034284970/
神詭世界,我能修改命數 https://book.qidian.com/info/1031996546/
從肉體凡胎到粉碎星球 https://book.qidian.com/info/1036613110/
完美世界 https://book.qidian.com/info/2952453/
苟在東宮漲天賦,發現太子女兒身 https://book.qidian.com/info/1035677938/           

五、進階案例

(一)目标

連結位址:https://m.890h.com/10_10447/

爬取連結位址中小說的所有章節,每個章節使用标題為檔案名儲存成一個文本檔案,共19章,即生成19個文本檔案。

(二)網頁内容分析

  • 使用浏覽器打開連結位址,頁面中有兩部分:一是小說的最新章節,二是小說的全部章節,第二部分是我們需要爬取的内容。
  • 在小說第一章标題上點選右鍵→檢查,代碼中有兩個相同的div标簽,内容為:<div class="directoryArea">,第一個div标簽裡是小說的最新章節部分,第二個div标簽裡面就是小說的全部章節标題和連結。
  • 每個章節的小說正文都是一個靜态html檔案,在正文頁面空白處點右鍵→檢查,發現文字内容在一個 id 屬性為'chaptercontent'的div标簽中,查找該标簽即可。

(三)爬蟲代碼分析

1、擷取小說所有章節标題和連結的函數

由于我們本次要爬取的是小說的所有正文,是以第一步需要建構一個函數,用來擷取小說的所有章節标題和正文連結位址。

函數代碼如下:

def get_novel_chapters():
    root_url = 'https://m.890h.com/10_10447/'
    url_list = []
    resp = requests.get(root_url)
    resp.encoding = 'utf-8'
    if resp.status_code != 200:
        raise Exception('爬取小說目錄時發生請求錯誤!')

    soup = BeautifulSoup(resp.text, 'html.parser')
    links = soup.find_all('div', class_='directoryArea')[1].find_all('a')

    for link in links:
        url_list.append(('https://m.890h.com' + link['href'], link.get_text()))
    return url_list           

注釋:

  • 首先使用requests庫擷取小說章節頁面的html内容,如果狀态碼不等于200,即網站未正常響應,則抛出錯誤資訊
  • 建立bs4的執行個體對象,查找所有class屬性為'directoryArea'的div标簽,結果集中一共有兩個資料元素,我們取第二個,即第二個div标簽的内容。代碼中采用soup.find_all('div', class_='directoryArea')[1] 這種形式擷取,然後再繼續查找所有的a标簽,就是我們需要的内容
  • 周遊查找結果資料集,将連結和标題以元組的形式追加到清單中,最後傳回函數結果

2、擷取一個章節正文内容的函數

再建立一個函數,根據傳入的連結位址,擷取到連結中的正文内容,并将文本内容傳回給函數。

函數代碼如下:

def get_chapter_content(url):
    resp = requests.get(url)
    resp.encoding = 'utf-8'
    if resp.status_code != 200:
        raise Exception('爬取小說内容時發生請求錯誤!')
    soup = BeautifulSoup(resp.text, 'html.parser')
    return soup.find('div', id='chaptercontent').get_text()           

3、主程式

在主程式中,首先使用第一個函數擷取所有章節的連結位址和标題。周遊所有章節内容,使用小說标題建立同名文本檔案,使用第二個函數擷取每個章節的正文文本内容,并寫入到文本檔案中。

完整代碼如下:

import requests
from bs4 import BeautifulSoup


# 擷取小說所有章節标題和連結的函數
def get_novel_chapters():
    root_url = 'https://m.890h.com/10_10447/'
    url_list = []
    resp = requests.get(root_url)
    resp.encoding = 'utf-8'
    if resp.status_code != 200:
        raise Exception('爬取小說目錄時發生請求錯誤!')
    soup = BeautifulSoup(resp.text, 'html.parser')
    links = soup.find_all('div', class_='directoryArea')[1].find_all('a')
    for link in links:
        url_list.append(('https://m.890h.com' + link['href'], link.get_text()))
    return url_list


# 擷取一個章節正文内容的函數
def get_chapter_content(url):
    resp = requests.get(url)
    resp.encoding = 'utf-8'
    if resp.status_code != 200:
        raise Exception('爬取小說内容時發生請求錯誤!')
    soup = BeautifulSoup(resp.text, 'html.parser')
    return soup.find('div', id='chaptercontent').get_text()


# 主程式
if __name__ == '__main__':
    for chapter in get_novel_chapters():
        url, title = chapter
        print('正在爬取:', title)
        with open('%s.txt' % title, 'w', encoding='utf-8') as wfile:
            wfile.write(get_chapter_content(url))

運作結果:
正在爬取: 第一章 重生
正在爬取: 第二章 向家小姐
正在爬取: 第三章 救下美女
正在爬取: 第四章 蘇珂的相親
正在爬取: 第五章 你可以滾了
正在爬取: 第六章 我陪你玩
正在爬取: 第七章 等五分鐘
正在爬取: 第八章 推拿按摩
正在爬取: 第九章 廢物老公
正在爬取: 第十章 害怕的應該不是我
正在爬取: 第十一章 再見刀疤
正在爬取: 第十二章 不受恩惠
正在爬取: 第十三章 我等皆下等
正在爬取: 第十四章 向心雨的姐姐?
正在爬取: 第十五章 我真是路過
正在爬取: 第十六章 那個男生
正在爬取: 第十七章 枯木逢春
正在爬取: 第十八章 任何條件
正在爬取: 第十九章 解決了蘇珂的事
正在爬取: 第十九章 何必逞強           

【注】在螢幕輸出提示資訊的同時,在程式目錄下生成了19個文本檔案,就是小說的所有内容。