python爬蟲之urllib庫(二)

python爬蟲之urllib庫(二)

  urllib庫

  超時設置

   網頁長時間沒法響應的,系統會判斷網頁超時,沒法打開網頁。對於爬蟲而言,咱們做爲網頁的訪問者,不能一直等着服務器給咱們返回錯誤信息,耗費時間過久。所以,咱們在爬取網頁的時候能夠設置超時異常的值。html

import urllib.request


file=urllib.request.urlopen("http://yum.iqianyue.com",timeout=30) #timeout=30,表示30秒之後產生超時異常 data=file.read()

   HTTP協議請求

  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

  GET請求實現

  在百度首頁輸入關鍵詞查詢,不斷更換關鍵詞能夠看到地址欄中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請求多用於提交表單來實現註冊登陸。爬蟲面對須要註冊登陸的網頁必定是須要登陸訪問網頁之後才能夠對網頁內容進行爬取的,能夠構造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

  調試日誌是記錄程序運行狀態的記錄,對於自動化爬蟲而言,調試日誌是不可或缺的。經過設置Debuglog,能夠實現程序邊運行邊打印調試日誌的需求。

  調試日誌的配置方法:

  1. 分別使用urllib.request.HTTPHandler()和urllib.request.HTTPSHandler()設置參數debuglevel=1,開啓並設置bug級別。
  2. 使用urllib.request.build_opener()建立自定義的opener對象,並設置1中的值做爲參數。
  3. 使用urllib.request.install_opener()建立全局的opener對象,使得opener對象不只能夠調用open()方法,也可使用urlopen()發送HTTP請求。
  4. 調用open()方法或者使用urllib.request.urlopen()發送HTTP請求。
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

  程序在執行或者搭建過程當中,不可避免的會出現錯誤或者異常,錯誤一般是指不合語言自己規則且不可控的使用方式,異常是指合乎語言規則且可控的使用方式。網絡爬蟲中,網頁內容和結構的迭代更新以及網絡環境等因素都會產生影響,甚至異常。所以,合理處理異常對於爬蟲而言是很重要的。

  異常主要爲URLError類以及其子類HTTP類,處理方法是使用urllib.error模塊和try...except語句,產生URLError異常的緣由有:

  1. 鏈接不上服務器
  2. 遠程URL不存在
  3. 無網絡
  4. 觸發了HTTPError
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
View Code

  源碼中能夠看到,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)
相關文章
相關標籤/搜索