更新《超圖解Python程式設計入門》第一版範例程式原始碼

新版《超圖解Python程式設計》已經上市,本文旨在補充《超圖解Python程式設計入門》第一版下列章節的範例程式原始碼,第一版書籍的讀者可重新下載範例檔。

第6章 自動收集網路資訊
第7章 儲存檔案:純文字檔、CSV檔與Google試算表
第12章 留言板網站應用程式
第13章 打造 LINE 聊天機器人

第5章下載YouTube影片的程式庫,請改用Juan Bindez先生維護的pytubefix套件,語法不變。

更新網頁爬蟲(自動網頁資訊)程式

舊版selenium程式庫要求我們先手動下載Webdrive(瀏覽器驅動器),新版程式庫則會在初次執行底下建立「網頁驅動器」物件時,自動下載操控瀏覽器所需的驅動程式:

driver = webdriver.Chrome()  # 建立瀏覽器驅動物件,並自動下載驅動程式。 

驅動程式將被存入使用者家目錄的“.cache”資料夾(而非Python虛擬環境的資料夾),所以日後控制瀏覽器就不用重新下載驅動程式,以Windows和macOS系統為例:

Webdrive(瀏覽器驅動器)

Selenium選定網頁元素的語法更新

Selenium(以下簡稱Se)定位元素的方法,舊版的語法格式為“find_element_by_○○○”代表「藉由○○○找尋元素」,例如:

  • find_element_by_id:元素的id(唯一識別)名稱
  • find_element_by_name:元素的name(欄位名稱)屬性

新版語法則改成下圖的模樣,同樣分成單、複數:

find_element()方法

其中的By.○○○可能值:

By.ID 使用id屬性定位 By.NAME 使用name屬性定位
By.LINK_TEXT 使用超連結文字定位 By.PARTIAL_LINK_TEXT 使用部分超連結文字定位
By.TAG_NAME 使用標籤名稱定位 By.CLASS_NAME 使用class屬性值定位
By.XPATH 使用XPath定位 By.CSS_SELECTOR 使用CSS選擇器定位

以透過Se瀏覽到Yahoo奇摩首頁、在搜尋欄輸入“台灣矮黑人”並提交搜尋為例,請先在Python直譯器中執行底下的敘述,。

from selenium import webdriver
from selenium.webdriver.common.by import By  # 必須引用By

driver = webdriver.Chrome()
driver.get('https://tw.yahoo.com')

等瀏覽器開啟Yahoo頁面後,即可定位到輸入欄位“p”。底下敘述將把定位結果存入search_field變數,然後對它執行send_key()方法,代表「送出按鍵或文字」:

操控瀏覽器

瀏覽器將呈現搜尋結果頁面。

同樣地,底下程式片段透過WebDriver驅動器物件的find_element()方法和XPath語法,選定h1元素,並取得該元素的文字內容:

find_element()方法

程式執行結果如下:

find_element()方法

範例檔的“V2”資料夾,包含更新程式碼以及兩個擷取購物網站內容的範例。

更新Google Sheets(試算表)程式

Google憑證檔可任意命名,存在任意路徑,例如,存在應用程式專案路徑底下的model資料夾:

Google憑證檔

gspread程式庫原本透過底下敘述驗證憑證:

import gspread
from oauth2client.service_account import ServiceAccountCredentials as sac

scope = ['https://spreadsheets.google.com/feeds',
         'https://www.googleapis.com/auth/drive']

# 建立憑證
cr = sac.from_json_keyfile_name('google_auth.json', scope)
gs = gspread.authorize(cr)

新版本僅需執行gspread的service_account(服務帳戶)函式,並傳入Google憑證的檔名路徑;這個函式將傳回一個取得授權的用戶端(client)物件。

service_account()函式

若省略上面敘述的filename(檔名)參數,則須將憑證檔命名成“service_account.json”,存入底下路徑:

  • Windows系統:位於“C:\Users\使用者名稱\AppData\Roaming”(此路徑可透過Windows內建的%APPDATA%變數取得)底下的gspread資料夾。
  • macOS和Linux系統:位於“~/.config/gspread”路徑。

請在指定的路徑新增gspread資料夾,再存入service_account.json。

service_account.json存檔路徑

如此,service_account()函式就不用傳入憑證檔的路徑檔名了:

import gspread

try:
    client = gspread.service_account()  # 從預設路徑讀取憑證
except FileNotFoundError:
    print("請確認憑證檔存放在正確的路徑")
    exit()

範例檔“V2”資料夾包含更新過的存取Google試算表程式。

留言版網站應用程式

根據「產生SQLite資料庫檔案與操作資料」一節(12-16頁),執行底下敘述:

產生SQLite資料庫檔案與操作資料

SQLAlchemy將於目前所在路徑新建一個bbs.db檔。

新版的Flask程式,則要先指定Flask app物件的“SQLALCHEMY_DATABASE_URI”(資料庫路徑)參數,並設定檔案路徑,例如:’sqlite:///bbs.db’,代表在程式所在位置建立bbs.db檔。

“SQLALCHEMY_DATABASE_URI”(資料庫路徑)參數

建立資料庫的敘述必須寫在with app.app_context()區塊裡面:

建立資料庫

執行上述指令之後,它將在程式所在路徑(src)當中新增“instance”(代表「應用程式實體」),並在其中存入bbs.db資料庫檔案:

資料庫儲存路徑

補充說明app.app_context()的意義。多數Flask程式裡面只定義一個app,但同一個Flask程式檔其實可包含多個app定義。我們可以把整個Flask程式想像成一家公司,一個app相當於一個部門,以底下的虛構程式片段為例,它定義了sales_app和account_app,分別處理不同部門的事務。

sales_app = Flask('sales')    # 建立一個Flask app(銷售部門)
sales_app.config['SECRET_KEY'] = 'sales_secret_key'       # 銷售部的密鑰

account_app = Flask('account') # 建立另一個Flask app(會計部門)
account_app.config['SECRET_KEY'] = ' account_secret_key'  # 會計部的密鑰

每個app都可以用config定義屬於自己的設置參數,如:密鑰和資料庫檔案,app的路由(route)函式,將自動進入該app領域(context),無需透過app_context(),相當於走入該部門,可存取該app的相關資源。

app_context概念

理論上,一個app只能存取自身領域的資源,但事實上,它也能存取其他app的資源,像這樣:

@sales_app.route('/')   # 定義「銷售部」的路由
def sales_home():
    print(f'會計部的密鑰:{account_app.config['SECRET_KEY']}')
    return "<h1>歡迎光臨!</h1>"

Python語法上來說,sales_app和account_app都屬於「全域變數」,所以相同程式檔的所有函數都能存取;從程式架構來說,是錯的,上面的寫法很糟糕,相當於進入別的部門窺探,破壞了應用程式的模組化和獨立性。

所以Flask規定,操作資料庫的程式必須用app.app_context()敘述,明確進入Flask App的領域(部門)進行操作,否則會出錯。

範例檔“V2”資料夾包含採用新版Flask建立的留言板網站資料庫程式。

LINE聊天機器人程式更新說明

LINE通訊程式的開發設置過程,新舊版大致相同:在LINE開發人員網頁新增頻道(channel)、取得LINE存取令牌access token、頻道密鑰(secret),並且設置Webhook(calllback回呼)網址。

新版LINE開發人員頁面的網址改了,點擊首頁右上方的“Log in to console(登入主控台)”連結,它會要求你建立(免費)並使用商用ID(Business ID)登入,請依照LINE網站的(中文)指引建立商用ID。

登入LINE

圖文選單則是透過「LINE官方帳號」管理網頁設定,這個網站首頁會列舉跟你的LINE帳號關聯的聊天機器人,點擊你的聊天機器人,再點擊網頁左側導覽列的「圖文選單」選項,即可進行編輯。

「LINE官方帳號」管理網頁

LINE程式庫V3版

LINE聊天機器人開發最大的改變是程式庫的語法變了。以echo bot程式為例,接收MessageEvent(訊息事件)的事件處理程式,代表文字訊息的message參數值是TextMessage(文字訊息)類型物件,而回覆文字要包裝成TextSendMessage(文字傳送訊息)物件,再透過replay_message()方法傳送給使用者,像這樣:

