學習自:https://www.jianshu.com/p/4c3e228940c8
使用參數、關鍵字訪問服務器
訪問網絡的兩種方法:
1、GET
- 利用參數給服務器傳遞信息
- 參數data為dict類型,然后用parse.urlencode()編碼為str類型,用編碼后的data+baseURL構成完整的URL
- GET中不需要用encode
- 打開網頁
- 讀取頁面內容
- 內容編碼轉換
2、POST
- 一般向服務器傳遞參數使用
- post把信息自動加密處理
- 如果想使用post信息,需要用到data參數
- 使用post,意味着HTTP的請求頭請求需要修改:
- Content-Type:application/x-www.form-urlencode
- Content-Length:數據長度
- 簡而言之,一旦修改請求方法,請注意與其它請求的頭部信息相適應
- urllib.parse.urlencode可以將dict類型轉化為str,encode將str轉化為bytes
- 如果想要設置更多的頭部信息,那么urlopen()是無法滿足要求的,因此可以用request.Request().add_header(Header信息名,Header信息值)方法
- request.Request()用來創建Request實例,它包含了所有信息,如url、data、headers、method等。最后只需要用request.urlopen()打開這個實例即可。
3、urllib.error
當使用request進行爬蟲時,盡量采用try...except代碼塊,並把爬蟲代碼放入try中,避免錯誤。
- URLError產生的原因
- 沒網絡
- 服務器鏈接失敗
- OSError的子類
- HTTPError
- URLError的子類
- 兩者區別
- HTTPError是對應的HTTP請求返回的錯誤
- URLError對應的是一般的網絡問題,包括URL錯誤
- 繼承關系有:OSError - URLError - HTTPError
GET訪問方式
1、parse.urlencode()的簡單應用
在使用搜索引擎時,如搜索“學習Python”,當搜索結果出來后,可以在URL地址欄發現如下的URL字符串:
https://www.baidu.com/s?wd=學習Python&...#后邊省略
如果我們只保留其中主要的部分
https://www.baidu.com/s?wd=學習Python
其搜索結果還是一樣的。
這就說明,在使用搜索引擎時,前面的 'https://www.baidu.com/s?' 是固定不變的,為baseURL,只需要輸入關鍵字即可返回結果。
此時,在瀏覽器中按F12進入開發者模式,查看Network->Name欄的Headers部分。這里就是HTTP請求頭Header的信息內容。
可以看到,“學習”二字變成了一串由16進制字符組成的字符串%E5%AD%A6%E4%B9%A0,說明瀏覽器在發送請求時對URL進行了編碼
①利用parse模塊模擬GET請求
在使用爬蟲時,可以只需要輸入關鍵字,並且將關鍵字進行編碼,將其轉換成服務器識別的形式,最后把關鍵字與baseURL連接起來即可實現訪問。
這里的編碼采用的方法就是parse模塊下的parse.urlencode()方法。
from urllib import parse,request baseURL='https://www.baidu.com/s?' wd=input('Input your keyword:') #這里必須是wd,不能是其他的字符串 data={'wd':wd} data=parse.urlencode(data) URL=baseURL+data with request.urlopen(URL) as f: html=f.read().decode('utf-8') print(html)
運行程序,輸入“學習Python”,結果:
最終的URL: https://www.baidu.com/s?wd=%E5%AD%A6%E4%B9%A0Python <!DOCTYPE html> <html> <head> </head> <body> </body> </html>
打印出來的結果就是html文檔文件,也就是搜索“學習Python”后的網頁內容。
同時也能看到最終提交到服務器端的URL地址,其中“學習Python”中的漢字已經轉化為bytes類型了,這就是parse.urlencode()的作用。
注意:
1、baseURL最后是/s?而不是/?s
2、輸入的關鍵字,必須存儲為wd,不能自定義為其他的變量名
3、編碼前要先把關鍵字wd添加到一個dict對象中去,對得到的dict對象編碼
data={'wd':wd}
4、兩個關鍵語句:
data=parse.urlencode(data) with request.urlopen(URL) as res: html=res.read().decode('utf-8')
其中,①parse.urlencode的參數必須是dict對象。
②request.urlopen(URL)獲取URL句柄,標記為對象res
對句柄res調用read()獲取最原始的信息,res.read()
對原始信息進行解碼后顯示:res.read().decode()
5、從服務器端讀取的內容為bytes類型,要用decode將之轉化為str類型,才能打印出來。
POST訪問方式
post方式訪問,是提交表單類的數據到服務器。
request.urlopen()中的data參數
以百度翻譯舉例使用data參數:
利用parse模塊模擬POST請求:
- 首先進入百度翻譯首頁,並用F12進入開發者模式,查看Network欄
- 輸入單詞girl,可以發現每輸入一個字母后邊都有請求
- 請求地址是:https://fanyi.baidu.com/sug
- 請求方式是:POST
- Form data的值是kw:girl,所以字典的Key名為kw(這點注意與get中的Key名為wd相區分)
- 查看返回內容格式,content-length是返回內容長度;content-type是返回內容格式——返回的是json格式內容,需要用到json包
利用parse模擬POST請求
步驟:
- 利用data參數構造內容,這里的內容就是之前所說的關鍵字內容Form data
- 使用request.urlopen(url , data)打開url,並傳入內容data;baseURL不需要和data連接以構成一個完整的URL
- 返回的JSON數據類型,用json.loads()轉換為str字符串類型
- 返回的結果就是搜索詞的釋義
from urllib import parse,request,error #返回內容為JSON格式,用json.loads()轉換為str import json #基本URL baseURL='https://fanyi.baidu.com/sug' #為防止報錯,使用try...except語句塊,使用error.URLerror try: #輸入Form data kw=input('Input your keyword:') #用data存放Form data;Request傳入的數據必須是字典類型 #瀏覽器中開發模式下,Form data下字典的Key為kw,所以data的Key為kw data={'kw':kw} data=parse.urlencode(data).encode() print('即將發送的data數據的類型:',type(data)) #打開網頁,傳入data參數 #urlopen的參數為baseURL和data參數 with request.urlopen(baseURL,data=data) as res: json_data=res.read().decode() print('返回數據的類型:',type(json_data)) json_data=json.loads(json_data) print('轉換后的數據類型:',type(json_data)) for i in json_data['data']: print(i) except error.URLError as e: print(e)
結果:
Input your keyword:>? Girl 即將發送的data數據的類型: <class 'bytes'> 返回數據的類型: <class 'str'> 轉換后的數據類型: <class 'dict'> {'k': 'girl', 'v': 'n. 女孩; 姑娘; 女兒; 年輕女子; 女郎;'} {'k': 'girls', 'v': 'n. 女孩; 姑娘; 女兒; 年輕女子; 女郎; girl的復數;'} {'k': 'girlfriend', 'v': 'n. 女朋友; 女情人; (女子的)女伴,女友;'} {'k': 'girl friend', 'v': ' 未婚妻; 女性朋友;'} {'k': "Girls' Generation", 'v': ' 少女時代(韓國SM娛樂有限公司於2007年推出的九名女子少女組合);'}
這就是使用parse模擬瀏覽器訪問服務器的用法。
補充:
1、最開始傳入的data為dict類型,最后返回的數據也是轉化為dict類型
2、Post的傳入關鍵字構建data的Key必須命名為爬蟲頁開發者模式下的Form data
3、url.urlencode(data),將原始的dict轉化為str類型;encode('utf-8'),將str類型轉化為bytes類型;服務器接收的類型為bytes類型,所以中間將data進行類型轉化的語句是:
data=parse.urlencode(data).encode() #encode參數缺省時默認為'utf-8'
此時的data為bytes類型,可以向服務器發送了
4、發送——request.urlopen(baseURL,data=data)
with request.urlopen(baseURL,data=data) as res:
json_data=res.read()
用json_data接收返回信息。此時json_data為bytes類型,要通過decode()解碼為str類型,再通過json.loads()函數變為dict類型。
(實際應用時,我發現不用decode()解碼,直接對read()的結果進行json.loads(),同樣可以返回一個正常的dict對象,不知道以后有沒有影響?)
5、為防止報錯,可以用try...except程序塊
6、這里的urlopen中的URL為baseURL,與data各自為urlopen的兩個參數,不用連接成為一個完整的URL
7、有的網站返回的數據不是json類型,這時候就不能用json.load了,具體類型應去網頁中通過F12開發者模式header->Content-Type獲知。
request.Request(url= , data= , heads= , method='POST'/'GET')
request.urlopen()的功能有限,如果我們想為請求頭部添加更多的設置信息,如“Content-Length”,那么用urlopen()是無法實現的。
因此,在此基礎上,我們可以使用request.Request(),它的功能與urlopen()類似,可以打開網頁,但比urlopen()優秀的是,它可以為請求頭部自定義信息,無限擴展功能。
於是,在上面代碼的基礎上,可以修改一番:定義一個headers用以存放自定義的頭部信息,然后用request.Request()創建一個Request實例,該實例將所有信息全部包括,到最后只需要用urlopen()打開這個實例即可。
另外,如果想要往該Request實例中補充headers,假設該實例名為req,則可以用方法add_header()實現:
req.add_header(Key , Value)
但需要注意的是add_header的Key-Value必須是合法的,不合法的header會導致其他錯誤!
from urllib import request,error,parse import json baseURL='https://fanyi.baidu.com/sug' kw=input('Input your keyword:') data={'kw':kw} data=parse.urlencode(data).encode() #構造headers,這里的header是自定義要訪問的,如模擬瀏覽器訪問服務器 #構造的headers中,至少包含Content-Length #傳入Request參數的data和headers都是dict類型 headers={'Content-Length':len(data)} #用request.Request模擬瀏覽器訪問服務器 #requset.Request()構造了一個Request實例,其中可以包含大量header內容 #URL為baseURL,data為輸入的關鍵字參數,這里不用連接成一個完整的URL req=request.Request(baseURL,data=data,headers=headers,method='POST')
#添加一個User-Agent的Header信息 req.add_header('User-Agent', 'Mozilla/6.0 (iPhone; CPU iPhone OS 8_0 like Mac OS X) AppleWebKit/536.26 (KHTML, like Gecko) Version/8.0 Mobile/10A5376e Safari/8536.25') #最后用urlopen()打開該實例即可 try : with request.urlopen(req) as res: json_data=res.read() rec=json.loads(json_data) for k in rec['data']: print(k) except error.URLError as e: print(e)
結果與之前的相同。
比urlopen()好的是,Request可以無限擴展功能,模擬瀏覽器訪問服務器,不僅如此,對於之后的身份隱藏等操作,也只需在headers中設置即可。
Post爬蟲的基本操作流程:
①使用data參數創建內容(需要搜索的內容)
②用parse.urlencode和encode進行轉碼
③用urlopen或者request.Request打開URL,並傳入data
④返回結果
⑤對結果進行其他操作
總結:
1、關鍵字參數(需要搜索的內容比如data)必須是dict類型,發送請求時,首先用parse.urlencode(data),將dict類型轉化為str,再用encode()將str轉化為服務器接收的bytes類型。
而把接收到的數據轉化時,需要先read(),再decode(),再json.loads()。(實際應用時,發現不用decode()一樣可以得到相同結果,推測可能是json.loads()的作用。暫不知道這樣做有沒有副作用)
2、想要對某個網頁進行爬蟲操作,需要用F12打開開發者模式,查看這個頁面的信息。主要信息是:請求URL、訪問服務器的方式、返回數據的格式(如果是JSON,需要用json模塊轉化)、Form data 如果有該項,要確定參數的Key名
3、dict類型:Request參數的data、headers;urlopen參數的data
4、GET與POST在程序中的最大區別是:
GET中的urlopen的參數只有URL(可能有headers)
POST中的urlopen的參數是URL+data(可能有headers),data為POST請求的數據
#GET req=request.Request(URL) with request.urlopen(req) as res: ... #POST req=request.Request(baseURL , data=data , headers=headers , method = 'POST') with request.urlopen(req) as res: ...
5、header不是GET與POST的區別,這兩種請求均可以加header
方式是:
req=request.Request(URL,headers=headers)
header的目的是通過加首部信息模擬瀏覽器對服務器訪問
6、有的網站返回的數據不是json類型,這時候就不能用json.load了,具體類型應去網頁中通過F12開發者模式header->Content-Type獲知。
比如:訪問百度首頁時的請求,就是html類型