最近兩周都在學(xué)習(xí)Python抓取網(wǎng)頁方法,任務(wù)是批量下載網(wǎng)站上的文件。對于一個剛剛?cè)腴Tpython的人來說,在很多細(xì)節(jié)上都有需要注意的地方,,以下就分享一下我在初學(xué)python過程中遇到的問題及解決方法。
一,、用Python抓取網(wǎng)頁
基本方法:
- <span style="font-size:14px;">import urllib2,urllib
-
- url = 'http://www.baidu.com'
- req = urllib2.Request(url)
- content = urllib2.urlopen(req).read()</span>
1)、url為網(wǎng)址,需要加'http://'
2),、content為網(wǎng)頁的html源碼
問題:
1,、網(wǎng)站禁止爬蟲,不能抓取或者抓取一定數(shù)量后封ip
解決:偽裝成瀏覽器進(jìn)行抓取,,加入headers:
- <span style="font-size:14px;">import urllib2,urllib
-
- 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()</span>
- <span style="font-size:14px;">
- </span>
更復(fù)雜的情況(需要登錄,,多線程抓取)可參考:http://www./python-network-application/observer-spider,,很不錯的教程
2,、抓取網(wǎng)頁中的中文為亂碼問題
解決:用BeautifulSoup解析網(wǎng)頁(BeautifulSoup是Python的一個用于解析網(wǎng)頁的插件,其安裝及使用方法下文會單獨(dú)討論)
首先需要介紹一下網(wǎng)頁中的中文編碼方式,,一般網(wǎng)頁的編碼會在<meta>標(biāo)簽中標(biāo)出,,目前有三種,分別是GB2312,,GBK,,GB18030,三種編碼是兼容的,,
從包含的中文字符個數(shù)比較:GB2312 < GBK < GB18030,,因此如果網(wǎng)頁標(biāo)稱的編碼為GB2312,但是實(shí)際上用到了GBK或者GB18030的中文字符,,那么編碼工具就會解析錯誤,,導(dǎo)致編碼退回到最基本的windows-2152了。所以解決此類問題分兩種情況,。
1),、若網(wǎng)頁的實(shí)際的中文編碼和其標(biāo)出的相符的話,即沒有字符超出所標(biāo)稱的編碼,,下面即可解決
- <span style="font-size:14px;">import urllib,urllib2,bs4
-
- req = urllib2.Request(url)
- content = urllib2.urlopen(req).read()
- content = bs4.BeautifulSoup(content)
- return content</span>
2),、若網(wǎng)頁中的中文字符超出所標(biāo)稱的編碼時,需要在BeautifulSoup中傳遞參數(shù)from_encoding,,設(shè)置為最大的編碼字符集GB18030即可
- <span style="font-size:14px;">import urllib,urllib2,bs4
-
- req = urllib2.Request(url)
- content = urllib2.urlopen(req).read()
- content = bs4.BeautifulSoup(content,from_encoding='GB18030')
- return content</span>
詳細(xì)的中文亂碼問題分析參見:http://againinput4.blog.163.com/blog/static/1727994912011111011432810/
二,、用Python下載文件
使用Python下載文件的方法有很多,在此只介紹最簡單的一種
- <span style="font-size:14px;">import urllib
-
- urllib.urlretrieve(url, filepath)</span>
url為下載鏈接,,filepath即為存放的文件路徑+文件名
更多Python下載文件方法參見:http:///code-snippet/83/sanzhong-Python-xiazai-url-save-file-code
三,、使用正則表達(dá)式分析網(wǎng)頁
將網(wǎng)頁源碼抓取下來后,就需要分析網(wǎng)頁,,過濾出要用到的字段信息,,通常的方法是用正則表達(dá)式分析網(wǎng)頁,一個例子如下:
- <span style="font-size:14px;">import re
-
- content = '<a
- match = re.compile(r'(?<=href=["]).*?(?=["])')
- rawlv2 = re.findall(match,content)</span>
用re.compile()編寫匹配模板,,用findall查找,,查找content中所有與模式match相匹配的結(jié)果,,返回一個列表,上式的正則表達(dá)式意思為匹配以‘href="'起始,,以'"'結(jié)束的字段,,使用非貪婪的規(guī)則,只取中間的部分
關(guān)于正則表達(dá)式,,系統(tǒng)的學(xué)習(xí)請參見:http://www.cnblogs.com/huxi/archive/2010/07/04/1771073.html
或 http://wiki./Python%E6%AD%A3%E5%88%99%E8%A1%A8%E8%BE%BE%E5%BC%8F%E6%93%8D%E4%BD%9C%E6%8C%87%E5%8D%97
個人推薦第一篇,,條理清晰,不重不漏
在此就不贅述正則表達(dá)式的學(xué)習(xí),,只總結(jié)一下我在實(shí)際寫正則時的認(rèn)為需要注意的幾個問題:
1),、一定要使用非貪婪模式進(jìn)行匹配,即*?,+?(后加?),,因為Python默認(rèn)使用貪婪模式進(jìn)行匹配,,例如'a.*b',它會匹配文檔中從第一個a和最后一個b之間的文本,,也就是說如果遇到一個b,,它不會停止,會一直搜索至文檔末尾,,直到它確認(rèn)找到的b是最后一個,。而一般我們只想取某個字段的值,貪婪模式既不能返回正確的結(jié)果,,還大大浪費(fèi)了時間,,所以非貪婪是必不可少的
2)、raw字符串的使用:如果要匹配一個.,*這種元字符,,就需要加'\'進(jìn)行轉(zhuǎn)義,,即要表示一個'\',正則表達(dá)式需要多加一個轉(zhuǎn)義,,寫成'\\',,但是Python字符串又需要對其轉(zhuǎn)義,最終變成re.compile('\\\\'),,這樣就不易理解且很亂,,使用raw字符串讓正則表達(dá)式變得易讀,即寫成re.compile(r'\\'),,另一個方法就是將字符放到字符集中,,即[\],效果相同
3),、()特殊構(gòu)造的使用:一般來說,,()中的匹配模式作為分組并可以通過標(biāo)號訪問,但是有一些特殊構(gòu)造為例外,,它們適用的情況是:我想要匹配href="xxxx"這個模式,,但是我只需要xxxx的內(nèi)容,,而不需要前后匹配的模式,這時就可以用特殊構(gòu)造(?<=),,和(?=)來匹配前后文,,匹配后不返回()中的內(nèi)容,剛才的例子便用到了這兩個構(gòu)造,。
4)、邏輯符的使用:如果想匹配多個模式,,使用'|'來實(shí)現(xiàn),,比如
- <span style="font-size:14px;">re.compile(r'.htm|.mid$')</span>
匹配的就是以.htm或.mid結(jié)尾的模式,注意沒有'&'邏輯運(yùn)算符
四,、使用BeautifulSoup分析網(wǎng)頁
BeautifulSoup是Python的一個插件,,用于解析HTML和XML,是替代正則表達(dá)式的利器,,下文講解BS4的安裝過程和使用方法
1,、安裝BS4
下載地址:http://www./software/BeautifulSoup/#Download
下載 beautifulsoup4-4.1.3.tar.gz,解壓:linux下 tar xvf beautifulsoup4-4.1.3.tar.gz,,win7下直接解壓即可
linux:
進(jìn)入目錄執(zhí)行:
1, python setup.py build
2, python setup.py install
或者easy_install BeautifulSoup
win7:
cmd到控制臺 -> 到安裝目錄 -> 執(zhí)行上面兩個語句即可
2,、使用BeautifulSoup解析網(wǎng)頁
本文只介紹一些常用功能,詳細(xì)教程參見BeautifulSoup中文文檔:http://www./software/BeautifulSoup/bs3/documentation.zh.html
1),、包含包:import bs4
2),、讀入:
- <span style="font-size:14px;">req = urllib2.Request(url)
- content = urllib2.urlopen(req).read()
- content = bs4.BeautifulSoup(content,from_encoding='GB18030')
- </span>
3)、查找內(nèi)容
a,、按html標(biāo)簽名查找:
- <span style="font-size:14px;">frameurl = content.findAll('frame')</span>
framurl為存儲所有frame標(biāo)簽內(nèi)容的列表,,例如frame[0] 為 <framename="m_rtop"
target="m_rbottom"src="tops.htm">
b、按標(biāo)簽屬性查找
- <span style="font-size:14px;">frameurl = content.findAll(target=True)</span>
查找所有含target屬性的標(biāo)簽
- <span style="font-size:14px;">frameurl = content.findAll(target=‘m_rbottom’)</span>
查找所有含target屬性且值為'm_rbottom'的標(biāo)簽
c,、帶有正則表達(dá)式的查找
- <span style="font-size:14px;">rawlv2 = content.findAll(href=re.compile(r'.htm$'))</span>
查找所有含href屬性且值為以'.htm'結(jié)尾的標(biāo)簽
d,、綜合查找
- <span style="font-size:14px;">frameurl = content.findAll('frame',target=‘rtop’)</span>
查找所有frame標(biāo)簽,且target屬性值為'rtop'
4),、訪問標(biāo)簽屬性值和內(nèi)容
a,、訪問標(biāo)簽屬性值
- <span style="font-size:14px;">rawlv2 = content.findAll(href=re.compile(r'.htm$'))
- href = rawlv2[i]['href']</span>
通過[屬性名]即可訪問屬性值,如上式返回的便是href屬性的值
b),、訪問標(biāo)簽內(nèi)容
- <span style="font-size:14px;">rawlv3 = content.findAll(href=re.compile(r'.mid$'))
- songname = str(rawlv3[i].text)</span>
上式訪問了<a href=...>(內(nèi)容)</a>標(biāo)簽的實(shí)際內(nèi)容,,由于text為unicode類型,所以需要用str()做轉(zhuǎn)換
附上最終的成果,,程序功能是抓取www.上的所有midi文件并下載,,需要先建立./midi/dugukeji/文件夾和./midi/linklist文件
- <span style="font-size:14px;">
- import urllib2,urllib,cookielib,threading
- import os
- import re
- import bs4
- import sys
- reload(sys)
- sys.setdefaultencoding('utf-8')
-
-
- indexurl = 'http://www./'
- 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 content
-
-
- def crawlframe(sourceurl,target):
- global indexurl
- content = crawl(sourceurl)
-
-
- frameurl = content.findAll('frame',target=target)
- result = indexurl+frameurl[0]['src']
- return result
-
- def crawllv1(frameurl,st=-1,en=-1):
- global indexurl
- content = crawl(frameurl)
-
-
- 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'])
- else:
- for i in range(st,en):
- result.append(indexurl+rawlv2[i]['href'])
-
-
-
-
-
-
-
-
-
-
-
- return result
-
- def crawllv2(lv2url):
- global indexurl
- content = crawl(lv2url)
-
-
- rawlv3 = content.findAll(href=re.compile(r'[..].*?[0-9].htm|.mid$'))
-
- result = {}
- for i in range(len(rawlv3)):
- tmp = str(rawlv3[i]['href'])
-
- link = indexurl + tmp[tmp.rfind('..')+3:]
- songname = ''
- if tmp[-4:]=='.htm':
- try:
- conlv3 = crawl(link)
- except:
- print 'WARNING: visit lv3 url failed!\n'
- else:
- rawlv4 = conlv3.findAll(href=re.compile(r'.mid$'))
- if not rawlv4:
- continue
- else:
- tmp = str(rawlv4[0]['href'])
- link = indexurl + tmp[tmp.rfind('..')+3:]
-
- songname = str(rawlv3[i].text)
-
-
- songname = songname.replace(' ','_')
- 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
-
- return result
-
- def 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()
-
- if __name__ == '__main__':
- try:
- readdata(databasepath)
- except:
- print 'WARNING:read database file failed!\n'
- else:
- print 'There is ',len(oriresult),' links in database.\n'
-
- try:
- frameurl1 = crawlframe(indexurl,'rtop')
- except:
- print 'WARNING: crawl lv1 frameurl failed!\n'
- try:
- urllv1 = crawllv1(frameurl1,4,20)
- except:
- print 'WARNING: crawl lv1 url failed!\n'
-
- for i in urllv1:
- print 'lv1 url:',i
- try:
- frameurl2 = crawlframe(i,'rbottom')
- except:
- print 'WARNING: crawl lv2 frameurl failed!\n'
- else:
- print '\tlv2 frameurl:',frameurl2
- try:
- urllv2 = crawllv1(frameurl2)
- 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]
-
-
- print 'new added midi num:',len(totalresult)
- print '\nbegin to download...\n'
- download(totalresult)
- print '\nWrite database...\n'
- writedata(databasepath)
- print '\n\nDone!\n'
-
-
-
-
-
-
-
-
-
-
- </span>