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

http://www.cnblogs.com/binye-typing/p/6656595.html

  讀者可能會奇怪我標題怎么理成這個鬼樣子,主要是單單寫 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 的實現):
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

xpath?的類選擇器在做公共類名選擇時有短板,也勉強把它算作功能缺陷吧,比如:     
1  model = etree.HTML(response.body_as_unicode())
2  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 就略顯死板。

?

文本獲取

  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

?

?

其他方面比較

從參考資料上來講,bs4?有詳細中/英文版官方幫助文檔,lxml?好像?document?相對少。另外?BeautifulSoup 的結點對象在?Debugger?下面對于變量內容的監視更友好,它直接顯示匹配的?html?字符串,而?lxml?就是一個類似這種表示的對象:?<Element?html?at?0x####>,不是很友好,但這都不重要,筆者還是更喜歡 xpath 的高效,簡潔,一步到位。
  
?

轉載于:https://www.cnblogs.com/mapu/p/8337407.html

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/452908.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/452908.shtml
英文地址,請注明出處:http://en.pswp.cn/news/452908.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

java如何去掉html標簽_Java后端去掉HTML標簽獲取純文本-Fun言

今天又對我的博客首頁進行了一次版本的更新&#xff0c;使其自適應屏幕&#xff0c;獲得更好的用戶體驗&#xff0c;然后就出現點小問題&#xff0c;那就是原來的摘要是人為添加的&#xff0c;有長有短&#xff0c;對自適應屏幕有影響&#xff0c;所以我們現在是截取文章的前20…

單/雙中括號與測試條件

