第九章 表單和登陸爬蟲

Yishi發表於2015-07-04

當你想編寫更為複雜的網頁爬蟲的時候,你面臨的第一個問題也許會是:“如何獲取登陸頁面後的資訊?”,因為網路已經飛快地向互動、社交媒體、使用者生成內容方向發展。表格和登陸就成為這些型別網站幾乎不可或缺的主要組成。幸運的是,表單和登陸還是相對比較容易處理的。

截至當前,在我們之前的爬蟲示例中,絕大部分與伺服器的互動都是採用了HTTP GET方式去獲取資訊的。而在本章,我們所關注的是如何通過POST方式來向伺服器提交資訊用於儲存和分析。

表單給使用者提供了一種伺服器可以理解並使用的,提交POST請求的簡單方法。就像網址連結可以幫助使用者格式化GET請求,HTML表單可以幫使用者格式化POST請求。當然通過一小段程式碼,我們就可以輕鬆的通過爬蟲來建立並提交表單。

Python Requests類庫

儘管可以通過Python核心類庫來操作網頁表單,但有些時候更友好的程式碼可以讓生活更美好。當你開始使用urllib類庫的GET請求做更多事情的時候,放眼Python核心庫之外也許更有幫助。

Requests類庫在處理複雜HTTP請求、Cookies,請求頭等方面更為高效,此處便是Requests類庫的建立者Kenneth Reitz對Python核心工具的評價:

Python標準urlib2模組提供了你所需要的絕大多數HTTP功能,但是其API卻很垃圾。它的建立是用於不同時間以及不同網頁的,即使執行一個簡單的任務也需要大量的工作(甚至重寫方法等)。

事情並不應該如此,至少在Python中不應如此。

和其他Python類庫一樣,Requests類庫可以通過Python第三方類庫管理器如pip來安裝,或者直接下載原始碼進行安裝。

提交一個簡單表單

絕大多數網頁表單由部分HTML標籤、提交按鈕以及一個Action頁(具體執行表單任務的網址)所組成。HTML標籤一般含有文字標籤不過有時候也包含檔案上傳或其他非文字內容標籤。

大多數熱門網站在robots.txt檔案中遮蔽了針對登入表單的訪問(詳見附錄C),因此為了更安全的玩耍,我在pythonscraping.com上建立了一系列不同型別的表單和登入頁面。你可以執行你的爬蟲來訪問他們。其中最為簡單的表單地址為:http://bit.ly/1AGKPRU

完整表單程式碼如下:

<form method="post" action="processing.php">
First name: <input type="text" name="firstname"><br>
Last name: <input type="text" name="lastname"><br>
<input type="submit" value="Submit">
</form>

在此處需要注意的是:第一,兩個input標籤的名稱為firstnamelastname。這個非常重要,這些標籤的名稱決定了要POST到伺服器的表格變數的名稱。如果你要模擬提交表格,並提交自定義內容,那麼你需要確定你的變數名稱與標籤名稱匹配。

第二需要注意的是表格實際提交到的執行頁面是processing.php(絕對路徑為:http://bit.ly/1d7TPVk)。任何POST請求提交到表單都是上述(Action所指向的)地址,而不是包含表單的頁面地址。記住:HTML表單的主要目的僅是幫助網站訪問者格式化提交到處理頁面的請求。除非你在研究如何格式化請求,那麼你完全沒有必要被包含表單的頁面所困擾。

通過Requests類庫提交一個表單可以使用4行程式碼完成,包括import以及print內容函式。

import requests
params = {'firstname': 'Ryan', 'lastname': 'Mitchell'}
r = requests.post("http://pythonscraping.com/pages/files/processing.php", data=params)
print(r.text)

表單提交後,指令碼會返回頁面內容:

Hello there, Ryan Mitchell!

這個指令碼可以被應用到許多網際網路上的簡單表單。例如,登入O‘Reilly媒體通訊的表單,如下所示:

<form action="http://post.oreilly.com/client/o/oreilly/forms/
          quicksignup.cgi" id="example_form2" method="POST">
    <input name="client_token" type="hidden" value="oreilly" /> 
    <input name="subscribe" type="hidden" value="optin" /> 
    <input name="success_url" type="hidden" value="http://oreilly.com/store/
                 newsletter-thankyou.html" /> 
    <input name="error_url" type="hidden" value="http://oreilly.com/store/
                 newsletter-signup-error.html" /> 
    <input name="topic_or_dod" type="hidden" value="1" /> 
    <input name="source" type="hidden" value="orm-home-t1-dotd" />
    <fieldset>
        <input class="email_address long" maxlength="200" name=
                     "email_addr" size="25" type="text" value=
                     "Enter your email here" />
        <button alt="Join" class="skinny" name="submit" onclick=
                       "return addClickTracking('orm','ebook','rightrail','dod'
                                                );" value="submit">Join</button>
    </fieldset>
