1. 程式人生 > >Python 學習入門(6)—— 網頁爬蟲

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'
執行結果:

參考推薦: