網頁長時間沒法響應的,系統會判斷網頁超時,沒法打開網頁。對於爬蟲而言,咱們做爲網頁的訪問者,不能一直等着服務器給咱們返回錯誤信息,耗費時間過久。所以,咱們在爬取網頁的時候能夠設置超時異常的值。html
import urllib.request
file=urllib.request.urlopen("http://yum.iqianyue.com",timeout=30) #timeout=30,表示30秒之後產生超時異常 data=file.read()
HTTP請求即HTTP請求報文首行(協議方法,請求URL,協議版本)中的協議方法,HTTP請求方法主要有:python
GET請求:經過URL來傳遞請求信息,獲取服務器資源。因爲GET請求能夠把要傳遞的請求信息添加在URL上,不安全性表如今能夠經過地址欄URL信息看到傳遞的信息。瀏覽器
POST請求:向服務器提交數據,如表單提交,一般使用Post請求。安全
PUT請求:請求服務器存儲一個資源,一般須要指定存儲位置。服務器
DELETE請求:請求服務器刪除一個資源。網絡
HEAD請求:請求獲取響應的報頭信息,對於響應的主體內容不須要。app
OPTIONS請求:得到請求URL所支持的HTTP方法python爬蟲
四種HTTP請求方法區別:GET--查,POST--增,PUT--改,DELETE--刪。ide
在百度首頁輸入關鍵詞查詢,不斷更換關鍵詞能夠看到地址欄中URL的變化。分析能夠得出:‘https://www.baidu.com/s?wd=’爲URL主要部分,在關鍵詞檢索字段wd後添加關鍵詞,便可實現百度搜索。函數
構造get請求,實現自動爬取百度查詢關鍵詞爲hello的結果。
import urllib.request core_url = 'http://www.baidu.com/s?wd=' keywords = 'hello' full_url = core_url + keywords req = urllib.request.Request(full_url) data = urllib.request.urlopen(req).read() with open('hello.html', 'wb') as f: f.write(data)
上述關鍵詞若是變成中文,會出現報錯:UnicodeEncodeError: 'ascii' codec can't encode characters in position 10-11: ordinal not in range(128),緣由:python爬蟲之urllib庫(一)提到過URL編碼,URL只會認可一部分ASCII碼中字符,對於漢字等特殊符號是須要編碼的。對於一個參數使用字符串結合request模塊給URL傳參:urllib.request.quote(str);對於多個參數使用字典結合parse模塊給URL傳參:urllib.parse.urlencode(dict)。
一個參數
import urllib.request core_url = 'http://www.baidu.com/s?wd=' keywords = '您好' keywords_encode = urllib.request.quote(keywords) # URL參數編碼 full_url = core_url + keywords_encode req = urllib.request.Request(full_url) data = urllib.request.urlopen(req).read() with open('hello.html', 'wb') as f: f.write(data)
多個參數
import urllib.request import urllib.parse core_url = 'http://www.baidu.com/s?' # 關鍵詞字段減掉 keywords = { # 多個參數 'wd': '您好', 'rsv_spt': 1, 'rsv_iqid': 0x8c77175600037633, } keywords_encode = urllib.parse.urlencode(keywords) # 多個參數url編碼 full_url = core_url + keywords_encode req = urllib.request.Request(full_url) data = urllib.request.urlopen(req).read() with open('hello.html', 'wb') as f: f.write(data)
POST請求多用於提交表單來實現註冊登陸。爬蟲面對須要註冊登陸的網頁必定是須要登陸訪問網頁之後才能夠對網頁內容進行爬取的,能夠構造POST請求實現自動登陸爬取網頁。
import urllib.request import urllib.parse url = 'http://data.stats.gov.cn/login.htm' # url必須是登陸或者註冊頁面的url地址 國家數據統計局官網登陸url form_data = { 'username': '545859297@qq.com', # 表單數據,登陸時輸入的信息,對應郵箱和密碼。再也不是url參數了,注意區分 'keyp': 'bushizhenmima', # 注意字典中的key須要使用頁面中input輸入框的name屬性的屬性值。別試我帳號密碼!!! # 瀏覽器打開上述網頁,確實驗證碼輸入,登陸不會成功 } form_data_deal = urllib.parse.urlencode(form_data).encode('utf-8') # POST請求data屬性須要傳入bytes類型,並且字典須要經過urlencode鏈接 headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/69.0.3497.92 Safari/537.36' } req = urllib.request.Request(url, data=form_data_deal, headers=headers) data = urllib.request.urlopen(req).read() with open('country-data.html', 'wb') as f: f.write(data)
屢次使用一個IP地址去爬取網頁,服務器能夠輕易察覺屬於不正常訪問行爲,可能會對該IP地址設置拒絕訪問(封IP),也可能會對用戶帳號處理(封帳號)。使用代理服務器能夠輕鬆換IP地址,即便用代理服務器的IP地址訪問頁面,而不使用咱們真正的IP地址,所謂「偷樑換柱」。
代理服務器能夠分爲:免費代理、付費代理。免費代理能夠經過網頁搜索「免費代理服務器」得到,付費代理能夠經過購買得到。代理基本格式:IP:port(代理IP:端口號)
免費代理添加須要先建立處理器handler,再由build_open()建立opener對象,調用opener中的open()方法爬取頁面,不能再使用urlopen()發送請求爬取了。
使用handle+opener發送請求爬取頁面的方法:
import urllib.request def handler_opener(): url = 'https://www.baidu.com' handler = urllib.request.HTTPHandler() # 常見HTTP處理器 opener = urllib.request.build_opener(handler) # 調用buile_open()建立opener對象 response = opener.open(url) # 調用open()方法發送HTTP請求 response_str = response.read().decode('utf-8') return response_str result = handler_opener() with open('baidu.html', 'w', encoding='utf-8') as f: f.write(result)
免費代理添加及使用方式:
import urllib.request def free_proxy(): url = 'http://www.baidu.com' proxy = { 'http': 'http;//116.209.57.195:9999', # 分爲http和https兩種協議版本,https是更加安全的http,在http基礎上加入安全層SSL # 'https': 'https://118.182.33.7:42801' } proxy_handler = urllib.request.ProxyHandler(proxy) # 建立代理處理器,使用ProxyHandle opener = urllib.request.build_opener(proxy_handler) response = opener.open(url) response_str = response.read() # 注意與上例不一樣 return response_str result = free_proxy() with open('baidu-free.html', 'wb') as f: # 注意與上例不一樣 f.write(result)
付費代理添加有兩種方式:
方式一
import urllib.request def free_proxy(): url = 'https://www.baidu.com' proxy = { 'http': 'http;//222.139.245.130:58424', # 分爲http和https兩種協議版本,https是更加安全的http,在http基礎上加入安全層SSL # 'https': 'https://118.182.33.7:42801' } proxy_handler = urllib.request.ProxyHandler(proxy) # 建立代理處理器 opener = urllib.request.build_opener(proxy_handler, urllib.request.HTTPHandler) # 這個能夠缺省HTTPHandler,下面爲源碼解釋 ''' The opener will use several default handlers, including support for HTTP, FTP and when applicable HTTPS. If any of the handlers passed as arguments are subclasses of the default handlers, the default handlers will not be used. ''' response = opener.open(url) response_str = response.read() # 注意與上例不一樣 return response_str result = free_proxy() with open('baidu-free.html', 'wb') as f: # 注意與上例不一樣 f.write(result)
方式二
import urllib.request def fee_proxy(): url = 'http://www.baidu.com' # 付費代理IP第二種方式 user_name = 'admin' password = '123456' proxy_ip = 'http://121.61.1.222:9999' proxy_manager = urllib.request.HTTPPasswordMgrWithDefaultRealm() # 建立密碼管理器 proxy_manager.add_password(None, proxy_ip, user_name, password) proxy_handler = urllib.request.ProxyBasicAuthHandler(proxy_manager) # 代理IP驗證處理器 proxy_opener = urllib.request.build_opener(proxy_handler) response = proxy_opener.open(url) response_str = response.read().decode('utf-8') return response_str data = fee_proxy() with open('baidu-fee.html', 'w', encoding='utf-8') as f: f.write(data)
代理服務器地址是具備時效性的,尤爲是免費代理IP,若是代理服務器地址失效或者填寫錯誤,會返回URLError。一般使用代理服務器進行網頁爬取出現URLError,要考慮是不是代理IP失效的緣由。
調試日誌是記錄程序運行狀態的記錄,對於自動化爬蟲而言,調試日誌是不可或缺的。經過設置Debuglog,能夠實現程序邊運行邊打印調試日誌的需求。
調試日誌的配置方法:
import urllib.request url = 'http://www.baidu.com' http_handler = urllib.request.HTTPHandler(debuglevel=1) https_handler = urllib.request.HTTPSHandler(debuglevel=1) opener = urllib.request.build_opener(http_handler, https_handler) urllib.request.install_opener(opener) response = urllib.request.urlopen(url) # 請求方式一 # response = opener.open(url) # 請求方式二
程序在執行或者搭建過程當中,不可避免的會出現錯誤或者異常,錯誤一般是指不合語言自己規則且不可控的使用方式,異常是指合乎語言規則且可控的使用方式。網絡爬蟲中,網頁內容和結構的迭代更新以及網絡環境等因素都會產生影響,甚至異常。所以,合理處理異常對於爬蟲而言是很重要的。
異常主要爲URLError類以及其子類HTTP類,處理方法是使用urllib.error模塊和try...except語句,產生URLError異常的緣由有:
import urllib.request import urllib.error url = 'http://sad.blog.csdn.net' try: rep = urllib.request.urlopen(url) except urllib.error.URLError as e: print(e) else: print(rep)
當觸發HTTPError,能夠直接使用HTTPError類,能夠查看異常後的狀態碼以及緣由短語。
import urllib.request import urllib.error url = 'http://sad.blog.csdn.net' try: rep = urllib.request.urlopen(url) except urllib.error.HTTPError as e: print(e.code, e.reason) else: print(rep)
常見的狀態碼以及緣由短語有:
狀態碼 | 緣由短語(英文) | 緣由短語(中文) |
200 | OK | 正常 |
301 | Moved Permanently | 從新定向新的URL,永久性 |
302 | Found | 從新定向新的URL,非永久性 |
304 | Not Modified | 請求資源未更新 |
400 | Bad Request | 非法請求 |
401 | Unauthorized | 請求未經受權 |
403 | Forbidden | 禁止訪問 |
404 | Not Found | 沒有找到頁面 |
500 | Internal Server Error | 服務器內部錯誤 |
501 | Not Implemented | 服務器不支持實現請求功能 |
URLError和HTTPError中的屬性及源碼error.py:
1 """Exception classes raised by urllib. 2 3 The base exception class is URLError, which inherits from OSError. It 4 doesn't define any behavior of its own, but is the base class for all 5 exceptions defined in this package. 6 7 HTTPError is an exception class that is also a valid HTTP response 8 instance. It behaves this way because HTTP protocol errors are valid 9 responses, with a status code, headers, and a body. In some contexts, 10 an application may want to handle an exception like a regular 11 response. 12 """ 13 14 import urllib.response 15 16 __all__ = ['URLError', 'HTTPError', 'ContentTooShortError'] 17 18 19 class URLError(OSError): 20 # URLError is a sub-type of OSError, but it doesn't share any of 21 # the implementation. need to override __init__ and __str__. 22 # It sets self.args for compatibility with other OSError 23 # subclasses, but args doesn't have the typical format with errno in 24 # slot 0 and strerror in slot 1. This may be better than nothing. 25 def __init__(self, reason, filename=None): 26 self.args = reason, 27 self.reason = reason 28 if filename is not None: 29 self.filename = filename 30 31 def __str__(self): 32 return '<urlopen error %s>' % self.reason 33 34 35 class HTTPError(URLError, urllib.response.addinfourl): 36 """Raised when HTTP error occurs, but also acts like non-error return""" 37 __super_init = urllib.response.addinfourl.__init__ 38 39 def __init__(self, url, code, msg, hdrs, fp): 40 self.code = code 41 self.msg = msg 42 self.hdrs = hdrs 43 self.fp = fp 44 self.filename = url 45 # The addinfourl classes depend on fp being a valid file 46 # object. In some cases, the HTTPError may not have a valid 47 # file object. If this happens, the simplest workaround is to 48 # not initialize the base classes. 49 if fp is not None: 50 self.__super_init(fp, hdrs, url, code) 51 52 def __str__(self): 53 return 'HTTP Error %s: %s' % (self.code, self.msg) 54 55 def __repr__(self): 56 return '<HTTPError %s: %r>' % (self.code, self.msg) 57 58 # since URLError specifies a .reason attribute, HTTPError should also 59 # provide this attribute. See issue13211 for discussion. 60 @property 61 def reason(self): 62 return self.msg 63 64 @property 65 def headers(self): 66 return self.hdrs 67 68 @headers.setter 69 def headers(self, headers): 70 self.hdrs = headers
源碼中能夠看到,URLError類中有reason屬性,HTTPError類具備code屬性,HTTPError能夠繼承父類URLError中的reason屬性,而HTTPError是引發URLError的一個緣由,即當觸發HTTPError引發的URLError異常時,URLError是具備code和reason屬性,而HTTPError一直具備code和reason屬性。所以,可使用hasattr()函數在使用前判斷是否存在屬性,進而經過狀態碼的存在斷定異常URLError的緣由與HTTPError是否有關。
import urllib.request import urllib.error try: urllib.request.urlopen("http://blog.csdn.net") except urllib.error.URLError as e: if hasattr(e, "code"): print(e.code) if hasattr(e, "reason"): print(e.reason)