About
urllib是Python內置的HTTP請求庫。urllib 模塊提供的上層接口,使訪問 www 和 ftp 上的數據就像訪問本地文件一樣,並且它也是requests的底層庫。
其中包括4個主要模塊:
- urllib.request:請求模塊。
- urllib.error:異常處理模塊。
- urllib.parse:URL解析模塊。
- urllib.robotparser:robots.txt解析模塊。
這里以Python3.6為例。
urllib、urllib2、urllib3傻傻分不清楚
在Python2.x中,分為urllib和urllib2,簡單來說,urllib2是urllib的增強版,但urllib中的函數又比urllib2多一些,對於簡單的下載之類的,urllib綽綽有余,如果涉及到實現HTTP身份驗證或者cookie或者擴展編寫自定義協議,urllib2更好一些。
-
urllib支持設置編碼的函數urllib.urlencode,在模擬登陸的時候經常需要傳遞經過post編碼之后的參數,如果不想使用第三方庫完成模擬登錄,就必須使用到標准庫中的urllib。urllib提供一些比較原始基礎的方法而urllib2並沒有,比如urllib中的urlencode方法用來GET查詢字符串的產生。
-
urllib2比較有優勢的地方在於urllib2.openurl中可以接受一個Request類的實例來設置Request參數,來修改/設置Header頭從而達到控制HTTP Request的header部分的目的,也可以修改用戶代理,設置cookie等,但urllib僅可以接受URL。這就意味着,如果你訪問一個網站想更改User Agent(可以偽裝你的瀏覽器),你就需要使用urllib2。
-
urllib2模塊沒有加入urllib.urlretrieve函數以及urllib.quote等一系列quote和unquote功能,這個時候就需要urllib的輔助。
因此,在Python2.x中,這兩個庫可以搭配使用。
而urllib3則是增加了連接池等功能,兩者互相都有補充的部分。
那么Python3.x中呢,urllib和urllib2都合並到了urllib中了,所以,啥也別想了,Python3.x即將一統江湖,所以,我們還是踏實兒的學習Python3.x下的urllib吧。
再來看看Python2.x和Python3.x的區別。
在Python2.x中,urlopen
方法在urllib下:
import urllib2
response = urllib2.urlopen()
在Python3.x中,urlopen
方法就移動到了urllib.request
下:
import urllib.request
response = urllib.request.urlopen()
urllib.request
請求
先來看urllib怎么發送請求。
發送get請求
import urllib
response = urllib.request.urlopen('http://www.baidu.com')
# 獲取bytes類型的數據
# print(response.read())
# 想要獲取到字符串類型的數據,需要使用 decode 轉碼為字符串
print(response.read().decode('utf-8'))
發送post請求
import urllib.request
import urllib.parse
data = bytes(urllib.parse.urlencode({'world': 'hello'}), encoding='utf-8')
response = urllib.request.urlopen(url='http://httpbin.org/post', data=data)
print(response.read().decode('utf-8'))
首先,需要了解一個http://httpbin.org
這個網站,HTTPBin是以Python+Flask寫的一款工具,它包含了各類的HTTP場景,且每個接口一定都有返回。
data參數需要字節類型的數據,所以,我們首先用bytes轉碼,然后將我們的字典使用urllib.parse.urlencode
並指定編碼方式完成編碼。
另外,data參數用來控制請求方式,如果請求中有data則是post請求,否則為get請求。
timeout
import socket
import urllib.request
import urllib.error
try:
# 沒有超時,正常返回json格式的內容
# response = urllib.request.urlopen(url='http://www.httpbin.org/get', timeout=2)
# 超時會拋出異常,由異常語句捕獲
response = urllib.request.urlopen(url='http://www.httpbin.org/get', timeout=0.1)
print(response.read().decode('utf-8'))
except urllib.error.URLError as e:
# print(e) # <urlopen error timed out>
if isinstance(e.reason, socket.timeout):
print(e.reason) # timed out
e.reason
為錯誤原因。timeout
單位是秒。
響應
現在來研究urlopen返回對象提供的方法:
- read(),readline(),readlines(),fileno(), close():這些方法與文件對象一樣。
- info():返回一個httplibHTTPMessage對象,表示遠程服務器返回的頭信息。
- getcode():返回HTTP狀態碼。
- geturl():返回請求的url。
響應類型
import urllib.request
response = urllib.request.urlopen(url='http://www.httpbin.org/get')
print(response) # <http.client.HTTPResponse object at 0x07B65270>
print(type(response)) # <class 'http.client.HTTPResponse'>
狀態碼
import urllib.request
response = urllib.request.urlopen(url='http://www.httpbin.org/get')
print(response.status) # 200
響應頭
import urllib.request
response = urllib.request.urlopen(url='http://www.httpbin.org/get')
print(response.headers)
print(response.getheaders()) # 列表類型的響應頭
print(response.getheader('Server', None)) # 獲取特定的響應頭
響應體
import urllib.request
response = urllib.request.urlopen(url='http://www.httpbin.org')
# print(response.read())
print(response.read().decode('utf-8'))
之前說過,urllib獲取到的內容是字節形式,我們通過read讀出來之后需要使用decode解碼。
request對象
上面我們發送一些簡單的請求沒問題,但是復雜的請求(比如帶headers)就力有不逮了。這個時候,我們需要學習一個Request
來定制復雜的請求參數了。
import urllib.request
request = urllib.request.Request(url='http://www.httpbin.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
首先,我們使用urllib.request.Request
來構建一個request對象,然后傳給urlopen
即可。
再來看一個復雜點的帶headers。
import urllib.request
import urllib.parse
# 指定url
url='http://www.httpbin.org/post'
# 自定義headers
headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"}
# 自定義data參數並編碼為bytes類型
data_dict = {"username": "zhangkai"}
data = bytes(urllib.parse.urlencode(data_dict), encoding='utf-8')
# 使用urllib.request.Request構造request對象
request = urllib.request.Request(url=url, data=data, headers=headers, method='POST')
# 將request對象交給urlopen發請求去吧
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
再來看一個add_headers
方法。
import urllib.request
import urllib.parse
url='http://www.httpbin.org/post'
# 這里跟上面的示例區別就是不在單獨指定headers字典
# headers = {"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"}
data_dict = {"username": "zhangkai"}
data = bytes(urllib.parse.urlencode(data_dict), encoding='utf-8')
request = urllib.request.Request(url=url, data=data, method='POST')
# 而是使用構造好的request對象下面的add_header方法添加header,換湯不換葯....
request.add_header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36")
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))
add_header
方法作為添加headers的補充方式,如果有多個header鍵值對,可以使用for循環一一添加進去.....
會了構造request對象,我們基本能應付大部分的網站了。
再來看看更復雜的一些用法,以應對更多的復雜環境,比如攜帶cookie什么的。
Cookie
獲取cookie
import urllib.request
import http.cookiejar
# 聲明 cookiejar 對象
cookie = http.cookiejar.CookieJar()
# 使用handler處理cookie對象
handler = urllib.request.HTTPCookieProcessor(cookie)
opener = urllib.request.build_opener(handler)
# 當opener.open發送請求
response = opener.open('http://www.baidu.com')
# 當response有了結果之后,會自動將cookie信息保存到上面聲明的cookie對象中去
print(cookie)
http.cookiejar
提供一個永久性的cookie對象。並且cookiejar
有3個子類:
- CookieJar:管理HTTP cookie值、存儲HTTP請求生成的cookie、向傳出的HTTP請求添加cookie的對象。整個cookie都存儲在內存中,對CookieJar實例進行垃圾回收后cookie也將丟失。
- FileCookieJar (filename,delayload=None,policy=None):從CookieJar派生而來,用來創建FileCookieJar實例,檢索cookie信息並將cookie存儲到文件中。filename是存儲cookie的文件名。delayload為True時支持延遲訪問訪問文件,即只有在需要時才讀取文件或在文件中存儲數據。
- MozillaCookieJar (filename,delayload=None,policy=None):從FileCookieJar派生而來,創建與Mozilla瀏覽器 cookies.txt兼容的FileCookieJar實例。
- LWPCookieJar (filename,delayload=None,policy=None):從FileCookieJar派生而來,創建與libwww-perl標准的 Set-Cookie3 文件格式兼容的FileCookieJar實例。
大多數情況下,我們只用CookieJar()
,如果需要和本地文件交互,就用 MozillaCookjar()
或 LWPCookieJar()
補充:libwww-perl集合是一組Perl模塊,它為萬維網提供簡單而一致的應用程序編程接口。該庫的主要重點是提供允許您編寫WWW客戶端的類和函數。
將cookie寫入文件
我們知道cookie是保持登錄會話信息的憑證,那么在獲取到cookie后寫入本地,在cookie失效前,后續訪問再讀取本地文件,並在請求中攜帶,以達到繼續保持登錄信息的狀態。
import urllib.request
import http.cookiejar
'''
寫入cookie對象到本地
'''
url = 'http://www.baidu.com'
file_name = 'cookie.txt'
# 這里將cookie信息處理成火狐瀏覽器cookie格式
cookie = http.cookiejar.MozillaCookieJar(file_name)
# LWPCookieJar格式cookie
# cookie = http.cookiejar.LWPCookieJar(file_name)
# 使用handler處理cookie對象
handler = urllib.request.HTTPCookieProcessor(cookie1)
opener = urllib.request.build_opener(handler)
response1 = opener.open(url)
# 將cookie信息保存到本地文件
cookie1.save(ignore_discard=True, ignore_expires=True)
'''
讀取本地cookie信息
'''
# 以什么格式保存,就以什么格式讀出來,這里都用MozillaCookieJar
cookie2 = http.cookiejar.MozillaCookieJar()
# 使用load方法從本地讀取cookie信息
cookie2.load(file_name, ignore_discard=True, ignore_expires=True)
# 放到handle中
handler = urllib.request.HTTPCookieProcessor(cookie2)
opener = urllib.request.build_opener(handler)
response2 = opener.open(url)
print(response2.read().decode('utf-8'))
在save
和load
中,有兩個參數需要說明:
- ignore_discard:保存cookie,即使設置為丟棄的cookie也保存。
- ignore_expires:如果cookie已過期也要保存到文件,並且如果文件存在則覆蓋。后續可以使用
load
或者revert
方法恢復保存的文件。
代理
import urllib.request
# 調用ProxyHandler 代理ip的形式是字典
# 付費代理
# money_proxy = {"協議":"username:pwd@ip:port"}
proxy_handler = urllib.request.ProxyHandler({'sock5': 'localhost:1080'})
# 使用build_opener構建opener對象
opener = urllib.request.build_opener(proxy_handler)
# 調用opener的open方法向指定url發請求
response = opener.open('http://www.httpbin.org/get')
print(response.read().decode('utf-8'))
'''
{
"args": {},
"headers": {
"Accept-Encoding": "identity",
"Host": "www.httpbin.org",
"User-Agent": "Python-urllib/3.6"
},
"origin": "124.64.16.181, 124.64.16.181",
"url": "https://www.httpbin.org/get"
}
'''
{'sock5': 'localhost:1080'}
中sock5
是協議,localhost:1080
是走本地的1080端口。
其實urlopen
在內部也是調用了opener.open
方法發送請求。
另外,返回結果origin
是訪問IP,而這個IP正好是我們使用的代理IP。
下載
我們可以使用urllib下載圖片,文件等到本地。
import urllib.request
url = 'http://n.sinaimg.cn/news/1_img/upload/cf3881ab/69/w1000h669/20190912/bed4-iepyyhh6925213.jpg'
filename = 'a.jpg'
# 使用urlretrieve進行下載操作
urllib.request.urlretrieve(url=url, filename=filename)
除此之外,我們還可以使用urllib.urlcleanup()
來清除urllib.urlretrieve
所產生的緩存。
urllib.error
urllib中的異常處理有3個:
urllib.error.URLError
:捕獲關於url的錯誤,只有一個返回方法reason,錯誤原因。urllib.error.HTTPError
:捕獲關於http的錯誤,有3個返回方法。code,http中定義的http狀態碼;reason,錯誤原因;headers,響應頭。urllib.error.ContentTooShortError
:當urlretrieve
檢測到下載數據量小於預期時,會引發此異常。
urllib.error.URLError
import urllib.request
import urllib.error
try:
# 這里將 cnblogs 寫成 cnblogsww
# response = urllib.request.urlopen(url='https://www.cnblogsww.com/Neeo/p/1083589221.html')
print(response.read().decode('utf-8'))
except urllib.error.ContentTooShortError as e:
print('ContentTooShortError: ', e.reason)
urllib.error.HTTPError
import urllib.request
import urllib.error
try:
headers = {
"User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/76.0.3809.132 Safari/537.36"}
# 不存在的 103048922222.html
response = urllib.request.urlopen(url='https://www.cnblogs.com/Neeo/p/103048922222.html')
print(response.read())
except urllib.error.HTTPError as e:
print('HTTPError: ', e.reason, e.headers, e.code)
urllib.error.ContentTooShortError
這個錯誤比較重要,因為我們在下載文件時,很可能會因為各種原因導致下載沒有完成就失敗了。這個時候,我們就可以使用ContentTooShortError
來捕捉錯誤從新下載。如果重新下載還是下載失敗,可以考慮使用遞歸,下載失敗就重新下,直到下載成功。
import urllib.request
import urllib.error
try:
url = 'http://n.sinaimg.cn/news/1_img/upload/cf3881ab/69/w1000h669/20190912/bed4-iepyyhh6925213.jpg'
filename = 'a.jpg'
urllib.request.urlretrieve(url=url, filename=filename)
except urllib.error.ContentTooShortError as e:
print('ContentTooShortError: ', e.reason)
# 如果下載失敗,就重新下載
urllib.request.urlretrieve(url=url, filename=filename)
def get_file(url, filename):
""" 使用遞歸進行重新下載,直到下載成功為止 """
try:
urllib.request.urlretrieve(url=url, filename=filename)
print('download done ......')
except urllib.error.ContentTooShortError:
get_file(url, filename)
url = "http://n.sinaimg.cn/news/1_img/upload/cf3881ab/69/w1000h669/20190912/bed4-iepyyhh6925213.jpg"
filename = 'a.jpg'
get_file(url, filename)
urllib.parse
urllib.parse
定義了URL的標准接口,實現對於URL的各種操作,包括解析、合並、編碼、解碼,使用之前需導入。
urllib.parse.urlparse
urllib.parse.urlparse
以元組的形式返回URL解析后的6個組件。對應的URL結構一般是這樣的:
scheme://netloc/path;parameters?query#fragment
每個元組項都是一個字符串,也可能是空的。
返回的元組中:
- scheme:協議。
- netloc:域名。
- path:路徑。
- params:參數。
- query:查詢條件,一般用於個get請求的URL。
- fragment:錨點。
基本使用:
import urllib.parse
urllib.parse.urlparse(url, scheme='', allow_fragments=True)
- url:待解析的url
- scheme:協議,如果URL中沒有協議,則使用該參數設置協議,如果有協議,該參數不生效
- allow_fragments:是否忽略錨點,默認為True,表示表示不忽略,False表示忽略
示例:
import urllib.parse
result = urllib.parse.urlparse('https://www.cnblogs.com/Neeo/p/10864123.html?k1=v1#python')
print(result)
'''
ParseResult(scheme='https', netloc='www.cnblogs.com', path='/Neeo/p/10864123.html', params='', query='k1=v1', fragment='python')
'''
urllib.parse.urlunparse
與urlparse
相反,urllib.parse.urlunparse
用來構造URL。
import urllib.parse
url_components = ['https', 'www.cnblogs.com', '/Neeo/p/10864123.html', '', '', 'python']
result = urllib.parse.urlunparse(url_components)
print(result) # https://www.cnblogs.com/Neeo/p/10864123.html#python
在url_components
中,順序必須遵循:
scheme, netloc, url, params, query, fragment
也就是6個選項都要有值,如果沒有也要填充空字符串占位。
urllib.parse.urljoin
urllib.parse.urljoin
用來拼接URL。
import urllib.parse
# 將兩個url拼接成一個完整的url,base_url + sub_url
print(urllib.parse.urljoin('https://www.cnbogs.com', '/Neeo/p/10864123.html'))
# 如果每個參數中都有相應的參數的話,比如都有協議,那么將以后面的為准
print(urllib.parse.urljoin('https://www.cnbogs.com', 'http://www.baidu.com/Neeo/p/10864123.html'))
注意,urljoin
方法只能接受兩個url參數:
urljoin(base, url, allow_fragments=True)
別意淫它能跟os.path.join
一樣可勁兒的往后拼!
urllib.parse.urlencode
urllib.parse.urlencode
將字典類型的參數序列化為url編碼后的字符串,常用來構造get/post請求的參數k1=v1&k2=v2
。
import urllib.parse
params = {"k1": "v1", "k2": "v2"}
encode_params = urllib.parse.urlencode(params)
print('https://www.baidu.com?' + encode_params) # https://www.baidu.com?k1=v1&k2=v2
# 這里用urljoin不好使, 它在處理base url時會將 ? 干掉
print(urllib.parse.urljoin('https://www.baidu.com', encode_params)) # https://www.baidu.com/k1=v1&k2=v2
# 或者你繼續使用拼接....好蛋疼
print(urllib.parse.urljoin('https://www.baidu.com', '?' + encode_params)) # https://www.baidu.com?k1=v1&k2=v2
urllib.parse.quote系列
按照標准,URL只允許一部分ASCII字符(數字字母和部分符號),其他的字符如漢字是不符合URL標准的。所以在使用URL的時候,要進行URL編碼。
urllib提供了urllib.parse.quote
和urllib.parse.quote_plus
進行URL編碼。
而urllib.parse.quote_plus
比urllib.parse.quote
更近一步,會對/
符號進行編碼。
而urllib.parse.unquote
和urllib.parse.unquote_plus
則是將編碼后的URL進行還原。
import urllib.parse
url = 'https://www.baidu.com/s?&wd=張開'
result = urllib.parse.quote(url)
print(result) # https%3A//www.baidu.com/s%3F%26wd%3D%E5%BC%A0%E5%BC%80
result_plus = urllib.parse.quote_plus(url)
print(result_plus) # https%3A%2F%2Fwww.baidu.com%2Fs%3F%26wd%3D%E5%BC%A0%E5%BC%80
un_result = urllib.parse.unquote(result)
print(un_result) # https://www.baidu.com/s?&wd=張開
un_result_plus = urllib.parse.unquote_plus(result_plus)
print(un_result_plus) # https://www.baidu.com/s?&wd=張開
urllib.robotparser
我們可以利用urllib.robotparser
對爬取網站的Robots協議進行分析。
那問題來了,什么是Robots協議?
Robots協議
Robots協議(也稱為爬蟲協議、機器人協議等)的全稱是“網絡爬蟲排除標准”(Robots Exclusion Protocol),網站通過Robots協議告訴搜索引擎哪些頁面可以抓取,哪些頁面不能抓取。robots.txt文件是一個文本文件,放在站點的根目錄下。
當一個搜索蜘蛛訪問一個站點時,它會首先檢查該站點根目錄下是否存在robots.txt,如果存在,搜索機器人就會按照該文件中的內容來確定訪問的范圍;如果該文件不存在,所有的搜索蜘蛛將能夠訪問網站上所有沒有被口令保護的頁面。
禁止所有爬蟲訪問任何內容
User-Agent: *
Disallow: /
允許所有爬蟲訪問任何內容
User-Agent: *
Disallow:
允許某些爬蟲訪問某些目錄
User-agent: Baiduspider
Allow: /article
Allow: /oshtml
Disallow: /product/
Disallow: /
User-Agent: Googlebot
Allow: /article
Allow: /oshtml
Allow: /product
Allow: /spu
Allow: /dianpu
Allow: /oversea
Allow: /list
Disallow: /
User-Agent: *
Disallow: /
關於爬蟲名稱
爬蟲名稱 | 所屬公司 | 網址 |
---|---|---|
Baiduspider | 百度 | www.baidu.com |
Googlebot | 谷歌 | www.google.com |
Bingbot | 微軟必應 | cn.bing.com |
360Spider | 360搜索 | www.so.com |
Yisouspider | 神馬搜索 | http://m.sm.cn/ |
Sogouspider | 搜狗搜索 | https://www.sogou.com/ |
Yahoo! Slurp | 雅虎 | https://www.yahoo.com/ |
RobotFileParser
robotpaser模塊提供RobotFileParser類來解析robots.txt文件,判斷是否允許爬取網站的某一目錄。
在實例化類時傳入robots.txt。
import urllib.robotparser
response = urllib.robotparser.RobotFileParser(url='https://www.zhihu.com/robots.txt')
response.read()
或者通過實例化對象調用set_url
方法傳入robots.txt。
import urllib.robotparser
response = urllib.robotparser.RobotFileParser()
response.set_url('https://www.zhihu.com/robots.txt')
response.read()
whatever,都需要使用response.read()
讀出來,不然后面解析不到。
常用方法:
- set_url(url):用來設置 robots.txt 文件鏈接,如果在初次實例化 RobotFileParser 類的時候傳入了 url 參數,那么就不需要再次調用此方法設置了。
- read():讀取 robots.txt 文件並將讀取結果交給 parse() 解析器進行解析。
- parse(lines):用來解析 robots.txt 文件內容,分析傳入的某些行的協議內容。
- can_fetch(useragent, url):需要兩個參數,User-Agent、所要抓取的 URL 鏈接,返回此搜索引擎是否允許抓取此 URL,返回結果為 True、False。
- mtime():返回上次抓取分析 robots.txt 文件的時間,這對於需要對 robots.txt 進行定期檢查更新的長時間運行的網絡爬蟲非常有用 。
- modified():同樣的對於長時間分析和抓取的搜索爬蟲很有幫助,將當前時間設置為上次抓取和分析 robots.txt 的時間。
- crawl_delay(useragent):返回抓取延遲時間的值,從相應的 User-Agent 的 robots.txt 返回 Crawl-delay 參數的值。 如果沒有這樣的參數,或者它不適用於指定的 User-Agent,或者此參數的 robots.txt 條目語法無效,則返回 None。
- request_rate(useragent):從robots.txt返回Request-rate參數的內容,作為命名元組RequestRate(requests,seconds)。 如果沒有這樣的參數,或者它不適用於指定的useragent,或者此參數的robots.txt條目語法無效,則返回None。(Python3.6新增方法)
示例:
import urllib.robotparser
response = urllib.robotparser.RobotFileParser(url='https://www.zhihu.com/robots.txt')
# 要有讀操作
response.read()
# 判斷某一個網址是否能爬取
print(response.can_fetch('Googlebot', 'https://www.zhihu.com/question/268464407/answer/804631692')) # True
print(response.can_fetch('*', 'https://www.zhihu.com/question/268464407/answer/804631692')) # False
# 返回上一次抓取分析 robots.txt 的時間
print(response.mtime()) # 1568542822.1876643
# 將當前時間設置為上次抓取和分析 robots.txt 的時間
response.modified()
# 返回 robots.txt 文件對請求頻率的限制
print(response.request_rate('MSNBot').requests) # 1
print(response.request_rate('*')) # None
# 返回 robots.txt 文件對抓取延遲限制
print(response.crawl_delay('*')) # None
print(response.crawl_delay('MSNBot')) # 10
使用parser方法讀取和分析robots.txt。
import urllib.robotparser
import urllib.request
# 實例化 RobotFileParser 對象
response = urllib.robotparser.RobotFileParser()
# 使用parser讀取 robots.txt 文件
result = urllib.request.urlopen('https://www.zhihu.com/robots.txt').read().decode('utf-8').split('\n')
response.parse(result)
# 判斷url是否可爬取
print(response.can_fetch('Googlebot', 'https://www.zhihu.com/question/268464407/answer/804631692')) # True
print(response.can_fetch('*', 'https://www.zhihu.com/question/268464407/answer/804631692')) # False
歡迎斧正,that's all see also:[http://www.httpbin.org/](