urllib庫(kù)簡(jiǎn)介:
urllib庫(kù)的response對(duì)象是先創(chuàng)建http,request對(duì)象,裝載到reques.urlopen里完成http請(qǐng)求。
urllib庫(kù)的作?
爬?的第?個(gè)步驟是獲取?頁(yè),urllib庫(kù)是?來(lái)實(shí)現(xiàn)這個(gè)功能:想服務(wù)器發(fā)送請(qǐng)求,得到服務(wù)器響應(yīng),獲取?頁(yè)的內(nèi)容。
Python的強(qiáng)?在于提供了功能齊全的類庫(kù),來(lái)幫助我們完成這個(gè)請(qǐng)求,通過(guò)調(diào)?urllib庫(kù),我們不需要了解請(qǐng)求的數(shù)據(jù)結(jié)構(gòu),HTTP,TCP,IP層?絡(luò)傳輸同學(xué),以及服務(wù)器應(yīng)答原理等。
我們只需要關(guān)?以下三點(diǎn),然后通過(guò)??調(diào)?urllib庫(kù)的代碼,就能夠獲得我們想要的?頁(yè)內(nèi)容。
請(qǐng)求的URL是什么
傳遞的參數(shù)是什么
如何設(shè)置可選的請(qǐng)求頭
urllib庫(kù)的構(gòu)成
在python2中,曾經(jīng)有urllib和urllib2兩個(gè)庫(kù)來(lái)實(shí)現(xiàn)請(qǐng)求的發(fā)送,但?前通?的python3版本中,兩個(gè)庫(kù)的功能已經(jīng)合并成?個(gè)庫(kù),統(tǒng)?為urllib,它是python內(nèi)置函數(shù),不需要
額外安裝即可使?。
urllib的四個(gè)模塊
【1】requset:HTTP請(qǐng)求模塊,可以?來(lái)模擬發(fā)送請(qǐng)求,只需要傳?URL及額外參數(shù),就可以模擬瀏覽器訪問(wèn)?頁(yè)的過(guò)程。
【2】error:異常處理模塊,檢測(cè)請(qǐng)求是否報(bào)錯(cuò),捕捉異常錯(cuò)誤,進(jìn)?重試或其他操作,保證程序不會(huì)終?。
【3】parse:?具模塊,提供許多URL處理?法,如拆分、解析、合并等。
【4】robotparser:識(shí)別?站的robots.txt?件,判斷哪些?站可以爬,哪些?站不可以爬,使?頻率較少。
發(fā)送請(qǐng)求
urlopen是request模塊中的?法,?于抓取?絡(luò)。
我們以代碼?例,我們抓取百度的?頁(yè)
# 調(diào)?urllib庫(kù)中的request模塊
import urllib.request
# 發(fā)送請(qǐng)求獲取百度?頁(yè)的響應(yīng)
response = urllib.request.urlopen("http://www.baidu.com")
返回的是http,response對(duì)象,實(shí)際上是html屬性。使用.read().decode()解碼后轉(zhuǎn)化成了str字符串類型,decode解碼后中文字符能夠顯示出來(lái)。
# 打印響應(yīng)內(nèi)容
# read()是把響應(yīng)對(duì)象內(nèi)容全部讀取出來(lái),讀取出來(lái)為bytes碼
# decode('utf-8')把bytes碼解碼
print(response.read().decode('utf-8'))
返回的結(jié)果?較多,隨便截取其中?部分,可以看出是百度的?頁(yè)HTML源代碼。
我們只???代碼,就完成了百度的抓取,并打印了?頁(yè)的源代碼,接下來(lái),我們看?看我們獲得的響應(yīng)內(nèi)容response到底是什么?利?type()?法來(lái)輸出響應(yīng)的類型。
print(type(response))
注意:
通常爬取網(wǎng)頁(yè),在構(gòu)造http請(qǐng)求的時(shí)候,都需要加上一些額外信息,什么Useragent,cookie等之類的信息,或者添加代理服務(wù)器。往往這些都是一些必要的反爬機(jī)制。
requests庫(kù)
簡(jiǎn)介:
requests庫(kù)調(diào)用是requests.get方法傳入url和參數(shù),返回的對(duì)象是Response對(duì)象,打印出來(lái)是顯示響應(yīng)狀態(tài)碼。
通過(guò).text 方法可以返回是unicode 型的數(shù)據(jù),一般是在網(wǎng)頁(yè)的header中定義的編碼形式,而content返回的是bytes,二級(jí)制型的數(shù)據(jù),還有 .json方法也可以返回json字符串。
如果想要提取文本就用text,但是如果你想要提取圖片、文件等二進(jìn)制文件,就要用content,當(dāng)然decode之后,中文字符也會(huì)正常顯示。
requests的優(yōu)勢(shì):
Python爬蟲時(shí),更建議用requests庫(kù)。因?yàn)閞equests比urllib更為便捷,requests可以直接構(gòu)造get,post請(qǐng)求并發(fā)起,而urllib.request只能先構(gòu)造get,post請(qǐng)求,再發(fā)起。
response.text和response.content的區(qū)別:
response.content :這個(gè)是直接從網(wǎng)絡(luò)上抓取的數(shù)據(jù),沒有經(jīng)過(guò)任何的編碼,所以是一個(gè)bytes類型,其實(shí)在硬盤上和網(wǎng)絡(luò)上傳輸?shù)淖址际莃ytes類型
response.text:這個(gè)是str的數(shù)據(jù)類型,是requests庫(kù)將response.content進(jìn)行解碼的字符串,解碼需要指定一個(gè)編碼方式,requests會(huì)根據(jù)自己的猜測(cè)來(lái)判斷編碼的方式,所以有時(shí)候可能會(huì)猜測(cè)錯(cuò)誤,就會(huì)導(dǎo)致解碼產(chǎn)生亂碼,這時(shí)候就應(yīng)該進(jìn)行手動(dòng)解碼,比如使用response.content.decode('utf-8')
response.text和response.content的區(qū)別:
response.content :這個(gè)是直接從網(wǎng)絡(luò)上抓取的數(shù)據(jù),沒有經(jīng)過(guò)任何的編碼,所以是一個(gè)bytes類型,其實(shí)在硬盤上和網(wǎng)絡(luò)上傳輸?shù)淖址际莃ytes類型
response.text:這個(gè)是str的數(shù)據(jù)類型,是requests庫(kù)將response.content進(jìn)行解碼的字符串,解碼需要指定一個(gè)編碼方式,requests會(huì)根據(jù)自己的猜測(cè)來(lái)判斷編碼的方式,所以有時(shí)候可能會(huì)猜測(cè)錯(cuò)誤,就會(huì)導(dǎo)致解碼產(chǎn)生亂碼,這時(shí)候就應(yīng)該進(jìn)行手動(dòng)解碼,比如使用response.content.decode('utf-8')
ASCII
ASCII編碼的全稱是American Standard Code for Information Interchange,美國(guó)信息互換標(biāo)準(zhǔn)代碼。這是一種最早的、只用來(lái)保存英文文字的編碼方式。ASCII編碼方式只使用了1個(gè)字節(jié)(8比特位,可以組合出256種不同的狀態(tài))中0~127種組合存儲(chǔ)了英文的文字。
GBK
當(dāng)計(jì)算機(jī)普及到國(guó)內(nèi)時(shí),因?yàn)闈h字的常用字就有將近6000個(gè),使用ASCII編碼已經(jīng)完全不能滿足使用的需求了。
所以在1981年,國(guó)家標(biāo)準(zhǔn)總局發(fā)布了GB2312(中國(guó)國(guó)家標(biāo)準(zhǔn)簡(jiǎn)體中文字符集),使用2個(gè)字節(jié)的組合,當(dāng)兩個(gè)大于127的字符連在一起時(shí),就表示一個(gè)漢字,這樣就組合出了7000多個(gè)簡(jiǎn)體字。
后來(lái)因?yàn)闈h字的擴(kuò)展需求,發(fā)布了GBK標(biāo)準(zhǔn),K是擴(kuò)展一次漢語(yǔ)拼音的聲母。即不再要求第二個(gè)字節(jié)大于127,只要第一個(gè)字節(jié)大于127,則表示這是一個(gè)漢字的開始。這樣共收錄了將近22000個(gè)漢字和符號(hào)。且兼容了GB2312標(biāo)準(zhǔn)。
2005年時(shí)修訂了GB18030標(biāo)準(zhǔn),支持了國(guó)內(nèi)少數(shù)民族的文字,共收錄漢字70000余個(gè)。兼容了GBK標(biāo)準(zhǔn)。
Unicode
就如國(guó)內(nèi)定義了GB2312標(biāo)準(zhǔn)一樣,當(dāng)時(shí)各個(gè)國(guó)家都規(guī)定了適用于自己語(yǔ)言的一套編碼方式。但是這就導(dǎo)致各國(guó)相互之間誰(shuí)也不懂誰(shuí)的編碼,裝錯(cuò)字符系統(tǒng)就會(huì)導(dǎo)致顯示全是亂碼。
所以這時(shí)ISO(International Standards Organization,國(guó)際標(biāo)準(zhǔn)化組織)推出了Unicode標(biāo)準(zhǔn)用以解決這個(gè)問(wèn)題。
Unicode標(biāo)識(shí)以2個(gè)字節(jié)長(zhǎng)度的數(shù)字來(lái)標(biāo)識(shí)所有字符,除了英文以外的字符全部重新進(jìn)行了統(tǒng)一編碼。
注意Unicode只是一種標(biāo)準(zhǔn),不是編碼方式,給予了每個(gè)字符一個(gè)16比特位的數(shù)字標(biāo)識(shí),至于這個(gè)字符在內(nèi)存中是由幾個(gè)字節(jié)存儲(chǔ),并不是Unicode標(biāo)準(zhǔn)規(guī)定的。
UTF-8
Unicode標(biāo)準(zhǔn)制定后,在很長(zhǎng)的一段時(shí)間內(nèi)無(wú)法推廣,直到互聯(lián)網(wǎng)的普及,強(qiáng)烈要求出現(xiàn)一種統(tǒng)一的編碼方式。然后就誕生了UTF-8,這個(gè)使用Unicode標(biāo)準(zhǔn)的編碼方式。注意:因此,UTF-8是Unicode標(biāo)準(zhǔn)的一種實(shí)現(xiàn)方式。
UTF-8的編碼規(guī)則很簡(jiǎn)單,只有兩條:
對(duì)于單字節(jié)的符號(hào),字節(jié)的第一位設(shè)為0,后面7位為這個(gè)符號(hào)的 Unicode 碼。因此對(duì)于英語(yǔ)字母,UTF-8 編碼和ASCII碼是相同的。
對(duì)于n字節(jié)的符號(hào)(n > 1),第一個(gè)字節(jié)的前n位都設(shè)為1,第n + 1位設(shè)為0,后面字節(jié)的前兩位一律設(shè)為10。剩下的沒有提及的二進(jìn)制位,全部為這個(gè)符號(hào)的 Unicode 碼。
下表為編碼規(guī)則,字母x表示可用編碼的位。
Unicode符號(hào)范圍
UTF-8編碼方式(二進(jìn)制)
0000 0000-0000 007F
0xxxxxxx
0000 0080-0000 07FF
110xxxxx 10xxxxxx
0000 0800-0000 FFFF
1110xxxx 10xxxxxx 10xxxxxx
0001 0000-0010 FFFF
11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
以漢字嚴(yán)為例,演示如何實(shí)現(xiàn)UTF-8編碼:
嚴(yán)的Unicode碼是4E25(100111000100101),根據(jù)上表,可以發(fā)現(xiàn)4E25處在第三行的范圍內(nèi)(0000 0800 - 0000 FFFF),因此嚴(yán)的 UTF-8 編碼需要三個(gè)字節(jié),即格式是1110xxxx 10xxxxxx 10xxxxxx。然后,從嚴(yán)的最后一個(gè)二進(jìn)制位開始,依次從后向前填入格式中的x,多出的位補(bǔ)0。這樣就得到了,嚴(yán)的 UTF-8 編碼是11100100 10111000 10100101,轉(zhuǎn)換成十六進(jìn)制就是E4B8A5。
由此可見,漢字的Unicode碼和UTF-8編碼是不同的,它們之間可以通過(guò)規(guī)則進(jìn)行轉(zhuǎn)換。
注意 : 漢字的Unicode碼是2個(gè)字節(jié),而UTF-8碼是3個(gè)字節(jié)
Python 中的編碼方式轉(zhuǎn)換
Python3中的字符序列類型有兩種:str和bytes。
bytes對(duì)象是一串十六進(jìn)制格式字符序列,如b'\xe6\x88\x91',前方的b標(biāo)示這串字符是bytes對(duì)象。
將bytes對(duì)象通過(guò)上述的某種編碼方式可以解析為字符串,如將b'\xe6\x88\x91'使用UTF-8編碼方式解碼得到漢字我,而使用GBK編碼方式解碼無(wú)法得到完整的漢字。
Python3中使用encode()和decode()進(jìn)行字符與二進(jìn)制序列之間的轉(zhuǎn)換,可以這樣理解:encode(編碼)就是把人能看懂的漢字,轉(zhuǎn)換為機(jī)器能看懂的二進(jìn)制序列。decode(解碼)就是把人看不懂的二進(jìn)制序列,轉(zhuǎn)換為漢字。
使用encode()和decode()時(shí),有個(gè)encoding參數(shù),默認(rèn)值為UTF-8,指定了對(duì)字符串進(jìn)行編碼或解碼時(shí),使用的編碼方式。
有關(guān)文件的編碼方式
前面說(shuō)了那么多,還沒有講到有關(guān)文件的編碼方式,而且可能平時(shí)使用open()打開文件read()的時(shí)候,并沒有指定編碼方式,也能夠正常打印出來(lái)文件的內(nèi)容。
文件的編碼方式是在open()是指定的,有個(gè)encoding參數(shù),作用和字符串解碼一樣,如果以非二進(jìn)制模式(b)打開文件,會(huì)默認(rèn)通過(guò)UTF-8方式打開。
所以一份GBK編碼的文件,如果不以二進(jìn)制模式打開、且不設(shè)置這個(gè)encoding參數(shù),是會(huì)報(bào)解碼錯(cuò)誤的(UnicodeDecodeError)。
當(dāng)然,如果以二進(jìn)制模式打開文件,再讀取到的文本就已經(jīng)是二進(jìn)制序列了,不涉及encode問(wèn)題,而是該以什么解碼方式將二進(jìn)制序列轉(zhuǎn)換為人可以讀懂的漢字。
所以對(duì)于一個(gè)未知編碼方式的文件,如何通過(guò)代碼獲取其編碼方式,然后轉(zhuǎn)換為我們所期望的編碼方式呢?
Python提供了一個(gè)第三方庫(kù)chardet,是char detect的縮寫,字符監(jiān)測(cè)。
將二進(jìn)制序列傳入chardet.detect()方法,然后會(huì)返回一個(gè)字典。該字典有3個(gè)鍵值。
{
? ? 'encoding': 'GB2312',
? ? 'confidence': 0.99,
? ? 'language': 'Chinese'
}
encoding是所識(shí)別出來(lái)該二進(jìn)制序列的編碼方式。confidence是所識(shí)別出來(lái)的encoding正確概率(1.0表示100%)。language是該編碼方式適用的語(yǔ)言。
可以根據(jù)confidence概率決定是否使用encoding編碼方式。
偽造請(qǐng)求頭
import requests
url = 'https://www.zhihu.com/question/315387406/answer/812734512'
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"
}
response = requests.get(url=url, headers=headers)
print(response.status_code)? # 200
fake_useragent
from fake_useragent import UserAgent
# 實(shí)例化 user-agent 對(duì)象
ua = UserAgent()
url = 'https://www.zhihu.com/question/315387406/answer/812734512'
headers = {"user-agent": ua.chrome}? # 指定瀏覽器 user-agent
# 或者可以這樣寫
# headers = {"user-agent": UserAgent().random}? # 一步到位,隨機(jī)生成一個(gè) user-agent
response = requests.get(url=url, headers=headers)
print(response.status_code)? # 200
About
什么是fake_useragent?
簡(jiǎn)單來(lái)說(shuō),fake_useragent能靈活的幫助我們生成user-agent。
install
pip install fake_useragent
update
pip install -U fake-useragent
查看版本
import fake_useragent
print(fake_useragent.VERSION)? # 0.1.11
Usage
生成指定瀏覽器的user-agent
import fake_useragent
# 實(shí)例化 user-agent 對(duì)象
ua = fake_useragent.UserAgent()
# ua.ie
print(ua.ie)? # Mozilla/5.0 (compatible; MSIE 9.0; Windows NT 6.1; Trident/5.0; chromeframe/13.0.782.215)
# ua.msie
print(ua['Internet Explorer'])? # Mozilla/5.0 (compatible; MSIE 8.0; Windows NT 5.1; Trident/4.0; InfoPath.2; SLCC1; .NET CLR 3.0.4506.2152; .NET CLR 3.5.30729; .NET CLR 2.0.50727)
# ua.opera
print(ua.opera)? # Opera/9.80 (Windows NT 6.1; U; en-US) Presto/2.7.62 Version/11.01
# ua.chrome
print(ua.chrome)? # Mozilla/5.0 (Windows NT 5.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/31.0.1650.16 Safari/537.36
# ua.google
print(ua['google chrome'])? # Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2227.0 Safari/537.36
# ua.firefox
print(ua.firefox)? # Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:16.0.1) Gecko/20121011 Firefox/21.0.1
# ua.ff
print(ua.ff)? # Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:25.0) Gecko/20100101 Firefox/29.0
# ua.safari
print(ua.safari)? # Mozilla/5.0 (Windows; U; Windows NT 5.1; zh-TW) AppleWebKit/533.19.4 (KHTML, like Gecko) Version/5.0.2 Safari/533.18.5
隨機(jī)生成user-agent
import fake_useragent
# 實(shí)例化 user-agent 對(duì)象
ua = fake_useragent.UserAgent()
print(ua.random)? # Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.67 Safari/537.36
print(ua.random)? # Mozilla/5.0 (compatible; MSIE 10.0; Macintosh; Intel Mac OS X 10_7_3; Trident/6.0)
print(ua.random)? # Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/41.0.2228.0 Safari/537.36
每次都能隨機(jī)生成一個(gè)UA表示,大大增強(qiáng)了爬蟲的真實(shí)性。
爬蟲程序運(yùn)行速度是很快,在網(wǎng)站爬取數(shù)據(jù)時(shí),一個(gè)固定 IP 的訪問(wèn)頻率就會(huì)很高,這不
符合人為操作的標(biāo)準(zhǔn)。所以一些網(wǎng)站會(huì)設(shè)置一個(gè) IP 訪問(wèn)頻率的閾值,如果一個(gè) IP 訪問(wèn)頻率
超過(guò)這個(gè)閾值,說(shuō)明這個(gè)不是人在訪問(wèn),而是一個(gè)爬蟲程序。
方法一:設(shè)置延時(shí)
import time, random
from urllib import request
import chardet
# 獲取新浪國(guó)內(nèi)新聞的前十頁(yè)的新聞數(shù)據(jù)
base_url = "http://api.roll.news.sina.com.cn/zt_list?
channel=news&cat_1=gnxw&cat_2==gdxw1||=gatxw||=zs?pl||=mtjj&level==1||=2&show_ext=1&show_all=1&show_num=22&tag=1
&format=json&page={}&callback=newsloadercallback&_=1534912318040"
# 構(gòu)建前十頁(yè)的 url
target_urls = [ base_url.format(x) for x in range(1, 11)]
# enumerate 方法類似于 jQuery 中的 each 方法,返回迭代對(duì)象的索引和迭代對(duì)象的迭代數(shù)據(jù)
for index, url in enumerate(target_urls):
# 如果我們?cè)L問(wèn)的數(shù)據(jù)是很多頁(yè),這時(shí)候如果頻繁的訪問(wèn)數(shù)據(jù)
# 可能會(huì)觸發(fā)反爬機(jī)制,所以我們可以設(shè)置延時(shí),模擬人類行為,如
time.sleep(random.randint(1,10))
# 偽造 UA
req = request.Request(url)
req.add_header("User-Agent", "Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 6.0)")
# 發(fā)送請(qǐng)求和獲取數(shù)據(jù)
response = request.urlopen(req)
content = response.read()
# 嗅探編碼
charset = chardet.detect(content)['encoding']
html = content.decode(charset)
# 保存數(shù)據(jù)
with open("html/{}.json".format(index), "w", encoding=charset) as f:
f.write(html)
方法二:代理 IP 地址
爬蟲去爬取網(wǎng)站數(shù)據(jù)的數(shù)據(jù)的時(shí)候,如果單位時(shí)間內(nèi)爬取頻次過(guò)高,或者其他的原因,被對(duì)方識(shí)別出來(lái),ip可能會(huì)被封禁。這種情況下,通過(guò)使用代理ip來(lái)解決,作為反爬的策略。
代理ip匿名度:
透明的: 服務(wù)器知道了你使用代理ip,也知道你真實(shí)的ip
匿名代理: 知道使用了代理ip,不知道真實(shí)的ip
高匿代理: 不知道使用了代理ip,也不知道真實(shí)的ip(最好的選擇)
查看ip 的方法:
在cmd輸入命令行:ipconfig(內(nèi)網(wǎng)的ip——私有的地址)
瀏覽器訪問(wèn):ipip.net (外網(wǎng),上網(wǎng)的ip)
如果設(shè)置代理ip,可以通過(guò)httpbin.org/ip來(lái)查看。不設(shè)置代理ip查看則顯示我們的真實(shí)ip。即上邊的外網(wǎng)ip,上網(wǎng)的ip。
代理可以從快代理或豌豆代理處付費(fèi)獲得。(也有可以免費(fèi)試用的的幾個(gè))
查看我們的真實(shí)ip的代碼:
import requests
url = "http://httpbin.org/ip"
res = requests.get(url)
print(res.text)
復(fù)制
輸出結(jié)果顯示真實(shí)ip。
設(shè)置代理ip:
# 設(shè)置一個(gè)代理ip,以字典形式呈現(xiàn),代理ip寫在字典值中
proxy = {
? ? 'http': 'xxx.xx.xxx.xxx:xxxx'
}
res = requests.get(url, proxies=proxy)
print(res.text)
復(fù)制
輸出結(jié)果顯示設(shè)置的代理ip。
選出5個(gè)ip為例,從中隨機(jī)選擇一個(gè)試用,選5次,且出現(xiàn)異常時(shí)避免報(bào)錯(cuò)。時(shí)間間隔設(shè)置為1。
'''
36.6.149.154:xxx
114.233.125.55:xxxxx
117.26.229.24:xxx
122.241.27.24:xxxxx
61.132.171.215:xxx
'''
import random
ips = [('36.6.149.154:xxx'),('114.233.125.55:xxxxx'),('117.26.229.24:xxx'),('122.241.27.24:xxxxx'),('61.132.171.215:xxx'),]
url = 'http://httpbin.org/ip'
for i in range(5):
? ? try:
? ? ? ? ip = random.choice(ips)
? ? ? ? res = requests.get(url, proxies={'http':ip}, timeout=1)
? ? ? ? print(res.text)
? ? except Exception as e:
? ? ? ? print('出現(xiàn)異常', e)