最近要用 Python 模拟表單上傳檔案,搜尋了一下常見的解決方案。
如果隻是要模拟送出一個不包含檔案字段的表單,實作起來是很簡單的,但涉及到檔案上傳就有一點小複雜,需要自己對檔案進行編碼,或者使用第三方子產品。
不過,由于 PycURL 需要用到 curl,在 Windows 下安裝可能會有點麻煩,除 PycURL 外,也有一些其它實作 POST 檔案上傳的方式,比如這兒的 2 樓有人貼出了一個将檔案進行編碼之後再 POST 的方法,另外還有MultipartPostHandler、urllib2_file、poster等第三方子產品。但 MultipartPostHandler 這個子產品似乎比較老了,urllib2_file 我試用了一下遇到錯誤沒有成功,這兒我想介紹的是另外一個第三方子產品 poster。
如果機器上安裝了 Python 的setuptools,可以通過下面的指令來安裝 poster: sudo easy_install poster # test_client.py
from poster.encode import multipart_encode
from poster.streaminghttp import register_openers
import urllib2
# 在 urllib2 上注冊 http 流處理句柄
register_openers()
# 開始對檔案 "DSC0001.jpg" 的 multiart/form-data 編碼
# "image1" 是參數的名字,一般通過 HTML 中的 标簽的 name 參數設定
# headers 包含必須的 Content-Type 和 Content-Length
# datagen 是一個生成器對象,傳回編碼過後的參數,這裡如果有多個參數的話依次添加即可
datagen, headers = multipart_encode({"image1": open("DSC0001.jpg", "rb")})
# 建立請求對象
request = urllib2.Request("http://localhost:5000/upload_image", datagen, headers)
# 實際執行請求并取得傳回
print urllib2.urlopen(request).read()
很簡單,檔案就上傳完成了。
其中那個 register_openers() 相當于以下操作:
from poster.encode import multipart_encode
from poster.streaminghttp import StreamingHTTPHandler, StreamingHTTPRedirectHandler, StreamingHTTPSHandler
handlers = [StreamingHTTPHandler, StreamingHTTPRedirectHandler, StreamingHTTPSHandler]
opener = urllib2.build_opener(*handlers)
urllib2.install_opener(opener)
另外,poster 也可以攜帶 cookie,比如:
opener = poster.streaminghttp.register_openers()
opener.add_handler(urllib2.HTTPCookieProcessor(cookielib.CookieJar()))
params = {'file': open("test.txt", "rb"), 'name': 'upload test'}
datagen, headers = poster.encode.multipart_encode(params)
request = urllib2.Request(upload_url, datagen, headers)
result = urllib2.urlopen(request) 如果在上傳過程中遇到Authorization問題:可以自己定義register_openers()方法加入使用者驗證的handler,例如:
from poster.encode import multipart_encode
from poster.streaminghttp import StreamingHTTPHandler, StreamingHTTPRedirectHandler, StreamingHTTPSHandler
# create a password manager
password_mgr = urllib2.HTTPPasswordMgrWithDefaultRealm()
# Add the username and password.
# If we knew the realm, we could use it instead of None.
# add_password('realm','url','username','password')
password_mgr.add_password('realm', 'url', 'username', 'password')
handler = urllib2.HTTPBasicAuthHandler(password_mgr)
handlers = [handler,StreamingHTTPHandler, StreamingHTTPRedirectHandler, StreamingHTTPSHandler]
opener = urllib2.build_opener(*handlers)
urllib2.install_opener(opener)
或者直接将上面的内容加到上傳檔案的代碼中。
今天突然圖檔不能上傳了,發現上面的認證方式失效了。很是奇怪。于是再來一種解決方案。
其實問題就是解決python環境下HTTP Basic Authorization 的問題
在HTTP中,基本認證是一種用來允許Web浏覽器或其他用戶端程式在請求時提供使用者名和密碼形式的身份憑證的一種登入驗證方式。
在發送之前是以使用者名追加一個冒号然後串接上密碼,并将得出的結果字元串再用Base64算法編碼。例如,提供的使用者名是Aladdin、密碼是open sesame,則拼接後的結果就是Aladdin:open sesame,然後再将其用Base64編碼,得到QWxhZGRpbjpvcGVuIHNlc2FtZQ==。最終将Base64編碼的字元串發送出去,由接收者解碼得到一個由冒号分隔的使用者名和密碼的字元串。
雖然對使用者名和密碼的Base64算法編碼結果很難用肉眼識别解碼,但它仍可以極為輕松地被計算機所解碼,就像其容易編碼一樣。編碼這一步驟的目的并不是安全與隐私,而是為将使用者名和密碼中的不相容的字元轉換為均與HTTP協定相容的字元集。
例子
這一個典型的HTTP用戶端和HTTP伺服器的對話,伺服器安裝在同一台計算機上(localhost),包含以下步驟:
用戶端請求一個需要身份認證的頁面,但是沒有提供使用者名和密碼。這通常是使用者在位址欄輸入一個URL,或是打開了一個指向該頁面的連結。
服務端響應一個401應答碼,并提供一個認證域。
接到應答後,用戶端顯示該認證域(通常是所通路的計算機或系統的描述)給使用者并提示輸入使用者名和密碼。此時使用者可以選擇确定或取消。
使用者輸入了使用者名和密碼後,用戶端軟體會在原先的請求上增加認證消息頭(值是base64encode(username+":"+password)),然後重新發送再次嘗試。
在本例中,伺服器接受了該認證螢幕并傳回了頁面。如果使用者憑據非法或無效,伺服器可能再次傳回401應答碼,用戶端可以再次提示使用者輸入密碼。
注意:用戶端有可能不需要使用者互動,在第一次請求中就發送認證消息頭。
用戶端請求(沒有認證資訊):
GET /private/index.html HTTP/1.0
Host: localhost
(跟随一個換行
,以回車(CR)
加換行(LF)
的形式)
服務端應答:
HTTP/1.0 401 Authorization Required
Server: HTTPd/1.0
Date: Sat, 27 Nov 2004 10:18:15 GMT
WWW-Authenticate: Basic realm="Secure Area"
Content-Type: text/html
Content-Length: 311
/p>
"http://www.w3.org/TR/1999/REC-html401-19991224/loose.dtd">
Error
401 Unauthorized.
用戶端的請求(使用者名“"Aladdin”,密碼, password “open sesame”)
:
GET /private/index.html HTTP/1.0
Host: localhost
Authorization: Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==
(跟随一個空行,如上所述)
Authorization消息頭的使用者名和密碼的值可以容易地編碼和解碼:
服務端的應答
:
HTTP/1.0 200 OK
Server: HTTPd/1.0
Date: Sat, 27 Nov 2004 10:19:07 GMT
Content-Type: text/html
Content-Length: 10476 是以,用python解決問題的話:
import urllib2
import sys
import re
import base64
from urlparse import urlparse
theurl = 'http://api.minicloud.com.cn/statuses/friends_timeline.xml'
username = 'qleelulu'
password = 'XXXXXX' # 你信這是密碼嗎?
base64string = base64.encodestring(
'%s:%s' % (username, password))[:-1] #注意哦,這裡最後會自動添加一個\n
authheader = "Basic %s" % base64string
req.add_header("Authorization", authheader)
try:
handle = urllib2.urlopen(req)
except IOError, e:
# here we shouldn't fail if the username/password is right
print "It looks like the username or password is wrong."
sys.exit(1)
thepage = handle.read() 就是在header中加上:
base64string = base64.encodestring('username:password')[:-1]
authheader = "Basic %s" % base64string
headers['Authorization'] = authheader r案後再請求就可以了。
原理就是添加了請求頭Authorization。