測試命令 tesst[]內置命令[[]]bash中的關鍵字 單中括號 格式[#express1#op#express2#] 注意&#xff1a;   其中#代表括號不能省略   不能匹配模式   變量引用應用雙引號括起&#xff0c;尤其當變量引用有空格時   與或非形式-a –o -not   常量應用單/雙引號括起  …

暗時間--平凡與優秀間的距離

每個人都希望&#xff0c;在他所從事的領域很優秀&#xff0c;那么如何才能優秀呢&#xff1f;有人做過一個研究&#xff0c;說那些優秀的音樂家&#xff0c;在他們成名之前&#xff0c;已經訓練過10000小時。有人可能成功得早&#xff0c;如莫扎特16歲&#xff0c;有些可能需要…

IP分組

IP分組就是根據Ip地址來進行分組&#xff0c;目的可以是為了對不同 的地址組分配不同的帶寬&#xff08;限速&#xff09;配置地址組時&#xff0c;其輸入格式為A.B.C.D-A.B.C.E&#xff0c;例如&#xff1a;192.168.1.1-192.168.1.250

python3基礎3--數據類型--數據運算--表達式if -else-while-for

一、python3 數據類型 1.1 數字例如&#xff1a;1,2,3,4等1.2 int&#xff08;整型&#xff09; 在32位機器上&#xff0c;整數的位數為32位&#xff0c;取值范圍為-2**31&#xff5e;2**31-1&#xff0c;即-2147483648&#xff5e;2147483647在64位系統上&#xff0c;整數的位…

spark java教程_(Spark)學習進度十四(Spark之Java獨立應用編程)

環境如下:(更新了林子雨教程中不可使用的部分) Hadoop 2.6.0以上 java JDK 1.7以上 Spark 3.0.0-preview2 二、java獨立應用編程(在下載依賴jar包的過程中如遇到卡頓現象可以Ctrl+C停止下載,然后重新執行本條命令即可繼續下載相應的依賴jar包) 1、安裝maven ubuntu中沒有自帶…

nowcoderD Xieldy And His Password

題意:一個01序列,長度1e6,問有多少子串十進制是3的倍數 題解:DP[i][j]代表前i個并且以i為結尾,且十進制%3j的串的個數 #include<bits/stdc.h> #define maxn 1001000 using namespace std; char s[maxn]; long long dp[maxn][3], sum; int main(){while(~scanf("%s&q…

3D電視,你知道多少?

1.3D電視常見知識 系統概述篇 1、 什么是3D電視&#xff1f; 答&#xff1a;3D電視是一種能夠模擬實際景物的真實空間關系的新型電視&#xff0c;它利用人眼的視覺特性產生立體感&#xff0c;讓觀眾感受到觀看的影像是具有深度特性的三維立體場景&#xff0c;觀眾對延伸于屏幕…

testng.xml文件配置

TestNG的DTD檢查文件&#xff1a;http://testng.org/testng-1.0.dtd.php <?xml version"1.0" encoding"UTF-8"?> <!DOCTYPE suite SYSTEM "http://testng.org/testng-1.0.dtd"> <!--suite&#xff08;測試套件&#xff09;為根…

什么是分組轉發

分組轉發(forwarding)是指在互聯網絡中路由器轉發IP分組的物理傳輸過程與數據報轉發機制。根據分組的目的Ip地址與源Ip地址是否屬于同一個子網可分為直接轉發和間接轉發。 是直接轉發還是間接轉發&#xff0c;路由器需要根據分組的目的IP地址和源IP地址是否屬于同一網絡判斷。目…

java 棧 先進后出_數據結構: 先進后出——堆棧

棧是一種常用的數據結構&#xff0c;在生活中經常遇到這樣的例子&#xff0c;如鐵路調度站。本節將詳細介紹堆棧的實現過程。算法點撥(順序棧)棧是一種重要的數據結構。從數據結構的角度看&#xff0c;棧也是線性表&#xff0c;其特殊性在于棧的基本操作是線性表操作的子集&…

Spring Boot—07應用application.properties中的配置

方法1Value("${test.msg}") private String msg;方法2Autowired private Environment env; String value env.getProperty("test.msg");方法3RequestMapping(path"/${query.all}.json", methodRequestMethod.GET) ResponseBody public List&…

skip與direct模式區別 ,他們與CBP的關系

1 CBP表示殘差的編碼狀態,CBP一共6bit&#xff0c;低4位表示4個亮度8x8塊,第4位表示U,第五位表示V,如果相應的位為"1", 表示此塊有殘差系數,反之沒有殘差,此宏塊沒有被編碼.2 direct 是幀間宏塊的一種預測模式&#xff0c;而不是宏塊類型&#xff0c;而 S…

程序的裝入和鏈接過程

從用戶放入源程序進入操作系統到相應的裝程序在機器上運行&#xff0c;所經歷的主要階段有編譯階段 鏈接階段 裝入階段 和運行階段

[零基礎學JAVA]Java SE應用部分-34.Java常用API類庫

本季目標1、StringBuffer類 2、Runtime 類 3、包裝類與JDK 1.5的新特性——泛型 4、日期的操作類 5、Math類 6、Random類1、StringBuffer&#xff08;重點&#xff09; String 類的時候說過&#xff1a;String 類的內容一旦聲明則不可改變&#xff0c;改變的只是其地址。…

我所理解的機器學習

各位請移步到【http://www.cnblogs.com/cchHers/p/8945908.html】轉載于:https://www.cnblogs.com/cchHers/p/8933042.html

protobuf java文檔_Java中使用Protobuf

gradle依賴庫&#xff1a;implementation com.google.protobuf:protobuf-java:3.4.0implementation com.google.protobuf:protobuf-java-util:3.4.00.編寫.proto文件&#xff0c;編譯生成對應Java源文件&#xff1a;syntax "proto2";option java_generic_services …

python 數組和列表的區別

Python沒有數組&#xff1a; 只有元組(tuple)和列表(list)&#xff1b;元組一旦創建不可改變&#xff0c;例如&#xff1a;aatuple(1,2,3)&#xff1b;元組不能追加(append)元素&#xff0c;彈出(pop)元素等&#xff1b;只能對元組中的元素進行索引aa[0]&#xff0c;不能對其中…

內存空間 邏輯地址空間 相對地址 絕對地址

內存空間&#xff08;物理空間或絕對空間&#xff09;&#xff1a;由一系列存儲單元所限定 的地址范圍。 邏輯地址空間&#xff08;地址空間&#xff09;&#xff1a;由程序中邏輯地址組成的地址范圍。 相對地址&#xff08;邏輯地址&#xff09;&#xff1a;用戶程序經編譯后…

多租戶表設計

2019獨角獸企業重金招聘Python工程師標準>>> multi-tenant-databases-in-the-cloudtips-amp-tricks-to-build-multi-tenant-databases-with-sql-databases團隊開發框架實戰—多租戶支持轉載于:https://my.oschina.net/yangjiandong/blog/1612626