# Requests 模組 [![hackmd-github-sync-badge](https://hackmd.io/t4cI_xkxReOPUekwNpPkcg/badge)](https://hackmd.io/t4cI_xkxReOPUekwNpPkcg) ![image](https://hackmd.io/_uploads/rkDoshmaxe.png) PyPI 中的 requests 模組是一個廣受歡迎的第三方套件,專門用來處理 HTTP 請求。這個套件讓您能輕易地發送 HTTP/1.1 請求,方便地與網路上的資源進行互動。 :::info **內建的 urllib 套件** Python 標準庫中的 urllib.request 與 urllib.response 模組,也能處理 HTTP 請求,相較 requests 模組而言比較低階,適合那些對底層操作更感興趣的使用者。 若需要進行 URL 的操作,如解析、修改、構造 URL 的情況下,可以使用 urllib.parse 進行處理。 ::: ## 安裝模組 請先為專案建立虛擬環境,然後安裝 request 模組 ```bash= py -m venv env # 建立 env 虛擬環境 env\Scripts\activate # 啟動 env 虛擬環境 (env) pip install requests # 安裝 requests 套件 (env) pip list # 檢視在 env 虛擬環境中裝了那些套件? (env) deactivate # 離開 env 虛擬環境 ``` ## HTTP Request HTTP/1.1 協定中定義了 9 種方法 (Method) ![image](https://hackmd.io/_uploads/B1gXo6GPp.png =600x) 對於最常被使用的 4 種方法,requests 模組提供了專屬函式: 1. `get(url, params=None, **kwargs)`:發送 GET 請求。 2. `post(url, data=None, json=None, **kwargs)`:發送 POST 請求,可傳遞表單或 JSON 資料。 3. `put(url, data=None, **kwargs)`:發送 PUT 請求。 4. `delete(url, **kwargs)`:發送 DELETE 請求。 ![crud-operations](https://hackmd.io/_uploads/ByyOSvMPp.png) 其餘的 HTTP 請求方法(head, connect, options, trace, patch),則要用 5. `request(method, url, **kwargs)` 這個方法可以指定 method 參數為任意的 http method。 > request.get() 與 request.post() 後面有獨立的小節說明其細部內容。 ### 使用瀏覽器中的 DevTools 觀察封包 1. 在瀏覽器按下 F12,切換到 Network 頁籤 2. 在瀏覽器上輸入關鍵字進行查詢 3. 點選任一連線後,觀察右側的 Headers 頁籤 ![image](https://hackmd.io/_uploads/SkhpAozQkx.png) ## HTTP Response 上述所有的 HTTP 請求方法都會傳回一個 Response 物件,它代表了伺服器的回應,我們必須解析這個物件來取得所需的內容。 ![image](https://hackmd.io/_uploads/H1DghvMv6.png) Response 物件的常用屬性: 1. url:請求的 URL。 2. status_code:HTTP 回應的狀態碼 (Status Code)。 3. encoding:網頁編碼。 4. headers:包含 HTTP 回應標頭 (Headers) 的字典。 5. cookies:伺服器回傳的 Cookies 資訊。 6. text:HTTP 回應的 Body 內容,以純文字 (字串) 形式呈現。 7. Content-Type:文件的媒體類型,也就是 MIME類型。。 :::info **Content-Type 標頭** HTTP 的 Content-Type 標頭對於網路通訊非常重要,它確保了資料能夠被正確地解析和處理。想像一下,如果你寄送一個包裹,上面沒有寫清楚裡面是什麼,收件人可能會不知道該怎麼打開,甚至誤以為是其他東西。 ![mime2](https://hackmd.io/_uploads/SyAnRXpTxx.png) ::: Response 物件的常用方法: 1. json():解析 JSON 格式的回應內容,並將其轉換為 Python 的字典或列表。 2. raise_for_status(): 只要伺服器回應的狀態碼表示錯誤(4xx 或 5xx 範圍),就會引發 requests.exceptions.HTTPError 異常。這是一個很好的習慣,可以在請求失敗時快速中斷程式。 :::info [Httpbun 網站](https://httpbun.com/)可讓我們測試各種 HTTP 的請求與回應。 ::: **範例程式碼:** ```python= import requests # 1. 發送 GET 請求 # https://httpbun.com/get 會回應伺服器收到的 GET 請求內容 response = requests.get('https://httpbun.com/get') # 2. 檢查請求是否成功 try: response.raise_for_status() # 若狀態碼非 2xx,則拋出異常 print("Request 成功") except requests.exceptions.HTTPError as err: print(f"請求失敗: {err}") exit(1) # 使用 exit(1) 表示異常結束並返回錯誤碼 # 3. 顯示回應的物件屬性 print(f'網址:{response.url}') print(f'網頁編碼:{response.encoding}') response.encoding = 'utf-8' # 建議手動設定為 utf-8 以避免亂碼 print('標頭資訊', response.headers) print(response.headers['content-type']) print('cookies 資訊') for key, value in response.cookies.items(): print(f"{key}: {value}") print('網頁內容') print(response.text) print() # 4. GET 請求 + 自訂標頭 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' } response = requests.get('http://httpbun.com/get', headers=headers) print(response.text) # 某些網站會檢查 User-Agent,若沒有此標頭會拒絕存取 # response = requests.get('https://www.kingstone.com.tw/', headers=headers) # print(response.text[:400]) print() # 5. GET 請求 + 傳遞參數 payload = {'id': '6943'} # 等同於在網址後加上 ?id=6943 response = requests.get('https://www.vscinemas.com.tw/vsweb/film/detail.aspx', params=payload) print(response.text[:200]) # 只顯示前 200 個字元 print() # 6. GET 請求 + 圖片存檔 response = requests.get('https://httpbun.com/image/svg') with open('demo.svg', mode='wb') as file: file.write(response.content) # 7. POST 請求 + form-data data_dict = {'key1': 'value1', 'key2': 'value2'} response = requests.post('https://httpbun.com/post', data=data_dict) print(response.text) print() # 8. POST 請求 + json response = requests.post('https://httpbun.com/post', json=data_dict) print(response.text) print() ``` :::warning HTTP 可以同時發送 POST 和 GET 的複合型請求,即可以在同一請求中使用 data 與 params 或者使用 json 與 params。在這種情況下,params 會被視為 GET 參數附加在 URL 上,而 data 或 json 則是 POST 請求主體的一部分。然而,如果同時指定了 data 和 json,則 data 將被忽略,因為這兩者都是 POST 請求的主體資料。 ::: #### 更完整的錯誤與異常處理 網路連線充滿了不確定性。一個穩健的程式必須能處理各種預期外的網路問題。requests 在遇到不同問題時會拋出特定的異常。 * requests.exceptions.HTTPError: 由 response.raise_for_status() 觸發,當 HTTP 狀態碼為 4xx 或 5xx 時拋出。 * requests.exceptions.ConnectionError: 網路連線層級的問題(如 DNS 查詢失敗、連線被拒絕)。 * requests.exceptions.Timeout: 請求在指定時間內未收到伺服器回應。 * requests.exceptions.RequestException: requests 所有異常的基底類別,可以用來捕捉所有相關錯誤。 ```python= import requests try: # 一個不存在或無法連線的網址 response = requests.get('http://a.very.non.existent.domain.com', timeout=2) response.raise_for_status() # 檢查 HTTP 錯誤狀態碼 except requests.exceptions.Timeout: print("請求超時,伺服器沒有在指定時間內回應。") except requests.exceptions.ConnectionError as e: print(f"連線失敗,請檢查網路連線或網址是否正確: {e}") except requests.exceptions.HTTPError as e: print(f"HTTP 請求失敗,狀態碼錯誤: {e}") except requests.exceptions.RequestException as e: # 捕捉所有其他的 requests 錯誤 print(f"發生未預期的錯誤: {e}") ``` ## 發出 GET 請求:request.get() GET 請求傳送的資訊會在 URL 裡出現,因此僅適合傳送較少且沒有資安疑慮的文字資訊。 ![image](https://hackmd.io/_uploads/SJkQEaGDa.png) requests.get() 是 requests 模組提供的用於發送 HTTP GET 請求的方法。它可以向指定的 URL 發送 GET 請求,並返回一個伺服器回應的 Response 物件。 ![image](https://hackmd.io/_uploads/ByzG7azw6.png) requests.get() 方法常用參數的如下: * url(網址):欲訪問的 URL。 * params(參數):附加到 URL 的查詢參數,可為 dict, tuple, list 或 str。 * headers(標頭):請求的標頭信息,以 dict 形式提供。 * cookies(Cookie):要附加到請求的 Cookie,可以是 dict 或 CookieJar 類型。 * proxies(代理):若網站會限制單位時間同一 IP 的請求次數,可設定代理來繞過此限制。proxies 參數是一個字典。 * auth(身份驗證):HTTP 基本身份驗證的 tuple (username, password)。 * timeout(超時):請求的超時時間(秒),可以是連接超時和讀取超時的 tuple (connect_timeout, read_timeout)。 * allow_redirects(允許重定向):布林值,用於指定是否允許重定向,預設為 True。 * stream(串流處理): 布林值,預設為 False。設為 True 時,不會立即下載整個回應內容,適合處理大檔案下載或串流資料。 範例程式碼: ```python= import requests url = 'https://httpbun.com/get' # 準備要傳遞的參數與標頭 params = {'name': 'John', 'phone': '0912-345678'} headers = {'User-Agent': 'My-Python-Script/1.0'} timeout_seconds = 5 # 設定超時為 5 秒 try: # 使用 requests.get() 方法並提供常用參數 response = requests.get( url, params=params, headers=headers, timeout=timeout_seconds, ) response.raise_for_status() # 顯示結果 print("狀態碼:", response.status_code) print("回應內容:", response.json()) except requests.exceptions.RequestException as e: print(f"請求失敗: {e}") ``` :::success 免費代理伺服器 - http 代理可參考 https://free-proxy-list.net/ - https 代理可參考 https://www.sslproxies.org/ ::: #### 代理 (proxies) 參數說明 proxies 字典的鍵 (key) http 或 https,是根據你「目標 URL」的協定來決定的,而不是代理伺服器本身的協定。 * 當你請求 http://example.com 時,requests 會去 proxies 字典裡尋找鍵為 'http' 的代理伺服器位址來使用。 * 當你請求 https://example.com 時,requests 則會尋找鍵為 'https' 的代理伺服器位址。 大部分情況下,同一個代理伺服器可以同時處理 HTTP 和 HTTPS 的請求。因此,最常見的作法是將 http 和 https 都指向同一個代理伺服器位址,這樣你的程式無論要爬取哪種網站,都能透過該代理進行。 範例程式碼: ```python= import requests # 假設這是一個有效的代理伺服器 IP 與 Port proxy_ip_port = '202.152.44.18:8081' # 設定代理 # 即使代理伺服器本身是 http,它也能轉發 https 請求 # 所以 http 和 https 兩個 key 都設定成同一個代理位址是最常見的作法 proxies = { 'http': f'http://{proxy_ip_port}', 'https': f'http://{proxy_ip_port}', } # 註:代理伺服器本身的協定也可能是 https 或 socks5, # 例如 'https': 'https://user:pass@host:port' # 或 'https': 'socks5://user:pass@host:port' (需額外安裝 pysocks) # 目標1:一個 HTTPS 網站 target_https_url = 'https://httpbun.com/ip' try: # 因為目標是 https://...,requests 會使用 proxies['https'] 的值 print(f"正在透過代理 {proxy_ip_port} 請求 HTTPS 網站...") response = requests.get(target_https_url, proxies=proxies, timeout=10) response.raise_for_status() # 回應的 origin 欄位會顯示代理伺服器的 IP,而不是你自己的 IP print("請求成功,回應來源 IP:", response.json()['origin']) except requests.exceptions.RequestException as e: print(f"透過代理請求失敗: {e}") ``` ## 發出 POST 請求:request.post() | 表單使用 GET 請求| 表單使用 POST 請求 | | -------- | -------- | |![image](https://hackmd.io/_uploads/Bk7hVaMPa.png)|![image](https://hackmd.io/_uploads/SyVESafD6.png)| requests.post 方法用於發送 HTTP POST 請求,允許您提交數據到指定的 URL。 > POST 請求傳送的資訊不會在 URL 裡出現,因此適合傳送大量資訊。但若包含敏感資訊(如密碼),則務必確認伺服器使用 HTTPS 加密傳輸協定。 requests.post() 方法常用的參數如下: * url: 要訪問的 URL,這是必需的參數。 * data: 要傳遞的資料,通常用於提交表單資料。可以是 dict、tuple、list或文件物件。 * json: 要傳遞的 JSON 資料。如果同時指定了 data 和 json,則 data 將被忽略,並且 Content-Type 將被設置為 "application/json"。 * files: 要上傳的文件,以字典描述 * key 是文件欄位(file field)的名稱,通常是 file, image * value 是代表檔案資料的 tuple,包含3個元素: * file_name:要上傳的文件的名稱,通常是字串。 * file_object:檔案物件,通常是使用 open 函數打開的文件。 * content_type:文件的媒體類型(MIME type),通常是字串。這個部分是可選的,如果省略,requests 會嘗試猜測文件類型。 * 其他參數如 headers, cookies, auth, timeout 等與 get() 相同。 :::info **MIME 類型** Content-Type 標頭的值就是 MIME (Multipurpose Internet Mail Extensions)。 MIME 類型最初是為了在電子郵件中傳輸非文字資料(如圖片、音訊、視訊、應用程式檔案等)而設計的標準。後來,它被廣泛應用於其他網路協議,包括 HTTP,成為描述檔案或資料格式的標準方式。 ![mime3](https://hackmd.io/_uploads/rJBvAm66ee.png) MIME 類型通常由兩部分組成:type/subtype。以下是一些常見的例子: * text/html:HTML 檔案。 * text/plain:純文字檔案。 * image/jpeg:JPEG 圖檔。 * image/png:PNG 圖檔。 * image/gif:GIF 圖檔。 * audio/mpeg:MPEG 音訊檔案。 * video/mpeg:MPEG1 視訊檔案。 * video/mp4:MPEG4 視訊檔案。 * application/json:JSON 格式的檔案。 * application/xml:XML 檔案。 * application/zip:ZIP 壓縮檔案。 * application/pdf:Adobe PDF 檔案。 * application/ms-excel:MS Excel 檔案。 * application/ms-powerpoint:MS powerpoint 檔案。 ::: 範例程式碼: ```python= import requests url = 'https://httpbun.com/post' data = {'name': 'John', 'phone': '0912-345678'} # 要傳遞的資料 headers = { # 自訂標頭 'Content-Type': 'application/x-www-form-urlencoded', 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', } cookies = {'session_id': 'abc123'} # 要附加的 Cookie auth = ('username', 'password') # HTTP 基本身份驗證 timeout = (5, 10) # 連接超時 5 秒,讀取超時 10 秒 allow_redirects = True # 是否允許重定向 stream = False # 是否立即下載回應內容 # 產生要上傳的檔案 with open('README.md', 'w') as file: file.write('# README\n\nThis is a test file.') # 以字典描述要上傳的 'README.md' 檔案 files = {'file': ('README.md', open('README.md', 'rb'), 'text/plain')} # 使用 requests.post() 方法發送 POST 請求 response = requests.post( url, data=data, # 要傳遞的資料 json=None, # 在這個例子中未使用 json headers=headers, # 自訂標頭 cookies=cookies, # 附加 Cookie auth=auth, # HTTP 基本身份驗證 timeout=timeout, # 設定連接與讀取超時 allow_redirects=allow_redirects, # 是否允許重定向 stream=stream, # 是否立即下載回應內容 files=files, # 上傳檔案 ) # 顯示結果 print("狀態碼:", response.status_code) print("文字內容:", response.text) ``` ## Session 物件:保持連線狀態與效能優化 當您需要與同一個網站進行多次請求時(例如:登入後,再爬取多個會員頁面),使用 Session 物件是至關重要的。 為何重要? * Cookie 持續性:Session 物件會自動處理並在後續的請求中發送 Cookies。這對於處理需要登入或有使用者狀態的網站來說是必要的。 * 效能提升:Session 會重複使用底層的 TCP 連線 (Connection Pooling),避免了每次請求都重新建立連線的開銷,大幅提升爬取效能。 * 預設標頭:可以在 Session 物件上設定一次標頭 (headers) 或其他參數,後續所有透過此 Session 發出的請求都會自動帶上這些設定。 ```python= import requests # 建立一個 Session 物件 s = requests.Session() # 在 Session 層級設定標頭,之後所有請求都會使用此 User-Agent s.headers.update({'User-Agent': 'MyAwesomeBot/1.0'}) # 第一次請求,例如登入網站 # 網站回傳的 Set-Cookie 會被 Session 自動儲存 login_data = {'username': 'test', 'password': 'password123'} s.post('https://httpbun.com/post', data=login_data) print("已發送登入請求...") # 第二次請求,存取需要登入才能看的頁面 # Session 會自動帶上之前收到的 Cookie print("\n正在請求 Cookies 頁面...") response = s.get('https://httpbun.com/cookies') # 可以看到伺服器收到了我們 Session 保存的 Cookie print("伺服器收到的 Cookies:") print(response.json()) ``` ## 網路爬蟲的倫理與最佳實踐 在開始撰寫爬蟲程式之前,建立負責任的爬蟲觀念至關重要。這不僅是為了尊重目標網站,也是為了保護自己免於不必要的麻煩。 1. 遵守 robots.txt:robots.txt 是網站給爬蟲的君子協定,說明了哪些頁面不希望被爬取。雖然 requests 不會自動遵守,但開發者應主動檢查並遵循其規則。一個網站的 robots.txt 通常位於 `https://<domain>/robots.txt`。 2. 設定合理的請求間隔:在迴圈中爬取資料時,務必使用 time.sleep() 在每次請求之間加入延遲(例如 1-3 秒),模擬人類使用者的行為,避免對伺服器造成過大壓力。過於頻繁的請求可能被視為惡意攻擊,導致您的 IP 被封鎖。 3. 優先使用 API:如果網站提供公開的 API (Application Programming Interface),應優先使用 API 來獲取資料。這通常比直接爬取網頁更有效率、更穩定,也更符合網站的期望。 4. 避免離峰時間:如果需要進行大規模爬取,盡量選擇網站的離峰時間(例如凌晨)進行,以減少對其他正常使用者的影響。 ## 爬蟲概念 「網路爬蟲」是一個透過程式「自動抓取」網站資料的過程。本單元以靜態網頁為對象,其基本流程如下: 1. 確認爬蟲對象,取得其網址。 2. 使用 requests 將網頁的 HTML 原始碼抓下來。 3. 使用正規表示式 (Regular Expression) 或 BeautifulSoup、lxml 等工具分析網頁,抓出感興趣的內容。 4. 將抓取的資訊存入資料庫、JSON 檔案或 Python 物件中,供後續使用。 以下範例示範使用正規表示式進行爬蟲,請先著眼於 requests 扮演的角色與爬蟲的架構。 ### 爬蟲範例1:抓取網頁中的 Email 與姓名 ```python= import re import requests URL = "https://csie.ncut.edu.tw/content.php?key=86OP82WJQO" headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36' } try: response = requests.get(URL, headers=headers, timeout=10) response.raise_for_status() response.encoding = 'utf-8' # 確保中文內容正確解碼 # 描述 Email 的正規表示式 email_pattern = re.compile(r'\b[\w.%+-]+@[\w.-]+\.[a-zA-Z]{2,}\b') # 描述姓名的正規表示式 name_pattern = re.compile(r'<div class="member_name"><a href="[^"]+">([^<]+)</a>') # 尋找所有 Email emails = sorted(list(set(email_pattern.findall(response.text)))) print("--- 找到的 Email ---") for email in emails: print(email) # 尋找所有姓名 names = name_pattern.findall(response.text) print("\n--- 找到的姓名 ---") for name in names: print(name.strip()) except requests.exceptions.RequestException as e: print(f"爬取失敗: {e}") ``` ### 爬蟲範例2:抓取免費 Proxy 網站的 IP ```python= import requests import re from pprint import pprint headers = { 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/141.0.0.0 Safari/537.36', 'accept-language': 'zh-TW,zh;q=0.9', } response = requests.get('https://free-proxy-list.net/zh-tw/ssl-proxy.html', headers=headers) print(response.status_code) proxy_ips = re.findall(r'\d+\.\d+\.\d+\.\d+:\d+', response.text) print(f"共取得 {len(proxy_ips)} 個 Proxy IP") pprint(proxy_ips) ``` ### 爬蟲範例3 (進階): 本程式是一個功能非常完整的代理 IP 驗證器,程式碼邏輯相對複雜,包含了檔案讀寫、集合操作、錯誤處理的迴圈等。 程式碼分3部分: 1. 使用正規表示式從網站取得 Proxy IP (34行前) 2. 測試 Proxy IP 是否有效,並保留至檔案 (34-70行) 3. 自檔案隨機取用有效的 Proxy IP,對目標網站進行請求 (70行後) ```python= # 透過 Proxy IP 來源網站取得 Proxy IP,並測試 Proxy IP 是否有效 import json import random import re from pprint import pprint import requests SOURCE = "https://www.sslproxies.org/" # Proxy IP 來源網站 (https) # SOURCE = "https://free-proxy-list.net/zh-tw/" # Proxy IP 來源網站 (http) TARGET = "https://httpbun.com/get" # 測試網站 (https) # 設定 HTTP 標頭 headers = { 'Accept-Language': 'zh-TW,zh;q=0.9,en-US;q=0.8,en;q=0.7,ja;q=0.6,zh-CN;q=0.5,la;q=0.4', 'User-Agent': 'Mozilla/5.0 (Windows NT10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/131.0.0.0 Safari/537.36', } # 從 valid_proxy.json 讀取之前的有效 Proxy IP try: with open('valid_proxy.json', 'r') as f: valid_ips = set(json.load(f)) except FileNotFoundError: valid_ips = set() # 取得新的 Proxy IP response = requests.get(SOURCE, headers=headers) # 取得網頁內容 # 使用正規表達式取得 Proxy IP:「\d+」代表數字一個位數以上 proxy_ips = re.findall(r'\d+\.\d+\.\d+\.\d+:\d+', response.text) print(f"自 {SOURCE} 共取得 {len(proxy_ips)} 個 Proxy IP") # valid_ips 加入新取得的 Proxy IP valid_ips.update(proxy_ips) # 測試 valid_ips 中的 Proxy IP 是否有效 for ip in list(valid_ips): # 將集合轉換為列表進行迭代 try: response = requests.get( TARGET, headers=headers, proxies={'https': ip, 'http': ip}, timeout=5, ) if response.status_code == 200: data = response.json() # 解析 JSON 格式的回應內容 # 檢查回應 proxy_ip = ip.split(':')[0] # 只取 IP 部分 if 'origin' in data and proxy_ip in data['origin']: print(f"使用 Proxy IP:{ip} 成功(驗證 IP 切換)") else: valid_ips.remove(ip) print(f"使用 Proxy IP:{ip} 失敗(未切換 IP)") else: # 移除失效的 Proxy IP valid_ips.remove(ip) print(f"使用 Proxy IP:{ip} 失敗") except Exception as e: valid_ips.remove(ip) print(f"使用 Proxy IP:{ip} 失敗, 錯誤訊息:{e}") # 輸出有效的 Proxy IP print("\n有效的 Proxy IP:") if valid_ips: pprint(valid_ips) print(f"共 {len(valid_ips)} 個") # 將有效的 Proxy IP 寫入檔案 with open('valid_proxy.json', 'w') as f: json.dump(list(valid_ips), f) # 使用範例:從 valid_proxy.json 讀取有效的 Proxy IP 並隨機選擇一個 Proxy IP 進行測試 if valid_ips: ip = random.choice(list(valid_ips)) print(f"\n隨機選擇的 Proxy IP:{ip}") try: response = requests.get( TARGET, headers=headers, proxies={'https': ip, 'http': ip}, timeout=5 ) if response.status_code == 200: data = response.json() # 解析 JSON 格式的回應內容 # 檢查回應 proxy_ip = ip.split(':')[0] # 只取 IP 部分 if 'origin' in data and proxy_ip in data['origin']: print("正確使用 Proxy IP,以下是回應內容:") else: print("Proxy IP 未切換,以下是回應內容:") pprint(response.json()) # 輸出網頁內容 except Exception as e: print(f"使用 Proxy IP:{ip} 失敗, 錯誤訊息:{e}") else: print("沒有有效的 Proxy IP 可供選擇") ``` 從上述的爬蟲範例觀察,在簡單的爬蟲時,正規表示式還是很好用的。也因為正規表示式擅長文字的比對,因此在表單欄位的驗證,甚至基因的定位也會用到它,讓我們接下來一起學習 👉 [正規表示式](https://hackmd.io/@peterju/Hycfbj-O3) 吧。 --- ## HTTP Request 標頭欄位詳解 (補充資料) 當我們嘗試去看 HTTP Request Headers 時,會看到很多不明所以的標頭欄位,這可能是因為瀏覽器與伺服器在首次連線時進行了協商,或是特定服務(如 AWS 的追蹤系統)所加入的。以下就可能造成疑惑的標頭欄位進行說明: * `"Accept":"text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7"` 這個 "Accept" 標頭告訴伺服器瀏覽器優先接受 HTML 內容,其次是 XHTML、XML、三種圖片格式,接著是通用的任何內容,最後是簽名的內容交換格式。質量因子(q值)用於定義這些媒體類型的相對優先順序。 * text/html: 表示客戶端希望接受 HTML 內容,這是瀏覽器顯示網頁的主要格式。 * application/xhtml+xml: 表示客戶端能夠接受 XHTML(可擴展超文本標記語言) 格式的內容。 * application/xml;q=0.9: 表示客戶端能夠接受 XML 格式的內容,並賦予其質量因子 0.9,這表示它的優先順序較低。 * image/avif, image/webp, image/apng: 表示客戶端能夠接受三種圖片格式,分別是 AVIF、WebP 和 APNG。 * */*;q=0.8: 表示客戶端能夠接受任何類型的內容,並賦予其質量因子 0.8,這表示它的優先順序相對較低,當與其他可接受的內容發生衝突時,這個通配符類型的內容會被視為次要選擇。 * application/signed-exchange;v=b3;q=0.7: 表示客戶端能夠接受簽名的內容交換格式,並賦予其質量因子 0.7,這表示它的優先順序較低。 * `"Accept-Encoding": "gzip, deflate, br"` 表示客戶端支援這三種壓縮算法,而伺服器可以根據這些資訊,選擇適合客戶端的壓縮算法來進行資料傳輸,以提高效率和節省頻寬。 瀏覽器的標頭內容可能會在首次與伺服器建立連線時進行雙向的協商和交握,這些協商和交握的結果可能會影響後續的連線行為,包括是否添加特定的標頭。 舉例來說,當瀏覽器首次訪問一個網站時,伺服器可以回應 HSTS 標頭,告知瀏覽器在未來的某段時間內必須使用安全的 HTTPS 連線。這樣,即使使用者在地址欄中輸入的是不安全的 HTTP URL,瀏覽器也會自動將其升級為 HTTPS。這種協商的行為是由伺服器發起的,並在後續的連線中生效。 類似地,有些標頭(例如 X-Amzn-Trace-Id)可能是在首次連線時由伺服器產生和返回,並在之後的請求中一直存在。這通常與特定的分佈式追蹤系統或其他類型的服務相關聯。 所以若你在標頭中看到不熟悉的屬性,或許就是因為伺服器要求的關係。 * `"Upgrade-Insecure-Requests": "1"` 客戶端瀏覽器在發送請求時附加,用以指示伺服器它可以升級非安全請求(HTTP)到安全請求(HTTPS)。 * `"X-Amzn-Trace-Id": "Root=1-65858fc2-50c4e4447d61a3501e2a7e5b"` 這個標頭是由 AWS X-Ray 或 AWS CloudWatch 之類的服務產生並添加到 HTTP 請求中的,使得在整個 AWS 分佈式系統中能夠追蹤特定的請求。分析這個標識符可以提供有關請求的追蹤信息,例如請求的開始和結束時間,以及請求在分佈式系統中的流程。 以下是一系列安全性(Security)相關的 HTTP 標頭,它們主要用於提高網頁的安全性和保護使用者隱私。 * `"Sec-Ch-Ua": ""Not_A Brand";v="8", "Chromium";v="120", "Brave";v="120""`` 這個標頭用於描述瀏覽器的使用者代理(User Agent)的安全性特性。 * Not_A Brand 表示瀏覽器不屬於特定的品牌,這可能是為了隱藏具體的瀏覽器識別信息。 * v="8" 表示這個標頭中提到的瀏覽器版本是 8。 * Chromium;v="120" 表示瀏覽器是基於 Chromium 項目的,並且版本號是 120。 * Brave;v="120" 表示瀏覽器同時也標明自己是 Brave 瀏覽器,版本號是 120。 * `"Sec-Ch-Ua-Mobile": "?0"` 這個標頭指示瀏覽器不是行動裝置(Mobile Device)。"?0" 的值表示不是行動裝置。 * `"Sec-Ch-Ua-Platform": ""Windows""` 這個標頭指示瀏覽器運行在 Windows 平台。 * `"Sec-Fetch-Dest": "document"` 這個標頭指示瀏覽器的 Sec-Fetch 機制中,這個請求的目的是獲取文檔(document)。 * `"Sec-Fetch-Mode": "navigate"` 這個標頭指示瀏覽器的 Sec-Fetch 機制中,這個請求的模式是導航(navigate),通常表示這是一個鏈接或地址欄輸入導致的請求。 * `"Sec-Fetch-Site": "none"` 這個標頭指示瀏覽器的 Sec-Fetch 機制中,這個請求的站點(site)是 "none",表示不是從同源站點發出的請求。 * `"Sec-Fetch-User": "?1"` 這個標頭指示瀏覽器的 Sec-Fetch 機制中,這個請求是否包含使用者身份信息。"?1" 的值表示這個請求包含使用者身份信息。 * `"Sec-Gpc": "1"` 這個標頭是 Secure Policy 特性(Sec-GPC)的一部分,它是瀏覽器中的一個標識符,用於支援更高級的安全和隱私功能。 ## 參考 - [Requests 函式庫](https://steam.oxxostudio.tw/category/python/spider/requests.html) - [強大的 HTTP 請求套件 requests](https://quantpass.org/python_requests/) - [requests發送http請求](https://www.youtube.com/watch?v=xua4Gno7xLo) - [Postman Echo](https://postman-echo.com/)