【Python】 http客戶端庫requests & urllib2 以及ip地址處理IPy


requests

  requests是個HTTPClient庫,相比於urllib,urllib2等模塊比更加簡潔易用

 ■  get請求

  作為示例,講一下關於requests如何發起並處理一個get請求

r = requests.get("http://www.baidu.com")    #可以加入timeout參數來設置超時

  r是一個Response對象,可以用r查看很多信息

    如r.status_code  查看本次請求的http返回碼

    r.headers  頭部信息(是個類字典對象)

    r.url    本次請求用的url

    r.encoding  編碼方式

    r.text    正文

    r.content  正文

    *text和content的區別是text是以unicode的形式存在內存中的,而content是把text用r.encoding編碼進行encode之后的產物。所以當返回內容是html文本時,type(text)是unicode而type(content)是str。當返回的是類似於圖片這樣的東西時,編碼可以是base64這種二進制編碼,所以如果要下載一張圖或一個文件時,應該寫入本地文件的也是content而不是text。

    再啰嗦一句。。下載的如果是文本文件的話寫入方式似乎不帶b也行得通,但是如果想下載圖片等其他文件就必須是二進制寫入了,要不然會打不開的。

    r.cookies  獲得返回內容中的cookie內容,若想在請求中加入cookie的話只要在參數中加上cookies={...}即可

  r對象還有一些方法:

    r.raise_for_status()  #當返回碼不是200的時候拋出一個HTTPError異常

  另外,get方法還可以把一個字典整合到url中去形成一個帶參數的GET請求。比如

par = {'wd':'word'}
r = requests.get("http://www.baidu.com/s",params=par)

>>>r.url
u'http://www.baidu.com/s?wd=word'

  如果返回的是個json串,那么r還可以用json()方法構造出這個串對應的python字典

r = requests.get("url")  #假設返回的json是 {"code":"200","msg":"OK"}

res = r.json()
print res.get("code"),res.get("msg")
#得到的就是200 OK

  

  除了params參數之外,get方法還可以有proxies參數以設置代理;headers參數來偽造請求頭部(現在很多網站因為防止爬蟲等一些原因會檢查請求頭部,如果不偽造頭部的話很可能會出現500狀態的返回)

  比如headers={'User-Agent':'test'};r = requests.get(url,headers=headers)

  加入cookies={一個字典}來增加請求中的cookie信息 

■  POST

  POST請求用post方法,和get類似的設置。

  在加上post的數據時注意參數是data不是params了!! ==> r = requests.post(url,data=par)

 

■  請求https地址

  請求https地址時,由於涉及到SSL證書的驗證等工作,和普通的HTTP請求還不太一樣。比如get方法中默認會帶上參數verify=True表示請求前要先驗證對方的SSL公鑰證書。如果確認不用驗證可以將此參數置為False,這樣就不用驗證了。

  有時候驗證會報錯hostname does not match xxxx,這可能是因為你請求的url的域名,其上面的SSL公鑰證書還沒有被導入當前主機,一個解決辦法是將證書導出后存放在某個地方,然后verify參數寫指向證書文件的路徑。證書導出隨各種瀏覽器、工具不同而不同。比如火狐的話右鍵頁面--查看頁面信息--安全--查看證書詳細,中可以選擇導出。

  如果選擇了正確的證書之后,請求仍然報錯:SSL3_GET_SERVER_CERTIFICATE:certificate verify failed,這個錯誤可能是由於openssl版本過低。

 

■  上傳帶中文名的文件

  用requests的post方法可以方便地上傳文件,一般的做法是這樣的:

import requests

data = {
    'other_data': 'value'
}
fi = open('test.jpg','rb')
files = {
    'test.jpg': ('test.jpg',fi,'image/jpg')
}

url = 'http://127.0.0.1/uploadtest/'
res = requests.post(url, data=data, files=files)
fi.close()
print res.status_code,res.content

 

  可以看到,除了data參數之外添加了files參數。files參數的內容是一個字典,字典中的key可以寫file或者其他的什么內容。其對標的是網頁表單提交時input標簽中name屬性的值。一般而言只要保證多個上傳文件的鍵值對中,key沒有重復的即可。value是一個tuple,其中各項元素的意義是(filename, file_object, content_type, ...)等,后面還有,不常用就沒寫。文件名是定義在這里的。如果要上傳多個文件,那么就在這個字典中多加幾個鍵值對。需要注意文件對象fi一直到post方法被調用后才能close,否則會報錯。

  這里面隱藏着一個問題,當上傳的文件名是中文名的時候后端可能會獲取不到文件的內容,比如用flask做后端時看一下request.files可以看到是個空字典。網上給出了一些解決方法(https://segmentfault.com/q/1010000002633223/a-1020000002657633),比如修改requests中packages/urllib3/fields.py中的一些源代碼,嘗試了一下確實可以解決。不過修改源碼不是很好的解決方法,如果后端也是自己寫的話那么可以參考這篇教程中的解決方法三(http://www.mamicode.com/info-detail-1499317.html)。

  我的解決辦法就是(由於服務器端也是我自己寫的),在data中加上filename字段指出中文的,實際的文件名。然后在files中的value那個tuple中的第一項(注意,是{'test.jpg': ('test.jpg', fi)}這個文件名)那個文件名改成file_name或者其他不帶中文的名字。在服務端的代碼中,首先按照提供的非中文文件名保存下來,然后改名為中文文件名;或者直接按照提供的中文文件名保存,這個取決於后端的規則和寫法。比如通過flask搭建的一個服務接受中文名文件上傳:

#####服務端代碼#####
@app.route('/uploadtest/',methods=['POST'])
def upload_file():
    filenames = request.form.get('filename').split(',')
    count = 0
    for _,storage in request.files.iteritems():
        storage.save(os.path.join(BASEDIR,'upload',filenames[count]))
        count += 1
    # ...


#####客戶端代碼#####
from collections import OrderedDict

data = {
    'filename': 'a.txt,b.jpg,c.exe'
}
f1,f2,f3 = open('a.txt','rb'),open('b.jpg','rb'),open('c.exe','rb')
files['a.txt'] = ('f1',f1)
files['b.jpg'] = ('f2',f2)
files['c.exe'] = ('f3',f3)
res = requests.post('http://localhost/uploadtest/',data=data,files=files)
f1.close();f2.close();f3.close()

 

  用了OrderedDict的原因是為了讓filename參數得到的文件名列表和上傳文件通過順序一一對應,避免文件名和文件錯位。

 

urllib2

  urllib2是比requests更加貼近底層,可以實現更加個性化的HTTPClient?

■  基本用法

req = urllib2.Request('...')    #用一個url來建立一個Request對象,這個url可以是http協議的也可以是ftp協議的

response = urllib2.urlopen(req)    #打開這一request對象

page = response.read()    #response可以像一個文件一樣read內容

 如果是使用http協議進行通信的話,用戶可以通過POST或GET方法來豐富自己的http請求方式,比如自己附上一些表單data,或者添加http的headers信息

  ①  增加表單data

    首先需要一個python字典來抽象化要發送的數據,然后一定要用urllib.urlencode將這個字典處理成urlopen可以識別的東西(好像就是個key=value&key2=value2這樣的,跟在get請求的url后面的那種形式)。然后在創建Request對象的時候就有兩種方法,分別對應着POST請求和GET請求

par = {'wd':'word'}
processed_par = urllib.urlencode(par)    #對字典進行處理,用到的是urllib中的urlencode方法

req = urllib2.Request('url',data=processed_par)    #寫個參數data=處理好的數據這種方式呢對應的是POST請求
req = urllib2.Request(''url'+'?'+processed_par)    #直接加上處理好的數據是指GET請求的方式(不要忘了中間的問號!!!

urllib2.urlopen(req)    #打開Request對象來進行訪問

 

   ②  增加header信息

    header即頭部,http頭部屬於http訊息的一部分,其邏輯形式大致是一個字典,內容包括客戶端可接受的內容類型,編碼,語言,證書信息,用戶所用代理(瀏覽器的牌子)是什么等等。從應用邏輯上分,header又被分成了general header,request header,response header和entity header四部分

    因為urllib2主要是個http客戶端庫,所以主要關注request header的部分

    如以下做法可以為header添加上User-Agent信息(用戶的瀏覽器是什么)

    

url = 'http://www.xxxxxxxx...'

userAgent = 'Mozilla 4.0 xxxxx'    #模擬火狐瀏覽器的信息

headers = {'User-Agent':userAgent}    #設置header字典

data = urllib.urlencode(...)        #POST請求的表單數據
req = urllib2.Request(url,data=data,headers=headers)
res = urllib2.urlopen(req)    #這就是打開了帶有自己定義的頭部信息的請求了

 

 

■  零碎的記錄

  *關於錯誤 URLError & HTTPerror

  這兩個錯誤是在應用http客戶端庫時常見的錯誤。首先,HTTPerror是URLError的一個子類,即發生HTTPError時必然發生URLError

  另外,URLError通常是指沒有網絡連接(沒有特定路由到指定服務器),或者服務器不存在的錯誤。對應的,HTTPError指的是那些服務器指向正確並且連接OK,但是無法完成請求的情況。

  URLError對象通常都有reason屬性,是個tuple,[0]是錯誤號,[1]是錯誤信息,所以在except URLError的時候可以print e.reason來查看具體信息。類似的HTTPError則有一個code屬性,記錄的是http返回碼,如眾所周知的404代表找不到文件,401代表需要驗證,500代表服務器內部出錯等等。

  

  *關於url的解碼和編碼

  有些符號如'{','\'是無法被識別的,需要對其進行編碼成%7B,%5C之類的某種我也說不太清到底是什么碼的碼,可以用urllib.quote(url)對url進行一個編碼,同樣的urllib.unquote(url)是把編好碼的url解碼成人看得懂的形式。

 

IPy

  一個小模塊。。沒地方放了,反正這篇好像都是跟網絡沾邊就放着把。

  本來我想找個能驗證ip地址合法性的模塊,而找到的這個IPy似乎功能要更加強大一些。IPy下載地址https://github.com/haypo/python-ipy/

  ●  對ip的處理

  首先用IPy.IP來建立一個IP對象

ip = IP('x.x.x.x')    #在構造IP對象時就已經對ip合法性做出判斷,如果不對就報錯了。我最開始的需求就這么實現了。。

  IP對象有__repr__方法,可以直接被print成字符串。

  ip.make_net('子網掩碼')  返回這個ip所在的網段地址,比如

ip = IP(127.0.0.1)
net = ip.make_net('255.0.0.0')
print net
##輸出結果就是 IP('127.0.0.0/8')

print ip in net ##還可以直接用 in 語句來判斷某個ip在不在一個網段里

  ●  對網段地址的處理

  在構造的時候,如果寫的是一個網絡,那么IP類會自動將其識別成一個網絡

net = IP('127.0.0.0/24')

  這個net對象時iterable的,可以迭代出這個網段里所有的ip地址,用len(net)自然就可以看到這個網段里一共能有多少ip了

  在print net的是時候同樣會打印出來這個網絡,只是表示一個網絡有多種方法,到底取哪一種呢?

  可以用net.strNormal(n)這個方法來改變打印出來的格式,比如上面那個net,如果設置了n是0的話只打印出127.0.0.0(網絡地址)

  n為1的場合 打印出類似於127.0.0.0/24 這種表現形式

  n為2的場合 打印出127.0.0.0/255.255.255.0  的形式

  n為3的場合 打印出127.0.0.0 - 127.0.0.255  的形式


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM