python網絡爬蟲教程(三):最全的請求庫urllib詳解與編程實戰

前兩章講網頁的構成以及爬蟲的基本原理,如果您還不瞭解,推薦您看上一章python網絡爬蟲教程(二):網頁基礎。學習了這些以後,就可以開始寫代碼了。

學習爬蟲,最初的操作就是模擬瀏覽器向服務器發出請求,幸運的是,python爲我們提供了強大且便捷的類庫來完成這些請求,本章我們先來詳細瞭解一下python自帶的urllib庫,他是python內置的HTTP請求庫,不需要額外安裝即可使用。在python2中,有urllib和urllib2兩個庫來實現請求的發送,而在python3中統一爲了urllib,作者使用的是python3.7,也就是以python3的urllib來說明。

urllib包含了4個模塊:

request:它是最基本的http請求模塊,用來模擬發送請求。

error:異常處理模塊,如果出現錯誤可以捕獲這些異常。

parse:一個工具模塊,提供了許多URL處理方法,如:拆分、解析、合併等。

robotparser:主要用來識別網站的robots.txt文件,然後判斷哪些網站可以爬。

1. urllib.request(構造請求)

urllib.request模塊提供了最基本的構造HTTP請求的方法,其中比較重要的類爲urllib…request.urlopen,以python官網爲例,我們來體驗一下它的強大之處:

from urllib.request import urlopen

response = urlopen('https://www.python.org')
print(response.read().decode())

運行結果如下:在這裏插入圖片描述上述代碼中,我們用urlopen來構造HTTP請求,返回值是一個HTTPRsponse對象,用read()方法讀取網頁內容之後,返回的結果是網頁字節流,用decode()解碼成unicode之後即可得到網頁源代碼。除此之外,HTTPRsponse對象還擁有如下屬性和方法:

import urllib.request import urlopen

response=urlopen('https://www.python.org')  #請求站點獲得一個HTTPResponse對象
print(response.read().decode('utf-8'))   #返回網頁內容
print(response.getheader('server')) #返回響應頭中的server值
print(response.getheaders()) #以列表元祖對的形式返回響應頭信息
print(response.fileno()) #返回文件描述符
print(response.version)  #返回版本信息
print(response.status)  #返回狀態碼200404代表網頁未找到
print(response.debuglevel) #返回調試等級
print(response.closed)  #返回對象是否關閉布爾值
print(response.geturl()) #返回檢索的URL
print(response.info()) #返回網頁的頭信息
print(response.getcode()) #返回響應的HTTP狀態碼
print(response.msg)  #訪問成功則返回ok
print(response.reason) #返回狀態信息

urlopen參數信息如下:
urllib.request.urlopen(url,data=None,[timeout,],cafile=None,capath=None,cadefault=False,context=None)

data參數: 該參數是可選的,如果要添加該參數,需要將其內容編碼爲字節流格式即bytes類型,另外,如果傳遞了該參數,請求方式將使用POST請求:

from urllib.request import urlopen
import urllib.parse

dict= {'Hello': 'Word!'}
data = bytes(urllib.parse.urlencode(dict), encoding='utf-8')
response = urlopen('http://httpbin.org/post', data=data)
print(response.read().decode())

data需要字節類型的參數,用urllib.parse的urlencode()可以將參數字典轉換爲字符串,然後再用bytes()函數將其轉化爲字節流。這裏請求的站點是httpbin.org,它可以提供HTTP請求測試,它可以輸出一些請求信息,運行結果如下:

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "Hello": "Word!"
  },
  "headers": {
    "Accept-Encoding": "identity",
    "Content-Length": "13",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "Python-urllib/3.7",
    "X-Amzn-Trace-Id": "Root=1-5ecb5d2d-8ee3f56e31828d78e4a10158"
  },
  "json": null,
  "origin": "171.107.139.104",
  "url": "http://httpbin.org/post"
}

可以看到傳遞的參數出現在了form字段中,這表明該請求以POST方式傳輸數據。

timeout參數:timeout參數用於設置超時時間,單位爲秒,如果請求超出了設置時間還未得到響應則拋出異常:

from urllib.request import urlopen

response = urlopen('http://httpbin.org/post', timeout=0.1)
print(response.read().decode())

運行結果如下:

urllib.error.URLError: <urlopen error timed out>

我們可以使用error模塊來捕獲異常:

import urllib.request
import urllib.error
import socket
try:
    response=urllib.request.urlopen('http://httpbin.org/get',timeout=0.1)
    print(response.read())
except urllib.error.URLError as e:
    if isinstance(e.reason,socket.timeout): #判斷對象是否爲類的實例
        print(e.reason) #返回錯誤信息

運行結果如下:

timed out

我們捕獲了URLError異常,接着判斷異常是socket.timeout,從而得出它確實是由於超時而報錯。

其他參數:context參數,她必須是ssl.SSLContext類型,用來指定SSL設置,此外,cafile和capath這兩個參數分別指定CA證書和它的路徑,會在https鏈接時用到。

urllib.request模塊還有更爲強大的Request()類,參數信息如下:
urllib.request.Request(url,data=None,headers={},origin_req_host=None,unverifiable=False,method=None)

url:請求的URL,必須傳遞的參數,其他都是可選參數

data:上傳的數據,必須傳bytes字節流類型的數據,如果它是字典,可以先用urllib.parse模塊裏的urlencode()編碼。

headers:是一個字典,傳遞的是請求頭數據。

origin_req_host:指請求方的host名稱或者IP地址。

unverifiable:表示這個請求是否是無法驗證的,默認爲False,如我們請求一張圖片如果沒有權限獲取圖片那它的值就是true。

method:是一個字符串,用來指示請求使用的方法,如:GET,POST,PUT等。

通過實例來感受一下:

from urllib import request,parse

url='http://httpbin.org/post'
headers={
    'User-Agent':'Mozilla/5.0 (compatible; MSIE 5.5; Windows NT)',
    'Host':'httpbin.org'
}  #定義頭信息

dict={'name':'germey'}
data = bytes(parse.urlencode(dict),encoding='utf-8')
req = request.Request(url=url,data=data,headers=headers,method='POST')
response = request.urlopen(req) 
print(response.read().decode())

結果如下:

{
  "args": {},
  "data": "",
  "files": {},
  "form": {
    "name": "germey"
  },
  "headers": {
    "Accept-Encoding": "identity",
    "Content-Length": "11",
    "Content-Type": "application/x-www-form-urlencoded",
    "Host": "httpbin.org",
    "User-Agent": "Mozilla/5.0 (compatible; MSIE 5.5; Windows NT)",
    "X-Amzn-Trace-Id": "Root=1-5ecb645d-e062b9bcea50bcf9790167b3"
  },
  "json": null,
  "origin": "171.107.139.104",
  "url": "http://httpbin.org/post"
}

我們通過四個參數構造了一個請求,同時也可以發現我們依然用urlopen()來發送請求,不過這次請求的參數不再是URL,而是Request對象,通過這個數據結構,我們可以將請求獨立成一個對象,也可以更加豐富和靈活地配置參數。

2. urllib.request的高級類

如果我們需要更加高級的操作,如Cookie處理、代理設置等,怎麼辦呢?這就需要更加強大的工具 Handler,在urllib.request模塊裏的BaseHandler類,他是所有其他Handler的父類,他是一個處理器,比如用它來處理登錄驗證,處理cookies,代理設置,重定向等。

Handler的子類包括:

HTTPDefaultErrorHandler:用來處理http響應錯誤,錯誤會拋出HTTPError類的異常

HTTPRedirectHandler:用於處理重定向

HTTPCookieProcessor:用於處理cookies

ProxyHandler:用於設置代理,默認代理爲空

HTTPPasswordMgr:永遠管理密碼,它維護用戶名和密碼錶

HTTPBasicAuthHandler:用戶管理認證,如果一個鏈接打開時需要認證,可以使用它來實現驗證功能

另一個比較重要的類是OpenerDirector,可以簡稱爲opener,之前用過的urlopen()方法,實際上就是urllib爲我們提供的一個Opener。我們需要用到更高級的功能,所以需要更深入一層進行配置,需要使用更底層的實例進行操作,所以這裏就用到了Opener,我們需要利用Handler來構建Opener。

如果我們訪問的頁面會彈出提示框,提示輸入用戶名和密碼,可以使用密碼驗證:

from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_opener
from urllib.error import URLError

username='18177459263'
passowrd='mimagaile9625'
url='https://passport.zhihuishu.com/login?service=https://onlineservice.zhihuishu.com/login/gologin'
p=HTTPPasswordMgrWithDefaultRealm() #構造密碼管理實例
p.add_password(None,url,username,passowrd) #添加用戶名和密碼到實例中
auth_handler=HTTPBasicAuthHandler(p) #傳遞密碼管理實例構建一個驗證實例
opener=build_opener(auth_handler)  #構建一個Opener
try:
    result=opener.open(url)  #打開鏈接,完成驗證,返回的結果是驗證後的頁面內容
    html=result.read().decode('utf-8')
    print(html)
except URLError as e:
    print(e.reason)

設置代理:

from urllib.error import URLError
from urllib.request import ProxyHandler,build_opener

proxy_handler=ProxyHandler({
    'http':'http://127.0.0.1:8888',
    'https':'http://127.0.0.1:9999'
})
opener=build_opener(proxy_handler) #構造一個Opener
try:
    response=opener.open('https://www.baidu.com')
    print(response.read().decode('utf-8'))
except URLError as e:
    print(e.reason)

使用Cookies:

import http.cookiejar,urllib.request
cookie=http.cookiejar.CookieJar() #實例化cookiejar對象
handler=urllib.request.HTTPCookieProcessor(cookie) #構建一個handler
opener=urllib.request.build_opener(handler) #構建Opener
response=opener.open('http://www.baidu.com') #請求
print(cookie)
for item in cookie:
    print(item.name+"="+item.value)

3. urllib.error(異常處理)

urllib的error模塊定義了由request模塊產生的異常,如果出現問題,request模塊便會拋出error模塊中定義的異常。其中有兩個子類URLError和HTTPError。

1. URLError

URLError類來自urllib庫的error模塊,它繼承自OSError類,是error異常模塊的基類,由request模塊產生的異常都可以通過捕獲這個類來處理

它只有一個屬性reason,即返回錯誤的原因:

from urllib import request, error

try:
    response = request.urlopen('https://jiangyvzang.org/index')
except error.URLError as e:
    print(e.reason)#如果網頁不存在不會拋出異常,而是返回捕獲的異常錯誤的原因(Not Found)

2. HTTPError

它是URLError的子類,專門用來處理HTTP請求錯誤,比如認證請求失敗,它有3個屬性:

code:返回HTTP的狀態碼,如404頁面不存在,500服務器錯誤等

reason:同父類,返回錯誤的原因

headers:返回請求頭

下面用一個實例來看看:

from urllib import request,error

try:
    response=request.urlopen('http://cuiqingcai.com/index.htm')
except error.HTTPError as e:  #先捕獲子類異常
    print(e.reason,e.code,e.headers,sep='\n')
except error.URLError as e:  #再捕獲父類異常
    print(e.reason)
else:
    print('request successfully')

運行結果如下:

Not Found
404
Server: nginx/1.10.3 (Ubuntu)
Date: Mon, 25 May 2020 07:06:37 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
Set-Cookie: PHPSESSID=1dgv0fn7l6bttp08hhp9nse657; path=/
Pragma: no-cache
Vary: Cookie
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
Link: <https://cuiqingcai.com/wp-json/>; rel="https://api.w.org/"

4. urllib.parse(解析鏈接)

urllib庫提供了parse模塊,它定義了處理URL的標準接口,如實現URL各部分的抽取,合併以及鏈接轉換。

1. urlparse()

該方法可以實現URL的識別和分段:

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html;user?id=5#comment')
print(type(result))
print(result)

結果如下

<class 'urllib.parse.ParseResult'>
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5', fragment='comment')

可以看到輸出結果是一個ParseResult類型對象,它包含6部分,分別是scheme(協議)、netloc(域名)、path(訪問路徑)、params(參數)、query(條件)和fragment(錨點),由此我們可以得到一個標準的鏈接格式:
scheme://netloc/path;params?query#fragment
一個標準的URL都會符合這個格式,利用urlparse可以將它們拆分開來。

接下來我們看看它的API用法:
urllib.parse.urlparse(urlstring,scheme=’’,allow_fragments=True)

urlstring:待解析的URL,字符串

scheme:它是默認的協議,如http或者https,URL如果不帶http協議,可以通過scheme來指定,如果URL中制定了http協議則URL中生效

allow_fragments:是否忽略fragment即錨點,如果設置爲False,fragment部分會被忽略,反之不忽略,通過一個實例來看一下:

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html;user?id=5#comment', allow_fragments=False)
print(type(result))
print(result)

運行結果如下:

<class 'urllib.parse.ParseResult'>
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html', params='user', query='id=5#comment', fragment='')

假如URL中不包含params和query:

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html#comment', allow_fragments=False)
print(type(result))
print(result)

運行結果如下:

<class 'urllib.parse.ParseResult'>
ParseResult(scheme='https', netloc='www.baidu.com', path='/index.html#comment', params='', query='', fragment='')

結果表明,如果URL中不包含params和query,fragment便會被解析爲path的一部分。

返回結果實際上是一個元組,我們可以通過索引來獲取,也可以通過屬性名來獲取:

from urllib.parse import urlparse

result = urlparse('https://www.baidu.com/index.html;user?id=5#comment')
print(result.scheme, result[0], result.netloc, result[1], sep='\n')

結果如下:

https
https
www.baidu.com
www.baidu.com

2. urlunparse()

有了urlparse(),自然也有它的對立方法urlunparse(),它接受一個可迭代對象,但它的長度必須爲6,否則會拋出數量不足或者過多的問題:

from urllib.parse import urlunparse

url = ['https', 'www.baidu.com', 'index.html', 'user', 'id=6', 'comment']
print(urlunparse(url))

結果如下:

https://www.baidu.com/index.html;user?id=6#comment

3. urlsplit()

這個方法和urlparse()類似,不過它不再單獨解析params這一部分,只返回5個結果,params會合併到path中:

from urllib.parse import urlsplit

result = urlsplit('https://www.baidu.com/index.html;user?id=6#comment')
print(type(result))
print(result)
print(result.scheme, result[0], result.netloc, result[1], sep='\n')

結果如下:

<class 'urllib.parse.SplitResult'>
SplitResult(scheme='https', netloc='www.baidu.com', path='/index.html;user', query='id=6', fragment='comment')
https
https
www.baidu.com
www.baidu.com

從結果可以看到返回值是一個SplitResult類型,並且既可以用屬性獲取值,也支持索引。

4. urlunsplit()

與urlunparse()類似,略。

5. urljoin()

有了urlunparse()和urlunsplit()方法,可以完成鏈接的合併,不過前提是必須要有特定長度的對象。此外,生成鏈接還有另外一種方法,那就是urljoin(),我們可以提供一個base_url(基礎鏈接)作爲第一個參數,將新的鏈接作爲第二個參數,該方法會分析base_url的scheme、netloc和path這3個內容並對新的鏈接缺失的部分進行補充:

from urllib.parse import urljoin

print(urljoin('http://www.baidu.com','index.html'))
print(urljoin('http://www.baidu.com','http://cdblogs.com/index.html'))
print(urljoin('http://www.baidu.com/home.html','https://cnblog.com/index.html'))
print(urljoin('http://www.baidu.com?id=3','https://cnblog.com/index.html?id=6'))
print(urljoin('http://www.baidu.com','?id=2#comment'))
print(urljoin('www.baidu.com','https://cnblog.com/index.html?id=6'))

運行結果如下:

http://www.baidu.com/index.html
http://cdblogs.com/index.html
https://cnblog.com/index.html
https://cnblog.com/index.html?id=6
http://www.baidu.com?id=2#comment
https://cnblog.com/index.html?id=6

通過urljoin()方法,可以輕鬆實現鏈接的解析、拼合與生成。

6. urlencode()

urlencode()在構造GET請求參數時很有用,它可以將字典轉化爲GET請求參數:

from urllib.parse import urlencode

params = {
    'name': 'germey',
    'age': '22'
}
base_url = 'https://www.baidu.com?'
url = base_url + urlencode(params)
print(url)

運行結果如下:

https://www.baidu.com?name=germey&age=22

7. parse_qs()

parse_qs()與urlencode()正好相反,它是用來反序列化的,如將GET參數轉換回字典格式:

from urllib.parse import parse_qs

query = 'name=germey&age=22'
print(parse_qs(query))

運行結果如下:

{'name': ['germey'], 'age': ['22']}

8. parse_qsl()

另外還有一個parse_qsl()方法,用來將參數轉化爲元組組成的列表:

from urllib.parse import parse_qsl

query = 'name=germey&age=22'
print(parse_qsl(query))

運行結果如下:

[('name', 'germey'), ('age', '22')]

9. quote()

該方法可以將內容轉化爲URL編碼的格式,當URL中含有中文參數時,可能會導致亂碼的問題,此時用這個方法可以將中文參數轉化爲URL編碼:

from urllib.parse import quote

keyword = '壁紙'
base_url = 'https://www.baidu.com/s?wd='
url = base_url + quote(keyword)
print(url)

