2020年9月12日星期六

54 《零基礎入門學習Python》筆記 第054講:論一隻爬蟲的自我修養2:實戰

《零基礎入門學習Python》筆記    第054講:論一隻爬蟲的自我修養2:實戰


目錄
0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!
測試題
0. urlopen() 方法的timeout 參數用於設置什麼?
1. 如何從urlopen() 返回的對像中獲取HTTP 狀態碼?
2. 在客戶端和服務器之間進行請求-響應時,最常用的是哪兩種方法?
3. HTTP 是基於請求-響應的模式,那是客戶端發出請求,服務端做出響應;還是服務端發出請求,客戶端做出響應呢?
4. User-Agent 屬性通常是記錄什麼信息?
5. 如何通過urlopen() 使用POST 方法像服務端發出請求?
6. 使用字符串的什麼方法將其它編碼轉換為Unicode 編碼?
7. JSON 是什麼鬼?
動動手
0. 配合EasyGui,給“下載一隻貓“的代碼增加互動:
1. 寫一個登錄豆瓣的客戶端。

0. 請寫下這一節課你學習到的內容:格式不限,回憶並複述是加強記憶的好方式!

今天我們決定在實戰中來進行學習,會舉兩個例子,第一個例子是我們會下載一隻貓,第二個例子是我們用Python來模擬瀏覽器通過在線的谷歌翻譯進行文本的翻譯。
如果你認為上節課我只是簡單介紹了一下urlopen() 函數的用法,那你就錯了,上節課我已經說了,相關的文檔在哪裡,要教你的東西在文檔裡都有, OK,我們來第一個例子吧。
(一)使用Python下載一隻貓
我們常說,林子大了,什麼鳥都有。互聯網這麼大,那當然不管什麼樣的奇葩網站都會有。我們今天舉的例子就是要訪問一個http://placekitten.com/,這個網站是為貓農量身定制的一個站點,網站後面你只需要加上/寬度/高度,就可以得到一隻相應寬度和高度的貓的圖片。這些圖片都是JPG格式的,你可以通過右鍵將其簡單保存到桌面上。
我們第一個例子就是使用Python實現剛才的操作,事實上我們上節課教過的內容也是完全足夠的,我們新建一個download_cat.py 文件。
首先,我們需要import urllib.request,然後使用urlopen()函數得到response,得到的cat_img可以用一個文件保存,我們命名這個文件為cat_500_600.jpg,我們說過,圖片也是文件,它也是二進制數據組成的,我們這裡用'wb'將收到的二進制數據寫入jpg格式的文件就可以了。
  1. #download_cat.py
  2. import urllib.request
  3. response = urllib.request.urlopen("http://placekitten.com/500/600")
  4. cat_img = response.read()
  5. with open('cat_500_600.jpg', 'wb') as f:
  6. f.write(cat_img)
運行之後,就在桌面上有了一張名為 cat_500_600.jpg 的圖片。我們接著繼續解釋一下上面的代碼:
上節課,我們說過,urlopen()函數中的url參數可以是字符串,也可以是Request object,其實,在上面的程序中,我們傳入的是地址字符串,它也是將地址字符串轉換為Request對象,然後再將對像傳入urlopen()函數。因此,
response = urllib.request.urlopen("http://placekitten.com/500/600")
等價於
  1. req = urllib.request.Resquest("http://placekitten.com/500/600")
  2. response = urllib.request.urlopen(req)
另外,urlopen()函數返回的response其實是一個對象(object),看下圖文檔解釋,因此你可以使用read()方法來讀取內容,
文檔還告訴我們,除了可以使用read() 方法之外,還可以是使用geturl() 、info() 和getcode() 方法,我們試一下這三個函數分別返回什麼:
我們運行 download_cat.py 之後,調用這幾個方法:
  1. >>>
  2. =========== RESTART: C:\Users\XiangyangDai\Desktop\download_cat.py ===========
  3. >>> response.geturl()
  4. 'http://placekitten.com/500/600'
  5. >>> response.info()
  6. <http.client.HTTPMessage object at 0x00000150F729AB70>
  7. >>> print(response.info())
  8. Date: Tue, 11 Dec 2018 06:57:33 GMT
  9. Content-Type: image/jpeg
  10. Content-Length: 20921
  11. Connection: close
  12. Set-Cookie: __cfduid=d2f9e8e46b6e9940463cf24baf0b7f0fb1544511453; expires=Wed, 11-Dec-19 06:57:33 GMT; path=/; domain=.placekitten.com; HttpOnly
  13. Access-Control-Allow-Origin: *
  14. Cache-Control: public, max-age=86400
  15. Expires: Wed, 12 Dec 2018 06:57:33 GMT
  16. CF-Cache-Status: HIT
  17. Accept-Ranges: bytes
  18. Vary: Accept-Encoding
  19. Server: cloudflare
  20. CF-RAY: 48760ec5d6fc99c1-LAX
  21. >>> response.getcode()
  22. 200
geturl() 得到的就是你訪問的具體的地址;
info() 得到的是一個 HTTPMessage 的對象,你可以將它打印出來,包含了遠程服務器返回的Head 信息;
getcode() 返回的是Http 的狀態碼,200 表示OK,就是正常響應。
(二)利用在線有道翻譯來翻譯文本
我們怎樣編寫Python 程序模擬瀏覽器,讓它翻譯呢?我們首先要介紹的是審查元素這個功能。基本上現在所有的瀏覽器都會自帶這樣這個調試插件,以360瀏覽器為例,右鍵選擇-審查元素,或者直接按F12,就會顯示審查元素窗口。
我們要看的是Network 這一塊,當我們點下自動翻譯按鈕時,在下面會看到有很多Method,其中有Get , 有Post ,這些內容都是瀏覽器與客戶端的通信內容,在客服端與服務器之間進行請求的時候,兩種最常用的方法:一種就是Get,一種就是Post,在定義上來說,Get是指從服務器請求獲得數據,而Post是向指定服務器提交被處理的數據,當然在現實情況中,Get也常常用作提交數據。但是我們這裡有Post,剛剛我們是提交數據,提交I love you!這個語句讓它翻譯,我們點進去:
我們看到有Headers 和Preview 等,我們先看一下Preview
我們看到這裡有我們所需要的結果,說明我們就找對地方了,但是在編寫程序之前,我們還是有必要講解一下Headers 中的內容:
Request URL: http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule,有人會認為urlopen()函數打開的應該是  http://fanyi.youdao.com/  這個地址,其實在內部嵌入的是前面的這個地址,你要實現翻譯的機制是在這。
Request Method: POST,請求的方法是Post的形式。
Status Code: 200 OK,狀態碼200表示正常響應。如果是404就是頁面不見了。更多關於HTTP狀態碼的信息請查閱:
Remote Address是服務器的IP地址加上打開的端口號。
Resquest Headers:是客服端(這裡就是瀏覽器,用Python代碼的時候就是我們的代碼)發送請求的Headers,這個常常用於服務端來判斷是否非人類訪問,什麼意思呢?假設我們寫一個Python代碼,然後用這個代碼批量的訪問網站的數據,這樣子,服務器的壓力就很大了,所以呢,服務器一般是不歡迎非人類的訪問的。一般我們就是使用Resquest Headers裡面的User-Agent來識別是瀏覽器訪問還是代碼訪問,大家可以看到,這裡的User-Agent顯示的系統的架構是(Windows NT 10.0; WOW64),後面你還包括瀏覽器的核心及其版本號等信息。如果你使用Python訪問的話,這個User-Agent默認就是Python URL 3.5,這樣就可能被屏蔽掉。(不過呢,如果服務器君以為這樣就可以阻擋我們前進的腳步的話,他就太天真了,這個User-Agent是可以進行自定義的,嘻嘻,後面會給大家介紹)
Form Data:其實就是我們這個Post提交的主要內容,在i這裡看到了提交的待翻譯的內容。
介紹到這裡就已經夠用了,接下來看看文檔,了解Python如何提交Post呢?
urllib.request.urlopenurl , data=None , [ timeout , ] * , cafile=None , capath=None , cadefault=False , context=None )
Open the URL url , which can be either a string or a Requestobject.
data must be a bytes object specifying additional data to be sent to the server, or Noneif no such data is needed. data may also be an iterable object and in that case Content-Length value must be specified in the headers. Currently HTTP requests are the only ones that use data ; the HTTP request will be a POST instead of a GET when the data parameter is provided.
data should be a buffer in the standard application/x-www-form-urlencoded format. The function takes a mapping or sequence of 2-tuples and returns an ASCII text string in this format. It should be encoded to bytes before being used as the data parameter.urllib.parse.urlencode()
上面藍色文字已經寫得很清楚了(這些內容來自urllib的Python文档的urllib.request部分),urlopen有一個data參數,如果這個參數被賦值,那麼它就是以POST的形式取代GET的形式,也就是說,如果data = None的話,就默認是以GET的形式。這裡還說了,data參數必須是基於application/x-www-form-urlencoded的格式,它還很貼心的告訴我們,你可以使用urllib.parse.urlencode()函數將字符串轉換為所需要的形式。
事實上,我們有了這兩段話的描述,我們就可以來寫代碼了:(命名為:translation.py)
  1. #translation.py
  2. import urllib.request
  3. import urllib.parse
  4. url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule"
  5. #直接从审查元素中copy过来的url会报错,必须把translate_o中的_o 删除才可以
  6. #url = "http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule"
  7. data = {} #这里就是把 Form Data 中的内容贴过来
  8. data['i'] = '我爱你'
  9. data['from'] = 'AUTO'
  10. data['to'] = 'AUTO'
  11. data['smartresult'] = 'dict'
  12. data['client'] = 'fanyideskweb'
  13. data['salt'] = '15445124815349'
  14. data['sign'] = 'a824eba4c23c6f541ffadfee26b1e500'
  15. data['ts'] = '1544512481534'
  16. data['bv'] = 'bbb3ed55971873051bc2ff740579bb49'
  17. data['doctype'] = 'json'
  18. data['version'] = '2.1'
  19. data['keyfrom'] = 'fanyi.web'
  20. data['action'] = 'FY_BY_REALTIME'
  21. data['typoResult'] = 'false'
  22. #需要使用urllib.parse.urlencode() 把data转换为需要的形式
  23. data = urllib.parse.urlencode(data).encode('utf-8')
  24. response = urllib.request.urlopen(url, data)
  25. html = response.read().decode('utf-8')
  26. print(html)
運行結果為:
  1. =========== RESTART: C:\Users\XiangyangDai\Desktop\translation.py ===========
  2. {"type":"ZH_CN2EN","errorCode":0,"elapsedTime":0,"translateResult":[[{"src":"我爱你","tgt":"I love you"}]]}
結果倒是可以了,只是這樣的結果是給程序員看的,如果是給用戶看,那也太不友好了。(另外,如果大家對於編碼還有什麼困惑的,可以查看:Python編碼問題的解決方案總結),我們打印出來的是一個字符串,有人就說,我們可以通過字符串查找的形式把tgt找出來,但這樣太被動了。
其實,這是一個json 結構,json 是一種輕量級的數據交換結構,說白了,這裡就是用字符串的形式把Python 的輸出結果給封裝起來,這個字符串裡麵包含的其實是一個字典, "translateResult" 裡面的值是一個列表的列表的字典,我們可以使用下面的方法來解決:
  1. =========== RESTART: C:\Users\XiangyangDai\Desktop\translation.py ===========
  2. {"type":"ZH_CN2EN","errorCode":0,"elapsedTime":0,"translateResult":[[{"src":"我爱你","tgt":"I love you"}]]}
  3. >>> import json
  4. >>> json.loads(html)
  5. {'errorCode': 0, 'type': 'ZH_CN2EN', 'elapsedTime': 0, 'translateResult': [[{'tgt': 'I love you', 'src': '我爱你'}]]}
  6. >>> target = json.loads(html)
  7. >>> type(target)
  8. <class 'dict'>
  9. >>> target['translateResult'][0][0]['tgt']
  10. 'I love you'
綜上,我們就可以把我們的翻譯程序美化一下:
  1. #translation.py
  2. import urllib.request
  3. import urllib.parse
  4. import json
  5. content = input('请输入需要翻译的内容:')
  6. url = "http://fanyi.youdao.com/translate?smartresult=dict&smartresult=rule"
  7. #直接从审查元素中copy过来的url会报错,必须把translate_o中的_o 删除才可以
  8. #url = "http://fanyi.youdao.com/translate_o?smartresult=dict&smartresult=rule"
  9. data = {} #这里就是把 Form Data 中的内容贴过来
  10. data['i'] = content
  11. data['from'] = 'AUTO'
  12. data['to'] = 'AUTO'
  13. data['smartresult'] = 'dict'
  14. data['client'] = 'fanyideskweb'
  15. data['salt'] = '15445124815349'
  16. data['sign'] = 'a824eba4c23c6f541ffadfee26b1e500'
  17. data['ts'] = '1544512481534'
  18. data['bv'] = 'bbb3ed55971873051bc2ff740579bb49'
  19. data['doctype'] = 'json'
  20. data['version'] = '2.1'
  21. data['keyfrom'] = 'fanyi.web'
  22. data['action'] = 'FY_BY_REALTIME'
  23. data['typoResult'] = 'false'
  24. #需要使用urllib.parse.urlencode() 把data转换为需要的形式
  25. data = urllib.parse.urlencode(data).encode('utf-8')
  26. response = urllib.request.urlopen(url, data)
  27. html = response.read().decode('utf-8')
  28. target = json.loads(html)
  29. print('翻译结果:%s' %(target['translateResult'][0][0]['tgt']))
運行結果:
  1. =========== RESTART: C:\Users\XiangyangDai\Desktop\translation.py ===========
  2. 请输入需要翻译的内容:人生苦短,我学Python
  3. 翻译结果:Life is too short, I learn Python
我們的要求實現了,但是這樣的代碼還不能應用到我們的生產實踐中,因為你這樣搞多了,服務器就會發現非人類的User Agent頻繁訪問,就會把你屏蔽掉了。還有就是發現這個IP怎麼訪問的這麼頻繁,就把你拉黑了。其實這些問題,Python都是有解決方法的,欲知詳情如何,請聽下回分解。

測試題


0. urlopen() 方法的timeout 參數用於設置什麼?

答:timeout 參數用於設置連接的超時時間,單位是秒。


1. 如何從urlopen() 返回的對像中獲取HTTP 狀態碼?

答:
  1. response = urllib.request.urlopen(url)
  2. code = response.getcode()

2. 在客戶端和服務器之間進行請求-響應時,最常用的是哪兩種方法?

答:GET 和POST。


3. HTTP 是基於請求-響應的模式,那是客戶端發出請求,服務端做出響應;還是服務端發出請求,客戶端做出響應呢?

答:發出請求的永遠是客戶端,做出響應的永遠是服務端。


4. User-Agent 屬性通常是記錄什麼信息?

答:普通瀏覽器會通過該內容向訪問網站提供你所使用的瀏覽器類型、操作系統、瀏覽器內核等信息的標識。


5. 如何通過urlopen() 使用POST 方法像服務端發出請求?

答:urlopen 函數有一個data 參數,如果給這個參數賦值,那麼HTTP 的請求就是使用POST 方式;如果da​​ta 的值是None,也就是默認值,那麼HTTP 的請求就是使用GET 方式。


6. 使用字符串的什麼方法將其它編碼轉換為Unicode 編碼?

答:decode。decode 的作用是將其他編碼的字符串轉換成unicode 編碼,相反,encode 的作用是將unicode 編碼轉換成其他編碼的字符串。


7. JSON 是什麼鬼?

答:JSON 是一種輕量級的數據交換格式,說白了這裡就是用字符串把Python 的數據結構封裝起來,便與存儲和使用。

動動手


0. 配合EasyGui,給“下載一隻貓“的代碼增加互動:

  • 讓用戶輸入尺寸;
  • 如果用戶不輸入尺寸,那麼按默認寬400,高600下載喵;
  • 讓用戶指定保存位置。
程序實現如下圖:
代碼清單:
  1. import easygui as g
  2. import urllib.request
  3. def main():
  4. msg = "请填写喵的尺寸"
  5. title = "下载一只喵"
  6. fieldNames = ["宽:", "高:"]
  7. fieldValues = []
  8. size = width, height = 400, 600
  9. fieldValues = g.multenterbox(msg, title, fieldNames, size)
  10. while 1:
  11. if fieldValues == None:
  12. break
  13. errmsg = ""
  14. try:
  15. width = int(fieldValues[0].strip())
  16. except:
  17. errmsg += "宽度必须为整数!"
  18. try:
  19. height = int(fieldValues[1].strip())
  20. except:
  21. errmsg += "高度必须为整数!"
  22. if errmsg == "":
  23. break
  24. fieldValues = g.multenterbox(errmsg, title, fieldNames, fieldValues)
  25. url = "http://placekitten.com/g/%d/%d" % (width, height)
  26. response = urllib.request.urlopen(url)
  27. cat_img = response.read()
  28. filepath = g.diropenbox("请选择存放喵的文件夹")
  29. if filepath:
  30. filename = '%s/cat_%d_%d.jpg' % (filepath, width, height)
  31. else:
  32. filename = 'cat_%d_%d.jpg' % (width, height)
  33. with open(filename, 'wb') as f:
  34. f.write(cat_img)
  35. if __name__ == "__main__":
  36. main()

1. 寫一個登錄豆瓣的客戶端。

這道題可能要難為大家了,因為需要N 多你沒學過的知識!
不過我也不打算讓你斷送希望,下邊是一個可行的Python 2 的代碼片段,請修改為Python 3 版本。其中一些庫和知識點你可能還沒學過,但憑藉著過人的自學能力,你可以在不看答案的情況下完成任務的,對嗎?
程序實現如下圖:
Python2 實現的代碼:
  1. # -- coding:gbk --
  2. import re
  3. import urllib, urllib2, cookielib
  4. loginurl = 'https://www.douban.com/accounts/login'
  5. cookie = cookielib.CookieJar()
  6. opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie))
  7. params = {
  8. "form_email":"your email",
  9. "form_password":"your password",
  10. "source":"index_nav" #没有的话登录不成功
  11. }
  12. #从首页提交登录
  13. response=opener.open(loginurl, urllib.urlencode(params))
  14. #验证成功跳转至登录页
  15. if response.geturl() == "https://www.douban.com/accounts/login":
  16. html=response.read()
  17. #验证码图片地址
  18. imgurl=re.search('<img id="captcha_image" src="(.+?)" alt="captcha" class="captcha_image"/>', html)
  19. if imgurl:
  20. url=imgurl.group(1)
  21. #将图片保存至同目录下
  22. res=urllib.urlretrieve(url, 'v.jpg')
  23. #获取captcha-id参数
  24. captcha=re.search('<input type="hidden" name="captcha-id" value="(.+?)"/>' ,html)
  25. if captcha:
  26. vcode=raw_input('请输入图片上的验证码:')
  27. params["captcha-solution"] = vcode
  28. params["captcha-id"] = captcha.group(1)
  29. params["user_login"] = "登录"
  30. #提交验证码验证
  31. response=opener.open(loginurl, urllib.urlencode(params))
  32. ''' 登录成功跳转至首页 '''
  33. if response.geturl() == "http://www.douban.com/":
  34. print 'login success ! '
答:Python 3 對比Python 2 有不少的改變。
在本題中:
  • urllib 和urllib2 合併,大多數功能放入了urllib.request 模塊;
  • 原來的urllib.urlencode() 變為urllib.parse.urlencode().encode(),由於編碼的關係,你還需要在後邊加上encode('utf-8');
  • cookielib 被改名為http.cookiejar;
課堂中我們還沒講,所以這裡藉機會給大家簡單科普一下cookie 是什麼東西:
我們說HTTP 協議是基於請求響應模式,就是客戶端發一個請求,服務端回復一個響應醬紫……
但HTTP 協議是無狀態的,也就是說客戶端這會兒給服務端提交了賬號密碼,服務端回複驗證通過,但下一秒客戶端說我要訪問XXOO 資源,服務端回复:“啊? ?你是誰?!”
為了解決這個尷尬的困境,有人就發明出了cookie。cookie 相當於服務端(網站)用於驗證你的身份的密文。於是客戶端每次提交請求的時候,服務端通過驗證cookie 即可知道你的身份信息。那麼正如你所猜測的,CookieJar 是Python 用於存放cookie 的對象。
當然,這裡已經給你提供了Python 2 的代碼,你不懂上邊這些,也不影響完成作業。
代碼清單:
  1. import re
  2. import urllib.request
  3. from http.cookiejar import CookieJar
  4. # 豆瓣的登录url
  5. loginurl = 'https://www.douban.com/accounts/login'
  6. cookie = CookieJar()
  7. opener = urllib.request.build_opener(urllib.request.HTTPCookieProcessor)
  8. data = {
  9. "form_email":"your email",
  10. "form_password":"your password",
  11. "source":"index_nav"
  12. }
  13. data = {}
  14. data['form_email'] = '你的账号'
  15. data['form_password'] = '你的密码'
  16. data['source'] = 'index_nav'
  17. response = opener.open(loginurl, urllib.parse.urlencode(data).encode('utf-8'))
  18. #验证成功跳转至登录页
  19. if response.geturl() == "https://www.douban.com/accounts/login":
  20. html = response.read().decode()
  21. #验证码图片地址
  22. imgurl = re.search('<img id="captcha_image" src="(.+?)" alt="captcha" class="captcha_image"/>', html)
  23. if imgurl:
  24. url = imgurl.group(1)
  25. # 将验证码图片保存至同目录下
  26. res = urllib.request.urlretrieve(url, 'v.jpg')
  27. # 获取captcha-id参数
  28. captcha = re.search('<input type="hidden" name="captcha-id" value="(.+?)"/>' ,html)
  29. if captcha:
  30. vcode = input('请输入图片上的验证码:')
  31. data["captcha-solution"] = vcode
  32. data["captcha-id"] = captcha.group(1)
  33. data["user_login"] = "登录"
  34. # 提交验证码验证
  35. response = opener.open(loginurl, urllib.parse.urlencode(data).encode('utf-8'))
  36. # 登录成功跳转至首页 '''
  37. if response.geturl() == "http://www.douban.com/":
  38. print('登录成功!')

0 留言:

發佈留言