返回文章列表

Python Requests 模組簡化 HTTP 客戶端

本文介紹 Python 的 Requests 模組,它提供更簡潔易用的 HTTP 客戶端功能,取代標準函式庫 urllib 的複雜性。文章涵蓋 Requests 的安裝、基本用法,包含 GET、POST、HEAD 請求示範,並著重於 Session 物件的運用,解決網站登入狀態維持問題。此外,文章也提供使用

Web 開發 Python

Python 的 requests 模組提供更簡潔的 HTTP 客戶端功能,相較於標準函式庫 urlliburllib2 更易於使用,尤其在處理複雜情境如 Cookie 管理。本文將介紹 requests 的安裝、基本用法,並以實際案例說明如何使用 Session 物件來處理網站登入狀態。同時,我們也將探討如何使用 requests 重寫網站登入/登出檢查指令碼,以及如何設計一個簡易的監控系統。此係統包含監控伺服器、代理、感測器和資料物件,並以程式碼片段說明其運作方式。

透過 requests 模組,傳送 HTTP 請求變得相當直覺。例如,使用 requests.get() 就能輕鬆取得網頁內容,而 requests.head() 可在不下載完整網頁的情況下取得網頁 metadata。更重要的是,requests 的 Session 物件能有效管理 Cookie,讓網站登入狀態得以維持,簡化了與需要驗證的網站互動的流程。這對於自動化網頁操作、爬蟲程式開發等應用都非常有幫助。

簡化HTTP客戶端:Requests模組

在前面的章節中,我們使用了Python標準函式庫中的urlliburllib2來處理所有的網頁請求。然而,這些函式庫有時使用起來相當棘手,尤其是在處理較為複雜的任務(如維護會話cookie)時,其API可能會變得相當複雜且難以理解。

幸運的是,Kenneth Reitz也發現這兩個函式庫使用起來相當痛苦,因此他決定撰寫一個更好、更簡單的HTTP客戶端函式庫。於是在2011年初,他編寫了requests函式庫的初始版本,該版本很快就變得非常流行,因為它提供了一種更優雅、更有效的方式來處理網頁請求。

安裝Requests函式庫

大多數Linux發行版在其預設的套件集中都提供了requests模組。例如,在Fedora系統上,可以執行以下命令進行安裝:

$ sudo yum install python-requests

或者,您也可以使用PyPI套件:

$ sudo pip install requests

requests函式庫依賴另一個非標準函式庫urllib3,但它已包含在套件中,因此您無需單獨安裝它。並且由於這些套件被捆綁在一起,因此您也不必擔心套件版本的相容性問題。

基本用法

requests模組之所以如此流行,其中一個原因是它不需要撰寫任何樣板程式碼。我們只需要匯入模組並呼叫其中一個方便地與HTTP方法名稱相匹配的輔助函式。以下是如何檢索BBC新聞首頁並檢查HTML檔案的大小:

>>> import requests
>>> result = requests.get('http://www.bbc.co.uk/news')
>>> len(result.text)
145858
>>>

同樣輕鬆地,我們可以發出POST、DELETE和HEAD請求,因為它們都被暴露為輔助函式。例如,HEAD請求可用於在不下載整個頁面內容的情況下取得有關網頁的後設資料:

>>> result = requests.head('http://www.bbc.co.uk/news/')
>>> len(result.text)
0
>>> result.status_code
200
>>> for k, v in result.headers.iteritems():
...     print "%s: %s" % (k, v)
...
x-lb-nocache: true
content-length: 145858
content-type: text/html
content-language: en-GB
set-cookie: BBC-UID=f5933797120e92467907670df14f1beb2510f8d4b4f4d11e3a3144ee92a8ee880python-requests/2.3.0%20CPython/2.7.5%20Darwin/13.2.0; expires=Wed, 16-May-18 09:38:46 GMT; path=/; domain=.bbc.co.uk
expires: Sat, 17 May 2014 09:38:37 GMT
vary: X-CDN
server: Apache
connection: keep-alive
x-cache-hits: 9
x-cache-action: HIT
cache-control: private, max-age=0, must-revalidate
date: Sat, 17 May 2014 09:38:46 GMT
x-cache-age: 9
>>>

內容解密:

上述程式碼展示瞭如何使用requests模組發出GET和HEAD請求,並檢查HTTP回應的內容。其中,requests.get()用於檢索網頁內容,而requests.head()則用於取得網頁的後設資料。我們還可以看到如何存取HTTP回應頭,並遍歷其內容。