from flask import Flask, request, abort
from linebot import LineBotApi, WebhookHandler
from linebot.exceptions import InvalidSignatureError
from linebot.models import (
    MessageEvent, TextMessage, TextSendMessage
)

line_bot_api = LineBotApi('你的LINE存取代碼 access code')
handler = WebhookHandler('你的LINE頻道密鑰 secret')

app = Flask(__name__)

@app.route('/')
def index():
    return 'Welcome to Line Bot!'

@app.route("/callback", methods=['POST'])
def callback():
    signature = request.headers['X-Line-Signature']
    body = request.get_data(as_text=True)

    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        abort(400)

    return 'OK'

@handler.default()
def default(event):
    print('捕捉到事件:', event)

# 處理文字訊息
@handler.add(MessageEvent, message=TextMessage)
def handle_message(event):
    txt=event.message.text
    line_bot_api.reply_message(
        event.reply_token, TextSendMessage(text=txt)
    )

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80, debug=True)

底下是則是V3版程式庫的寫法,程式庫的識別名稱有標示“V3”,接收MessageEvent(訊息事件)的事件處理程式,代表文字訊息的message參數值是TextMessageContent(文字訊息內容)類型物件,而回覆文字要包裝成ReplyMessageRequest(回覆訊息請求)物件,再透過replay_message()方法傳送給使用者,像這樣:

from flask import Flask, request, abort

from linebot.v3 import WebhookHandler
from linebot.v3.exceptions import InvalidSignatureError  # 處理驗證錯誤

from linebot.v3.messaging import (
    Configuration,  # 存取設置
    ApiClient,      # API用戶端
    MessagingApi,   # 處理訊息
    ReplyMessageRequest,  # 回應訊息請求
    TextMessage,          # 文字訊息
    StickerMessage        # 貼圖訊息
)
from linebot.v3.webhooks import (
    MessageEvent,         # 訊息事件
    TextMessageContent,   # 文字訊息內容
)

app = Flask(__name__)

configuration = Configuration(access_token='你的頻道存取令牌')
handler = WebhookHandler('你的頻道密鑰')

@app.route('/')
def index():
    return 'Welcome to Line Bot!'

@app.route("/callback", methods=['POST'])
def callback():
    # 取得X-Line-Signature標頭值
    signature = request.headers['X-Line-Signature']

    # 取得請求本體
    body = request.get_data(as_text=True)

    # 處理webhook本體
    try:
        handler.handle(body, signature)
    except InvalidSignatureError:
        app.logger.info("驗證錯誤~請檢查您的頻道存取令牌/頻道密鑰。")
        abort(400) # 中止請求(400 Bad Request)

    return 'OK'

# 處理「文字訊息內容」事件
@handler.add(MessageEvent, message=TextMessageContent)
def handle_message(event):
    with ApiClient(configuration) as api_client:
        line_bot_api = MessagingApi(api_client)  # 建立「訊息API」物件

    user_msg = event.message.text  # 取得訊息文字
    profile = line_bot_api.get_profile(event.source.user_id) # 取得使用者的資料
    display_name = profile.display_name  # 取得使用者的名字

    reply = f"{display_name}您好:\n你說:{user_msg}"  # 建立回應文字

    # 回應訊息
    line_bot_api.reply_message(
        ReplyMessageRequest(              # 建立回應訊息物件
            reply_token=event.reply_token,
            messages=[
                TextMessage(text=reply),  # 文字訊息內容
                StickerMessage(           # 貼圖訊息內容
                    package_id='3',
                    sticker_id='233'
                )
            ]
        )
    )

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80, debug=True)

範例檔已更新echo bot以及LINE線上報修程式,存在“V3”資料夾。

Posts created 529

發佈留言

發佈留言必須填寫的電子郵件地址不會公開。 必填欄位標示為 *

Related Posts

Begin typing your search term above and press enter to search. Press ESC to cancel.

Back To Top