Python 學習入門(6)—— 網頁爬蟲
Python抓取網頁方法,任務是批量下載網站上的檔案。對於一個剛剛入門python的人來說,在很多細節上都有需要注意的地方,以下就分享一下在初學python過程中遇到的問題及解決方法。
1、Python抓取網頁
import urllib,urllib2url = "http://blog.ithomer.net"req = urllib2.Request(url)content = urllib2.urlopen(req).read()print content
python3.3沒有urllib2,改寫如下:
import urllib.requestdef getdata(): url='http://www.baidu.com' data = urllib.request.urlopen(url).read() print(data)getdata()
1)、url為網址,需要加'http://'2)、content為網頁的html原始碼
問題:
1.1、網站禁止爬蟲,不能抓取或者抓取一定數量後封ip
解決:偽裝成瀏覽器進行抓取,加入headers:
import urllib,urllib2headers = { 'Use-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6' }url = "http://blog.ithomer.net" req = urllib2.Request(url, headers=headers)content = urllib2.urlopen(req).read()print content
更復雜的情況(需要登入,多執行緒抓取)可參考:python爬蟲抓站的一些技巧總結1.2、抓取網頁中的中文亂碼
解決:用BeautifulSoup解析網頁,BeautifulSoup是Python的一個用於解析網頁的外掛,其安裝及使用方法下文會單獨討論。
首先需要介紹一下網頁中的中文編碼方式,一般網頁的編碼會在<meta>標籤中標出,目前有三種,分別是GB2312,GBK,GB18030,三種編碼是相容的。從包含的中文字元個數比較:GB2312 < GBK < GB18030,因此如果網頁標稱的編碼為GB2312,但內容裡實際上用到了屬於GBK或者GB18030的中文字元,那麼編碼工具就會解析錯誤,導致編碼退回到最基本的windows-2152。所以解決此類問題分兩種情況:
1)、若網頁實際的中文編碼和其標出的相符的話,即沒有字元超出所標稱的編碼,下面即可解決
import urllib,urllib2import bs4headers = { 'Use-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6' }url = "http://blog.ithomer.net"req = urllib2.Request(url, headers=headers)content = urllib2.urlopen(req).read()content = bs4.BeautifulSoup(content) # BeautifulSoupprint content
2)、若網頁中的中文字元超出所標稱的編碼時,需要在BeautifulSoup中傳遞引數from_encoding,設定為最大的編碼字符集GB18030即可import urllib,urllib2import bs4headers = { 'Use-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6' }url = "http://blog.ithomer.net"req = urllib2.Request(url, headers=headers)content = urllib2.urlopen(req).read()content = bs4.BeautifulSoup(content, from_encoding='GB18030') # BeautifulSoupprint content
2、Python下載檔案
使用Python下載檔案的方法有很多,在此只介紹最簡單的三種:
#!/url/bin/python# -*- coding:utf-8 -*-# ithomer.net import os, shutilimport urllib, urllib2import requestsurl = 'http://blog.ithomer.net/wp-content/themes/officefolders/images/logo.png' # 下載原網址savepath = './downloads/logo.png' # 本地儲存地址if os.path.isdir("downloads"): shutil.rmtree("downloads") # 如果存在資料夾, 則刪除if not os.path.isdir("downloads"): os.makedirs("downloads") # 建立資料夾# 方法1: urlliburllib.urlretrieve(url, savepath)# 方法2: urllib2f = urllib2.urlopen(url)data = f.read()with open(savepath, 'wb') as code: code.write(data)# 方法3: reqeustsreq = requests.get(url)with open(savepath, 'wb') as code: code.write(req.content)
上面方法3: 需要安裝 requests模組,方法如下:1) 先安裝 pip: sudo apt-get install python-pip python-dev build-essential
2) 安裝 requests: sudo pip install requests
3、使用正則表示式分析網頁
將網頁原始碼抓取下來後,就需要分析網頁,過濾出要用到的欄位資訊,通常的方法是用正則表示式分析網頁,一個例子如下:
import recontent = '<a target="blank" href="http://blog.ithomer.net">ithomer</a>'match = re.compile(r'(?<=href=["]).*?(?=["])')print re.findall(match, content) # ['http://blog.ithomer.net']
用re.compile()編寫匹配模板,用findall查詢,查詢content中所有與模式match相匹配的結果,返回一個列表,上式的正則表示式意思為匹配以‘href="'起始,以'"'結束的欄位,使用非貪婪的規則,只取中間的部分關於正則表示式,系統的學習請參見:正則表示式或 正則表示式操作指南 ,個人推薦第一篇,條理清晰,不重不漏。
在此就不贅述正則表示式的學習,只總結一下我在實際寫正則時的認為需要注意的幾個問題:
1)、一定要使用非貪婪模式進行匹配,即*?,+?(後加?),因為Python預設使用貪婪模式進行匹配,例如'a.*b',它會匹配文件中從第一個a和最後一個b之間的文字,也就是說如果遇到一個b,它不會停止,會一直搜尋至文件末尾,直到它確認找到的b是最後一個。而一般我們只想取某個欄位的值,貪婪模式既不能返回正確的結果,還大大浪費了時間,所以非貪婪是必不可少的。
2)、raw字串的使用:如果要匹配一個.,*這種元字元,就需要加'\'進行轉義,即要表示一個'\',正則表示式需要多加一個轉義,寫成'\\',但是Python字串又需要對其轉義,最終變成re.compile('\\\\'),這樣就不易理解且很亂,使用raw字串讓正則表示式變得易讀,即寫成re.compile(r'\\'),另一個方法就是將字元放到字符集中,即[\],效果相同。
3)、()特殊構造的使用:一般來說,()中的匹配模式作為分組並可以通過標號訪問,但是有一些特殊構造為例外,它們適用的情況是:想要匹配href="xxxx"這個模式,但是我只需要xxxx的內容,而不需要前後匹配的模式,這時就可以用特殊構造(?<=),和(?=)來匹配前後文,匹配後不返回()中的內容,剛才的例子便用到了這兩個構造。
4)、邏輯符的使用:如果想匹配多個模式,使用'|'來實現,比如
re.compile(r'.htm|.mid$')匹配的就是以.htm或.mid結尾的模式,注意沒有'&'邏輯運算子
4、使用BeautifulSoup分析網頁
BeautifulSoup是Python的一個外掛,用於解析HTML和XML,是替代正則表示式的利器,下文講解BS4的安裝過程和使用方法
1、安裝bs4
解壓: linux下 tar xvf beautifulsoup4-4.3.2.tar.gz,win7下直接解壓即可
linux,進入目錄執行:
1, python setup.py build
2, python setup.py install
或
easy_install BeautifulSoup
win7,cmd到控制檯 -> 到安裝目錄 -> 執行上面兩個語句即可
2、使用BeautifulSoup解析網頁
1)、包含包:import bs4
2)、讀入:
import urllib,urllib2import bs4url = "http://www.dugukeji.com/"req = urllib2.Request(url)content = urllib2.urlopen(req).read()content = bs4.BeautifulSoup(content, from_encoding='GB18030') # BeautifulSoupprint content.prettify() # BeautifulSoup 格式化程式碼
抓取列印結果:
<html> <head> <meta content="text/html; charset=utf-8" http-equiv="Content-Type"/> <title> MIDI音樂欣賞MID下載 </title> <meta content="Microsoft FrontPage 6.0" name="GENERATOR"/> <meta content="MID音樂,MID線上,MID下載,MID試聽,MID欣賞,MID播放,背景音樂,MID,MID" name="DESCRIPTION"/> <meta content="MID音樂,MID線上,MID下載,MID試聽,MID欣賞,MID播放,背景音樂,MID,MID" name="keywords"/> <meta content="連通線上:MID.lt263.COM" name="Author"/> </head> <frameset border="false" cols="160,*" frameborder="0" framespacing="0"> <frame marginheight="0" marginwidth="0" name="left" noresize="" scrolling="no" src="lm1.htm" target="rtop"/> <frameset rows="27%,*"> <frame name="m_rtop" src="tops.htm" target="m_rbottom"/> <frame name="m_rbottom" src="main.htm" target="m_rbottom"/> </frameset> <noframes> <body> <p> 15000首MID </p> </body> </noframes> </frameset></html>
3)、查詢內容a、按html標籤名查詢:
frameurl = content.findAll('frame', target='rtop') # 儲存所有frame標籤,且target='rtop'內容的列表
輸出結果:
[<frame marginheight="0" marginwidth="0" name="left" noresize="" scrolling="no" src="lm1.htm" target="rtop"/>]
b、按標籤屬性查詢
frameurl = content.findAll('frame', target=True) # 查詢所有含target屬性的標籤執行結果:
[<frame marginheight="0" marginwidth="0" name="left" noresize="" scrolling="no" src="lm1.htm" target="rtop"/>, <frame name="m_rtop" src="tops.htm" target="m_rbottom"/>, <frame name="m_rbottom" src="main.htm" target="m_rbottom"/>]
查詢所有含target屬性且值為'm_rbottom'的標籤
c、帶有正則表示式的查詢
rawlv2 = content.findAll(href=re.compile(r'.htm$')) # 查詢所有含href屬性且值為以'.htm'結尾的標籤示例:
#!/url/bin/python# -*- coding:utf-8 -*-# ithomer.net import urllib,urllib2import bs4, reimport sysreload(sys)sys.setdefaultencoding('utf-8')url = "http://www.dugukeji.com/lm1.htm"req = urllib2.Request(url)content = urllib2.urlopen(req).read()content = bs4.BeautifulSoup(content, from_encoding='GB18030') # BeautifulSoupprint content.prettify()rawlv2 = content.findAll(href=re.compile(r'.htm$'))print rawlv2
執行結果:[<a href="ftzw/index.htm" target="_blank"><font color="#FF6633">繁體中文版</font></a>, <a href="addopen.htm" target="_top">最新加入</a>, <a href="searchopen.htm" target="_top">歌曲搜尋</a>, <a href="chopen.htm" target="_top">國語歌曲</a>, <a href="twopen.htm" target="_top">臺語歌曲</a>, <a href="wwopen.htm" target="_top">西洋歌曲</a>, <a href="hkopen.htm" target="_top">粵語歌曲</a>, <a href="jpopen.htm" target="_top">日本歌曲</a>, <a href="ctopen.htm" target="_top">卡通歌曲</a>, <a href="tvopen.htm" target="_top">影視歌曲</a>, <a href="pianopen.htm" target="_top">鋼琴<font face="Arial Unicode MS">POP</font></a>, <a href="otopen.htm" target="_top">其它音樂</a>, <a href="newlyopen.htm" target="_top">網友創作</a>, <a href="eachopen.htm" target="_top">各國音樂</a>, <a href="clopen.htm" target="_top">古典音樂</a>, <a href="autochopen.htm" target="_top">原住民篇</a>, <a href="marchopen.htm" target="_top">進行曲篇</a>, <a href="schoolopen.htm" target="_top">校際校歌</a>, <a href="crystalopen.htm" target="_top">水晶音樂</a>, <a href="KLOK/index.htm" target="_blank">卡 拉 OK</a>, <a href="gbookopen.htm" target="_top">留 言 板</a>, <a href="index.htm" target="_top">返回首頁</a>]
4)、訪問標籤屬性值和內容
a、訪問標籤屬性值
rawlv2 = content.findAll(href=re.compile(r'.htm$')) href = rawlv2[i]['href']
通過[屬性名]即可訪問屬性值,如上式返回的便是href屬性的值b)、訪問標籤內容
rawlv3 = content.findAll(href=re.compile(r'.mid$')) songname = str(rawlv3[i].text)
上式訪問了<a href=...>(內容)</a>標籤的實際內容,由於text為unicode型別,所以需要用str()做轉換附上最終的成果,程式功能是抓取www.dugukeji.com上的所有midi檔案並下載,需要先建立./midi/dugukeji/資料夾和./midi/linklist檔案
#!/usr/bin/python# -*- coding:utf-8 -*- ## ithomer.netimport urllib2,urllib,cookielib,threadingimport osimport reimport bs4import sysreload(sys)sys.setdefaultencoding('utf-8') #允許列印unicode字元indexurl = 'http://www.dugukeji.com/'databasepath = './midi/linklist'path = './midi/dugukeji/'totalresult = {}oriresult = {}def crawl(url): # 偽裝為瀏覽器抓取 headers = { 'User-Agent':'Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US; rv:1.9.1.6) Gecko/20091201 Firefox/3.5.6' } req = urllib2.Request(url,headers=headers) content = urllib2.urlopen(req).read() content = bs4.BeautifulSoup(content, from_encoding='GB18030') return contentdef crawlframe(sourceurl,target): global indexurl content = crawl(sourceurl) #match = re.compile(r'(?<=target=["]'+target+'["] src=["]).*?(?=["])') #正則表示式方法 #frameurl = re.findall(match,content) frameurl = content.findAll('frame',target=target) #beautifulsoup方法 result = indexurl+frameurl[0]['src'] ### http://www.dugukeji.com/lm1.htm return resultdef crawllv1(frameurl,st=-1,en=-1): global indexurl content = crawl(frameurl) #match = re.compile(r'(?<=href=["]).*?(?=["])') #rawlv2 = re.findall(match,content) rawlv2 = content.findAll(href=re.compile(r'.htm$')) result = [] if st==-1 and en==-1: for i in range(len(rawlv2)): result.append(indexurl+rawlv2[i]['href']) ### http://www.dugukeji.com/ftzw/index.htm else: for i in range(st,en): result.append(indexurl+rawlv2[i]['href']) #result.sort() return resultdef crawllv2(lv2url): global indexurl content = crawl(lv2url) #match = re.compile(r'(?<=href=["]\.\.\/).*?[">].*?(?=[<])') #rawlv3 = re.findall(match,content) rawlv3 = content.findAll(href=re.compile(r'[..].*?[0-9].htm|.mid$')) #print rawlv3 result = {} #結果字典,key:連結,value:歌曲名 for i in range(len(rawlv3)): tmp = str(rawlv3[i]['href']) #print tmp link = indexurl + tmp[tmp.rfind('..')+3:] #有多個'..',找到最後一個 songname = '' if tmp[-4:]=='.htm': #需要訪問3級頁 try: conlv3 = crawl(link) except: print 'WARNING: visit lv3 url failed!\n' else: rawlv4 = conlv3.findAll(href=re.compile(r'.mid$')) if not rawlv4: #4級頁沒有.mid下載連結,略過 continue else: tmp = str(rawlv4[0]['href']) link = indexurl + tmp[tmp.rfind('..')+3:] songname = str(rawlv3[i].text) #將unicode型別的text轉化為string #songname.decode('GBK') #songname.encode('utf-8') songname = songname.replace(' ','_') #將songname中空格和換行轉化為下劃線 songname = songname.replace('\n','_') #原來存在的連結,直接略過 if oriresult.has_key(link): continue if totalresult.has_key(link) and len(songname)<len(totalresult[link]): #如果連結已儲存且歌曲名長度比當前的長,略過 continue else: totalresult[link] = songname result[link] = songname #加入字典 #result.sort() return resultdef download(totalresult): for link in totalresult.keys(): filepath = path + totalresult[link] + '.mid' print 'download: ',link,' -> ',filepath,'\n' urllib.urlretrieve(link, filepath)def readdata(databasepath): datafile = open(databasepath,'r') #讀資料檔案 link = datafile.readline() while link: oriresult[link]='' link = datafile.readline() datafile.close()def writedata(databasepath): datafile = open(databasepath,'a') #追加開啟資料檔案,將新連結寫入檔案尾 for link in totalresult.keys(): datafile.write(link,'\n') datafile.close()# mainif __name__ == '__main__': # 訪問檔案,記錄已下載的連結 try: readdata(databasepath) except: print 'WARNING:read database file failed!\n' else: print 'There is ',len(oriresult),' links in database.\n' # 抓取主頁中一級頁url所在frame的url try: frameurl1 = crawlframe(indexurl,'rtop') except: print 'WARNING: crawl lv1 frameurl failed!\n' try: urllv1 = crawllv1(frameurl1,4,20) #抓取一級頁url except: print 'WARNING: crawl lv1 url failed!\n' for i in urllv1: print 'lv1 url:',i try: frameurl2 = crawlframe(i,'rbottom') #抓取一級頁中二級頁url所在frame的url except: print 'WARNING: crawl lv2 frameurl failed!\n' else: print '\tlv2 frameurl:',frameurl2 try: urllv2 = crawllv1(frameurl2) #抓取二級頁url except: print 'WARNING: crawl lv2 url failed!\n' else: for j in urllv2: print '\t\tlv2 url:',j try: urllv3 = crawllv2(j) except: print 'WARNING: crawl lv3 url failed!\n' else: for k in urllv3.keys(): print '\t\t\tlv3 url:',k,'\tname:',urllv3[k] #download(urllv3) print 'new added midi num:',len(totalresult) print '\nbegin to download...\n' download(totalresult) print '\nWrite database...\n' writedata(databasepath) print '\n\nDone!\n'
執行結果:參考推薦: