Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537 Warning: error_log(/data/www/wwwroot/hmttv.cn/caches/error_log.php): failed to open stream: Permission denied in /data/www/wwwroot/hmttv.cn/phpcms/libs/functions/global.func.php on line 537
過前面幾期的推送,小編基本上已經將R語言爬蟲所需要的基本知識介紹完了。R雖然是以一門統計分析工具出現在大多數人印象中的,但其畢竟本質上是一門編程語言,對于爬蟲的支持雖不如Python那樣多快好省,但悉心研究一下總能做出一些讓你驚喜的效果。
大約很早之前,小編就寫過關于R語言爬蟲新貴rvest的抓取介紹,之前說rvest+SelectGadgetor是結構化網頁抓取的實戰利器,大家的溢美之詞不斷。詳情可見推文:
R語言爬蟲利器:rvest包+SelectorGadget抓取鏈家杭州二手房數據
但網絡爬蟲這個江湖太險惡,單靠一招rvest行走江湖必然兇多吉少,一不小心碰到什么AJAX和動態網頁憑僅掌握rvest的各位必定束手無策。本文小編就簡單介紹下在用R語言進行實際的網絡數據抓取時如何將動態數據給弄到手。
所謂動態網頁和異步加載,在之前的系列4的時候小編已通過AJAX介紹過了,簡單而言就是我明明在網頁中看到了這個數據,但到后臺HTML中卻找不到了,這通常就是XHR在作祟。這時候我們就不要看原始的HTML數據了,需要進行二次請求,通過web開發者工具找到真實請求的url。下面小編就以兩個網頁為例,分別通過GET和POST請求拿到動態網頁數據,全過程主要使用httr包來實現,httr包可謂是RCurl包的精簡版,說其短小精悍也不為過。httr包與RCurl包在主要函數的區別如下所示:
GET請求抓取微信好友列表數據
很早之前圈子里就看到過用Python抓取自己微信好友數據的案例分享,今天便以微信網頁版為例,探一探其網頁結構。首先登錄個人微信網頁版,右鍵打開web開發者工具,下來一大堆請求:
簡單找一下發現網頁中的微信好友列表信息并沒有呈現在HTML 中,大概可以斷定微信好友數據是通過動態加載來顯示的,所以直接定位到XHR中,經過幾番嘗試,結合右側的preview,我們會發現大量整齊劃一的數據,所以二次請求的url真的就是它了:
找到真正的url之后,接下來就是獲取請求信息了,切換到Headers版塊,Header版塊下的4個子信息我們都需要關注,它們是我們構造爬蟲請求頭的關鍵。
從Header中我們可以看到該信息是通過GET方法請求得到的,General信息下的Request URL,Request Method, Status Code; Response Headers信息下的Connection, Content-Type; Request Headers信息下的Accept, Cookie, Referer, User-Agent以及最后的Query String Parameters都是我們需要重點關注的。
找到相應的信息之后,我們便可以直接利用httr包在R中構建爬蟲請求:
#傳入微信cookie信息 Cookie <- “我的微信cookie” #構造請求頭 headers <- c('Accept'='application/json', 'Content-Type'='text/plain', 'User-Agent'='Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537. 36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X Met aSr 1.0', 'Referer'='https://wx.qq.com/', 'Connection'='keep-alive', 'cookie'=Cookie )
二次請求實際的url:
#實際請求的url url<-"https://wx.qq.com/cgi-bin/mmwebwx-bin/webwxgetcontact?r=1507597918348&seq=0&skey=@crypt_ee7cd3e3_70091604da65a07600cfdca47b81cfaf"
GET方法單次執行請求:
#執行請求 louwill <- GET(url,add_headers(.headers =headers))
響應結果如下:
-> GET /cgi-bin/mmwebwx-bin/webwxgetcontact?r=1507597918348&seq=0&skey=@crypt_ee7cd3e3_70091604da65a07600cfdca47b81cfaf HTTP/1.1 -> Host: wx.qq.com -> Accept-Encoding: gzip, deflate -> Accept: application/json -> Content-Type: text/plain -> User-Agent: Mozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0 -> Referer: https://wx.qq.com/ -> Connection: keep-alive -> cookie: 我的微信cookie -> <- HTTP/1.1 200 OK <- Connection: keep-alive <- Content-Type: text/plain <- Content-Encoding: gzip <- Content-Length: 90977 <-
響應狀態碼為200,okay。
從響應中提取原始字符內容:
content(louwill) [1] "{\n\"BaseResponse\": {\n\"Ret\": 0,\n\"ErrMsg\": \"\"\n}\n,\n\"MemberCount\": 658,\n\"MemberList\": [{\n\"Uin\": 0,\n\"UserName\": \"weixin\",\n\"NickName\": \"微信團隊\",\n\"HeadImgUrl\": \"/cgi-bin/mmwebwx-bin/webwxgeticon?seq=570002&username=weixin&skey=@crypt_ee7cd3e3_70091604da65a07600cfdca47b81cfaf\",\n\"ContactFlag\": 1,\n\"MemberCount\": 0,\n\"MemberList\": [],\n\"RemarkName\": \"\",\n\"HideInputBarFlag\": 0,\n\"Sex\": 0,\n\"Signature\": \"微信團隊官方帳號\",\n\"VerifyFlag\": 56,\n\"OwnerUin\": 0,\n\"PYInitial\": \"WXTD\",\n\"PYQuanPin\": \"weixintuandui\",\n\"RemarkPYInitial\": \"\",\n\"RemarkPYQuanPin\": \"\",\n\"StarFriend\": 0,\n\"AppAccountFlag\": 0,\n\"Statues\": 0,\n\"AttrStatus\": 4,\n\"Province\": \"\",\n\"City\": \"\",\n\"Alias\": \"\",\n\"SnsFlag\": 0,\n\"UniFriend\": 0,\n\"DisplayName\": \"\",\n\"ChatRoomId\": 0,\n\"KeyWord\": \"wei\",\n\"EncryChatRoomId\": \"\",\n\"IsOwner\": 0\n}\n,{\n\"Uin\": 0,\n\"UserName\": \"@34c5cc09db0a616522f7ccc7309b1d29\",\n\"NickName\": \"微信支付... <truncated>
從結果中可以看出,微信好友列表的信息就被抓取下來了,數據信息非常雜亂,需要進一步清洗整理,小編這里重在展示GET請求獲取動態網頁數據(主要是懶)就不往下整理啦。
POST請求抓取網易云課堂數據
雖說動態網站數據請求也有GET方法的,但小編發現POST方法才是動態網頁的主要的請求方式。受杜老師小魔方文章啟發,小編也試一下這個網頁上的效果。登錄網易云課堂賬號,右鍵開發者工具,直接定位到XHR,查找課程數據屬于哪個url。通過嘗試和preview,可以發現課程信息都被封裝在一個studycourse.json的文件中:
跟GET請求方法一樣,切換到Header版塊后繼續關注General等四個子信息,但POST請求下我們需要注意的一點是:POST請求下沒有像GET請求一樣的Query String Parameters,而是由Request Payload來構造請求頭表單參數,這一點和GET方法大不相同。總而言之,在動態網頁的HTTP請求中,如果是GET請求,請求頭表單參數以name=value&name1=value1的形式直接附在url后面,如果是POST請求,請求頭表單參數以相同的形式放在構造的表單體中,所以對于網易云課堂的數據請求在R中構造如下:
#構造請求頭 #這里小編沒有登錄賬號,cookie就不要了 headers <- c('Accept'='application/json', 'Content-Type'='application/json', 'User-Agent'='ozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0', 'edu-script-token'= '37aa682d1473455c8a77e6a4476e8f9e', 'Referer'='http://study.163.com/courses', 'Connection'='keep-alive' ) #POST請求需要構造請求頭表單參數 payload<-list( 'pageIndex'=1, 'pageSize'=50, 'relativeOffset'=0, 'frontCategoryId'=-1 )
二次請求實際的url:
url <- "http://study.163.com/p/search/studycourse.json"
POST方法單次執行請求:
louwill2<-POST(url,add_headers(.headers =headers),body =payload, encode="json")
結果如下:
-> POST /p/search/studycourse.json HTTP/1.1 -> Host: study.163.com -> Accept-Encoding: gzip, deflate -> Cookie: EDUWEBDEVICE=5d0eadccd2314c4d8bc6e758b8b23d4e; NTESSTUDYSI=d3d36984547a43d6924334ee6a184a08 -> Accept: application/json -> Content-Type: application/json -> User-Agent: ozilla/5.0 (Windows NT 6.1) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.221 Safari/537.36 SE 2.X MetaSr 1.0 -> edu-script-token: 83de95a25f5d45eb84bfeff8ec334e15 -> Referer: http://study.163.com/courses -> Connection: keep-alive -> cookie: 網易云課堂cookie -> Content-Length: 69 -> >> {"pageIndex":1,"pageSize":50,"relativeOffset":0,"frontCategoryId":-1} <- HTTP/1.1 200 OK <- Server: nginx <- Date: Tue, 10 Oct 2017 08:29:51 GMT <- Content-Type: application/json;charset=UTF-8 <- Transfer-Encoding: chunked <- Connection: keep-alive <- Vary: Accept-Encoding <- Server-Host: hzayq-study-platform7 <- Content-Encoding: gzip <- Response [http://study.163.com/p/search/studycourse.json] Date: 2017-10-10 08:29 Status: 200 Content-Type: application/json;charset=UTF-8 Size: 71 kB
請求狀態碼200,也okay。
從響應中提取原始字符內容:
head(content(louwill2)) $result$list[[39]] $result$list[[39]]$productId [1] 1002971001 $result$list[[39]]$courseId [1] 1002971001 $result$list[[39]]$productName [1] "英語知識點解析及小學單詞帶讀" $result$list[[39]]$productType [1] 0 $result$list[[39]]$startTime [1] -1 $result$list[[39]]$endTime [1] 9.223372e+18 $result$list[[39]]$description [1] "通過幾分鐘的微課片段,精講中小學的英語知識點,讓學生通過比較學習,把這些知識點編織成有系統的知識網。" $result$list[[39]]$provider [1] "中小學英語語法王"
跟前面一樣,后續的數據處理與清洗小編就懶得弄啦。POST方法與GET方法略有區別,就是需要構造請求頭表單參數。R語言針對動態網頁抓取,使用RCurl/httr包,認真分析網頁結構,一般都能搞定。
End.
運行人員:中國統計網小編(微信號:itongjilove)
微博ID:中國統計網
中國統計網,是國內最早的大數據學習網站,公眾號:中國統計網
http://www.itongji.cn
為一名毫無開發經驗的非計算機出身的數據愛好者,初入此坑時深受爬蟲難學之苦,當初未通Python之道,寫個scrapy框架就痛苦至極。想想現在大數據技術那么牛逼了,為什么我抓個數據還處處被封,后來又覺得是自己技術不夠強大。本文以拉勾網為例給大家介紹一款便捷快速的R語言爬蟲方法,通過Rvest包+SelectorGdaget選擇器即可輕松實現簡單的數據抓取。
下載安裝Rvest包:
install.packages("Rvest")
library(Rvest)
要想全面了解Rvest包的朋友可以去查官方幫助文檔:
help(package="Rvest")
Selectorgadget插件作為一個輕便快捷的CSS選擇器,好用程度簡直爆炸,鼠標點擊幾下即可生成你想要抓取的html節點信息。這么一款神器,調用方法也是極其簡單,打開任何一款搜索網頁,鍵入Selectorgadget,點擊第一個鏈接,也是Selectorgadget官方鏈接,拉到頁面底端倒數第二個鏈接,將其拖拽到你的瀏覽器收藏夾,待下次打開需要爬取的網頁時點擊即可啟用。
需拖拽的鏈接如圖(Or drag this link to your bookmark bar):
下次調用時,打開需要抓取的網頁,點擊我們拖拽到收藏夾的Selectorgadget會在網頁右下角出現一個長方形條框,點擊網頁中任何我們想抓取的信息,條框內即可生成相應的文本表達式,將這些文本表達式復制到Rvest包對應的爬蟲函數中,即可輕松完成抓取。需要注意的是,使用Selectorgadget選擇節點信息是一個篩選的過程,其間需要將我們不需要的信息(點擊后變紅)重復點擊以刪除,留下需要的信息(綠色和黃色部分)。
我們選擇抓取拉勾網數據分析師崗位信息:
抓取代碼如下:
library(stringr)library(xml2) library(rvest) #加載包 i<-1:30#設定抓取頁數 lagou_data<-data.frame()#創建數據框存儲數據 #寫個循環,對固定網頁結構重復抓取 for (i in 1:30){ web<-read_html(str_c("https://www.lagou.com/zhaopin/shujufenxi/",i),encoding="UTF-8")#read_html函數解析網頁并規定編碼str_c函數對頁數循環 job<-web%>%html_nodes("h2")%>%html_text()#"h2"即為Selectorgadget定位節點信息 job[16]<-NA job<-job[!is.na(job)]#將多余信息設置為NA并剔除 #以此類推,抓取崗位其他信息 company<-web%>%html_nodes(".company_name a")%>%html_text() inf1<-web%>%html_nodes(".p_bot .li_b_l")%>%html_text() inf2<-web%>%html_nodes(".industry")%>%html_text() temptation<-web%>%html_nodes(".li_b_r")%>%html_text()
#存儲以上信息
job_inf<-data.frame(job,company,inf1,inf2,temptation)
lagou_data<-rbind(lagou_data,job_inf)
}
write.csv(job_inf,file="D:/Rdata/datasets/job_inf.csv")#寫入數據
清洗整理后最終抓取部分數據示例如圖:
用rvest包結合SelectorGadget 選擇器能夠快速實現R語言下的網絡數據抓取,并適當結合stringr包中的字符串處理函數對網頁數據進行清洗和整理,抓取過程省時省力,適合R語言和爬蟲入門的朋友使用學習。
End.
來源:公眾號“R語言中文社區”
運行人員:中國統計網小編(微信號:itongjilove)
微博ID:中國統計網
中國統計網,是國內最早的大數據學習網站,公眾號:中國統計網
http://www.itongji.cn
讀:本文主要分為兩個部分:一部分是網絡爬蟲的概述,幫助大家詳細了解網絡爬蟲;另一部分是HTTP請求的Python實現,幫助大家了解Python中實現HTTP請求的各種方式,以便具備編寫HTTP網絡程序的能力。
作者:范傳輝
如需轉載請聯系華章科技
接下來從網絡爬蟲的概念、用處與價值和結構等三個方面,讓大家對網絡爬蟲有一個基本的了解。
1. 網絡爬蟲及其應用
隨著網絡的迅速發展,萬維網成為大量信息的載體,如何有效地提取并利用這些信息成為一個巨大的挑戰,網絡爬蟲應運而生。網絡爬蟲(又被稱為網頁蜘蛛、網絡機器人),是一種按照一定的規則,自動地抓取萬維網信息的程序或者腳本。下面通過圖3-1展示一下網絡爬蟲在互聯網中起到的作用:
▲圖3-1 網絡爬蟲
網絡爬蟲按照系統結構和實現技術,大致可以分為以下幾種類型:通用網絡爬蟲、聚焦網絡爬蟲、增量式網絡爬蟲、深層網絡爬蟲。實際的網絡爬蟲系統通常是幾種爬蟲技術相結合實現的。
搜索引擎(Search Engine),例如傳統的通用搜索引擎baidu、Yahoo和Google等,是一種大型復雜的網絡爬蟲,屬于通用性網絡爬蟲的范疇。但是通用性搜索引擎存在著一定的局限性:
為了解決上述問題,定向抓取相關網頁資源的聚焦爬蟲應運而生。
聚焦爬蟲是一個自動下載網頁的程序,它根據既定的抓取目標,有選擇地訪問萬維網上的網頁與相關的鏈接,獲取所需要的信息。與通用爬蟲不同,聚焦爬蟲并不追求大的覆蓋,而將目標定為抓取與某一特定主題內容相關的網頁,為面向主題的用戶查詢準備數據資源。
說完了聚焦爬蟲,接下來再說一下增量式網絡爬蟲。增量式網絡爬蟲是指對已下載網頁采取增量式更新和只爬行新產生的或者已經發生變化網頁的爬蟲,它能夠在一定程度上保證所爬行的頁面是盡可能新的頁面。
和周期性爬行和刷新頁面的網絡爬蟲相比,增量式爬蟲只會在需要的時候爬行新產生或發生更新的頁面,并不重新下載沒有發生變化的頁面,可有效減少數據下載量,及時更新已爬行的網頁,減小時間和空間上的耗費,但是增加了爬行算法的復雜度和實現難度。
例如:想獲取趕集網的招聘信息,以前爬取過的數據沒有必要重復爬取,只需要獲取更新的招聘數據,這時候就要用到增量式爬蟲。
最后說一下深層網絡爬蟲。Web頁面按存在方式可以分為表層網頁和深層網頁。表層網頁是指傳統搜索引擎可以索引的頁面,以超鏈接可以到達的靜態網頁為主構成的Web頁面。深層網絡是那些大部分內容不能通過靜態鏈接獲取的、隱藏在搜索表單后的,只有用戶提交一些關鍵詞才能獲得的Web頁面。
例如用戶登錄或者注冊才能訪問的頁面。可以想象這樣一個場景:爬取貼吧或者論壇中的數據,必須在用戶登錄后,有權限的情況下才能獲取完整的數據。
2. 網絡爬蟲結構
下面用一個通用的網絡爬蟲結構來說明網絡爬蟲的基本工作流程,如圖3-4所示。
▲圖3-4 網絡爬蟲結構
網絡爬蟲的基本工作流程如下:
通過上面的網絡爬蟲結構,我們可以看到讀取URL、下載網頁是每一個爬蟲必備而且關鍵的功能,這就需要和HTTP請求打交道。接下來講解Python中實現HTTP請求的三種方式:urllib2/urllib、httplib/urllib以及Requests。
1. urllib2/urllib實現
urllib2和urllib是Python中的兩個內置模塊,要實現HTTP功能,實現方式是以urllib2為主,urllib為輔。
1.1 首先實現一個完整的請求與響應模型
urllib2提供一個基礎函數urlopen,通過向指定的URL發出請求來獲取數據。最簡單的形式是:
import urllib2 response=urllib2.urlopen('http://www.zhihu.com') html=response.read() print html
其實可以將上面對http://www.zhihu.com的請求響應分為兩步,一步是請求,一步是響應,形式如下:
import urllib2 # 請求 request=urllib2.Request('http://www.zhihu.com') # 響應 response = urllib2.urlopen(request) html=response.read() print html
上面這兩種形式都是GET請求,接下來演示一下POST請求,其實大同小異,只是增加了請求數據,這時候用到了urllib。示例如下:
import urllib import urllib2 url = 'http://www.xxxxxx.com/login' postdata = {'username' : 'qiye', 'password' : 'qiye_pass'} # info 需要被編碼為urllib2能理解的格式,這里用到的是urllib data = urllib.urlencode(postdata) req = urllib2.Request(url, data) response = urllib2.urlopen(req) html = response.read()
但是有時會出現這種情況:即使POST請求的數據是對的,但是服務器拒絕你的訪問。這是為什么呢?問題出在請求中的頭信息,服務器會檢驗請求頭,來判斷是否是來自瀏覽器的訪問,這也是反爬蟲的常用手段。
1.2 請求頭headers處理
將上面的例子改寫一下,加上請求頭信息,設置一下請求頭中的User-Agent域和Referer域信息。
import urllib import urllib2 url = 'http://www.xxxxxx.com/login' user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' referer='http://www.xxxxxx.com/' postdata = {'username' : 'qiye', 'password' : 'qiye_pass'} # 將user_agent,referer寫入頭信息 headers={'User-Agent':user_agent,'Referer':referer} data = urllib.urlencode(postdata) req = urllib2.Request(url, data,headers) response = urllib2.urlopen(req) html = response.read()
也可以這樣寫,使用add_header來添加請求頭信息,修改如下:
import urllib import urllib2 url = 'http://www.xxxxxx.com/login' user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' referer='http://www.xxxxxx.com/' postdata = {'username' : 'qiye', 'password' : 'qiye_pass'} data = urllib.urlencode(postdata) req = urllib2.Request(url) # 將user_agent,referer寫入頭信息 req.add_header('User-Agent',user_agent) req.add_header('Referer',referer) req.add_data(data) response = urllib2.urlopen(req) html = response.read()
對有些header要特別留意,服務器會針對這些header做檢查,例如:
1.3 Cookie處理
urllib2對Cookie的處理也是自動的,使用CookieJar函數進行Cookie的管理。如果需要得到某個Cookie項的值,可以這么做:
import urllib2 import cookielib cookie = cookielib.CookieJar() opener = urllib2.build_opener(urllib2.HTTPCookieProcessor(cookie)) response = opener.open('http://www.zhihu.com') for item in cookie: print item.name+':'+item.value
但是有時候會遇到這種情況,我們不想讓urllib2自動處理,我們想自己添加Cookie的內容,可以通過設置請求頭中的Cookie域來做:
import urllib2 opener = urllib2.build_opener() opener.addheaders.append( ( 'Cookie', 'email=' + "xxxxxxx@163.com" ) ) req = urllib2.Request( "http://www.zhihu.com/" ) response = opener.open(req) print response.headers retdata = response.read()
1.4 Timeout設置超時
在Python2.6之前的版本,urllib2的API并沒有暴露Timeout的設置,要設置Timeout值,只能更改Socket的全局Timeout值。示例如下:
import urllib2 import socket socket.setdefaulttimeout(10) # 10 秒鐘后超時 urllib2.socket.setdefaulttimeout(10) # 另一種方式
在Python2.6及新的版本中,urlopen函數提供了對Timeout的設置,示例如下:
import urllib2 request=urllib2.Request('http://www.zhihu.com') response = urllib2.urlopen(request,timeout=2) html=response.read() print html
1.5 獲取HTTP響應碼
對于200 OK來說,只要使用urlopen返回的response對象的getcode()方法就可以得到HTTP的返回碼。但對其他返回碼來說,urlopen會拋出異常。這時候,就要檢查異常對象的code屬性了,示例如下:
import urllib2 try: response = urllib2.urlopen('http://www.google.com') print response except urllib2.HTTPError as e: if hasattr(e, 'code'): print 'Error code:',e.code
1.6 重定向
urllib2默認情況下會針對HTTP 3XX返回碼自動進行重定向動作。要檢測是否發生了重定向動作,只要檢查一下Response的URL和Request的URL是否一致就可以了,示例如下:
import urllib2 response = urllib2.urlopen('http://www.zhihu.cn') isRedirected = response.geturl() == 'http://www.zhihu.cn'
如果不想自動重定向,可以自定義HTTPRedirectHandler類,示例如下:
import urllib2 class RedirectHandler(urllib2.HTTPRedirectHandler): def http_error_301(self, req, fp, code, msg, headers): pass def http_error_302(self, req, fp, code, msg, headers): result = urllib2.HTTPRedirectHandler.http_error_301(self, req, fp, code, msg, headers) result.status = code result.newurl = result.geturl() return result opener = urllib2.build_opener(RedirectHandler) opener.open('http://www.zhihu.cn')
1.7 Proxy的設置
在做爬蟲開發中,必不可少地會用到代理。urllib2默認會使用環境變量http_proxy來設置HTTP Proxy。但是我們一般不采用這種方式,而是使用ProxyHandler在程序中動態設置代理,示例代碼如下:
import urllib2 proxy = urllib2.ProxyHandler({'http': '127.0.0.1:8087'}) opener = urllib2.build_opener([proxy,]) urllib2.install_opener(opener) response = urllib2.urlopen('http://www.zhihu.com/') print response.read()
這里要注意的一個細節,使用urllib2.install_opener()會設置urllib2的全局opener,之后所有的HTTP訪問都會使用這個代理。這樣使用會很方便,但不能做更細粒度的控制,比如想在程序中使用兩個不同的Proxy設置,這種場景在爬蟲中很常見。比較好的做法是不使用install_opener去更改全局的設置,而只是直接調用opener的open方法代替全局的urlopen方法,修改如下:
import urllib2 proxy = urllib2.ProxyHandler({'http': '127.0.0.1:8087'}) opener = urllib2.build_opener(proxy,) response = opener.open("http://www.zhihu.com/") print response.read()
2. httplib/urllib實現
httplib模塊是一個底層基礎模塊,可以看到建立HTTP請求的每一步,但是實現的功能比較少,正常情況下比較少用到。在Python爬蟲開發中基本上用不到,所以在此只是進行一下知識普及。下面介紹一下常用的對象和函數:
接下來演示一下GET請求和POST請求的發送,首先是GET請求的示例,如下所示:
import httplib conn =None try: conn = httplib.HTTPConnection("www.zhihu.com") conn.request("GET", "/") response = conn.getresponse() print response.status, response.reason print '-' * 40 headers = response.getheaders() for h in headers: print h print '-' * 40 print response.msg except Exception,e: print e finally: if conn: conn.close()
POST請求的示例如下:
import httplib, urllib conn = None try: params = urllib.urlencode({'name': 'qiye', 'age': 22}) headers = {"Content-type": "application/x-www-form-urlencoded" , "Accept": "text/plain"} conn = httplib.HTTPConnection("www.zhihu.com", 80, timeout=3) conn.request("POST", "/login", params, headers) response = conn.getresponse() print response.getheaders() # 獲取頭信息 print response.status print response.read() except Exception, e: print e finally: if conn: conn.close()
3. 更人性化的Requests
Python中Requests實現HTTP請求的方式,是本人極力推薦的,也是在Python爬蟲開發中最為常用的方式。Requests實現HTTP請求非常簡單,操作更加人性化。
Requests庫是第三方模塊,需要額外進行安裝。Requests是一個開源庫,源碼位于:
GitHub: https://github.com/kennethreitz/requests
希望大家多多支持作者。
使用Requests庫需要先進行安裝,一般有兩種安裝方式:
如何驗證Requests模塊安裝是否成功呢?在Python的shell中輸入import requests,如果不報錯,則是安裝成功。如圖3-5所示。
▲圖3-5 驗證Requests安裝
3.1 首先還是實現一個完整的請求與響應模型
以GET請求為例,最簡單的形式如下:
import requests r = requests.get('http://www.baidu.com') print r.content
大家可以看到比urllib2實現方式的代碼量少。接下來演示一下POST請求,同樣是非常簡短,更加具有Python風格。示例如下:
import requests postdata={'key':'value'} r = requests.post('http://www.xxxxxx.com/login',data=postdata) print r.content
HTTP中的其他請求方式也可以用Requests來實現,示例如下:
r = requests.put('http://www.xxxxxx.com/put', data = {'key':'value'}) r = requests.delete('http://www.xxxxxx.com/delete') r = requests.head('http://www.xxxxxx.com/get') r = requests.options('http://www.xxxxxx.com/get')
接著講解一下稍微復雜的方式,大家肯定見過類似這樣的URL:
http://zzk.cnblogs.com/s/blogpost?Keywords=blog:qiyeboy&pageindex=1
就是在網址后面緊跟著“?”,“?”后面還有參數。那么這樣的GET請求該如何發送呢?肯定有人會說,直接將完整的URL帶入即可,不過Requests還提供了其他方式,示例如下:
import requests payload = {'Keywords': 'blog:qiyeboy','pageindex':1} r = requests.get('http://zzk.cnblogs.com/s/blogpost', params=payload) print r.url
通過打印結果,我們看到最終的URL變成了:
http://zzk.cnblogs.com/s/blogpost?Keywords=blog:qiyeboy&pageindex=1
3.2 響應與編碼
還是從代碼入手,示例如下:
import requests r = requests.get('http://www.baidu.com') print 'content-->'+r.content print 'text-->'+r.text print 'encoding-->'+r.encoding r.encoding='utf-8' print 'new text-->'+r.text
其中r.content返回的是字節形式,r.text返回的是文本形式,r.encoding返回的是根據HTTP頭猜測的網頁編碼格式。
輸出結果中:“text-->”之后的內容在控制臺看到的是亂碼,“encoding-->”之后的內容是ISO-8859-1(實際上的編碼格式是UTF-8),由于Requests猜測編碼錯誤,導致解析文本出現了亂碼。Requests提供了解決方案,可以自行設置編碼格式,r.encoding='utf-8'設置成UTF-8之后,“new text-->”的內容就不會出現亂碼。
但是這種手動的方式略顯笨拙,下面提供一種更加簡便的方式:chardet,這是一個非常優秀的字符串/文件編碼檢測模塊。安裝方式如下:
pip install chardet
安裝完成后,使用chardet.detect()返回字典,其中confidence是檢測精確度,encoding是編碼形式。示例如下:
import requests r = requests.get('http://www.baidu.com') print chardet.detect(r.content) r.encoding = chardet.detect(r.content)['encoding'] print r.text
直接將chardet探測到的編碼,賦給r.encoding實現解碼,r.text輸出就不會有亂碼了。
除了上面那種直接獲取全部響應的方式,還有一種流模式,示例如下:
import requests r = requests.get('http://www.baidu.com',stream=True) print r.raw.read(10)
設置stream=True標志位,使響應以字節流方式進行讀取,r.raw.read函數指定讀取的字節數。
3.3 請求頭headers處理
Requests對headers的處理和urllib2非常相似,在Requests的get函數中添加headers參數即可。示例如下:
import requests user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers={'User-Agent':user_agent} r = requests.get('http://www.baidu.com',headers=headers) print r.content
3.4 響應碼code和響應頭headers處理
獲取響應碼是使用Requests中的status_code字段,獲取響應頭使用Requests中的headers字段。示例如下:
import requests r = requests.get('http://www.baidu.com') if r.status_code == requests.codes.ok: print r.status_code# 響應碼 print r.headers# 響應頭 print r.headers.get('content-type')# 推薦使用這種獲取方式,獲取其中的某個字段 print r.headers['content-type']# 不推薦使用這種獲取方式 else: r.raise_for_status()
上述程序中,r.headers包含所有的響應頭信息,可以通過get函數獲取其中的某一個字段,也可以通過字典引用的方式獲取字典值,但是不推薦,因為如果字段中沒有這個字段,第二種方式會拋出異常,第一種方式會返回None。
r.raise_for_status()是用來主動地產生一個異常,當響應碼是4XX或5XX時,raise_for_status()函數會拋出異常,而響應碼為200時,raise_for_status()函數返回None。
3.5 Cookie處理
如果響應中包含Cookie的值,可以如下方式獲取Cookie字段的值,示例如下:
import requests user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers={'User-Agent':user_agent} r = requests.get('http://www.baidu.com',headers=headers) # 遍歷出所有的cookie字段的值 for cookie in r.cookies.keys(): print cookie+':'+r.cookies.get(cookie)
如果想自定義Cookie值發送出去,可以使用以下方式,示例如下:
import requests user_agent = 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)' headers={'User-Agent':user_agent} cookies = dict(name='qiye',age='10') r = requests.get('http://www.baidu.com',headers=headers,cookies=cookies) print r.text
還有一種更加高級,且能自動處理Cookie的方式,有時候我們不需要關心Cookie值是多少,只是希望每次訪問的時候,程序自動把Cookie的值帶上,像瀏覽器一樣。Requests提供了一個session的概念,在連續訪問網頁,處理登錄跳轉時特別方便,不需要關注具體細節。使用方法示例如下:
import Requests oginUrl = 'http://www.xxxxxxx.com/login' s = requests.Session() #首先訪問登錄界面,作為游客,服務器會先分配一個cookie r = s.get(loginUrl,allow_redirects=True) datas={'name':'qiye','passwd':'qiye'} #向登錄鏈接發送post請求,驗證成功,游客權限轉為會員權限 r = s.post(loginUrl, data=datas,allow_redirects= True) print r.text
上面的這段程序,其實是正式做Python開發中遇到的問題,如果沒有第一步訪問登錄的頁面,而是直接向登錄鏈接發送Post請求,系統會把你當做非法用戶,因為訪問登錄界面時會分配一個Cookie,需要將這個Cookie在發送Post請求時帶上,這種使用Session函數處理Cookie的方式之后會很常用。
3.6 重定向與歷史信息
處理重定向只是需要設置一下allow_redirects字段即可,例如:
r=requests.get('http://www.baidu.com',allow_redirects=True)
將allow_redirects設置為True,則是允許重定向;設置為False,則是禁止重定向。如果是允許重定向,可以通過r.history字段查看歷史信息,即訪問成功之前的所有請求跳轉信息。示例如下:
import requests r = requests.get('http://github.com') print r.url print r.status_code print r.history
打印結果如下:
https://github.com/ 200 (<Response [301]>,)
上面的示例代碼顯示的效果是訪問GitHub網址時,會將所有的HTTP請求全部重定向為HTTPS。
3.7 超時設置
超時選項是通過參數timeout來進行設置的,示例如下:
requests.get('http://github.com', timeout=2)
3.8 代理設置
使用代理Proxy,你可以為任意請求方法通過設置proxies參數來配置單個請求:
import requests proxies = { "http": "http://0.10.1.10:3128", "https": "http://10.10.1.10:1080", } requests.get("http://example.org", proxies=proxies)
也可以通過環境變量HTTP_PROXY和HTTPS_PROXY?來配置代理,但是在爬蟲開發中不常用。你的代理需要使用HTTP Basic Auth,可以使用http://user:password@host/語法:
proxies = { "http": "http://user:pass@10.10.1.10:3128/", }
本文主要講解了網絡爬蟲的結構和應用,以及Python實現HTTP請求的幾種方法。希望大家對本文中的網絡爬蟲工作流程和Requests實現HTTP請求的方式重點吸收消化。
關于作者:范傳輝,資深網蟲,Python開發者,參與開發了多項網絡應用,在實際開發中積累了豐富的實戰經驗,并善于總結,貢獻了多篇技術文章廣受好評。研究興趣是網絡安全、爬蟲技術、數據分析、驅動開發等技術。
本文摘編自《Python爬蟲開發與項目實戰》,經出版方授權發布。
延伸閱讀《Python爬蟲開發與項目實戰》
推薦語:零基礎學習爬蟲技術,從Python和Web前端基礎開始講起,由淺入深,包含大量案例,實用性強。
*請認真填寫需求信息,我們會在24小時內與您取得聯系。