python爬蟲之Beautiful Soup基礎知識+實例

python爬蟲之Beautiful Soup基礎知識

Beautiful Soup是一個可以從HTML或XML文件中提取數據的python庫。它能通過你喜歡的轉換器實現慣用的文檔導航,查找,修改文檔的方式。

需要注意的是,Beautiful Soup已經自動將輸入文檔轉換為Unicode編碼,輸出文檔轉換為utf-8編碼。因此在使用它的時候不需要考慮編碼方式,僅僅需要說明一下原始編碼方式就可以了。

一、安裝Beautiful Soup庫

使用pip命令工具安裝Beautiful Soup4庫

pip install beautifulsoup4

二、BeautifulSoup庫的主要解析器

解析器 使用方法 條件
bs4的html解析器 BeautifulSoup(markup, ‘html.parser’) 安裝bs4庫
lxml的html解析器 BeautifulSoup(markup, ‘lxml’) pip install lxml
lxml的lxml解析器 BeautifulSoup(markup, ‘lxml’) pip install lxml
html5lib的解析器 BeautifulSoup(markup, ‘html5lib’) pip install html5lib

具體操作:

html = '//www.baidu.com'
bs = BeautifulSoup(html, 'html.parser')

三、BeautifulSoup的簡單使用

提取百度搜索頁面的部分源程式碼為例:

<!DOCTYPE html>
<html>
<head>
  <meta content="text/html;charset=utf-8" http-equiv="content-type" />
  <meta content="IE=Edge" http-equiv="X-UA-Compatible" />
  <meta content="always" name="referrer" />
  <link
href="//ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.
css" rel="stylesheet" type="text/css" />
  <title>百度一下,你就知道 </title>
</head>
<body link="#0000cc">
 <div id="wrapper">
  <div id="head">
    <div class="head_wrapper">
     <div id="u1">
      <a class="mnav" href="//news.baidu.com" name="tj_trnews">新聞
</a>
      <a class="mnav" href="//www.hao123.com"
name="tj_trhao123">hao123 </a>
      <a class="mnav" href="//map.baidu.com" name="tj_trmap">地圖 </a>
      <a class="mnav" href="//v.baidu.com" name="tj_trvideo">影片 </a>
      <a class="mnav" href="//tieba.baidu.com" name="tj_trtieba">貼吧
</a>
      <a class="bri" href="//www.baidu.com/more/" name="tj_briicon"
style="display: block;">更多產品 </a>
     </div>
    </div>
  </div>
 </div>
</body>
</html>

綜合requests和使用BeautifulSoup庫的html解析器,對其進行解析如下:

import requests
from bs4 import BeautifulSoup

# 使用requests庫載入頁面程式碼
r = requests.get('//www.baidu.com')
r.encoding = r.apparent_encoding
html = r.text

bs = BeautifulSoup(html, 'html.parser')

print(bs.prettify())    # prettify 方式輸出頁面

結果如下:

<!DOCTYPE html>
<!--STATUS OK-->
<html>
 <head>
  <meta content="text/html;charset=utf-8" http-equiv="content-type"/>
  <meta content="IE=Edge" http-equiv="X-UA-Compatible"/>
  <meta content="always" name="referrer"/>
  <link href="//ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="stylesheet" type="text/css"/>
  <title>
   百度一下,你就知道
  </title>
 </head>
 <body link="#0000cc">
  <div id="wrapper">
   <div id="head">
    <div class="head_wrapper">
     <div class="s_form">
      <div class="s_form_wrapper">
       <div id="lg">
        <img height="129" hidefocus="true" src="//www.baidu.com/img/bd_logo1.png" width="270"/>
       </div>
       <form action="//www.baidu.com/s" class="fm" id="form" name="f">
        <input name="bdorz_come" type="hidden" value="1"/>
        <input name="ie" type="hidden" value="utf-8"/>
        <input name="f" type="hidden" value="8"/>
        <input name="rsv_bp" type="hidden" value="1"/>
        <input name="rsv_idx" type="hidden" value="1"/>
        <input name="tn" type="hidden" value="baidu"/>
        <span class="bg s_ipt_wr">
         <input autocomplete="off" autofocus="autofocus" class="s_ipt" id="kw" maxlength="255" name="wd" value=""/>
        </span>
        <span class="bg s_btn_wr">
         <input autofocus="" class="bg s_btn" id="su" type="submit" value="百度一下"/>
        </span>
       </form>
      </div>
     </div>
     <div id="u1">
      <a class="mnav" href="//news.baidu.com" name="tj_trnews">
       新聞
      </a>
      <a class="mnav" href="//www.hao123.com" name="tj_trhao123">
       hao123
      </a>
      <a class="mnav" href="//map.baidu.com" name="tj_trmap">
       地圖
      </a>
      <a class="mnav" href="//v.baidu.com" name="tj_trvideo">
       影片
      </a>
      <a class="mnav" href="//tieba.baidu.com" name="tj_trtieba">
       貼吧
      </a>
      <noscript>
       <a class="lb" href="//www.baidu.com/bdorz/login.gif?login&amp;tpl=mn&amp;u=http%3A%2F%2Fwww.baidu.com%2f%3fbdorz_come%3d1" name="tj_login">
        登錄
       </a>
      </noscript>
      <script>
       document.write('<a href="//www.baidu.com/bdorz/login.gif?login&tpl=mn&u='+ encodeURIComponent(window.location.href+ (window.location.search === "" ? "?" : "&")+ "bdorz_come=1")+ '" name="tj_login" class="lb">登錄</a>');
      </script>
      <a class="bri" href="//www.baidu.com/more/" name="tj_briicon" style="display: block;">
       更多產品
      </a>
     </div>
    </div>
   </div>
   <div id="ftCon">
    <div id="ftConw">
     <p id="lh">
      <a href="//home.baidu.com">
       關於百度
      </a>
      <a href="//ir.baidu.com">
       About Baidu
      </a>
     </p>
     <p id="cp">
      ©2017 Baidu
      <a href="//www.baidu.com/duty/">
       使用百度前必讀
      </a>
      <a class="cp-feedback" href="//jianyi.baidu.com/">
       意見回饋
      </a>
      京ICP證030173號
      <img src="//www.baidu.com/img/gs.gif"/>
     </p>
    </div>
   </div>
  </div>
 </body>
</html>

四、BeautifulSoup類的基本元素

BeautifulSoup將複製的HTML文檔轉換成一個複雜的樹型結構,每個節點都是python對象,所有對象可以歸納為四種Tag,NavigableString,Comment,Beautifulsoup

基本元素 說明
Tag 標籤,最基本的資訊組織單元,分別用<>和</>標明開頭和結尾,格式:bs.a或者bs.p(獲取a標籤中或者p標籤中的內容)。
Name 標籤的名字,格式為.name.
Attributes 標籤的屬性,字典形式,格式:.attrs.
NavigableString 標籤內非屬性字元串,<>…</>中的字元串,格式:.string.
Comment 標籤內的注釋部分,一種特殊的Comment類型。

Tag

任何存在於HTML語法中的標籤都可以bs.tag訪問獲得,如果在HTML文檔中存在多個相同的tag對應的內容時,bs.tag返回第一個。示例程式碼如下:

import requests
from bs4 import BeautifulSoup

# 使用requests庫載入頁面程式碼
r = requests.get('//www.baidu.com')
r.encoding = r.apparent_encoding
html = r.text
bs = BeautifulSoup(html, 'html.parser')
# 獲取第一個a標籤的所有內容
print(bs.a)	# <a class="mnav" href="//news.baidu.com" name="tj_trnews">新聞</a>
print(type(bs.a))	# <class 'bs4.element.Tag'>

在Tag標籤中最重要的就是html頁面中的nam和attrs屬性,使用方法如下:

print(bs.a.name)    # a
# 把a標籤的所有屬性列印輸出出來,返回一個字典類型
print(bs.a.attrs)   # {'href': '//news.baidu.com', 'name': 'tj_trnews', 'class': ['mnav']}
# 等價 bs.a.get('class')
print(bs.a['class'])    # ['mnav']
bs.a['class'] = 'newClass'  # 對class屬性的值進行修改
print(bs.a) # <a class="newClass" href="//news.baidu.com" name="tj_trnews">新聞</a>
del bs.a['class']   # 刪除class屬性
print(bs.a) # <a href="//news.baidu.com" name="tj_trnews">新聞</a>

NavigableString中的string方法用於獲取標籤內部的文字,程式碼如下:

import requests
from bs4 import BeautifulSoup

# 使用requests庫載入頁面程式碼
r = requests.get('//www.baidu.com')
r.encoding = r.apparent_encoding
html = r.text
bs = BeautifulSoup(html, 'html.parser')

print(bs.title.string)  # 百度一下,你就知道
print(type(bs.title.string))    # <class 'bs4.element.NavigableString'>

Comment

Comment對象是一個特殊類型的NavigableString對象,其輸出的內容不包括注釋符號,用於輸出注釋的內容。

from bs4 import BeautifulSoup

html = """<a class="mnav" href="//news.baidu.com" name="tj_trnews"><!--新聞--></a>"""
bs = BeautifulSoup(html, 'html.parser')

print(bs.a.string)  # 新聞
print(type(bs.a.string))    # <class 'bs4.element.Comment'>

BeautifulSoup

bs對象表示的是一個文檔的全部內容,大部分時候,可以把它當作Tag對象,支援遍歷文檔樹和搜索文檔中描述的大部分方法。

因為Beautifulsoup對象並不是真正的HTML或者XML的tag,所以它沒有name和attribute屬性。所以BeautifulSoup對象一般包含值為”[document]”的特殊屬性.name

print(bs.name)	# [document]

五、基於bs4庫的HTML內容的遍歷方法

在HTML中有如下特定的基本格式,也是構成HTML頁面的基本組成成分。

而在這種基本的格式下有三種基本的遍歷流程

  • 下行遍歷
  • 上行遍歷
  • 平行遍歷

三種遍歷方式分別是從當前節點出發,對之上、之下、平行的格式以及關係進行遍歷。

下行遍歷

下行遍歷分別有三種遍歷屬性,如下所示:

屬性 說明
.contents 子節點的列表,將所有兒子節點存入列表。
.children 子節點的迭代類型,用於循環遍歷兒子節點。
.descendants 子孫節點的迭代類型,包涵所有子孫節點,用於循環遍歷。

程式碼如下:

import requests
from bs4 import BeautifulSoup

# 使用requests庫載入頁面程式碼
r = requests.get('//www.baidu.com')
r.encoding = r.apparent_encoding
html = r.text
bs = BeautifulSoup(html, 'html.parser')

# 循環遍歷兒子節點
for child in bs.body.children:
    print(child)

# 循環遍歷子孫節點
for child in bs.body.descendants:
    print(child)

# 輸出子節點,以列表的形式
print(bs.head.contents)
print(bs.head.contents[0])  # 用列表索引來獲取它的某一個元素

上行遍歷

上行遍歷有兩種方式,如下所示:

屬性 說明
.parent 節點的父親標籤。
.parents 節點先輩標籤的迭代類型,用於循環遍歷先輩節點,返回一個生成器。

程式碼如下:

import requests
from bs4 import BeautifulSoup

# 使用requests庫載入頁面程式碼
r = requests.get('//www.baidu.com')
r.encoding = r.apparent_encoding
html = r.text
bs = BeautifulSoup(html, 'html.parser')

for parent in bs.a.parents:
    if parent is not None:
        print(parent.name)

print(bs.a.parent.name)

平行遍歷

平行遍歷有四種屬性,如下所示:

屬性 說明
.next_sibling 返回按照HTML文本順序的下一個平行節點標籤。
.previous_sibling 返回按照HTML文本順序的上一個平行節點標籤。
.next_siblings 迭代類型,返回按照HTML文本順序的所有後續平行節點標籤。
.previous_siblings 迭代類型,返回按照HTML文本順序的前序所有平行節點標籤。

程式碼如下:

import requests
from bs4 import BeautifulSoup

# 使用requests庫載入頁面程式碼
r = requests.get('//www.baidu.com')
r.encoding = r.apparent_encoding
html = r.text
bs = BeautifulSoup(html, 'html.parser')

for sibling in bs.a.next_siblings:
    print(sibling)

for sibling in bs.a.previous_siblings:
    print(sibling)

其它遍歷

屬性 說明
.strings 如果Tag包含多個字元串,即在子孫節點中有內容,可以用此獲取,然後進行遍歷。
.stripped_strings 與strings用法一致,可以去除掉那些多餘的空白內容。
.has_attr 判斷Tag是否包含屬性。

六、文件樹搜索

使用bs.find_all(name, attires, recursive, string, **kwargs)方法,用於返回一個列表類型,存儲查找的結果。

屬性 說明
name 對標籤的名稱的檢索字元串。
attrs 對標籤屬性值的檢索字元串,可標註屬性檢索。
recursive 是否對子孫全部檢索,默認為True。
string 用與在資訊文本中特定字元串的檢索。

name參數

如果是指定的字元串:會查找與字元串完全匹配的內容,程式碼如下:

a_list = bs.find_all("a")
print(a_list)

使用正則表達式:將會使用BeautifulSoup4中的search()方法來匹配,程式碼如下:

import requests
from bs4 import BeautifulSoup
import re

# 使用requests庫載入頁面程式碼
r = requests.get('//www.baidu.com')
r.encoding = r.apparent_encoding
html = r.text
bs = BeautifulSoup(html, 'html.parser')

t_list = bs.find_all(re.compile("p"))
for item in t_list:
    print(item)

傳入一個列表:Beautifulsoup4將會與列表中的任一元素匹配到的節點返回,程式碼如下:

import requests
from bs4 import BeautifulSoup

# 使用requests庫載入頁面程式碼
r = requests.get('//www.baidu.com')
r.encoding = r.apparent_encoding
html = r.text
bs = BeautifulSoup(html, 'html.parser')

t_list = bs.find_all(["meta", "link"])
for item in t_list:
    print(item)

傳入一個函數或方法:將會根據函數或者方法來匹配,程式碼如下:

import requests
from bs4 import BeautifulSoup

# 使用requests庫載入頁面程式碼
r = requests.get('//www.baidu.com')
r.encoding = r.apparent_encoding
html = r.text
bs = BeautifulSoup(html, 'html.parser')


def name_is_exists(tag):
    return tag.has_attr("name")


t_list = bs.find_all(name_is_exists)
for item in t_list:
    print(item)

attrs參數

並不是所有的屬性都可以使用上面這種方法進行搜索,比如HTML的data屬性,用與指定屬性搜索。

import requests
from bs4 import BeautifulSoup

# 使用requests庫載入頁面程式碼
r = requests.get('//www.baidu.com')
r.encoding = r.apparent_encoding
html = r.text
bs = BeautifulSoup(html, 'html.parser')

t_list = bs.find_all(attrs={"class": "mnav"})


for item in t_list:
    print(item)

string參數

通過string參數可以搜索文檔中的字元串內容,與name參數的可選值一樣,string參數接受字元串,正則表達式,列表。

import requests
from bs4 import BeautifulSoup
import re

# 使用requests庫載入頁面程式碼
r = requests.get('//www.baidu.com')
r.encoding = r.apparent_encoding
html = r.text
bs = BeautifulSoup(html, 'html.parser')

t_list = bs.find_all(attrs={"class": "mnav"})
for item in t_list:
    print(item)

# text用於搜索字元串
t_list = bs.find_all(text="hao123")
for item in t_list:
    print(item)

# text可以通其它參數混合使用用來過濾tag
t_list = bs.find_all("a", text=["hao123", "地圖", "貼吧"])
for item in t_list:
    print(item)

t_list = bs.find_all(text=re.compile("\d\d"))
for item in t_list:
    print(item)

使用find_all()方法,常用到的正則表達式形式import re程式碼如下:

bs.find_all(string = re.compile('python'))	# 指定查找內容

# 或者指定使用正則表達式要搜索的內容
string = re.compile('python')	# 字元為python
bs.find_all(string)	# 調用方法模版

七、常用的find()方法如下

方法 說明
<>find() 搜索且只返回一個結果,字元串類型,同.find_all()參數。
<>find_parent() 在先輩節點中返回一個結果,字元串類型,同.find_all()參數。
<>.find_parents() 在先輩節點中搜索,返回列表類型,同.find_all()參數。
<>.find_next_sibling() 在後續平行節點中返回一個結果,同.find_all()參數。
<>.find_next_siblings() 在後續平行節點中搜索,返回列表類型,同.find_all()參數。
<>.find_previous_sibling() 在前序平行節點中返回一個結果,字元串類型,同.find_all()參數。
<>.find_previous_siblings() 在前序平行節點中搜索,返回列表類型,同.find_all()參數。

八、爬取京東電腦數據

爬取的例子直接輸出到螢幕。

(1)要爬取京東一頁的電腦商品資訊,下圖所示:

(2)所爬取的網頁連接://search.jd.com/search?keyword=macbook pro&qrst=1&suggest=5.def.0.V09&wq=macbook pro

(3)我們的目的是需要獲取京東這一個頁面上所有的電腦數據,包括價格,名稱,ID等。具體程式碼如下:

#!/usr/bin/env python
# -*- coding:utf-8 -*-

import requests
from bs4 import BeautifulSoup

headers = {
        'User-agent': "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) "
                      "Chrome/66.0.3359.139 Safari/537.36"
    }

URL = "//search.jd.com/search?keyword=macbook%20pro&qrst=1&suggest=5.def.0.V09&wq=macbook%20pro"

r = requests.get(URL, headers=headers)
r.encoding = r.apparent_encoding
html = r.text
bs = BeautifulSoup(html, 'html.parser')

all_items = bs.find_all('li', attrs={"class": "gl-item"})

for item in all_items:
    computer_id = item["data-sku"]
    computer_name = item.find('div', attrs={'class': 'p-name p-name-type-2'})
    computer_price = item.find('div', attrs={'class': 'p-price'})
    print('電腦ID為:' + computer_id)
    print('電腦名稱為:' + computer_name.em.text)
    print('電腦價格為:' + computer_price.find('i').string)
    print('------------------------------------------------------------')

部分結果如下圖所示: