BeautifulSoup 解析網頁

BeautifulSoup解析網頁

前言

BeautifulSoup 模組是爬蟲程式設計師非常常用工具,本文使用許多實例,從最基礎開始講解,這將是未來進入更高深網路爬蟲的基石。

大綱

解析網頁使用 BeautifulSoup 模組

從前面章節,讀者應該已經了解如何下載網頁 HTML 原始檔案,也應該對網頁的基本架構有基本的認識,本節要介紹的是使用 BeautifulSoup4 模組解析 HTML 文件。目前這個模組是第四版,模組名稱是 beautifulsoup4,以下列方式安裝:

pip install beautifulsoup4

雖然安裝是 beautifulsoup4 但是導入模組時,用下列方式:

import bs4

建立 BeautifulSoup 物件

可使用下列語法建立 BeautifulSoup 物件

htmlFile = requests.get('https://icook.tw/')    # 下載愛料理網頁內容
objSoup = bs4.BeautifulSoup(htmlFile.text, 'lxml')  # lxml 是解析HTML文件方式

上述是下載愛料理網頁內容為例,當網頁下載後,將網頁內容的 Response 物件傳送給 bs4.BeautifulSoup()  方法,就可以建立 BeautifulSoup 物件了。至於另一個參數 "lxml",目的是註明解析HTML文件的方法,常用有下列方法:

  • 'html.parser' : 這是老舊的方法(3.2.2 版本以前),相容性比較不好。
  • 'lxml'速度快,相容性佳,本樹採用此方法。使用時需要安裝 lxml。
    • pip install lxml
  • 'html5lib' : 速度較慢,但解析能力強,需另外安裝 html5lib。
    • pip install html5lib

範例 pythonBS4-01.py : 解析 https://icook.tw/ 網頁,主要是列出資料型態。

# pythonBS4-01.py
import requests, bs4

htmlFile = requests.get('https://icook.tw/')    # 下載愛料理網頁內容
objSoup = bs4.BeautifulSoup(htmlFile.text, 'lxml')  # lxml 是解析HTML文件方式
print("列印BeautifulSoup物件資料型態 : ", type(objSoup))    # 列印串列方法

執行結果

列印 BeautifulSoup 物件資料型態 :  <class 'bs4.BeautifulSoup'>

從上述我們獲得了 BeautifulSoup 的資料型態,表示我們獲得初步的成果。

基本HTML文件解析 - 從簡單開始

真實世界的網頁是很複雜的,所以我們先從一個簡單的 HTML 文件開始解析網頁。在程式 pythonBS4-01.py 第 5 行的第一個參數 htmlFile.txt 是網頁內容的 Response 物件,我們可以在工作目錄中放置一個簡單的 HTML 文件,然後先學習使用 BeautifulSoup  解析此文件。

範例 htmlExampleBS4-02.html : 簡單的 HTML 文件。

<!doctype html>
<html lang="zh-Hant-TW">

  <head>
    <meta charset="utf-8">
    <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet"
      integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous">
    <title>HTML 文件基礎架構</title>
    <style>
      header {
        background-color: rgb(218, 180, 231);
        min-height: 100px;
      }

      main {
        background-color: rgb(239, 238, 238);
        min-height: 300px;
      }

      footer {
        min-height: 80px;
      }

    </style>
  </head>

  <body>
    <header class="d-flex justify-content-center align-items-center">
      <h1 class="text-center" id='header'>這是表頭區塊</h1>
    </header>
    <main>
      <h1 class="text-center">這是主要內容區</h1>
      <section class="d-flex justify-content-center align-items-center">
        <div class="card" style="width: 18rem;">
          <img src="media/components/user-1.jpg" class="card-img-top" alt="card-1">
          <div class="card-body">
            <p class="card-text text-center" data-author='author1'>人物介紹-1</p>
          </div>
        </div>
        <div class="card" style="width: 18rem;">
          <img src="media/components/user-2.jpg" class="card-img-top" alt="card-2">
          <div class="card-body">
            <p class="card-text text-center" data-author='author2'>人物介紹-2</p>
          </div>
        </div>
        <div class="card" style="width: 18rem;">
          <img src="media/components/user-3.jpg" class="card-img-top" alt="card-2">
          <div class="card-body">
            <p class="card-text text-center" data-author='author3'>人物介紹-2</p>
          </div>
        </div>
      </section>
    </main>
    <footer class="bg-dark text-white d-flex justify-content-center align-items-center">
      <h3 class="text-center">這是表尾</h3>
    </footer>
    <!-- Bootstrap Bundle with Popper -->
    <script src="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/js/bootstrap.bundle.min.js"
      integrity="sha384-MrcW6ZMFYlzcLA8Nl+NtUVF0sA7MsXsP1UyJoMp4YLEuNSfAP+JcXn/tWtIaxVXM"
      crossorigin="anonymous"></script>
  </body>

</html>

執行結果

範例 htmlExample-1.html 之 Tag 標籤的節點圖,依順序由上到下,由左到右,如下所示 :


範例 pythonBS4-02.py : 分析 htmlExample-1.html 文件,列出物件類型。

# pythonBS4-02.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
print("列印BeautifulSoup物件資料型態 : ", type(objSoup))

執行結果

列印BeautifulSoup物件資料型態 :  <class 'bs4.BeautifulSoup'>

去除標籤回傳文字屬性 text

範例 pythonBS4-03.py : 分析 htmlExample-1.html 文件,列出標籤  <title> 內容。

# pythonBS4-03.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
print("列印title = ", objSoup.title)
print("title內容 = ", objSoup.title.text)

執行結果

列印title = <title>HTML 文件基礎架構</title>
title內容 = HTML 文件基礎架構

傳回找尋的第一個符合的標籤 find()

這個方法可以找尋 HTML 文件內第一個符合的標籤內容,例如 find('h1') 找到第一個 h1 標籤,如果找到了,就傳回該標籤字串,我們可以使用 text 或是 string 屬性獲得內容,如果沒找到,就傳回 None。

範例 pythonBS4-04.py : 傳回第一個 <h1> 內容。

# pythonBS4-04.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
objTag = objSoup.find('h1')
print("資料型態       = ", type(objTag))
print("列印Tag        = ", objTag)
print("Text屬性內容   = ", objTag.text)
print("String屬性內容 = ", objTag.string)

執行結果

資料型態       =  <class 'bs4.element.Tag'>
列印Tag        =  <h1 class="text-center">這是表頭區塊</h1>
Text屬性內容   =  這是表頭區塊
String屬性內容 =  這是表頭區塊 

傳回找尋所有符合的標籤 find_all()

這個方法可以尋找HTML文件內所有符合的標籤內容,例如 : find_all('h1') 是找所有 h1 標籤,如果找到就回傳所有 h1 的標籤串列,如果沒找到就傳回空串列

範例 pythonBS4-05.py : 傳回所有 <h1> 內容。

# pythonBS4-05.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
objTag = objSoup.find_all('h1')
print("資料型態    = ", type(objTag))     # 列印資料型態
print("列印Tag串列 = ", objTag)           # 列印串列
print("以下是列印串列元素 : ")
for data in objTag:                       # 列印串列元素內容
    print(data.text)

執行結果

資料型態    =  <class 'bs4.element.ResultSet'>
列印Tag串列 =  [<h1 class="text-center">這是表頭區塊</h1>, <h1 class="text-center">這是主要內容區</h1>]
以下是列印串列元素 :
這是表頭區塊
這是主要內容區

find_all() 基本上是使用迴圈方式尋找所有符合的標籤節點,我們可以使用參數限制尋找的節點數:

  • limit = n   #限制尋找最多 n 個標籤。
  • recursive = False     #限制尋找次一層次的標籤。

範例 pythonBS4-05-1.py : 傳回所有 <h1> 內容,限制尋找最多 1 個標籤。

# pythonBS4-05-1.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
objTag = objSoup.find_all('h1', limit=1)
print("資料型態    = ", type(objTag))     # 列印資料型態
print("列印Tag串列 = ", objTag)           # 列印串列
print("以下是列印串列元素 : ")
for data in objTag:                       # 列印串列元素內容
    print(data.text)

執行結果

資料型態    =  <class 'bs4.element.ResultSet'>
列印Tag串列 =  [<h1 class="text-center">這是表頭區塊</h1>]
以下是列印串列元素 :
這是表頭區塊

認識 HTML 元素內容屬性與 getText()

HTML 元素內容的屬性有三種

  • textContent : 元素內容,不含任何標籤。
  • innerHTML : 元素內容,包含內容與標籤,但不含本身的標籤碼。
  • outerHTML : 元素內容,包含內容與標籤,也包含本身的標籤碼。

例如,有個元素如下:

<p>尋找HTML文件內所有符合 h1 標籤<b>find_all('h1')</b></p>

 則上述3個屬性的觀念與內容如下 :

  • textContent : find_all('h1')。
  • innerHTML : 尋找HTML文件內所有符合 h1 標籤<b>find_all('h1')</b>。
  • outerHTML : <p>尋找HTML文件內所有符合 h1 標籤<b>find_all('h1')</b></p>。

當使用 BeautifulSoup 模組解析 HTML 文件,如果傳回是串列時,也可以配合索引應用 getText() 取得串列元素內容,所取得的內容是 textContent。 意義與 "回傳文字屬性 text" 之屬性相同。

範例 pythonBS4-06.py : 使用 getText(),擴充設計 範例 pythonBS4-03.py

# pythonBS4-06.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
objTag = objSoup.find_all('h1')
print("資料型態    = ", type(objTag))     # 列印資料型態
print("列印Tag串列 = ", objTag)           # 列印串列
print("\n使用Text屬性列印串列元素 : ")
for data in objTag:                       # 列印串列元素內容
    print(data.text)
print("\n使用getText()方法列印串列元素 : ")
for data in objTag:
    print(data.getText())

執行結果

資料型態    =  <class 'bs4.element.ResultSet'>
列印Tag串列 =  [<h1 class="text-center">這是表頭區塊</h1>, <h1 class="text-center">這是主要內容區</h1>]

使用Text屬性列印串列元素 :
這是表頭區塊
這是主要內容區

使用getText()方法列印串列元素 :
這是表頭區塊
這是主要內容區

HTML 屬性的搜尋

我們可以根據 HTML 標籤屬性搜尋,可以參考以下範例 : 

範例 pythonBS4-06-1.py : 搜尋第一個含有 id='header' 的標籤節點。

# pythonBS4-06-1.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
objTag = objSoup.find(id='header')
print(objTag)
print(objTag.text)

執行結果

<h1 class="text-center" id="header">這是表頭區塊</h1>
這是表頭區塊

範例 pythonBS4-06-2.py : 搜尋所有含有 class_='card-text' 的標籤節點。

# pythonBS4-06-2.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
objTag = objSoup.find_all(class_='card-text')
for tag in objTag:
    print(tag)
    print(tag.text)

執行結果

<p class="card-text text-center">人物介紹-1</p>
人物介紹-1
<p class="card-text text-center">人物介紹-2</p>
人物介紹-2
<p class="card-text text-center">人物介紹-2</p>
人物介紹-2

使用 attrs 參數, 搜尋 自訂義 屬性 data-* 之類的標籤,這相當於將屬性用字典方式傳遞給 attrs 參數。

範例 pythonBS4-06-3.py : 使用 attrs 參數, 搜尋 自訂義 屬性 data-* 之類的標籤。

# pythonBS4-06-3.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
tag = objSoup.find(attrs={'data-author':'author1'})
print(tag)
print(tag.text)

執行結果

<p class="card-text text-center" data-author="author1">人物介紹-1</p>
人物介紹-1

使用 find() 或 find_all() 執行 CSS 搜尋

class 由於是 Python 的保留字,所以用 find() find_all() 搜尋 CSS 類別時,可以使用 class_  代表節點或是直接省略。

範例 pythonBS4-06-4.py : 使用 class_和直接省略方式搜尋 CSS 類別的標籤。

# pythonBS4-06-4.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
tag = objSoup.find('p', class_='card-text')
print(tag)
print(tag.getText())
print('-'*70)
tag = objSoup.find('p', 'card-text')
print(tag)
print(tag.text)

執行結果

<p class="card-text text-center" data-author="author1">人物介紹-1</p>
人物介紹-1
----------------------------------------------------------------------
<p class="card-text text-center" data-author="author1">人物介紹-1</p>
人物介紹-1

使用正則表達式搜尋部分字串符合的標籤

範例 pythonBS4-06-5.py : 使用正則表達式搜尋部分字串符合的標籤。

# pythonBS4-06-5.py
import bs4
import re

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
tag = objSoup.find('h1', class_=re.compile('text'))
print(tag)
print(tag.text)

執行結果

<h1 class="text-center" id="header">這是表頭區塊</h1>
這是表頭區塊

select()

select() 主要是以 CSS 選擇器( selector )的觀念尋找元素,如果找到回傳的是串列 list ,select() 的特色是一次可以尋找所有相符的元素,如果找不到則傳回空串列。下列是使用實例 :

  • objSoup.select('p') : 尋找所有 <p> 標籤的元素。
  • objSoup.select('img') : 尋找所有 <img> 標籤的元素。
  • objSoup.select('.card') : 尋找所有 CSS class 屬性為 card 的元素。
  • objSoup.select('.#author') : 尋找所有 CSS id 屬性為 author 的元素。
  • objSoup.select('p .card') : 尋找所有<p> 標籤且 CSS class 屬性為 card 的元素。
  • objSoup.select('p #author') : 尋找所有<p> 標籤且 CSS id 屬性為 author 的元素。
  • objSoup.select('p b') : 尋找所有<p> 標籤內 的 <b> 元素。
  • objSoup.select('p>b') : 尋找所有<p> 標籤內 的直接 <b> 元素,中間沒有其他元素。
  • objSoup.select('input[name]') : 尋找所有<input> 標籤且有屬性為 name 的元素。
  • objSoup.select('a{link1}') : 尋找所有<a> 標籤內容為 link1 的元素。

範例 pythonBS4-07.py : 使用正則表達式搜尋部分字串符合的標籤。

# pythonBS4-07.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
objTag = objSoup.select('#header')
print("資料型態     = ", type(objTag))          # 列印資料型態
print("串列長度     = ", len(objTag))           # 列印串列長度
print("元素資料型態 = ", type(objTag[0]))       # 列印元素資料型態
print("元素內容     = ", objTag[0].getText())   # 列印元素內容

執行結果

資料型態     =  <class 'bs4.element.ResultSet'>
串列長度     =  1
元素資料型態 =  <class 'bs4.element.Tag'>
元素內容     =  這是表頭區塊

如果將元素傳給 str() ,則回傳的是含開始與結束的標籤字串。

範例 pythonBS4-08.py : 將解析的串列傳給 str() 並列印出來。

# pythonBS4-08.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
objTag = objSoup.select('#header')
print("列出串列元素的資料型態    = ", type(objTag[0]))
print(objTag[0])
print("列出str()轉換過的資料型態 = ", type(str(objTag[0])))
print(str(objTag[0]))

執行結果

列出串列元素的資料型態    =  <class 'bs4.element.Tag'>
<h1 class="text-center" id="header">這是表頭區塊</h1>
列出str()轉換過的資料型態 =  <class 'str'>
<h1 class="text-center" id="header">這是表頭區塊</h1>

範例 pythonBS4-09.py : 將解析的串列應用 attrs 列印出所有屬性的字典資料型態

# pythonBS4-09.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
objTag = objSoup.select('#header')
print(type(objTag[0].attrs))
print(str(objTag[0].attrs))

執行結果

<class 'dict'>
{'class': ['text-center'], 'id': 'header'}

標籤字串的 get()

假設我們要搜尋 <img> 標籤,請參考以下範例。

範例 pythonBS4-10.py : 搜尋 <img> 標籤,並列印出結果。

# pythonBS4-10.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
imgTag = objSoup.select('img')
print("含<img>標籤的串列長度 = ", len(imgTag))
for img in imgTag:              
    print(img)

執行結果

含<img>標籤的串列長度 =  3
<img alt="card-1" class="card-img-top" src="media/components/user-1.jpg"/>
<img alt="card-2" class="card-img-top" src="media/components/user-2.jpg"/>
<img alt="card-2" class="card-img-top" src="media/components/user-3.jpg"/>

<img> 這是一個插入圖片的標籤,沒有結束標籤,所以沒有內文如果讀者嘗試使用 text 屬性列印內容,將看不到任何結果。<img> 這對網路爬蟲設計是很重要的,因為可以由此獲得網頁的圖檔資料,從上述執行結果可以看到,對我們而言很重要的是 <img> 標籤內的屬性 src,這個屬性設定的圖片的路徑。這個時候我們可以使用標籤字串的 img.get() 取得或是使用 img[‘src’] 方式取得路徑。

範例 pythonBS4-11.py :  擴充範例 pythonBS4-10.py,取得所有圖檔的路徑。

# pythonBS4-11.py
import bs4

htmlFile = open('htmlExampleBS4-02.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
imgTag = objSoup.select('img')
print("含<img>標籤的串列長度 = ", len(imgTag))
for img in imgTag:              
    print("列印標籤串列 = ", img)
    print("列印圖檔     = ", img.get('src'))
    print("列印圖檔     = ", img['src'])

執行結果

列印標籤串列 =  <img alt="card-1" class="card-img-top" src="media/components/user-1.jpg"/>
列印圖檔     =  media/components/user-1.jpg
列印圖檔     =  media/components/user-1.jpg
列印標籤串列 =  <img alt="card-2" class="card-img-top" src="media/components/user-2.jpg"/>
列印圖檔     =  media/components/user-2.jpg
列印圖檔     =  media/components/user-2.jpg
列印標籤串列 =  <img alt="card-2" class="card-img-top" src="media/components/user-3.jpg"/>
列印圖檔     =  media/components/user-3.jpg
列印圖檔     =  media/components/user-3.jpg

上述程式最重要的是 第10行 img.get(‘src’) 和 第11行 img[‘src’] 這兩個方法,可以取得標籤字串的 src 屬性內容。在程式範例 pythonBS4-08.py 中,曾經說明標籤字串與純字串不同,就是在這裡,純字串無法呼叫 get() 方法執行上述將圖檔字串取出 。

其他 HTML 文件解析

爬取項目清單文件

範例 htmlExampleBS4-03.html :  有關項目清單之簡單 HTML 文件。

<!doctype html>
<html lang="zh-Hant-TW">
   <html>
      <head>
         <meta charset="utf-8">
         <title>htmlExampleBS4-03.html</title>
      </head>
      <body>
         <h1>台灣旅遊景點排名</h1>
         <ol type="a">
            <li>故宮博物院</li>
            <li>日月潭</li>
            <li>阿里山</li>
         </ol>
         <h2>台灣夜市排名</h2>
         <ol type="A">
            <li>士林夜市</li>
            <li>永康夜市</li>
            <li>逢甲夜市</li>
         </ol>
         <h2>台灣人口排名</h2>
         <ol type="i">
            <li>新北市</li>
            <li>台北市</li>
            <li>桃園市</li>
         </ol>
         <h2>台灣最健康大學排名</h2>
         <ol type="I">
            <li>明志科大</li>
            <li>台灣體院</li>
            <li>台北體院</li>
         </ol>
      </body>
   </html>

執行結果

範例 pythonBS4-11-1.py :  爬取台灣最健康大學排名,這程式會列印出標題和最健康學校清單。

# pythonBS4-11-1.py
import bs4

htmlFile = open('htmlExampleBS4-03.html', encoding='utf-8')
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')
titleobj = objSoup.find_all('h2')               # h2標題
print(titleobj[2].text)

itemobj = objSoup.find('ol', type='I')          # type='I'
items = itemobj.find_all('li')
for item in items:               
    print(item.text)

執行結果

台灣最健康大學排名
明志科大
台灣體院
台北體院

爬取自定義清單文件

範例 htmlExampleBS4-04.html :  有關自定義清單簡單 HTML 文件。

<!doctype html>
<html lang="zh-Hant-TW">
<html>
   <head>
      <meta charset="utf-8">
      <title>htmlExampleBS4-04.html</title>
   </head>
   <body>
      <h1>國家首都資料表</h1>
      <dl>
         <dt>Washington</dt>
         <dd>美國首都</dd>
         <dt>Tokyo</dt>
         <dd>日本首都</dd>
         <dt>Paris</dt>
         <dd>法國首都</dd>
      </dl>
   </body>
</html>

執行結果

範例 pythonBS4-11-2.py :  爬取國家首都資料,這程式會以字典方式列印出國家、首都資料。

# pythonBS4-11-2.py
import requests, bs4

url = 'htmlExampleBS4-04.html'
htmlFile = open(url, encoding='utf-8')              
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')

mycity = []
cityobj = objSoup.find('dl')          
cities = cityobj.find_all('dt')
for city in cities:
    mycity.append(city.text)                # mycity串列

mycountry = []    
countryobj = objSoup.find('dl')
countries = countryobj.find_all('dd')
for country in countries:
    mycountry.append(country.text)          # mycountry串列

print("國家 = ", mycountry)
print("首都 = ", mycity)
data = dict(zip(mycountry, mycity))
print(data)                                 # 字典顯示結果

執行結果

國家 =  ['美國首都', '日本首都', '法國首都']
首都 =  ['Washington', 'Tokyo', 'Paris']
{'美國首都': 'Washington', '日本首都': 'Tokyo', '法國首都': 'Paris'}

爬取表格文件

範例 htmlExampleBS4-05.html :  簡單的 HTML 表格文件。

<!doctype html>
<html lang="zh-Hant-TW">
   <html>
      <head>
         <meta charset="utf-8">
         <title>htmlExampleBS4-05.html</title>
         <style>
            table, th, td {
               border: 1px solid black;
            }

         </style>
      </head>
      <body>
         <table>
            <thead>
               <!--建立表頭 -->
               <tr>
                  <th colspan="3">聯合國水資源中心</th>
               </tr>
               <tr>
                  <th>河流名稱</th>
                  <th>國家</th>
                  <th>洲名</th>
               </tr>
            </thead>
            <tbody>
               <!-- 建立表格本體 -->
               <tr>
                  <td>長江</td>
                  <td>中國</td>
                  <td>亞洲</td>
               </tr>
               <tr>
                  <td>尼羅河</td>
                  <td>埃及</td>
                  <td>非洲</td>
               </tr>
               <tr>
                  <td>亞馬遜河</td>
                  <td>巴西</td>
                  <td>南美洲</td>
               </tr>
            </tbody>
            <tfoot>
               <!-- 建立表尾 -->
               <tr>
                  <td colspan="3">製表2017年5月30日</td>
               </tr>
            </tfoot>
         </table>
      </body>
   </html>

執行結果

範例 pythonBS4-11-3.py :  爬取河川資料,這程式會以字典方式列印出國家、河川資料。

# pythonBS4-11-3.py
import requests, bs4

url = 'htmlExampleBS4-05.html'
htmlFile = open(url, encoding='utf-8')              
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')

myRiver = []                        # 河川
tableObj = objSoup.find('table').find('tbody')
tables = tableObj.find_all('tr')
for table in tables:
    river = table.find('td')
    myRiver.append(river.text)

myCountry = []                      # 國家
for table in tables:
    countries = table.find_all('td')
    country = countries[1]
    myCountry.append(country.text)
    
print("國家 = ", myCountry)
print("河川 = ", myRiver)
data = dict(zip(myCountry, myRiver))
print(data)                         # 字典顯示結果

執行結果

國家 =  ['中國', '埃及', '巴西']
河川 =  ['長江', '尼羅河', '亞馬遜河']
{'中國': '長江', '埃及': '尼羅河', '巴西': '亞馬遜河'}

find_next_sibling() 和 find_previous_sibling()

在範例 pythonBS4-11-3.py 中,我們使用索引方式取得表格本體的國家資訊,然後儲存在 myCountry[] 串列中,其實爬蟲處理中,我們可以使用以下兩種方式:

  1. find_next_sibling()    # 同父節點的下一個同階層的節點。
  2. find_previous_sibling()    # 同父節點的上一個同階層的節點

範例 pythonBS4-11-4.py :  使用 find_next_sibling() 修改 範例 pythonBS4-11-3.py

# pythonBS4-11-4.py
import requests, bs4

url = 'htmlExampleBS4-05.html'
htmlFile = open(url, encoding='utf-8')              
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')

myRiver = []                                    # 河川
myCountry = []                                  # 國家
tableObj = objSoup.find('table').find('tbody')
tables = tableObj.find_all('tr')
for table in tables:
    river = table.find('td')
    myRiver.append(river.text)
    country = river.find_next_sibling('td')     # 下一個節點
    myCountry.append(country.text)
    
print("國家 = ", myCountry)
print("河川 = ", myRiver)
data = dict(zip(myCountry, myRiver))
print(data)                                     # 字典顯示結果

執行結果

國家 =  ['中國', '埃及', '巴西']
河川 =  ['長江', '尼羅河', '亞馬遜河']
{'中國': '長江', '埃及': '尼羅河', '巴西': '亞馬遜河'}

範例 pythonBS4-11-5.py :  使用 find_next_sibling() find_previous_sibling() 方法,建立洲別和河川的字典。

# pythonBS4-11-5.py
import requests, bs4

url = 'htmlExampleBS4-05.html'
htmlFile = open(url, encoding='utf-8')              
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')

myRiver = []                                    # 河川
myState = []                                    # 洲名
tableObj = objSoup.find('table').find('tbody')
tables = tableObj.find_all('tr')

for table in tables:
    countries = table.find_all('td')
    country = countries[1]                      # 國家節點
    river = country.find_previous_sibling('td') # 前一個節點
    myRiver.append(river.text)
    state = country.find_next_sibling('td')     # 下一個節點
    myState.append(state.text)
    
print("洲名 = ", myState)
print("河川 = ", myRiver)
data = dict(zip(myState, myRiver))
print(data)  

執行結果

洲名 =  ['亞洲', '非洲', '南美洲']
河川 =  ['長江', '尼羅河', '亞馬遜河']
{'亞洲': '長江', '非洲': '尼羅河', '南美洲': '亞馬遜河'}

find_next_siblings() 和 find_previous_siblings()

這裡多了個 s 表示爬取 前 或 後 的所有節點

  1. find_next_siblings()    # 同父節點的下一個同階層的所有節點。
  2. find_previous_siblings()    # 同父節點的上一個同階層的所有節點

範例 pythonBS4-11-6.py :  使用 find_next_siblings() find_previous_siblings() 方法,爬取同階層的以下所有節點,然後再爬取第3個節點的前面所有的2個節點。

# pythonBS4-11-6.py
import requests, bs4

url = 'htmlExampleBS4-03.html'
htmlFile = open(url, encoding='utf-8')              
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')

titleObj = objSoup.find('h2')                       # h2標題
title = titleObj.find_next_siblings('h2')           # 下一系列節點
print('find_next_siblings     = ', title)

titleObj = objSoup.find_all('h2')
title = titleObj[2].find_previous_siblings('h2')    # 前一系列節點
print('find_previous_siblings = ', title)

執行結果

find_next_siblings     =  [<h2>台灣人口排名</h2>, <h2>台灣最健康大學排名</h2>]
find_previous_siblings =  [<h2>台灣人口排名</h2>, <h2>台灣夜市排名</h2>]

parent()

目前階層的節點,移至上層的復節點。

範例 pythonBS4-11-7.py :  先列出尼羅河,再爬取並印出父節點。

# pythonBS4-11-7.py
import requests, bs4

url = 'htmlExampleBS4-05.html'
htmlFile = open(url, encoding='utf-8')              
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')

myRiver = []                        # 河川
tableObj = objSoup.find('table').find('tbody')
tables = tableObj.find_all('tr')
river = tables[1].find('td')
print(river.text)

river_parent = river.parent()
print(river_parent)

執行結果

尼羅河
[<td>尼羅河</td>, <td>埃及</td>, <td>非洲</td>]

parent() 搭配使用 find_next_sibling()、find_next_siblings()、find_previous_sibling()、find_previous_siblings()

  • parent.find_next_sibling()   # 上移至父節點爬取父階層的前一個節點
  • parent.find_next_siblings()   # 上移至父節點爬取父階層之前所有的節點
  • parent.find_previous_sibling()   # 上移至父節點爬取父階層的後一個節點
  • parent.find_previous_siblings()   # 上移至父節點爬取父階層之後所有的節點

範例 pythonBS4-11-8.py :  先列出尼羅河,再爬取並印出父節點的前一行節點,然後後一行節點。

# pythonBS4-11-8.py
import requests, bs4

url = 'htmlExampleBS4-05.html'
htmlFile = open(url, encoding='utf-8')              
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')

myRiver = []                        # 河川
tableObj = objSoup.find('table').find('tbody')
tables = tableObj.find_all('tr')
river = tables[1].find('td')
print(river.text)

previous_row = river.parent.find_previous_sibling()
print(previous_row)
next_row = river.parent.find_next_sibling()
print(next_row)

執行結果

尼羅河
<tr>
<td>長江</td>
<td>中國</td>
<td>亞洲</td>
</tr>
<tr>
<td>亞馬遜河</td>
<td>巴西</td>
<td>南美洲</td>
</tr>

範例 pythonBS4-11-9.py :  先到長江,先到父階層,然後印出之後的所有節點。再到亞馬遜河,然後印出之前的所有節點。

# pythonBS4-11-9.py
import requests, bs4

url = 'htmlExampleBS4-05.html'
htmlFile = open(url, encoding='utf-8')              
objSoup = bs4.BeautifulSoup(htmlFile, 'lxml')

myRver = []                        # 河川
tableObj = objSoup.find('table').find('tbody')
tables = tableObj.find_all('tr')
river = tables[0].find('td')
print(river.text)
previous_rows = river.parent.find_next_siblings()
print(previous_rows)
print('-'*70)
river = tables[2].find('td')
print(river.text)
next_rows = river.parent.find_previous_siblings()
print(next_rows)

執行結果

江
[<tr>
<td>尼羅河</td>
<td>埃及</td>
<td>非洲</td>
</tr>, <tr>
<td>亞馬遜河</td>
<td>巴西</td>
<td>南美洲</td>
</tr>]
----------------------------------------------------------------------
亞馬遜河
[<tr>
<td>尼羅河</td>
<td>埃及</td>
<td>非洲</td>
</tr>, <tr>
<td>長江</td>
<td>中國</td>
<td>亞洲</td>
</tr>]

網路爬蟲實戰 圖片下載

我們已經用 HTML 文件來解說網路爬蟲的基本原理了,其實在真實的網路世界一切比上述複雜多了。

範例 pythonBS4-12.py :  到上奇資訊的網站下載所有的圖片,並放到目錄 "outputPythonBS4" 內。

# pythonBS4-12.py
import bs4, requests, os

url = 'https://www.grandtech.com/'                  # 上奇資訊網頁
html = requests.get(url)
print("網頁下載中 ...")
html.raise_for_status()                             # 驗證網頁是否下載成功                      
print("網頁下載完成")

destDir = 'outputPythonBS4'                                 # 設定未來儲存圖片的資料夾
if os.path.exists(destDir) == False:
    os.mkdir(destDir)                               # 建立資料夾供未來儲存圖片

objSoup = bs4.BeautifulSoup(html.text, 'lxml')      # 建立BeautifulSoup物件

imgTag = objSoup.select('img')                      # 搜尋所有圖片檔案
print("搜尋到的圖片數量 = ", len(imgTag))           # 列出搜尋到的圖片數量
if len(imgTag) > 0:                                 # 如果有找到圖片則執行下載與儲存
    for i in range(len(imgTag)):                    # 迴圈下載圖片與儲存
        imgUrl = imgTag[i].get('src')               # 取得圖片的路徑
        print("%s 圖片下載中 ... " % imgUrl)
        finUrl = url + imgUrl                       # 取得圖片在Internet上的路徑
        print("%s 圖片下載中 ... " % finUrl)
        picture = requests.get(finUrl)              # 下載圖片
        picture.raise_for_status()                  # 驗證圖片是否下載成功
        print("%s 圖片下載成功" % finUrl)

        # 先開啟檔案, 再儲存圖片
        pictFile = open(os.path.join(destDir, os.path.basename(imgUrl)), 'wb')
        for diskStorage in picture.iter_content(10240):
            pictFile.write(diskStorage)
        pictFile.close()     

範例 pythonBS4-13.py :  到網站下載所有的圖片,並放到目錄 "outputPythonBS4" 內,使用偽裝伺服器的 header 宣告。

# pythonBS4-13.py
import bs4, requests, os

headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64)\
            AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101\
            Safari/537.36', }
url = 'http://aaa.24ht.com.tw/'                     # 這個伺服器會擋住網頁
html = requests.get(url, headers=headers)           
print("網頁下載中 ...")
html.raise_for_status()                             # 驗證網頁是否下載成功                      
print("網頁下載完成")

destDir = 'outputPythonBS4'                                 # 設定儲存資料夾
if os.path.exists(destDir) == False:
    os.mkdir(destDir)                               # 建立目錄供未來儲存圖片

objSoup = bs4.BeautifulSoup(html.text, 'lxml')      # 建立BeautifulSoup物件

imgTag = objSoup.select('img')                      # 搜尋所有圖片檔案
print("搜尋到的圖片數量 = ", len(imgTag))           # 列出搜尋到的圖片數量
if len(imgTag) > 0:                                 # 如果有找到圖片則執行下載與儲存
    for i in range(len(imgTag)):                    # 迴圈下載圖片與儲存
        imgUrl = imgTag[i].get('src')               # 取得圖片的路徑
        print("%s 圖片下載中 ... " % imgUrl)
        finUrl = url + imgUrl                       # 取得圖片在Internet上的路徑
        print("%s 圖片下載中 ... " % finUrl)
        picture = requests.get(finUrl, headers=headers) # 下載圖片
        picture.raise_for_status()                  # 驗證圖片是否下載成功
        print("%s 圖片下載成功" % finUrl)

        # 先開啟檔案, 再儲存圖片
        pictFile = open(os.path.join(destDir, os.path.basename(imgUrl)), 'wb')
        for diskStorage in picture.iter_content(10240):
            pictFile.write(diskStorage)
        pictFile.close()                            # 關閉檔案  

網路爬蟲實戰 找出台灣彩券公司最新一期威力彩開獎結果

範例 pythonBS4-14.py :  找出威力彩最新一期開獎結果,在程式第12行先找出 Class 是 "contents_box02" ,因為我們發現這裡面有包含最新一期開獎結果。程式第13行發現總共有4組串列,程式第14-15行則列出此4組串列。觀察這4組串列,發現威力彩是在第1組串列中,所以程式第18行 : 

balls = dataTag[0].find_all('div', {'class':'ball_tx ball_green'})
  • dataTag[0] 即要找出第1串列
  • find_all : 找出 所有 div 標籤,並且屬性的 class 是 ball_tx ball_green。
程式第20-21行是列出前6球是開出順序,程式第24-25行是列出第7球以後大小順序。程式第28行找出紅球特別號,因為只有一顆,所以用 find_all() 或 find() 都可以。 
# pythonBS4-14.py
import bs4, requests

url = 'http://www.taiwanlottery.com.tw'
html = requests.get(url)
print("網頁下載中 ...")
html.raise_for_status()                             # 驗證網頁是否下載成功                      
print("網頁下載完成")

objSoup = bs4.BeautifulSoup(html.text, 'lxml')      # 建立BeautifulSoup物件

dataTag = objSoup.select('.contents_box02')         # 尋找class是contents_box02
print("串列長度", len(dataTag))
for i in range(len(dataTag)):                       # 列出含contents_box02的串列                 
    print(dataTag[i])
        
