轉載本文請以連結形式註明出處。
1.前言
Python的網路抓取有很多包可以實現,比如:urllib、urllib2、httplib、httplib2。其中httplib、httplib2是專門處理與http相關的;而urllib、urllib2是藉助於httplib、httplib2實現的,相當於在httplib、httplib2上又封裝了一層來進行處理web資料。而urllib2是urllib的高版本,httplib2是httplib的高版本。
這裡我給出一個學習python庫的網址,裡面有各種lib庫的講解:http://docs.python.org/library/index.html 。有興趣的讀者可以看一下。
由於最近在使用httplib2進行網上抓取資料,所以下面對httplib2進行介紹。
2.httplib2
(1)安裝
httplib2的安裝:首先下載python的httplib2的安裝包,為:http://code.google.com/p/httplib2/downloads/list;其次, 在dos視窗下進入httplib2的解壓目錄,執行命令:python setup.py install 。 即完成安裝。
(2)使用講解
下面再給出一個httplib2的學習地址,是httplib2的一個wiki,裡面有幾個httplib2的簡單例子:http://code.google.com/p/httplib2/wiki/Examples 。
(a)下面給出第一個httplib2的例子,根據weibo使用者的ID,抓取使用者相關資訊:
這個例子涉及到cookies的使用,下面簡單介紹一下cookies的問題:
當使用者抓取的網頁需要登陸時,一般的登入頁面只需要使用者名稱和密碼,此時我們可以採用username和password的python代碼抓取網頁。但是當使用者頻繁的登入抓取時,此時登入頁面就需要輸入使用者名稱、密碼和一個隨機數字圖片驗證,圖片驗證無法屏蔽掉。此時,我們這裡可以採用先擷取使用者登陸後的cookies,再以http的get請求的方式向使用者發送headers(該headers包含使用者登陸後的cookies資訊),這樣就避免了登陸。
擷取cookies的方法我感覺有2種,一種是手工方式:通過fiddler軟體來查看,當使用者在瀏覽器裡輸入請求的網址時,通過fiddler就可以查看使用者的http的響應,具體的過程如下面2張圖片所示,第一張圖片是fiddler查看cookies的header,第二張圖是拷貝該header:
第二種擷取cookies的方式,可以第一次通過使用者名稱和密碼向url發出請求,然後將請求後的response中的headers的cookies儲存下來,下次就可以根據cookies去登入該url並擷取頁面內容。
好,cookies介紹完畢,咱們回來,下面給出第一個demo(通過cookie登陸擷取微博頁面,並抓取微博使用者資訊):
#!/usr/bin/python# -*-coding:utf-8 -*-import httplib2import urllib2import re #Regex模組class WeiboClass: #定義一個weibo類 #擷取指定url的網頁內容 def get_content(self,url,headers,id): http=httplib2.Http() response,content=http.request(url+str(id),'GET',headers=headers) #print url+str(id) return content.decode('unicode-escape').encode('utf-8') #判斷weibo的使用者是否是企業使用者 def is_company(self,url,headers,id): content=self.get_content(url,headers,id) title=r'行業' company_title=re.compile(title) if company_title.search(content): #使用Regex對title進行匹配 return 1 else: return 0 #擷取使用者的weibo資訊:ID,首頁url,暱稱 def get_info(self, url,headers,id): flag=self.is_company(url,headers,id) content=self.get_content(url,headers,id) if flag==0: #如果使用者是個人使用者 #print content #微博ID id_flag=r'\$CONFIG\[\'oid\'\] = \'([0-9].+?)\';' id_re=re.compile(id_flag) id_regx=id_re.search(content) id=id_regx.group(1) print id #微博url url_flag=r'<meta http-equiv="mobile-agent" content="format=xhtml;" url="weibo.cn/(.+?)\?' url_re=re.compile(url_flag) url_regx=url_re.search(content) url_0=url_regx.group(1) url='http://weibo.com/'+url_0 print url #暱稱 name_flag='<div class="name clearfix">.+?<div class="left">(.+?)<' name_re=re.compile(name_flag,re.S) name_regx=name_re.search(content) name=name_regx.group(1) name=name.decode('utf-8').encode('GBK') print namedef main(): headers={"cookie":'NSC_wjq_xfjcp.dpn_w3.6_w4=ffffffff0941137b45525d5f4f58455e445a4a423660; SUS=SID-1806453925-1342851885-XD-hgrjc-03210d75ca203f3ad0d57666a05ae49d; SUE=es%3Deabd0a14bc6e6123c5c4d058d9a2c96f%26ev%3Dv1%26es2%3Db50ae59b82b457a1ba54b2b7708fbb5b%26rs0%3DdP4RVYzORRwV64PFw6wRdNGBk0HP47V8C5SXUp%252F7Q9K2RcduYt4ECQbEDNZk%252Bs8GHDpW5wk%252B3%252FmYKP12zIyQbD1bUMd2wNBgdRX45p2rygizXgHMjH%252FFnU53HJFC2OfSvEHZADJkZD%252BTdLHidgoyy4maajVHi%252B%252B1en0zIKIf3mo%253D%26rv%3D0; SUP=cv%3D1%26bt%3D1342851885%26et%3D1342938285%26d%3Dc909%26i%3D4dc3%26us%3D1%26vf%3D0%26vt%3D0%26ac%3D0%26uid%3D1806453925%26user%3Dqipeng.sdsy%2540163.com%26ag%3D4%26name%3Dqipeng.sdsy%2540163.com%26nick%3D%25E4%25B8%2580%25E5%258C%25B9%25E5%259C%25A8%25E8%25B7%25AF%25E4%25B8%258A%25E7%259A%2584%25E9%25A9%25AC%26fmp%3D%26lcp%3D; SSOLoginState=1342851885; ads_ck=1; UOR=hao.360.cn,weibo.com,spr_web_360_hao360_weibo_t001:1342851234578; _olympicMedalsTable=; ULV=1342851891453:53:53:53:3919105774821.296.1342851890484:1342851070843; SinaRot/u/1806453925=25; un=qipeng.sdsy@163.com; __utma=182865017.1486634646.1342787238.1342787238.1342787238.1; __utmz=182865017.1342787238.1.1.utmcsr=weibo.com|utmccn=(referral)|utmcmd=referral|utmcct=/u/1806453925; myuid=1806453925; wvr=4; ALF=1343135290; SinaRot/u/1806453925%3Fgid%3D201108260278357557=77; SinaRot/u/1806453925%3Fc%3Dspr_web_360_hao360_weibo_t001=59; _s_tentry=-; Apache=3919105774821.296.1342851890484; SINAGLOBAL=3919105774821.296.1342851890484; BAYEUX_BROWSER=4e70u68yfp1x9xreh4wbcw2jfhs; JSESSI\ONID=3nvoas5ykh0e'} url='http://weibo.com/' print headers page = WeiboClass() page.get_info(url,headers,1842764911)if __name__ == "__main__": main()
(b)下面給出httplib2的第二個例子
#!/usr/bin/python# -*-coding:utf-8 -*-import sys;import os;import urllib2;import httplib2;import Cookie;import random;import re;import time;from urllib import urlencode;import hashlib;import datetime;import socket;#httplib2.debuglevel=1;def error_log(log): sys.stderr.write("%s\n" % (log));class ErrorCode: Succ = 0; Unknown = -1; # 未知原因class SimBrowser : socket.setdefaulttimeout(10); # 逾時限制 10秒 UserAgent=""; cookie=None; httplink = httplib2.Http(); httplink.follow_redirects = False; hostname=""; def __init__(self, cookie, UserAgent="Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; .NET CLR 2.0.50727)"): self.cookie = cookie; self.UserAgent = UserAgent; def gen_cookie_str(self): cookiestr = '; '.join(self.cookie.output(attrs=[], header="").split('\r\n')); if len(cookiestr) <= 0: return ""; else: return cookiestr; def prepare_request_header(self, header): newheader = {}; cookiestr = self.gen_cookie_str(); if len(cookiestr) > 0: newheader['Cookie'] = cookiestr; # set agent newheader['User-Agent'] = self.UserAgent; # replace or append user specified values in header for key in header.keys(): newheader[key] = header[key]; return newheader; # maintain cookies def maintain_cookie(self, response_header): if 'set-cookie' in response_header: self.cookie.load(response_header['set-cookie']); def get_redirect_url(self, prevurl, res): if 'location' not in res: error_log('no location in res'); return ""; location = res['location']; if len(location) <= 0: error_log('location length is zero'); return ""; # check location contain fullpath of target if location.find("http://") != 0: p = re.compile(r"[(http://)]*[.\-_0-9A-Za-z]+"); m = p.match(prevurl); if m != None: host = m.group(); return host + location; else: error_log('cannot get host link'); host = ""; else: return location; def request(self, url, method="GET", headers={}, body="", follow_redirects=False): newheaders = self.prepare_request_header(headers); newurl = url; newbody = body; while (True): try: res, content = self.httplink.request(newurl, method=method, headers=newheaders, body=newbody); self.maintain_cookie(res); except Exception , what: try: res, content = self.httplink.request(newurl, method=method, headers=newheaders, body=newbody); self.maintain_cookie(res); except Exception , what: try: res, content = self.httplink.request(newurl, method=method, headers=newheaders, body=newbody); self.maintain_cookie(res); except Exception , what: # 訪問擷取 三次 不成功返回失敗 res=''; content=''; break; # check redirects if follow_redirects==False: break; elif res.status in(300, 301, 302): prevurl = newurl; newheaders = self.prepare_request_header({}); newurl = self.get_redirect_url(prevurl, res); body = ""; method="GET"; if len(url) > 0: continue; else: sys.stderr.write("Error:failed to get redirect location\n"); break; else: break; return res, content;def main(): cookie = Cookie.SimpleCookie(); sim = SimBrowser(cookie); aurl='http://s.weibo.com/weibo/computer&Refer=STopic_box'; myCookie = ('UOR=,weibo.com,; myuid=1369574593; un=zhaolianxiang@126.com; \un=zhaolianxiang@126.com; wvr=4; __utma=15428400.565128140.1342666019.13426660\19.1342666019.1; __utmz=15428400.1342666019.1.1.utmcsr=blog.sina.com.cn|utmccn\=(referral)|utmcmd=referral|utmcct=/s/blog_84313fa001010n90.html; NSC_wjq_xfjc\p.dpn_w3.6_w4=ffffffff0941010945525d5f4f58455e445a4a423660; SSOLoginState=1342\867370; _s_tentry=login.sina.com.cn; Apache=9964875415898.86.1342867464581; UL\V=1342867464624:7:7:4:9964875415898.86.1342867464581:1342514299919; SUE=es%3D6\c406ebb66f15ce0e5b852efa908d728%26ev%3Dv1%26es2%3D658950facb6ad8c9c8627639f31a\61de%26rs0%3Df6AWRRwcy3r7HJ7y1mdnQR5icnbFHj6Qt%252F6Og2%252FaDBwMtLGJbQhanphxu\EWsXCI1CPdl1yhB%252BHNobWvCmQPmF0xjrJhrvxCWAoiiE7D9cPDXQVOvlQPDsAopv10Un5DDuES\%252FZcPXtwnaCYnD5hcMAoDinTKgBxbeZ%252FBSiLzvEdQ%253D%26rv%3D0; SUP=cv%3D1%26b\t%3D1343009147%26et%3D1343095547%26d%3Dc909%26i%3D3d45%26us%3D1%26vf%3D0%26vt%\3D0%26ac%3D1%26uid%3D1842764911%26user%3Dzhaolianxiang%2540126.com%26ag%3D4%26\name%3Dzhaolianxiang%2540126.com%26nick%3DSean%26fmp%3D%26lcp%3D2011-12-25%252\012%253A59%253A36; SUS=SID-1842764911-1343009147-XD-cyk5d-215c6a72f1b3a340c301\533e2b4ce49d; ALF=1343095212; ads_ck=1; SinaRot/z/zhaolianxiang=44; SINAGLOBAL\=9964875415898.86.1342867464581; _olympicMedalsTable=; USRHAWB=usrmdins213_206'); headers={'Cookie':myCookie,'Content-Type':'application/x-www-form-urlencoded'};# print "myCookie:",myCookie;# body={'path':'GET/material','userid':self.BaiduId,'token':self.token,'params':'{"level":"useracct","fields":["wregion","wbudget","userstat"]}'}; #以http的方式向aurl發出get請求,同時將http的頭headers發送過去 #這個headers包含了使用者的登陸的cookies資訊。cookies的擷取可以通過fiddler軟體來查看。 res, content = sim.request(aurl,'GET', headers=headers); print "res:",res #輸出http的響應response #將擷取的網頁內容先解碼,再以utf-8的形式編碼 print "content:",content.decode('unicode-escape').encode('utf-8'); if __name__ == "__main__": main();
註:上面的兩個程式中的cookies均已失效,需要您通過我上面介紹的方法替換cookies中的內容。
(3)總結
使用cookie登陸抓取指定頁面,可以像下面這樣,這是一個最簡潔的代碼
#!/usr/bin/python# coding:utf-8 import httplib2import urllib2import re #Regex模組class PageClass: #擷取指定url的網頁內容 def get_page(self,url,headers): http=httplib2.Http() response,content=http.request(url,'GET',headers=headers) #return content.decode('unicode-escape').encode('utf-8') return content.decode('unicode-escape').encode('utf-8') def main(): headers={"cookie":'your cookie'} url = 'http://fengchao.baidu.com' #print headers page = PageClass() content = page.get_page(url,headers) print contentif __name__ == "__main__": main()
只需要輸入url和headers就可以抓取到指定的頁面。這裡需要輸入你自己的cookie。上面的代碼執行後的結果如下,可以看到我們抓取到了內容:
3.XPath
將抓取到的頁面源碼,如何進行解析,一般採用下面三種技術:
(1)lxml的xpath:基於XML的語義進行解析的(推薦)。
(2)Regex(RE):基於純文字的處理。
(3)純字串處理(不推薦)
總結:RE對付簡單的頁面沒有問題,如果頁面結構複雜度較高的時候,建議採用xpath,因為此時設計一個合適的RE pattern可能會遠比寫一個xpath要複雜。
lxml的:http://pypi.python.org/pypi/lxml/2.3
上面介紹的httplib2的2個樣本均是通過RE(Regex)來進行頁面解析的。下面我們介紹XPath來進行頁面解析想要的內容。
XPATH基本上是用一種類似分類樹的方法來描述在XML文檔中的路徑。比如用“/”來作為上下層級間的分隔。第一個“/”表示文檔的根節點(注意,不是指文檔最外層的tag節點,而是指文檔本身)。比如對於一個HTML檔案來說,最外層的節點應該是”/html”。同樣的,“..”和“.”分別被用來表示父節點和本節點。
XPATH返回的不一定就是唯一的節點,而是合格所有節點。比如在HTML文檔裡使用“/html/head/scrpt”就會把head裡的所有script節點都取出來。
為了縮小定位範圍,往往還需要增加過濾條件。過濾的方法就是用“[”“]”把過濾條件加上。比如在HTML文檔裡使用“/html/body/div[@id='main']”,即可取出body裡id為main的div節點。其中@id表示屬性id,類似的還可以使用如@name, @value, @href, @src, @class…. 。
而函數text()的意思則是取得節點包含的文本。比如:<div>hello<p>world</p>< /div>中,用”div[text()='hello']“即可取得這個div,而world則是p的text()。
函數position()的意思是取得節點的位置。比如“li[position()=2]”表示取得第二個li節點,它也可以被省略為“li[2]”。
不過要注意的是數字定位和過濾條件的順序。比如“ul/li[5][@name='hello']”表示取ul下第五項li,並且其name必須是hello,否則返回空。而如果用“ul/li[@name='hello'][5]”的意思就不同,它表示尋找ul下第五個name為”hello“的li節點。
此外,“*”可以代替所有的節點名,比如用”/html/body/*/span”可以取出body下第二級的所有span,而不管它上一級是div還是p或是其它什麼東東。
而 “descendant::”首碼可以指代任意多層的中間節點,它也可以被省略成一個“/”。比如在整個HTML文檔中尋找id為“leftmenu”的 div,可以用“/descendant::div[@id='leftmenu']”,也可以簡單地使用“ //div[@id='leftmenu']”。
至於“following-sibling::”首碼就如其名所說,表示同一層的下一個節點。”following-sibling::*”就是任意下一個節點,而“following-sibling::ul”就是下一個ul節點。
下面給出一個樣本,其功能為提取百度首頁搜尋方塊上面的導航條的內容:
#該程式是對百度的首頁進行分析,並提取出其搜尋方塊上面的導航條import httplib2import urllib2import refrom lxml import etreedef main(): http = httplib2.Http() response,content = http.request("http://www.baidu.com",'GET') print "response:",response print "content:",content tree = etree.HTML(content) #上面的注釋為要尋找的部分html #<p id=nv><a href=http://news.baidu.com>新聞</a><b>網頁</b> #<a href=http://tieba.baidu.com>貼吧</a><a href=http://zhidao.baidu.com>知道</a> #<a href=http://mp3.baidu.com>MP3</a><a href=http://image.baidu.com>圖片</a> #<a href=http://video.baidu.com>視頻</a><a href=http://map.baidu.com>地圖</a></p> #下面開始尋找id為nv的p標籤下的所有<a>的href值 hyperlinks = tree.xpath(u'//p[@id="nv"]/a/@href') print "hyperlinks:",hyperlinks for hyperlink in hyperlinks: print "hyperlink:",hyperlink #尋找id為nv的p標籤下的所有<a>節點 a_nodes = tree.xpath(u'//p[@id="nv"]/a') print "a_nodes_length:",len(a_nodes) for a_node in a_nodes: print "<a>:",a_node.text,a_node.attrib['href'] print "\n" #通過Regex尋找<p id="nv">的標籤內容,匹配的內容為Regex中的"()"內的內容 name_flag='<p id="nv">(.+?)</p>' name_re=re.compile(name_flag,re.S) name_regx=name_re.search(content) print name_regx name=name_regx.group(1) print "name:",name if __name__ == "__main__": main()
其執行結果為:
下面對其進行分析:
首先,對www.baidu.com進行http的get請求,然後將相應的頁面結果進行分析。這裡我使用了3種方式進行頁面分析,前2種方式為使用xpath提取,第3種方式為通過Regex匹配提取。程式中有詳細的注釋。
4.HTMLParser
該模組是用來解析HTML元素的。可以從HTML中篩選出指定的標籤。下面給出一個例子,讀取百度首頁www.baidu.com中的所有連結,並列印出來。
import HTMLParserimport urllibimport sys#定義HTML解析器class parseLinks(HTMLParser.HTMLParser): #該方法用來處理開始標籤的,eg:<div id="main"> def handle_starttag(self, tag, attrs): if tag == 'a': #如果為<a>標籤 #name為標籤的屬性名稱,如href、name、id、onClick等等 for name,value in attrs: if name == 'href': #這時選擇href屬性 print "name_value: ",value #href屬性的值 print "first tag:",self.get_starttag_text() #<a>標籤的開始tag print "\n"if __name__ == "__main__": #建立HTML解析器的執行個體 lParser = parseLinks() #開啟HTML檔案 lParser.feed(urllib.urlopen("http://www.baidu.com").read()) lParser.close()
但調用feed函數時,會自動調用handle_starttag函數,這裡的handle_starttag函數是對原函數的重寫。handle_starttag(self,tag,attrs)中的參數tag是標籤的名字;參數attrs是一個(name,value)索引值對,是通過尋找到tag的<>括弧來確定的,其中name是tag的<>中的屬性名稱,value是去除引號後的值。
上面程式的執行結果如下:(下面的結果只是部分的螢幕)
5.Regex(RE)
由於上面涉及到很到Regex的匹配問題。下面對Python中的RE使用進行簡單的示範和說明。下面給出一段,我測試用的code:
import re text = "JGood is a handsome booy, he is cool, clever, and so on..." regex1 = re.match(r"\w*oo\w*", text) if regex1: print "regex1:" , regex1 print "result1:" ,regex1.group(0) else: print 'not match'print "\n"regex2 = re.compile(r'(\w*oo\w*)')print "result2:" , regex2.findall(text)print "\n"regex3 = re.compile(r'(\w*oo\w*).+?(\w*eve\w*).*')regex3_result = regex3.search(text)if regex3_result: print "regex3:", regex3 print " result3:",regex3_result.group(1)," ",regex3_result.group(2)else: print 'not match'
程式的運行結果:
下面簡單的講解一下:
regex1是通過match方法進行匹配,match只匹配字串的開始,如果字串開始不符合Regex,則匹配失敗,函數返回None;regex2是通過findall,尋找所有滿足的匹配;regex3是通過search匹配整個字串,直到找到一個匹配。
對於regex3_result.group(1),regex3_result.group(2)是什麼意思呢,我搞了半天才弄明白。group(i)表示匹配的Regex"()"中的內容。如regex3_result.group(1)表示匹配的Regex中第一個()內的內容,regex3_result.group(2)表示匹配的Regex中第二個“()”的對應的字串。group(0)表示整個運算式。group是從左向右計數的,從1開始。組可以被嵌套。計數的數值可以通過從左至右計算開啟的括弧數來確定。
再給出一個小例子,一目瞭然:
#!python>>> p = re.compile('(a(b)c)d')>>> m = p.match('abcd')>>> m.group(0)'abcd'>>> m.group(1)'abc'>>> m.group(2)'b'
6.如何判斷網路連接
有的軟體如果需要訪問網路,這時我們就需要定時地檢測網路的串連,下面的代碼給出了如何檢測網路連接:當串連時返回1;當網路斷掉時返回0。原理就是通過http去請求網頁,如果能夠請求到則網路正常,如果請求出現異常則網路斷掉。
def check_network(): import httplib2 try: http = httplib2.Http() resp, content = http.request("http://www.baidu.com") except: return 0 return 1