爬蟲之urllib庫

一.urllib庫簡介

簡介html

Urllib是Python內置的HTTP請求庫。其主要做用就是能夠經過代碼模擬瀏覽器發送請求。它包含四個模塊:python

urllib.request :請求模塊
urllib.error :異常處理模塊
urllib.parse url : 解析模塊
urllib.robotparser :robots.txt解析模塊,用的比較少

相比Python2與3變化:瀏覽器

其常被用到的子模塊在Python3中的爲urllib.request和urllib.parse,在Python2中是urllib和urllib2。cookie

Python2: import urllib2 response=urllib2.urlopen(‘http://www.baidu.com’)
Python3: import urllib.request response=urllib.request.urlopen(‘http://www.baidu.com’)

二.使用簡介:

parse 

用於url解析轉換網絡

dic = {'wd': '趙麗穎'}
ps = parse.urlencode(dic)
print(ps)  # wd=%E8%B5%B5%E4%B8%BD%E9%A2%96
print(parse.parse_qs(ps))  # {'wd': ['趙麗穎']}

 

urlopen

這個函數很簡單,就是請求一個url的內容。其實這就是爬蟲的第一步:網頁請求,獲取內容。socket

    urllib.request.urlopen(url, 
                           data=None,
                           [timeout] *,
                           cafile=None,
                           capath=None,
                           cadefault=False,
                           context=None)
    # urlopen前三個分別是(網站,網站的數據,超時設置,主要前兩個其餘不經常使用)

 

能夠看到,這個函數能夠有很是多的參數,前三個用的最多,咱們來逐一看看。函數

1.url參數工具

import urllib.request
response =urllib.request.urlopen('http://www.baidu.com') #把請求的結果傳給response
print(response.read().decode('utf-8')) #轉換成字符串
print(response.read(10).decode('utf-8')) #只讀取10個
print(response.readline().decode('utf-8')) #只讀取一行

在交互模式下執行以上命令就能夠打印出百度首頁的源代碼了。
這是一種GET類型的請求,只傳入了一個參數(url)。
下面演示一種POST類型的請求:post

2.data參數
示例代碼:測試

import urllib.parse
import urllib.request

data=bytes(urllib.parse.urlencode({'word':'hello'}),encoding='utf8')
#須要傳入一個data參數,須要的是bytes類型,這裏用了urlencode方法傳入一個字典,並指定編碼
response=urllib.request.urlopen('http://httpbin.org/post',data=data)
#給urlopen函數傳入兩個參數,一個是url,一個是data
print(response.read())

(http://httpbin.org/ 是一個HTTP測試頁面)
能夠看到打印出了一些Json字符串:

咱們能夠從打印結果看到,咱們成功的把’word’:'hello’這個字典經過urlopen函數以POST形式把它傳遞過去了。這樣咱們就完成了一個POST的請求。
總結:加了data參數就是以POST形式發送請求,不然就是GET方式了。

3.timeout
再來看看第三個參數:超時時間。若是超過了這個時間沒有獲得響應的話,就會拋出異常。
示例代碼:

import urllib.request
response=urllib.request.urlopen('http://httpbin.org/get',timeout=1)#設置超時時間
print(response.read())

再看另外一種狀況,咱們把超時時間設置爲0.1:

import socket
import urllib.request
import urllib.error

try:
response=urllib.request.urlopen('http://httpbin.org/get',timeout=0.1)
#必須在0.1秒內獲得響應,不然就會拋出異常
except urllib.error.URLError as e:
if isinstance(e.reason,socket.timeout):#類型判斷,若是是超時錯誤,那麼打印
print('TIME OUT')

響應(response)

獲取響應類型:type()
示例:

import urllib.request
response =urllib.request.urlopen('https://www.python.org')
print(type(response))#打印響應的類型

經過調用這個方法咱們能夠看到響應的類型。

狀態碼、響應頭
一個響應裏面包含了兩個比較有用的信息:狀態碼和響應頭。

以上面提到的http://httpbin.org/ 爲例,咱們能夠在審查中,找到狀態碼和響應頭(上圖紅框所示)。
這兩個信息是判斷響應是否成功的很是重要的標誌。
在這裏咱們能夠用status參數獲取響應的狀態碼,用getheaders()方法獲取響應頭。
示例:

import urllib.request

response =urllib.request.urlopen('https://www.python.org')
print(response.status)#狀態碼
print(response.getheaders())#響應頭
print(response.getheader('Server'))#能夠取得響應頭中特定的信息

另外,關於read()方法,它獲取的是響應體中的內容(bytes形式)

import urllib.request
response =urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))#將字節流解碼爲utf-8

請求(request)

若是咱們要在請求中加入別的信息怎麼辦呢?用上面的urlopen函數是沒法知足的。
例如咱們要在請求中加上Headers參數,可是urlopen函數中是沒有提供這個參數的。
若是咱們想要發送一些更爲複雜的請求的話,好比加上Headers,怎麼辦呢?
那麼我能夠建立一個request對象——使用Request(它也是屬於urllib.request模塊的)。
來看示例:

import urllib.request
request=urllib.request.Request('https://python.org')#把url構形成一個request對象
response=urllib.request.urlopen(request)#再把request對象傳給urlopen
print(response.read().decode('utf-8'))

這樣也能夠成功實現請求。
有了這樣的方法,咱們就能更方便地指定請求的方式,還能夠加一些額外的數據。
那麼如今嘗試構造一個POST請求,而且把headers加進來。

from urllib import request,parse
#構造一個POST請求
url='http://httpbin.org/post'
headers={
    'Users-Agent':'..............',
    'Host':'httpbin.org'
}
dic={
    'name':'iron'
}
data=bytes(parse.urlencode(dic),encoding='utf8')#fontdata數據
req=request.Request(url=url,data=data,headers=headers,method='POST')#構建一個Request()的一個結構
response = request.urlopen(req)
print(response.read().decode('utf-8'))

咱們把以上代碼寫成一個py文件並運行:

 

能夠看到,咱們構造的Request包含了以前所提到的信息,請求的時候咱們是把Request當成一個總體傳遞給了urlopen,就能夠實現這樣的請求了。
好處是整個Request的結構是很是清晰的。
此外還有另一種實現方式,就是用add_header()方法,也能夠實現相同的效果:

from urllib import request, parse

# 構造一個POST請求
url = 'http://httpbin.org/post'

dic = {
'name': 'iron'
}
data = bytes(parse.urlencode(dict), encoding='utf8') # fontdata數據
req = request.Request(url=url, data=data, method='POST') # 構建一個Request()的一個結構
req.add_header('User-Angent', '................')
req.add_header('Host', 'httpbin.org')
response = request.urlopen(req)
print(response.read().decode('utf-8'))

add_header() 方法做爲添加header的另外一種方式,能夠用來比較複雜的狀況,好比要添加許多鍵值對,那麼能夠用一個for循環來不斷調用這個方法,這也是比較方便的。

urlretrieve

直接打開url並寫到本地

request.urlretrieve('http://www.baidu.com', 'baidu.html')

 例如直接下載圖片或者視頻

url = input('你想下載的視頻或圖片連接:')

request.urlretrieve(url, 'binfile')

 

Headler

Headler至關於一個輔助工具,來幫助咱們處理一些額外的工做,好比FTP、Cache等等操做,咱們都須要藉助Headler來實現。好比在代理設置的時候,就須要用到一個ProxyHandler。更多的用法,請參閱官方文檔。

1.代理
用來對ip地址進行假裝成不一樣地域的,防止ip在爬蟲運行時被封掉。
示例:

from urllib import request
proxy_handler = request.ProxyHandler( #構建ProxyHandler,傳入代理的網址
{'http':'http://127.0.0.1:9743',
'https':'https://127.0.0.1:9743'
}) #實踐代表這個端口已經被封了,這裏沒法演示了
 
opener = request.build_opener(proxy_handler)#再構建一個帶有Handler的opener
 
response = opener.open('http://www.baidu.com')
print(response.read())

2.Cookie
Cookie是在客戶端保存的用來記錄用戶身份的文本文件。
在爬蟲時,主要是用來維護登陸狀態,這樣就能夠爬取一些須要登陸認證的網頁了。


實例演示:

from urllib import request
 
from http import cookiejar
cookie =cookiejar.CookieJar()#將cookie聲明爲一個CookieJar對象
 
handler = request.HTTPCookieProcessor(cookie)
 
opener = request.build_opener(handler)
 
response  =opener.open('http://www.baidu.com')#經過opener傳入,並打開網頁
 
for item in cookie:#經過遍歷把已經賦值的cookie打印出來
    print(item.name+'='+item.value)#經過item拿到每個cookie並打印

3.Cookie的保存
咱們還能夠把cookie保存成文本文件,若cookie沒有失效,咱們能夠從文本文件中再次讀取cookie,在請求時把cookie附加進去,這樣就能夠繼續保持登陸狀態了。
示例代碼:

from urllib import request
from http import cookiejar

filename="cookie.txt"
cookie=cookiejar.MozillaCookieJar(filename)
#把cookie聲明爲cookiejar的一個子類對象————MozillaCookieJar,它帶有一個save方法,能夠把cookie保存爲文本文件
handler=request.HTTPCookieProcessor(cookie)
opener=request.build_opener(handler)
response=opener.open('http://www.baidu.com')
cookie.save(ignore_discard=True,ignore_expires=True)#調用save方法

執行代碼後,咱們就能夠在運行目錄下找到已經保存好的cookie文本文件了:

 

還有另一種格式:
在上面那段代碼的基礎上,換一個子類對象就能夠了:

cookie=cookiejar.LWPCookieJar(filename)

能夠看到,此次生了一個不一樣格式的cookie文本文件:

4.Cookie的讀取

咱們能夠選擇相對應的格式來完成讀取。以上面的LWP格式爲例:

from urllib import request
from http import cookiejar


cookie=cookiejar.LWPCookieJar() #z注意選擇相應的格式,這裏是LWP
cookie.load('cookie.txt',ignore_discard=True,ignore_expires=True)#load方法是讀取的關鍵
handler=request.HTTPCookieProcessor(cookie)
opener=request.build_opener(handler)
response=opener.open('http://www.baidu.com')
print(response.read().decode('utf-8'))

以上的代碼就能夠完成讀取了。這樣,咱們就能夠在對網頁進行請求時,自動把以前的cookie附着進去,以保持一個登陸的狀態了。

異常處理

這是屬於urllib的另外一大模塊。

rom urllib import request,error

#咱們試着訪問一個不存在的網址
try:
    response = request.urlopen('http://www.cuiqingcai.com/index.html')
except error.URLError as e:
    print(e.reason)#經過審查能夠查到咱們捕捉的異常是否與之相符

能夠看到,返回了錯誤信息。這樣的異常處理能夠保證爬蟲在工做時不會輕易中斷。
那麼,urllib能夠捕捉哪些異常呢?詳見官方文檔。
其實通常碰到有兩個:HTTP和URL。咱們通常只須要捕捉這兩個異常就能夠了。

from urllib import request,error

#咱們試着訪問一個不存在的網址
try:
    response = request.urlopen('http://www.cuiqingcai.com/index.html')
except error.HTTPError as e:#最好先捕捉HTTP異常,由於這個異常是URL異常的子類
    print(e.reason,e.code,e.headers,sep='\n')
except error.URLError as e:
    print(e.reason)
else:
    print('Request Successfully!')

打印出錯誤的相關信息。
此外,e.reason也是一個類,它能夠獲得異常的類型。
咱們試着看看:

from urllib import request,error
import socket


try:
    response = request.urlopen('http://www.baidu.com',timeout = 0.01)#超時異常
except error.URLError as e:
    print(type(e.reason))
    if isinstance(e.reason,socket.timeout):#判斷error類型
        print('TIME OUT')

異常類型被打印出來了,確實是超時異常。

URL解析

這是一個工具性質的模塊,即拿即用就行。

1.urlparse
這個方法將將url進行分割,分割成好幾個部分,再依次將其複製。

urllib.parse.urlparse(urlstring,scheme='',allow_fragments = True)
#分割成(url,協議類型,和#後面的東西)

來看具體的例子:

from urllib.parse import urlparse
 
result = urlparse('https://www.baidu.com/s?wd=urllib&ie=UTF-8')
print(type(result),result)  #<class 'urllib.parse.ParseResult'>
 
#無協議類型指定,自行添加的狀況
result1 = urlparse('www.baidu.com/s?wd=urllib&ie=UTF-8',scheme = 'https')
print(result1)
 
#有指定協議類型,默認添加的狀況?
result2 = urlparse('http://www.baidu.com/s?wd=urllib&ie=UTF-8',scheme = 'https')
print(result2)

#allow_fragments參數使用
result3 = urlparse('http://www.baidu.com/s?#comment',allow_fragments = False)
 
result4 = urlparse('http://www.baidu.com/s?wd=urllib&ie=UTF-8#comment',allow_fragments = False)
print(result3,result4)
#allow_fragments=False表示#後面的東西不能填,本來在fragment位置的參數就會往上一個位置拼接,能夠對比result1和result2的區別

從這個例子咱們也能夠知道,一個url能夠分紅6個字段。

2.urlunparse(urlparse的反函數)
這個函數用來拼接url。
看看這個例子:

from urllib.parse import urlunparse
#注意即便是空符號也要寫進去,否則會出錯

data = ['http',  'www.baidu.com', 'index.html','user','a=6' 'comment']
print(urlunparse(data))

3.urljoin

這個函數用來拼合url。
經過例子感覺如下:
之後面的參數爲基準,會覆蓋掉前面的字段。若是後面的url,存在空字段而前面的url有這個字段,就會用前面的做爲補充。

from urllib.parse import urljoin
print(urljoin('http://www.baidu.com','FQA.html'))
#http://www.baidu.com/FQA.html
 
print(urljoin('http://www.baidu.com','http://www.caiqingcai.com/FQA.html'))
#http://www.caiqingcai.com/FQA.html
 
print(urljoin('https://www.baidu.com/about.html','http://www.caiqingcai.com/FQA.html'))
#http://www.caiqingcai.com/FQA.html
 
print(urljoin('http://www.baidu.com/about.html','https://www.caiqingcai.com/FQA.html'))
#https://www.caiqingcai.com/FQA.html

4.urlencode
這個函數用來將字典對象轉化爲get請求參數。

from urllib.parse import urlencode
 
params = {
'name':'zhuzhu',
'age':'23'
}
 
base_url = 'http://www.baidu.com?'
 
url = base_url+urlencode(params)#將params對象編碼轉換
 
print(url)

robotparser

用來解析robot.txt。用的比較少,這裏再也不贅述。

三.由易到難的爬蟲程序:

1.爬取百度首頁面全部數據值

 1 #!/usr/bin/env python 
 2 # -*- coding:utf-8 -*-
 3 #導包
 4 import urllib.request
 5 import urllib.parse
 6 if __name__ == "__main__":
 7     #指定爬取的網頁url
 8     url = 'http://www.baidu.com/'
 9     #經過urlopen函數向指定的url發起請求,返回響應對象
10     reponse = urllib.request.urlopen(url=url)
11     #經過調用響應對象中的read函數,返回響應回客戶端的數據值(爬取到的數據)
12     data = reponse.read()#返回的數據爲byte類型,並不是字符串
13     print(data)#打印顯示爬取到的數據值。
#補充說明
urlopen函數原型:urllib.request.urlopen(url, data=None, timeout=<object object at 0x10af327d0>, *, cafile=None, capath=None, cadefault=False, context=None)

在上述案例中咱們只使用了該函數中的第一個參數url。在平常開發中,咱們能用的只有url和data這兩個參數。

url參數:指定向哪一個url發起請求
data參數:能夠將post請求中攜帶的參數封裝成字典的形式傳遞給該參數(暫時不須要理解,後期會講)

urlopen函數返回的響應對象,相關函數調用介紹:
response.headers():獲取響應頭信息
response.getcode():獲取響應狀態碼
response.geturl():獲取請求的url
response.read():獲取響應中的數據值(字節類型)

2.將爬取到百度新聞首頁的數據值寫入文件進行存儲

#!/usr/bin/env python 
# -*- coding:utf-8 -*-
import urllib.request
import urllib.parse
if __name__ == "__main__":
    url = 'http://news.baidu.com/'
    reponse = urllib.request.urlopen(url=url)
    #decode()做用是將響應中字節(byte)類型的數據值轉成字符串類型
    data = reponse.read().decode()
    #使用IO操做將data表示的數據值以'w'權限的方式寫入到news.html文件中
    with open('./news.html','w') as fp:
        fp.write(data)
    print('寫入文件完畢')

3.爬取網絡上某張圖片數據,且存儲到本地

#!/usr/bin/env python 
# -*- coding:utf-8 -*-
import urllib.request
import urllib.parse
#以下兩行代碼表示忽略https證書,由於下面請求的url爲https協議的請求,若是請求不是https則該兩行代碼可不用。
import ssl
ssl._create_default_https_context = ssl._create_unverified_context

if __name__ == "__main__":
    #url是https協議的
    url = 'https://timgsa.baidu.com/timg?image&quality=80&size=b9999_10000&sec=1536918978042&di=172c5a4583ca1d17a1a49dba2914cfb9&imgtype=0&src=http%3A%2F%2Fimgsrc.baidu.com%2Fimgad%2Fpic%2Fitem%2F0dd7912397dda144f04b5d9cb9b7d0a20cf48659.jpg'
    reponse = urllib.request.urlopen(url=url)
    data = reponse.read()#由於爬取的是圖片數據值(二進制數據),則無需使用decode進行類型轉換。
    with open('./money.jpg','wb') as fp:
        fp.write(data)
    print('寫入文件完畢')

  4.url的特性:

  url必須爲ASCII編碼的數據值。因此咱們在爬蟲代碼中編寫url時,若是url中存在非ASCII編碼的數據值,則必須對其進行ASCII編碼後,該url方可被使用。

  案例:爬取使用百度根據指定詞條搜索到的頁面數據(例如爬取詞條爲‘周杰倫’的頁面數據)

import urllib.request
import urllib.parse

if __name__ == "__main__":
    #原始url中存在非ASCII編碼的值,則該url沒法被使用。使用會報錯須要轉碼
    #url = 'http://www.baidu.com/s?ie=utf-8&wd=趙麗穎'
    #處理url中存在的非ASCII數據值
    url = 'http://www.baidu.com/s?'
    #將帶有非ASCII的數據封裝到字典中,url中非ASCII的數據每每都是'?'後面鍵值形式的請求參數
    param = {
        'ie':'utf-8',
        'wd':'趙麗穎'
    }
    #使用parse子模塊中的urlencode函數將封裝好的字典中存在的非ASCII的數值進行ASCII編碼
    param = urllib.parse.urlencode(param)
    #將編碼後的數據和url進行整合拼接成一個完整可用的url
    url = url + param
    print(url)
    response = urllib.request.urlopen(url=url)
    data = response.read()
    with open('./趙麗穎.html','wb') as fp:
        fp.write(data)
    print('寫入文件完畢')

 

5.經過自定義請求對象,用於假裝爬蟲程序請求的身份。

    以前在講解http經常使用請求頭信息時,咱們講解過User-Agent參數,簡稱爲UA,該參數的做用是用於代表本次請求載體的身份標識。若是咱們經過瀏覽器發起的請求,則該請求的載體爲當前瀏覽器,則UA參數的值代表的是當前瀏覽器的身份標識表示的一串數據。若是咱們使用爬蟲程序發起的一個請求,則該請求的載體爲爬蟲程序,那麼該請求的UA爲爬蟲程序的身份標識表示的一串數據。有些網站會經過辨別請求的UA來判別該請求的載體是否爲爬蟲程序,若是爲爬蟲程序,則不會給該請求返回響應,那麼咱們的爬蟲程序則也沒法經過請求爬取到該網站中的數據值,這也是反爬蟲的一種初級技術手段。那麼爲了防止該問題的出現,則咱們能夠給爬蟲程序的UA進行假裝,假裝成某款瀏覽器的身份標識。

    上述案例中,咱們是經過request模塊中的urlopen發起的請求,該請求對象爲urllib中內置的默認請求對象,咱們沒法對其進行UA進行更改操做。urllib還爲咱們提供了一種自定義請求對象的方式,咱們能夠經過自定義請求對象的方式,給該請求對象中的UA進行假裝(更改)操做。

import urllib.request
import urllib.parse

import ssl
ssl._create_default_https_context = ssl._create_unverified_context  # 證書相關

if __name__ == "__main__":
    #原始url中存在非ASCII編碼的值,則該url沒法被使用。
    #url = 'http://www.baidu.com/s?ie=utf-8&wd=趙麗穎'
    #處理url中存在的非ASCII數據值
    url = 'http://www.baidu.com/s?'
    #將帶有非ASCII的數據封裝到字典中,url中非ASCII的數據每每都是'?'後面鍵值形式的請求參數
    param = {
        'ie':'utf-8',
        'wd':'趙麗穎'
    }
    #使用parse子模塊中的urlencode函數將封裝好的字典中存在的非ASCII的數值進行ASCII編碼
    param = urllib.parse.urlencode(param)
    #將編碼後的數據和url進行整合拼接成一個完整可用的url
    url = url + param
    #將瀏覽器的UA數據獲取,封裝到一個字典中。該UA值能夠經過抓包工具或者瀏覽器自帶的開發者工具中獲取某請求,從中獲取UA的值
    headers={
        'User-Agent' : 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_12_0) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/68.0.3440.106 Safari/537.36'
    }
    #自定義一個請求對象
    #參數:url爲請求的url。headers爲UA的值。data爲post請求的請求參數(後面講)
    request = urllib.request.Request(url=url,headers=headers)

    #發送咱們自定義的請求(該請求的UA已經進行了假裝)
    response = urllib.request.urlopen(request)

    data=response.read()

    with open('./趙麗穎.html','wb') as fp:
        fp.write(data)
    print('寫入數據完畢')
相關文章
相關標籤/搜索