# 找尋開出順序與大小順序的球
balls = dataTag[0].find_all('div', {'class':'ball_tx ball_green'})
print("開出順序 : ", end='')
for i in range(6):                                  # 前6球是開出順序
    print(balls[i].text, end='   ')

print("\n大小順序 : ", end='')
for i in range(6,len(balls)):                       # 第7球以後是大小順序
    print(balls[i].text, end='   ')

# 找出第二區的紅球                   
redball = dataTag[0].find_all('div', {'class':'ball_red'})
print("\n第二區   :", redball[0].text)

執行結果

網頁下載中 ...
網頁下載完成
串列長度 4
<div class="contents_box02">
<div id="contents_logo_02"></div><div class="contents_mine_tx02"><span class="font_black15">110/10/11 第110000081
期 </span><span class="font_red14"><a href="Result_all.aspx#01">開獎結果</a></span></div><div class="contents_mine_tx04">開出順序<br/>大小順序<br/>第二區</div><div class="ball_tx ball_green">24 </div><div class="ball_tx ball_green">33 </div><div class="ball_tx ball_green">15 </div><div class="ball_tx ball_green">12 </div><div class="ball_tx ball_green">38 </div><div class="ball_tx ball_green">05 </div><div class="ball_tx ball_green">05 </div><div class="ball_tx ball_green">12 </div><div class="ball_tx ball_green">15 </div><div class="ball_tx ball_green">24 </div><div class="ball_tx ball_green">33 </div><div class="ball_tx ball_green">38 </div><div class="ball_red">02 </div>
</div>
<div class="contents_box02">
.....中間省略.....
</div>
開出順序 : 24    33    15    12    38    05
大小順序 : 05    12    15    24    33    38
第二區   : 02

網路爬蟲實戰 列出Yahoo 焦點新聞標題和超連結

首先我們需要使用 Chrome 解析 Yahoo 的網頁,請進入 Yahoo 網站,點選焦點新聞,將滑鼠移至第一條焦點新聞,然後按一下滑鼠右鍵,執行檢查指令,可以進入 Chrome 開發工具模式,我們可以找出焦點新聞皆有CSS 內容為 story-title 的 class。有了上述的觀念,我們就可以設計爬蟲程式了。

請留意每一條焦點新聞皆是在 <a> 元素內,同時在 class 有許多屬性,其中一定有 story-title ,可以利用這個特性找出所有的焦點新聞。至於屬性內則是標題,如下所示 

範例 pythonBS4-15.py :  列出 Yahoo 焦點新聞標題和超連結。 

# pythonBS4-15.py
import requests, bs4,re

htmlFile = requests.get('https://tw.yahoo.com/')
objSoup = bs4.BeautifulSoup(htmlFile.text, 'lxml')
headline_news = objSoup.find_all('a', class_='story-title')
for h in headline_news:
    print("焦點新聞 : " + h.text)
    print("新聞網址 : " + h.get('href'))

IP 偵測網站FileFab

FileFab 是一個偵測網路 IP 的網站,只要和他連線,此網站就會回應我們電腦目前的 IP、居住城市、經緯度資訊。

進入此網站後,點選滑鼠右鍵,選擇 "檢查",進入 Chrome 開發工具,發現以下資訊

有了上述資訊,我們就可以設計爬蟲程式了。

範例 pythonBS4-16.py :  列出自己的 IP,程式第13行 strip() 方法,會將字串頭尾的空白、換行符號去掉。 

# pythonBS4-16.py
import requests
import bs4

# 使用自己的IP
headers = { 'User-Agent':'Mozilla/5.0 (Windows NT 6.1; WOW64)\
            AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.101\
            Safari/537.36', }
url = 'http://ip.filefab.com/index.php'
htmlFile = requests.get(url, headers=headers)
objSoup = bs4.BeautifulSoup(htmlFile.text, 'lxml')
ip = objSoup.find('h1', id='ipd')
print(ip.text.strip())

執行結果

Your IP address: 180.177.109.201

參考資料

特色、摘要,Feature、Summary:

關鍵字、標籤,Keyword、Tag:

  • Web-Crawler,Data-Mining,Data-Science,Python,

留言

這個網誌中的熱門文章

Ubuntu 常用指令、分類與簡介

iptables的觀念與使用

網路設定必要參數IP、netmask(遮罩)、Gateway(閘道)、DNS

了解、分析登錄檔 - log

Python 與SQLite 資料庫

Blogger文章排版範本

Pandas 模組

如何撰寫Shell Script

查詢指令或設定 -Linux 線上手冊 - man

下載網頁使用 requests 模組