運行結果如下:

https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8

10. unquote()

有了quote()方法自然就有unquote()方法,它可以進行URL解碼:

from urllib.parse import unquote

url = 'https://www.baidu.com/s?wd=%E5%A3%81%E7%BA%B8'
print(unquote(url))

運行結果如下:

https://www.baidu.com/s?wd=壁紙

5. 分析Robots協議

利用urllib的robotparser模塊,我們可以實現網站Robots協議的分析

1. Robots協議

Robots協議也稱爲爬蟲協議、機器人協議,它的全名叫做網絡爬蟲排除標準(Robots Exclusion Protocol),用來告訴爬蟲和搜索引擎哪些網頁可以抓取,哪些不可以抓取,它通常是一個robots.txt的文本文件,一般放在網站的根目錄下。

當搜索爬蟲訪問一個站點時,它首先會檢查這個站點根目錄下是否存在robots.txt文件,如果存在,搜索爬蟲會根據其中定義的爬去範圍來爬取,如果沒有找到,搜索爬蟲會訪問所有可直接訪問的頁面

我們來看下robots.txt的樣例:

User-agent: *
Disallow: /
Allow: /public/

它實現了對所有搜索爬蟲只允許爬取public目錄的功能,將上述內容保存爲robots.txt文件放在網站根目錄下,和網站的入口文件(index.html)放在一起

User-agent描述了搜索爬蟲的名稱,將其設置爲*則代表協議對任何爬蟲有效,如設置爲Baiduspider則代表規則對百度爬蟲有效,如果有多條則對多個爬蟲受到限制,但至少需要指定一條

一些常見的搜索爬蟲名稱:

BaiduSpider  百度爬蟲 www.baidu.com

Googlebot  Google爬蟲 www.google.com

360Spider  360爬蟲 www.so.com

YodaoBot  有道爬蟲 www.youdao.com

ia_archiver  Alexa爬蟲 www.alexa.cn

Scooter  altavista爬蟲 www.altavista.com

Disallow指定了不允許抓取的目錄,如上例中設置的/則代表不允許抓取所有的頁面

Allow一般和Disallow一起使用,用來排除單獨的某些限制,如上例中設置爲/public/則表示所有頁面不允許抓取,但可以抓取public目錄

設置示例:

#禁止所有爬蟲
User-agent: *
Disallow: /

#允許所有爬蟲訪問任何目錄,另外把文件留空也可以
User-agent: *
Disallow:

#禁止所有爬蟲訪問某那些目錄
User-agent: *
Disallow: /home/
Disallow: /tmp/

#只允許某一個爬蟲訪問
User-agent: BaiduSpider
Disallow:
User-agent: *
Disallow: /

2. robotparser

rebotparser模塊用來解析robots.txt,該模塊提供了一個類RobotFileParser,它可以根據某網站的robots.txt文件來判斷一個抓取爬蟲時都有權限來抓取這個網頁

urllib.robotparser.RobotFileParser(url=’’)

robotparser類常用的方法:

set_url():用來設置robots.txt文件的連接,如果在創建RobotFileParser對象是傳入了連接,就不需要在使用這個方法設置了

read():讀取reobts.txt文件並進行分析,它不會返回任何內容,但執行那個了讀取和分析操作

parse():用來解析robots.txt文件,傳入的參數是robots.txt某些行的內容,並安裝語法規則來分析內容

can_fetch():該方法傳入兩個參數,第一個是User-agent,第二個是要抓取的URL,返回的內容是該搜索引擎是否可以抓取這個url,結果爲True或False

mtime():返回上次抓取和分析robots.txt的時間

modified():將當前時間設置爲上次抓取和分析robots.txt的時間

from urllib.robotparser import RobotFileParser
rp = RobotFileParser()  #創建對象
rp.set_url('https://www.cnblogs.com/robots.txt') #設置robots.txt連接,也可以在創建對象時指定
rp.read()  #讀取和解析文件
print(rp.can_fetch('*','https://i.cnblogs.com/EditPosts.aspx?postid=9170312&update=1')) #堅持鏈接是否可以被抓取

通過的學習我們可以使用urllib庫來進行網頁請求、資源的獲取,urllib庫功能強大,但使用起來也相對繁瑣,下一章我們可以學習功能同樣強大,卻簡單實用的requests庫,可以直接點擊如下鏈接跳轉:python網絡爬蟲教程(四):詳解requests庫

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章