http://www.cnblogs.com/binye-typing/p/6656595.html
讀者可能會奇怪我標題怎么理成這個鬼樣子,主要是單單寫 lxml 與 bs4 這兩個 py 模塊名可能并不能一下引起大眾的注意,一般講到網頁解析技術,提到的關鍵詞更多的是 BeautifulSoup 和 xpath ,而它們各自所在的模塊(python 中是叫做模塊,但其他平臺下更多地是稱作庫),很少被拿到明面上來談論。下面我將從效率、復雜度等多個角度來對比 xpath 與 beautifulsoup 的區別。
效率
使用復雜度
1 def get_nav(self,response): 2 # soup = BeautifulSoup(response.body_as_unicode(), 'lxml') 3 # nav_list = soup.find('ul', id='nav').find_all('li') 4 model = etree.HTML(response.body_as_unicode()) 5 nav_list = model.xpath('//ul[@id="nav"]/li') 6 for nav in nav_list[1:]: 7 # href = nav.find('a').get('href') 8 href = nav.xpath('./a/@href')[0] 9 yield Request(href, callback=self.get_url)
?
可以看到 xpath 除了其特殊的語法看上去有些別扭(跟正則表達式似的)以外,它在代碼簡潔度上還是可觀的,只是所有 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 在瀏覽器打開進行審查):
1 class RankSpider(spider): 2 name = 'PCauto_rank' 3 index_page = 'http://price.pcauto.com.cn/top/hot/s1-t1.html' 4 api_url = 'http://price.pcauto.com.cn%s' 5 6 def start_requests(self): 7 yield Request(self.index_page, callback=self.get_left_nav) 8 9 # 測試 BeautifulSoup 是否能連續使用兩個 find_all 方法 10 def get_left_nav(self,response): 11 # model = etree.HTML(response.body_as_unicode()) 12 # nav_list = model.xpath('//div[@id="leftNav"]/ul[@class="pb200"]/li//a[@class="dd "]') 13 soup = BeautifulSoup(response.body_as_unicode(), 'lxml') 14 nav_list = soup.find('div', id='leftNav').find('ul', class_='pb200').find_all('li').find_all('a', class_='dd') 15 for sub_nav in nav_list: 16 href = self.api_url % sub_nav.xpath('./@href')[0] 17 yield Request(href, callback=self.get_url) 18 19 def get_url(self): 20 pass
?
?
使用注釋部分的 xpath 寫法沒什么問題,可實現準確定位,但用到 BeautifulSoup 去實現相應邏輯的時候,就要連續使用兩個 find_all 方法 ,顯然這種寫法不符合規范,運行的時候會報?AttributeError: 'ResultSet' object has no attribute 'find_all' 錯誤,這時候我們要實現這種匹配,只能先去遍歷各個 li ,然后調 find_all 方法找到 li 下的各個 a 標簽,實在繁瑣,所以這種場景用 xpath 來解決會省下不少麻煩。
當然這里我只是單單為了詮釋這么個問題才在故意在拿目標 url 時分這么多級的,實際開發中我這里用的是:
1 # nav_list = model.xpath('//div[@id="leftNav"]///a[@class="dd "]') 2 nav_list = soup.find('div', id='leftNav').find_all('a', class_='dd')
?
但如果說我們的目標不是所有的 li 下面的 a 標簽,而是部分 class="*" 的 li 下面的 a 標簽,這時候我們就只能選擇使用 xpath 來達到目的,當然如果你喜歡寫遍歷,覺得這樣寫出來邏輯展示更清晰,那你可以跳過這一節。
?
功能缺陷總結——xpath
1 model = etree.HTML(response.body_as_unicode()) 2 model.xpath('//div[@class="box box-2 box-4"]')
model.xpath('//div[@class="box box-2 box-4 mt25"]') model.xpath('//div[@class="box box-2 box-4 mt17"]')
?
來匹配目標,這可能要歸結于 xpath 在設計的時候本身就是以類名的完全匹配來確定目標的,哪怕多一個空格:
model.xpath('//a[@class="dd"]')
?
文本獲取
xpath 目標結點的 text 屬性值對應的只是當前匹配元素下面的文本信息,如要獲取該結點下面包括子結點在內的所有文本內容要使用 .xpath('string()') 的方式:
1 model = etree.HTML(response.body_as_unicode()) 2 place = model.xpath('//div[@class="guide"]') 3 # nav and aiticle 4 if place: 5 mark = place[0].xpath('./span[@class="mark"]') 6 if mark: 7 # text = mark[0].text.strip().replace('\n','').replace('\r','') # false 8 text = mark[0].xpath('string()') 9 result['address'] = text
?
?