</form>

儘管第一次開起來上述程式碼有點嚇人,不過要記住在絕大多數案例中(我們稍後會講到其他例外),你只需要關注兩個事情:

新增需要的資訊並執行:

import requests
params = {'email_addr': 'ryan.e.mitchell@gmail.com'}
r = requests.post("http://post.oreilly.com/client/o/oreilly/forms/
                   quicksignup.cgi", data=params)
print(r.text)

在這個例子中,當你可以真正進入O'Reilly的郵件列表前,網站返回了另一個需要填寫的表單。不過同樣的原理可以應用到這個新表單上。不過,當你在家自己嘗試的時候,我需要你保證正當利用這個技術,而不是通過無效註冊給釋出者製造垃圾郵件。

單選、多選和其他輸入元件

很顯然,並不是所有表單都是由文字標籤加提交按鈕所組成。標準的HTML包含多種表單輸入欄位:單選按鈕、多選按鈕、下拉框等等。在HTML5中則擴充了滑動條、郵件、日期等。而且通過自定義Javascript還為輸入欄位帶來了無限可能的擴充,如顏色選擇、日曆等,只要程式設計師想去做就沒有不能實現的。

不管上述表單欄位看起來有多麼的複雜,你真正需要關心的只有兩個東西:元素的名稱和它的數值。元素的名稱可以通過檢視原始碼並找到name屬性來輕鬆辨別。元素數值的獲取則稍微有點麻煩,因為它可能在表單提交前瞬間通過Javascript生成。以相當獨特的顏色拾取器為例,它的數值就有可能是#F03030這樣的資料。

如果你不確定輸入欄位數值的格式,這裡有一些工具你可以用來跟蹤瀏覽器和網站之間的GETPOST請求。最好也是最直觀的辦法就是檢視GET請求,就像之前提到的檢視網站URL。如果網站的URL類似下面:

http://domainname.com?thing1=foo&thing2=bar

那你就知道對應的表單應該是如下形式的:

<form method="GET" action="someProcessor.php">
<input type="someCrazyInputType" name="thing1" value="foo" />
<input type="anotherCrazyInputType" name="thing2" value="bar" />
<input type="submit" value="Submit" />
</form>

對應的Python引數物件就是:

{'thing1':'foo', 'thing2':'bar'}

如圖9-1所示。

如果你被看起來很複雜的POST表單所困擾,同時還想檢視瀏覽器給伺服器,傳送的到底是什麼引數的話。那麼最簡單的方法就是使用瀏覽器的監視或開發者工具來檢視它們。

谷歌瀏覽器(Chrome)開發者工具可以通過【檢視】->【開發】->【開發者工具】來開啟。開發者工具列出了你瀏覽器與當前網站的所有訪問請求,並且可以檢視所有請求的訪問詳情。

提交檔案和圖片

儘管檔案上傳在網際網路中十分普遍,但是在網路爬蟲中卻不是十分常用。不過你可以自己編寫指令碼在你的網站上測試檔案上傳。不管怎樣,知道如何上傳檔案總是有用的。

這裡有一個練習用的檔案上傳表單:http://pythonscraping.com/pages/files/form2.html,該頁面的表單由如下程式碼組成:

<form action="processing2.php" method="post" enctype="multipart/form-data">
    Submit a jpg, png, or gif: <input type="file" name="image"><br>
    <input type="submit" value="Upload File">
</form>

除了<input>標籤的file型別欄位,它看起來和之前例子中的文字表單類似。幸運的是,通過Python的requests類庫操作表單也是極為類似的:

import requests
files = {'uploadFile': open('../files/Python-logo.png', 'rb')}
r = requests.post("http://pythonscraping.com/pages/processing2.php",  files=files)
print(r.text)

可以看到替換了之前的簡單字串,現在提交到表單欄位的數值(通過名稱uploadFile)是Python的檔案物件,通過open函式返回的。在這個例子中,我提交了一個儲存在本地的一個影像檔案,路徑為:../files/Python-logo.png,相對於當前執行的Python指令碼。

是的,就是這麼簡單!

處理登陸和Cookies

到目前為止,我們討論的表單都只允許你提交資訊到網站,或者檢視錶單提交後返回頁面所包含的資訊。這些與登陸表單,可以讓你保持登陸狀態訪問網站有何異同呢?

絕大多數現代網站使用Cookies來辨別登陸者的狀態。一旦網站驗證通過你的登陸資訊,它便會在你的瀏覽器中儲存一個Cookie值,其包含伺服器生成的Token、過期時間以及跟蹤資訊等內容。當你訪問網站任意頁面的時候,網站則通過Cookie來作為訪問者的授權標誌。在90年代中期,Cookie尚未大規模使用前,如何安全驗證並且跟蹤訪問者身份對於網站來說是一個大麻煩。

儘管Cookies對於網路開發者來說是一個非常好的解決方案,不過對於網路爬蟲卻造成了不小的困難。你可以不斷嘗試提交登陸表格,不過如果你沒有處理好登陸表單返回的Cookie資訊,那麼當你訪問下一個頁面的時候,你所表現的就好像從來沒有登陸過。

我建立了一個簡單的登陸表單:http://bit.ly/1KwvSSG(使用者名稱可以任意輸入,不過密碼必須是"password"

表單的處理地址為:http://bit.ly/1d7U2I1,其中包含了一個“主頁”的連結:http://bit.ly/1JcansT

如果你在沒有登陸的情況下訪問歡迎頁面或者個人資料頁面,你會得到一個錯誤資訊以及登陸操作指引。在個人資訊頁面,會檢查你瀏覽器Cookie資訊,判斷其是否是來自登陸頁面設定。

通過Requests類庫來跟蹤處理Cookies資訊非常簡單:

import requests

params = {'username': 'Ryan', 'password': 'password'}
r = requests.post("http://pythonscraping.com/pages/cookies/welcome.php", params)
print("Cookie is set to:")
print(r.cookies.get_dict())
print("-----------")
print("Going to profile page...")
r = requests.get("http://pythonscraping.com/pages/cookies/profile.php", 
                 cookies=r.cookies)
print(r.text)

此處我向歡迎頁面傳送了登陸引數,就像在登入表單處理時的一樣。我在得到返回的Cookies後,將其列印出來驗證,並且通過設定cookies引數傳送到個人資訊頁面。

對於簡單情況上述方式表現挺好,不過如果你要處理更為複雜的網站,例如在沒有提醒的情況下頻繁更新Cookies資訊,或者你一開始根本就不去考慮Cookies操作。使用Requests類庫的session函式在這種例子中表現更好:

import requests

session = requests.Session()

params = {'username': 'username', 'password': 'password'}
s = session.post("http://pythonscraping.com/pages/cookies/welcome.php", params)
print("Cookie is set to:")
print(s.cookies.get_dict())
print("-----------")
print("Going to profile page...")
s = session.get("http://pythonscraping.com/pages/cookies/profile.php")
print(s.text)

在這個例子中,session物件(通過執行requests.Session()獲得)負責跟蹤處理session資訊,如Cookies、請求頭,甚至包括在HTTP上執行的協議如HTTPAdapters。

Requests是一個非常棒的類庫,其健全的功能甚至不需要程式設計師去思考如何編寫,第二個類似的類庫可能就是Selenium(我們將在第十章詳述)。儘管將所有工作交給類庫去處理省事省力,但是非常重要的就是搞清楚Cookies是什麼以及在編寫網路爬蟲時如何去正確操作Cookies。它會節省很多痛苦除錯的時間,或者幫你弄清楚為何網站有奇怪的表現。


不斷更新中……

2015年07月08日最後更新


相關文章