關於爬蟲中常見的兩個網頁解析工具的分析 —— lxml / xpath 與 bs4 / BeautifulSoup

來源:互聯網
上載者:User

標籤:res   比較   object   dom   功能   over   error   callback   成功   

  讀者可能會奇怪我標題怎麼理成這個鬼樣子,主要是單單寫 lxml 與 bs4 這兩個 py 模組名可能並不能一下引起福士的注意,一般講到網頁解析技術,提到的關鍵詞更多的是 BeautifulSoup 和 xpath ,而它們各自所在的模組(python 中是叫做模組,但其他平台下更多地是稱作庫),很少被拿到明面上來談論。下面我將從效率、複雜度等多個角度來對比 xpath 與 beautifulsoup 的區別。

效率  從效率上來講,xpath 確實比 BeautifulSoup 高效得多,每次分步調試時,soup 對象的產生有很明顯的延遲,而 lxml.etree.HTML(html) 方式則在 step over 的一瞬間便構建成功了一個可執行 xpath 操作的對象,速度驚人。原理上來講,bs4 是用 python 寫的,lxml 是 c 語言實現的,而且 BeautifulSoup 是基於 DOM 的,會載入整個文檔,解析整個DOM樹,因此時間和記憶體開銷都會大很多。而lxml只會進行局部遍曆。 使用複雜度  從使用複雜度來講,beautifulsoup 的 find 方法要比 xpath 簡單,後者不僅要求通曉 xpath 文法,而且 xpath 方法的返回對象始終是一個 list,這使得對於頁面中一些唯一元素的處理有些尷尬,比如根據 id 擷取頁面某一標籤,下面我用兩種方式實現一個擷取網頁導覽列的方法 (注釋部分為 bs4 的實現):
    def get_nav(self,response):        # soup = BeautifulSoup(response.body_as_unicode(), ‘lxml‘)        # nav_list = soup.find(‘ul‘, id=‘nav‘).find_all(‘li‘)        model = etree.HTML(response.body_as_unicode())        nav_list = model.xpath(‘//ul[@id="nav"]/li‘)        for nav in nav_list[1:]:            # href = nav.find(‘a‘).get(‘href‘)            href = nav.xpath(‘./a/@href‘)[0]yield Request(href, callback=self.get_url)

  可以看到 xpath 除了其特殊的文法看上去有些彆扭(跟Regex似的)以外,它在代碼簡潔度上還是可觀的,只是所有 xpath 方法的返回結果都是一個 list ,如果匹配目標是單個元素,對於無腦下標取0的操作,強迫症患者可能有些難受。相比之下,BeautifulSoup 這一長串的 find 與 find_all 方法顯得有些呆板,如果碰到搜尋路線比較曲折的,比如:

# href = article.find(‘div‘, class_=‘txt‘).find(‘p‘, class_=‘tit blue‘).find(‘span‘).find(‘em‘).find(‘a‘).get(‘href‘)href = article.xpath(‘./div[@class="txt"]//p[@class="tit blue"]/span/em/a/@href‘)[0]

  這種情況下,BeautifulSoup 的寫法就顯得有些讓人反胃了,當然一般情況下不會出現這麼長的路徑定位。

 功能缺陷總結——BeautifulSoup

   BeautifulSoup 在使用上的一個短板,就是在嵌套列表中去匹配元素的時候會顯得很無力,下面是一個例子(具體網頁結構可根據 index_page 在瀏覽器開啟進行審查):

class RankSpider(spider):    name = ‘PCauto_rank‘    index_page = ‘http://price.pcauto.com.cn/top/hot/s1-t1.html‘    api_url = ‘http://price.pcauto.com.cn%s‘    def start_requests(self):        yield Request(self.index_page, callback=self.get_left_nav)    # 測試 BeautifulSoup 是否能連續使用兩個 find_all 方法    def get_left_nav(self,response):        # model = etree.HTML(response.body_as_unicode())        # nav_list = model.xpath(‘//div[@id="leftNav"]/ul[@class="pb200"]/li//a[@class="dd "]‘)        soup = BeautifulSoup(response.body_as_unicode(), ‘lxml‘)        nav_list = soup.find(‘div‘, id=‘leftNav‘).find(‘ul‘, class_=‘pb200‘).find_all(‘li‘).find_all(‘a‘, class_=‘dd‘)        for sub_nav in nav_list:            href = self.api_url % sub_nav.xpath(‘./@href‘)[0]            yield Request(href, callback=self.get_url)    def get_url(self):        pass

   使用注釋部分的 xpath 寫法沒什麼問題,可實現準確定位,但用到 BeautifulSoup 去實現相應邏輯的時候,就要連續使用兩個 find_all 方法 ,顯然這種寫法不符合規範,啟動並執行時候會報 AttributeError: ‘ResultSet‘ object has no attribute ‘find_all‘ 錯誤,這時候我們要實現這種匹配,只能先去遍曆各個 li ,然後調 find_all 方法找到 li 下的各個 a 標籤,實在繁瑣,所以這種情境用 xpath 來解決會省下不少麻煩。

  當然這裡我只是單單為了詮釋這麼個問題才在故意在拿目標 url 時分這麼多級的,實際開發中我這裡用的是:

        # nav_list = model.xpath(‘//div[@id="leftNav"]///a[@class="dd "]‘)        nav_list = soup.find(‘div‘, id=‘leftNav‘).find_all(‘a‘, class_=‘dd‘)

  但如果說我們的目標不是所有的 li 下面的 a 標籤,而是部分 class="*" 的 li 下面的 a 標籤,這時候我們就只能選擇使用 xpath 來達到目的,當然如果你喜歡寫遍曆,覺得這樣寫出來邏輯展示更清晰,那你可以跳過這一節。

 

功能缺陷總結——xpath  xpath 的類別選取器在做公用類名選擇時有短板,也勉強把它算作功能缺陷吧,比如:     
 model = etree.HTML(response.body_as_unicode())
model.xpath(‘//div[@class="box box-2 box-4"]‘)
  無法定位 html 中 class 為 box box-2 box-4 mt25 與 box box-2 box-4 mt17 的兩個 div,必須分別以: 
model.xpath(‘//div[@class="box box-2 box-4 mt25"]‘) model.xpath(‘//div[@class="box box-2 box-4 mt17"]‘)

  來匹配目標,這可能要歸結於 xpath 在設計的時候本身就是以類名的完全符合來確定目標的,哪怕多一個空格:

  頁面中一個 a 標籤是這樣寫的:  <a href="/top/hot/s1-t1.html" class="dd ">5萬以下</a> 用 xpath 去選擇,寫作:

      model.xpath(‘//a[@class="dd"]‘)

  死活匹配不到(當時真的是蠻懵逼的),必須要在後面加空格,但在通過 js 控制台 a.dd 這個類別選取器又可以定位到目標,而且 BeautifulSoup 調 find_all(‘a‘, class_=‘dd‘) 也是沒有問題的,這種應用情境下的 xpath 就略顯死板。

 

文本擷取

  

        # place = soup.find(‘div‘,class_="guide")        place = model.xpath(‘//div[@class="guide"]‘)        # nav and aiticle        if place:            # mark = place.find(‘span‘,class_="mark")            mark = place[0].xpath(‘./span[@class="mark"]‘)            if mark:                # text = mark.get_text().strip().replace(‘\n‘,‘‘).replace(‘\r‘,‘‘)                # text = mark[0].text.strip().replace(‘\n‘,‘‘).replace(‘\r‘,‘‘)  # false                text = mark[0].xpath(‘string()‘)                result[‘address‘] = text

 

 

 

關於爬蟲中常見的兩個網頁解析工具的分析 —— lxml / xpath 與 bs4 / BeautifulSoup

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.