爬蟲教程---第三章:信息提取之BeautifulSoup

3.2 BeautifulSoup

lxml一樣,BeautifulSoup也是一個HTML/XML的解析器,跟XPath的功能是一樣的。

區別在哪裏呢?lxml只會局部遍歷,如果你想提取a 標籤的內容,那麼我們需要寫出相關的xpath語法,此時lxml 只會對全部的a 標籤進行遍歷,找出滿足條件的a 標籤。

BeautifulSoup會載入整個文檔,將文檔內容組織成類似於DOM樹的結構,然後我們可以根據它提供的API來作出相應的操作,此時BeautifulSoup會遍歷整個樹,找出滿足條件的標籤。因此它的開銷比較大,速度慢。

使用BeautifulSoup 前需要先安裝:pip install bs4

那現在來看一下幾大解析工具對比:

解析工具 解析速度 使用難度
BeautifulSoup 最慢 最簡單
lxml 簡單
正則 最快 最難

簡單使用

# 第一步:從bs4中導入BeautifulSoup
from bs4 import BeautifulSoup
import requests

r = requests.get("http://python123.io/ws/demo.html")
demo = r.text

# 將內容組織成DOM樹
soup = BeautifulSoup(demo, 'html.parser')  # 第二個參數是指定一個解析器
print(soup.text)  # 輸出純文本
print(soup.prettify())  # 用比較美化得方式打印出來

soup = beatifulsoup(text, pattern)

  • 參數text 爲要提取的內容

  • 參數pattern爲解析器(可選)

    • html.parser(默認)
    • html5lib(容錯性最強)
    • lxml (平時使用最多)
    • xml

這裏值得一提的是,bs4庫將任何讀入的html文件或字符串都轉換爲utf-8編碼。


提升案例

from bs4 import BeautifulSoup
import requests

rp = requests.get("http://www.baidu.com")
text = rp.text
soup = BeautifulSoup(text, "lxml")

# 獲取30個div標籤
trs = soup.find_all('div', limit=30)  # 返回的是列表
for ts in trs:
    print(ts)
    print("-"*30)

# 獲取第二個div標籤
tr = soup.find_all('div',limit = 3)[1]
print(tr)

# 獲取class等於head_wrapper的div標籤
# trs = soup.find_all('div',class_='head_wrapper')  # 注意這裏是class_ ,因爲class在python中是關鍵字
# 上下這兩種方式等價
trs = soup.find_all('div',attrs={'class': 'head_wrapper'})
for tr in trs:
    print(tr)

# 將所有class=lb,並且name=tj_login的a標籤
trs = soup.find_all('a', attrs={'class': 'lb', 'name': 'tj_login'})
for tr in trs:
    print(tr)

# 所有a標籤的href屬性
alist = soup.find_all('a')
for a in alist:
    # 獲取標籤屬性的方法
    href = a['href']
    print(href)

# 獲取第一個tr下面的全部文本內容
# infos = tr.strings  # 包含空白符
infos = list(tr.stripped_strings)  # 去掉空白符
print(infos[0])

# 獲取所有的職位信息(假設網址中有這個信息)
# trs  = soup.find_all('tr')[1:]  # 獲取第一個以後的tr標籤
# message = []  # 存放職位信息
# for tr in trs:
#     mess = {}  # 存放單條職位信息
#	  # 找到tr下面所有的td標籤
#     tds = tr.find_all('td')
#
#     mess['title'] = tds[0].string  # 獲取第一個td的內容
#     mess['category'] = tds[1].string  # 獲取第二個td的內容
#     message.append(mess)
# print(message)

soup.find(tag) 查找 第一個 匹配的tag標籤等價於soup.tag,對應的有soup.find_all(tag[,limit]) 查找 所有 tag標籤,limit表示提取多少條。

string 獲取某個標籤下的直接內容。無法獲取多行內容

strings (返回的是生成器,可以轉換成list) 獲取所有子孫非標籤的字符串,有多行時獲取不到。

stripped_strings 同上,但會去掉空白文本。

get_text() 獲取所有子孫非標籤的文本,返回的是字符串。


CSS選擇器:select方式

此種方式通過CSS語法來進行提取特定標籤。

