1. 程式人生 > >python網頁解析利器——BeautifulSoup

python網頁解析利器——BeautifulSoup

python解析網頁,無出BeautifulSoup左右,此是序言

安裝

BeautifulSoup4以後的安裝需要用eazy_install,如果不需要最新的功能,安裝版本3就夠了,千萬別以為老版本就怎麼怎麼不好,想當初也是千萬人在用的啊。安裝很簡單

$ wget "http://www.crummy.com/software/BeautifulSoup/download/3.x/BeautifulSoup-3.2.1.tar.gz"
$ tar zxvf BeautifulSoup-3.2.1.tar.gz

然後把裡面的BeautifulSoup.py這個檔案放到你python安裝目錄下的site-packages目錄下
site-packages是存放Python第三方包的地方,至於這個目錄在什麼地方呢,每個系統不一樣,可以用下面的方式找一下,基本上都能找到

$ sudo find / -name "site-packages" -maxdepth 5 -type d
$ find ~ -name "site-packages" -maxdepth 5

當然如果沒有root許可權就查詢當前使用者的根目錄

$ find ~ -name "site-packages" -maxdepth 5 -type d

如果你用的是Mac,哈哈,你有福了,我可以直接告訴你,Mac的這個目錄在/Library/Python/下,這個下面可能會有多個版本的目錄,沒關係,放在最新的一個版本下的site-packages就行了。使用之前先import一下

from BeautifulSoup import BeautifulSoup

使用

在使用之前我們先來看一個例項
現在給你這樣一個頁面
http://movie.douban.com/tag/%E5%96%9C%E5%89%A7
它是豆瓣電影分類下的喜劇電影,如果讓你找出裡面評分最高的100部,該怎麼做呢
好了,我先晒一下我做的,鑑於本人在CSS方面處於小白階段以及天生沒有美術細菌,介面做的也就將就能看下,別吐
http://littlewhite.us/douban/xiju/
接下來我們開始學習BeautifulSoup的一些基本方法,做出上面那個頁面就易如反掌了

鑑於豆瓣那個頁面比較複雜,我們先以一個簡單樣例來舉例,假設我們處理如下的網頁程式碼

<html>
<head><title>Page title</title></head>
<body>
    <p id="firstpara" align="center">
    This is paragraph
        <b>
        one
        </b>
        .
    </p>
    <p id="secondpara" align="blah">
    This is paragraph
        <b>
        two
        </b>
        .
    </p>
</body>
</html>

初始化

首先將上面的HTML程式碼賦給一個變數html如下,為了方便大家複製這裡貼的是不帶回車的,上面帶回車的程式碼可以讓大家看清楚HTML結構

html = '<html><head><title>Page title</title></head><body><p id="firstpara" align="center">This is paragraph<b>one</b>.</p><p id="secondpara" align="blah">This is paragraph<b>two</b>.</p></body></html>'

初始化如下:

soup = BeautifulSoup(html)

我們知道HTML程式碼可以看成一棵樹,這個操作等於是把HTML程式碼解析成一種樹型的資料結構並存儲在soup中,注意這個資料結構的根節點不是<html>,而是soup,其中html標籤是soup的唯一子節點,不信你試試下面的操作

print soup
print soup.contents[0]
print soup.contents[1]

前兩個輸出結果是一致的,就是整個html文件,第三條輸出報錯IndexError: list index out of range

查詢節點

查詢節點有兩種反回形式,一種是返回單個節點,一種是返回節點list,對應的查詢函式分別為find和findAll

單個節點

  1. 根據節點名

     ## 查詢head節點
     print soup.find('head') ## 輸出為<head><title>Page title</title></head>
     ## or
     ## head = soup.head

    這種方式查詢到的是待查詢節點最近的節點,比如這裡待查詢節點是soup,這裡找到的是離soup最近的一個head(如果有多個的話)

  2. 根據屬性

     ## 查詢id屬性為firstpara的節點
     print soup.find(attrs={'id':'firstpara'})  
     ## 輸出為<p id="firstpara" align="center">This is paragraph<b>one</b>.</p>
     ## 也可節點名和屬性進行組合
     print soup.find('p', attrs={'id':'firstpara'})  ## 輸出同上
  3. 根據節點關係
    節點關係無非就是兄弟節點,父子節點這樣的

     p1 = soup.find(attrs={'id':'firstpara'}) ## 得到第一個p節點
     print p1.nextSibling ## 下一個兄弟節點
     ## 輸出<p id="secondpara" align="blah">This is paragraph<b>two</b>.</p>
     p2 = soup.find(attrs={'id':'secondpara'}) ## 得到第二個p節點
     print p2.previousSibling ## 上一個兄弟節點
     ## 輸出<p id="firstpara" align="center">This is paragraph<b>one</b>.</p>
     print p2.parent ## 父節點,輸出太長這裡省略部分 <body>...</body>
     print p2.contents[0] ## 第一個子節點,輸出u'This is paragraph'

    contents上面已經提到過,它儲存的是所有子節點的序列

多個節點

將上面介紹的find改為findAll即可返回查詢到的節點列表,所需引數都是一致的

  1. 根據節點名

     ## 查詢所有p節點
     soup.findAll('p')
  2. 根據屬性查詢

     ## 查詢id=firstpara的所有節點
     soup.findAll(attrs={'id':'firstpara'})

    需要注意的是,雖然在這個例子中只找到一個節點,但返回的仍是一個列表物件

上面的這些基本查詢功能已經可以應付大多數情況,如果需要各個高階的查詢,比如正則式,可以去看官方文件

獲取文字

getText方法可以獲取節點下的所有文字,其中可以傳遞一個字元引數,用來分割每個各節點之間的文字

## 獲取head節點下的文字
soup.head.getText()         ## u'Page title'
## or
soup.head.text
## 獲取body下的所有文字並以\n分割
soup.body.getText('\n')     ## u'This is paragraph\none\n.\nThis is paragraph\ntwo\n.'

實戰

有了這些功能,文章開頭給出的那個Demo就好做了,我們再來回顧下豆瓣的這個頁面
http://movie.douban.com/tag/%E5%96%9C%E5%89%A7 
如果要得到評分前100的所有電影,對這個頁面需要提取兩個資訊:1、翻頁連結;2、每部電影的資訊(外鏈,圖片,評分、簡介、標題等)
當我們提取到所有電影的資訊後再按評分進行排序,選出最高的即可,這裡貼出翻頁提取和電影資訊提取的程式碼

## filename: Grab.py
from BeautifulSoup import BeautifulSoup, Tag
import urllib2
import re
from Log import LOG

def LOG(*argv):
    sys.stderr.write(*argv)
    sys.stderr.write('\n')

class Grab():
    url = ''
    soup = None
    def GetPage(self, url):
        if url.find('http://',0,7) != 0:
            url = 'http://' + url
        self.url = url
        LOG('input url is: %s' % self.url)
        req = urllib2.Request(url, headers={'User-Agent' : "Magic Browser"})
        try:
            page = urllib2.urlopen(req)
        except:
            return
        return page.read()  

    def ExtractInfo(self,buf):
        if not self.soup:
            try:
                self.soup = BeautifulSoup(buf)
            except:
                LOG('soup failed in ExtractInfo :%s' % self.url)
            return
        try:
            items = self.soup.findAll(attrs={'class':'item'})
        except:
            LOG('failed on find items:%s' % self.url)
            return
        links = []
        objs = [] 
        titles = []
        scores = []
        comments = []
        intros = []
        for item in items:
            try:
                pic = item.find(attrs={'class':'nbg'})
                link = pic['href']
                obj = pic.img['src']
                info = item.find(attrs={'class':'pl2'})
                title = re.sub('[ \t]+',' ',info.a.getText().replace(' ','').replace('\n',''))
                star = info.find(attrs={'class':'star clearfix'})
                score = star.find(attrs={'class':'rating_nums'}).getText().replace(' ','')
                comment = star.find(attrs={'class':'pl'}).getText().replace(' ','')
                intro = info.find(attrs={'class':'pl'}).getText().replace(' ','')
            except Exception,e:
                LOG('process error in ExtractInfo: %s' % self.url)
                continue
            links.append(link)
            objs.append(obj)
            titles.append(title)    
            scores.append(score)
            comments.append(comment)
            intros.append(intro)
        return(links, objs, titles, scores, comments, intros)

    def ExtractPageTurning(self,buf):
        links = set([])
        if not self.soup:
            try:
                self.soup = BeautifulSoup(buf)
            except:
                LOG('soup failed in ExtractPageTurning:%s' % self.url)
                return
        try:
            pageturning = self.soup.find(attrs={'class':'paginator'})
            a_nodes = pageturning.findAll('a')
            for a_node in a_nodes:
                href = a_node['href']
                if href.find('http://',0,7) == -1:
                    href = self.url.split('?')[0] + href
                links.add(href)
        except:
            LOG('get pageturning failed in ExtractPageTurning:%s' % self.url)

        return links

    def Destroy(self):
        del self.soup
        self.soup = None

接著我們再來寫個測試樣例

## filename: test.py
#encoding: utf-8
from Grab import Grab
import sys
reload(sys)
sys.setdefaultencoding('utf-8')

grab = Grab()
buf = grab.GetPage('http://movie.douban.com/tag/喜劇?start=160&type=T')
if not buf:
        print 'GetPage failed!'
        sys.exit()
links, objs, titles, scores, comments, intros = grab.ExtractInfo(buf)
for link, obj, title, score, comment, intro in zip(links, objs, titles, scores, comments, intros):
        print link+'\t'+obj+'\t'+title+'\t'+score+'\t'+comment+'\t'+intro
pageturning = grab.ExtractPageTurning(buf)
for link in pageturning:
        print link
grab.Destroy()

OK,完成這一步接下來的事兒就自個看著辦吧
本文只是介紹了BeautifulSoup的皮毛而已,目的是為了讓大家快速學會一些基本要領,想當初我要用什麼功能都是去BeautifulSoup的原始碼裡一個函式一個函式看然後才會的,一把辛酸淚啊,所以希望後來者能夠通過更便捷的方式去掌握一些基本功能,也不枉我一字一句敲出這篇文章,尤其是這些程式碼的排版,真是傷透了腦筋

本文為作者原創,轉載請註明出處,多謝!