登入Telegraph網站的範例

要登入Telegraph網站,我們需要使用HTTP POST請求將表單資料(電子郵件和密碼)傳送到網站:

>>> form_data = {'email': '[email protected]', 'password': 'secret'}
>>> result = requests.post('https://auth.telegraph.co.uk/sam-ui/login.htm', data=form_data)
>>> result.url
u'https://auth.telegraph.co.uk/customer-portal/myaccount/index.html'
>>>

然而,我們仍然遇到了一個小問題:網站無法在請求之間記住我們,因此隨後嘗試檢索帳戶頁面會將我們重定向回主登入頁面:

>>> form_data = {'email': '[email protected]', 'password': 'secret'}
>>> result = requests.post('https://auth.telegraph.co.uk/sam-ui/login.htm', data=form_data)
>>> result.url
u'https://auth.telegraph.co.uk/customer-portal/myaccount/index.html'
>>> result = requests.get('https://auth.telegraph.co.uk/customer-portal/myaccount/index.html')
>>> result.url
u'https://auth.telegraph.co.uk/sam-ui/login.htm?redirectTo=http%3A%2F%2Fauth.telegraph.co.uk%2Fcustomer-portal%2Fmyaccount%2Findex.html&logintype=tmg'
>>>

為瞭解決這個問題,我們可以使用requests模組中的Session類別來啟用cookie永續性:

>>> session = requests.Session()
>>> form_data = {'email': '[email protected]', 'password': 'secret'}
>>> result = session.post('https://auth.telegraph.co.uk/sam-ui/login.htm', data=form_data)
>>> result.url
u'https://auth.telegraph.co.uk/customer-portal/myaccount/index.html'
>>> result = session.get('https://auth.telegraph.co.uk/customer-portal/myaccount/index.html')
>>> result.url
u'https://auth.telegraph.co.uk/customer-portal/myaccount/index.html'
>>>

內容解密:

這段程式碼展示瞭如何使用requests模組中的Session類別來維持會話狀態,從而能夠成功登入Telegraph網站並存取受保護的頁面。這是透過建立一個Session例項並使用它來傳送HTTP請求來實作的。

使用 Requests 模組重寫網站登入/登出檢查指令碼

本章節將展示如何使用 Python 的 Requests 模組來檢查網站的可用性,特別是針對需要登入和登出的網站。我們將透過一個具體的範例來說明如何實作這一功能。

重寫網站登入/登出檢查指令碼

首先,我們來看一下如何使用 Requests 模組重寫網站登入/登出的檢查指令碼,如 清單 8-6 所示:

#!/usr/bin/env python
import sys
import time
import requests
from BeautifulSoup import BeautifulSoup
from optparse import OptionParser

NAGIOS_OK = 0
NAGIOS_WARNING = 1
NAGIOS_CRITICAL = 2

WEBSITE_LOGON = 'https://auth.telegraph.co.uk/sam-ui/login.htm'
WEBSITE_LOGOFF = 'https://auth.telegraph.co.uk/sam-ui/logoff.htm'
WEBSITE_USER = '[email protected]'
WEBSITE_PASS = 'secret'

def test_logon_logoff():
    session = requests.Session()
    form_data = {'email': WEBSITE_USER, 'password': WEBSITE_PASS}
    status = []
    try:
        # 測試登入
        result = session.post(WEBSITE_LOGON, data=form_data)
        html_logon = result.text
        soup_logon = BeautifulSoup(html_logon)
        logon_ok = validate_logon(soup_logon.head.title.text, result.url)
        
        # 測試登出
        result = session.get(WEBSITE_LOGOFF)
        html_logoff = result.text
        soup_logoff = BeautifulSoup(html_logoff)
        logoff_ok = validate_logoff(soup_logoff.head.title.text, result.url)
        
        if logon_ok and logoff_ok:
            status = [NAGIOS_OK, 'Logon/logoff operation']
        else:
            status = [NAGIOS_CRITICAL, 'ERROR: Failed to logon and then logoff to the web site']
    except:
        status = [NAGIOS_CRITICAL, 'ERROR: Failure in the logon/logoff test']
        import traceback
        traceback.print_exc()
    return status

#### 內容解密:
此函式 `test_logon_logoff` 主要用於測試網站的登入和登出功能首先它建立一個 `requests.Session()` 物件來維持整個過程中的 session然後它準備了登入所需的表單資料包括電子郵件和密碼接著它嘗試進行登入和登出操作並透過 `validate_logon``validate_logoff` 函式驗證操作是否成功如果兩者皆成功則傳回 `NAGIOS_OK` 狀態否則傳回 `NAGIOS_CRITICAL` 狀態並附上錯誤訊息

### 登入驗證函式

```python
def validate_logon(title, redirect_url):
    result = True
    if title.find('My Account') == -1:
        result = False
    if redirect_url != 'https://auth.telegraph.co.uk/customer-portal/myaccount/index.html':
        result = False
    return result

內容解密:

validate_logon 函式用於驗證登入操作是否成功。它檢查頁面的標題是否包含「My Account」字樣,以及重定向的 URL 是否正確。如果任一條件不符,則視為登入失敗。

登出驗證函式

def validate_logoff(title, redirect_url):
    result = True
    if title.find('My Account') != -1:
        result = False
    if redirect_url != 'http://www.telegraph.co.uk':
        result = False
    return result

內容解密:

validate_logoff 函式與 validate_logon 類別似,但用於驗證登出操作。它檢查登出後頁面的標題不應包含「My Account」,且重定向至正確的 URL。

主函式

def main():
    parser = OptionParser()
    parser.add_option('-w', dest='time_warn', default=3.8, help="Warning threshold in seconds, default: %default")
    parser.add_option('-c', dest='time_crit', default=5.8, help="Critical threshold in seconds, default: %default")
    (options, args) = parser.parse_args()
    
    if float(options.time_crit) < float(options.time_warn):
        options.time_warn = options.time_crit
    
    start = time.time()
    code, message = test_logon_logoff()
    elapsed = time.time() - start
    
    # 根據執行結果和耗時傳回對應的 Nagios 狀態碼

內容解密:

main 函式負責解析命令列引數,執行 test_logon_logoff 函式,並根據執行的結果和耗時來決定傳回的 Nagios 狀態碼。

管理與監控子系統

監控伺服器

監控伺服器負責向客戶端系統傳送請求並接收所有客戶端的感測器讀數。有兩種方法可以從客戶端取得資料:第一種是由伺服器發起連線,第二種是由客戶端發起連線。每種方法都有其優缺點。當客戶端發起連線時,伺服器端的負擔較小,因為不需要進行任何排程工作。同時,這種方式也更安全,因為無法請求資料,因此資料只會被註冊在客戶端的系統——監控伺服器——接收。

然而,客戶端發起連線的最大缺點是伺服器完全無法控制傳入的資訊流,這可能導致伺服器過載。理想情況下,應該由伺服器決定需要哪些資訊以及何時需要。例如,一個智慧系統可以在某些檢查沒有意義時停用它們。一個很好的例子是,在收到硬碟故障警示後停止磁碟區使用率檢查;顯然,硬碟故障將導致所有磁碟區檢查失敗,因此報告根本問題的症狀毫無意義。

在本簡易監控系統中,我將使用一種有效的伺服器發起的控制機制,但不會犧牲安全模型。伺服器程式將向客戶端傳送通知,要求提交感測器資料。當客戶端收到此類別通知時,它將執行檢查並將讀數提交回註冊的伺服器。因此,無法從客戶端取得資料;這是一種單向通訊通道,只有「受信任」的伺服器才能接收結果。

客戶端的組態以類別似的方式進行:客戶端接收外部訊號以更新其組態(無論是「受信任」的伺服器位址還是感測器程式碼),然後它會發起與伺服器的連線以取得所需的詳細資訊。

監控代理

監控代理程式完全是被動的,只有在收到伺服器的指示時才會採取行動。如前所述,這些指示可以是提交感測器讀數、更新伺服器位址以及從伺服器檢索新的感測器程式碼。

當代理被通知提交讀數時,它將呼叫外部工具來執行實際的讀取。然後,它將從程式中讀取輸出並將其連同程式傳回程式碼一起發送回監控伺服器。

伺服器位址更新命令指示監控代理連線到當前註冊的伺服器並請求新的位址。代理將嘗試連線到新的位址。如果操作成功,當前伺服器位址將被替換為新的位址;否則,位址將保持不變。

最後,當代理收到更新其中一個感測器的程式碼的命令時,它將連接回伺服器並請求感測器程式碼存檔。伺服器將把存檔的副本發送回請求的客戶端。當存檔被接收並儲存在臨時位置時,它會被解壓縮並執行基本健全性檢查。如果檢查成功,舊程式碼將被存檔,並且新程式碼將被佈署到適當的位置。

感測器

與其他監控系統不同,那些系統中的感測器或檢查包含一些邏輯(例如,如我們在前一章中看到的,Nagios 檢查傳回 OK、WARNING 或 CRITICAL 狀態訊息),我不會在我的感測器中嵌入任何驗證邏輯。畢竟,感測器的作用是報告狀態,不能也不應該知道它所報告的情況是否構成任何危險。由主監控伺服器決定讀數是否指示系統存在任何問題。

這種方法允許進一步擴充套件檢查邏輯以執行更先進和自適應的報告。例如,系統可能會擴充套件趨勢檢查,而不是簡單的閾值檢查。即使被觀察系統上的負載超過了設定的閾值,如果這是負載模式的正常表現,那麼它仍然可能是正常的。同樣,如果系統報告的負載遠低於給定時間段的正常負載,則可能表明存在問題,而簡單的閾值檢查將無法檢測到這些問題。

資料物件

自然,所有涉及的程式都將消耗或產生(或兩者)一些資料。最明顯的資料是感測器讀數,但還會有組態、排程設定等。因此,在編寫任何程式碼之前,我需要對監控系統要處理的資料進行合理定義和設計。

將會有四種不同的資料型別:

  • 組態資料,描述所有監控代理、感測器及其引數
  • 站點組態資料,定義需要在每個伺服器上執行的檢查以及在哪裡找到客戶端伺服器
  • 排程資料,定義檢查的時間間隔
  • 效能讀數資料,這是從客戶端伺服器上的感測器接收的資料

組態

組態資料包含有關感測器、感測器引數和監控代理的資料。所有可用的代理及其名稱和位址都是監控系統組態的一部分。除了簡單列出所有主機和感測器之外,組態還包含有關哪些感測器在哪些監控伺服器上可用的資訊。

代理伺服器在可用的硬體資源和組態方面可能有所不同,因此必須能夠為每個監控代理定義個別的閾值。

@startuml
skinparam backgroundColor #FEFEFE
skinparam defaultTextAlignment center
skinparam rectangleBackgroundColor #F5F5F5
skinparam rectangleBorderColor #333333
skinparam arrowColor #333333

title 組態

rectangle "傳送請求" as node1
rectangle "傳回感測器讀數" as node2
rectangle "接收指令" as node3
rectangle "傳回結果" as node4
rectangle "報告狀態" as node5

node1 --> node2
node2 --> node3
node3 --> node4
node4 --> node5

@enduml

此圖示展示了監控系統的主要元件及其互動作用。監控伺服器向客戶端傳送請求,客戶端傳回感測器讀數。監控代理接收指令並執行檢查,然後傳回結果。感測器報告狀態給監控伺服器。

內容解密:

  1. 圖表說明:此圖表呈現了監控系統中的主要元件及其互動關係。
  2. 主要元件:圖表包含了監控伺服器客戶端監控代理感測器等關鍵元件。
  3. 互動關係:透過箭頭標示了各元件之間的互動流程,例如監控伺服器客戶端傳送請求,以及客戶端傳回感測器讀數
  4. 邏輯關係:圖表清晰地展示了各元件在系統中的角色及其相互依賴關係,有助於理解整個監控系統的工作原理。

程式碼實作

以下是一個簡單的範例程式碼,用於展示如何實作監控代理的功能:

import subprocess

def execute_check(sensor_command):
    try:
        result = subprocess.run(sensor_command, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
        return result.stdout.decode('utf-8'), result.returncode
    except Exception as e:
        return str(e), -1

def main():
    sensor_command = "your_sensor_command_here"
    output, return_code = execute_check(sensor_command)
    print(f"Output: {output}")
    print(f"Return Code: {return_code}")

if __name__ == "__main__":
    main()

內容解密:

  1. execute_check 函式的作用:此函式用於執行指定的 sensor_command 命令,並捕捉其輸出和傳回碼。
  2. subprocess.run 的使用:透過 subprocess.run 方法執行 shell 命令,並設定 stdoutstderrsubprocess.PIPE 以捕捉輸出。
  3. 錯誤處理:若執行命令過程中發生異常,將捕捉異常並傳回錯誤訊息和錯誤碼 -1
  4. main 函式的邏輯:在 main 函式中呼叫 execute_check 函式,並列印命令的輸出結果和傳回碼。
  5. 程式邏輯與設計考量:此程式碼範例展示瞭如何透過 Python 的 subprocess 模組與系統命令進行互動,用於實作簡單的監控代理功能,並且具備基本的錯誤處理機制。