import requests
from bs4 import BeautifulSoup

rp = requests.get("http://www.baidu.com")
text = rp.text
soup = BeautifulSoup(text, "lxml")

# 查找p標籤  select功能跟find_all一樣,就是不能同時篩選類名和id
print(soup.select('p'))

# 查找div下面的所有滿足class=‘cp-feedback’的標籤
print(soup.select('div .cp-feedback'))
# 查找div下面的所有滿足id=‘cp’的標籤
print(soup.select('div #cp'))
# 獲取class=‘lg’的a標籤 注意,中間沒有空格
a = soup.select('a.lb')
# a = soup.select('a[class="lb"]') 等價於上面的寫法
print(a)

# 查找name="tj_trnews"的a標籤
print(soup.select('a[name="tj_trnews"]'))

# 查找div下的子p標籤
print(soup.select('div > p'))

小結:

  • 中間有空格:表示在該標籤下找滿足篩選條件的標籤
  • 中間沒空格:表示查找滿足篩選條件的該標籤。
  • select功能跟find_all一樣,返回的都是列表,但select無法同時篩選類名和id
  • > 表示在該標籤的子元素中查找

四個常用對象

Beautiful Soup 將複雜的HTML文檔轉換成一個複雜的樹形結構,每個節點都是python對象,這些對象可以分爲4種:

  • Tag :就是一個個的HTML標籤(擁有name、class等屬性)。
  • NavigableString:繼承自python本身的str類型。代表標籤中的內容字符串。
  • BeautifulSoup:繼承自Tag,本質上就是Tag。所以上面soup對象使用的find_all等方法實際上是Tag類中的方法,與此同時也說明了 爲什麼標籤可以像soup一樣直接使用find_all等方法 。它表示的是一個文檔的全部內容。
  • Comment:特殊的NavigableString,表示註釋的部分。

案例:

本次案例是用requests庫跟bs4結合使用,然後爬取CSDN推薦文章列表的一些信息。

在這裏插入圖片描述
在這裏插入圖片描述

其他要爬取的內容的方式跟這兩個方式差不多,這裏就不一一截圖出來了。。。

下面來看看代碼吧。

import requests
from bs4 import BeautifulSoup

url = 'https://blog.csdn.net/nav/python'
# 僞裝
headers = {
    'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) '
                  'AppleWebKit/537.36 (KHTML, like Gecko) '
                  'Chrome/82.0.4077.0 Safari/537.36'
}

res = requests.get(url, headers=headers)
text = res.text
# 建立文檔樹
soup = BeautifulSoup(text, 'lxml')

# 提取所有文章的li
lis = soup.find_all('li', attrs={'class': 'clearfix', 'data-type': 'blog'})

# 遍歷lis,單獨對每個li進行提取
for li in lis:
    title_div = li.find('div', attrs={'class': 'title'})
    # 獲取標題內容,因爲stripped_strings返回的是迭代器所以要先轉成list
    # "".join()的作用是把列表內容變成字符串
    title = "".join(list(title_div.find('a').stripped_strings))

    # 獲取作者名稱所在的dd標籤
    name_dd = li.find('dd', attrs={'class': 'name'})
    # 獲取作者名稱
    name = "".join(list(name_dd.find('a').stripped_strings))

    # 獲取閱讀量所在的dd標籤
    readNum_dd = li.find('dd', attrs={'class': 'read_num'})
    # 獲取閱讀量
    readNum = "".join(list(readNum_dd.stripped_strings))

    # 獲取評論數所在的dd標籤
    commonNum_dd = li.find('dd', attrs={'class': 'common_num'})
    # 獲取評論數
    # 因爲有些文章是沒有評論的,所以會缺少此標籤
    if commonNum_dd != None:
        commonNum = "".join(list(commonNum_dd.stripped_strings))
    else:
        commonNum = 0

    print('文章標題:{}\t作者名稱:{}\t瀏覽量:{}\t評論數:{}'
          .format(title, name, readNum, commonNum))

當然,你還可以自己獲取更多更好玩的內容,沖沖衝。
至於如何爬取每篇文章的內容詳情,後續教程中將會講到,一步一步來。
那這一節就到這裏啦~~~

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章