XPath與正則表達式在文本數據提取時該如何選擇?

  從互聯網上下載到網頁,只是咱們邁向成功的第一步。拿到網頁數據之後,咱們須要從中提取咱們想要的具體信息,html

好比標題、內容、時間、做者等。最多見的的提取方式有兩種:XPath和正則表達式。node

  先簡單介紹一下XPATH和正則表達式。python

  XPath即爲 XML 路徑語言(XML Path Language),它是一種用來肯定XML文檔中某部分位置的語言。 XPath基於正則表達式

XML的樹狀結構,提供在數據結構樹中找尋節點的能力(見維基百科 XPath)。數據結構

  正則表達式英語:Regular Expression,在代碼中常簡寫爲regex、regexp或RE),計算機科學的一個概念。正post

則表達式使用單個字符串來描述、匹配一系列匹配某個句法規則的字符串。優化

 

  發表一下我的看法:網站

  XPath主要是用來處理 XML 格式文檔的,它是基於 XML 文檔的層次結構來肯定一個到達指定節點的路徑的,所以特別url

適合處理這種層級結構明顯的數據(起初 XPath 的提出的初衷是將其做爲一個通用的、介於 XPointer 與 XSLT 間的語法模型。spa

可是 XPath 很快的被開發者採用來看成小型查詢語言)。

  正則表達式能夠處理任何格式的字符串文檔,它是基於文本的特徵來匹配、查找指定數據的。

 

  在網上看到一個很形象的比喻:若是提取信息就像找一個建築,那麼正則表達式就是告訴你,這個建築的左邊是什麼、右

邊是什麼、以及這個建築自己有哪些特徵,但這樣的描述在全國範圍內可能有不少地方的建築都符合條件,找起來仍是不太方

便,除非你限制範圍,好比指定北京海淀區等。而 XPath 就是告訴你這個建築在中國-北京-海淀區-中關村-中關村大街-1

號,這樣找起來就方便了不少,固然這不是說XPath就比正則表達式要好用,具體選擇還得看應用場景,好比讓你在一個廣場

上等人,告訴對方你在哪裏的時候,你總不會說我在廣場上從東數第23塊磚從北數第16塊磚上站着吧,你極可能會說我在一個

雕像旁邊或噴泉旁邊等。下面主要說一下在爬蟲中通常如何選擇使用XPath和正則表達式。

 

  在寫爬蟲的時候,通常會遇到 HTML、JSON、XML、純文本等格式的文檔,先來講一下 HTML 和 XML,HTML 是 XML

的一個子集,所以它們的處理方式是同樣的,首選使用 XPath 來獲取信息,以博客園首頁爲例,若是咱們須要的數據是文章

的 url 列表,最好使用 XPath,見圖

 

  文章 url 列表自己在視覺上是一個結構化特別明顯的數據,並且經過分析 HTML DOM 樹,咱們發現須要採集的a標籤的

層次規律特別整齊,很容易就能使用XPath語法來標示出a標籤的路徑,python代碼示例

#coding:utf8
from lxml import etree
import requests

url = 'http://www.cnblogs.com/'

response = requests.get(url)
response.encoding = 'utf8'
html = response.text

root = etree.HTML(html)
node_list = root.xpath("//div[@class='post_item_body']/h3/a")

for node in node_list:
    print node.attrib['href']


# 輸出
'''
http://www.cnblogs.com/olivers/p/6073506.html
http://www.cnblogs.com/-free/p/6073496.html
...
'''

   若是使用正則表達式的話,也能夠達成一樣的效果,不過,要轉換一下思路。因爲正則表達式是使用特徵匹配的,所以

最基本的思路有兩個,1、是基於url自己的特徵;2、基於url所在位置的特徵。(其實看到這裏,你就應該對該使用正則表

達式仍是XPath有所體會了,使用XPath想都不用想,正則表達式還得想一下子)下面咱們來分析一下,第一個思路基本行不

通,因爲網頁上並非只有咱們想要的這些主要的文章url,還有一些推薦的文章url。見圖

 

  而這些url自己是沒有什麼明顯的特徵區別的,區別只在於位置和人爲賦予的這個位置的意義,所以基於url自己特徵來使

用正則表達式是不可行的。第二個思路基於位置特徵,見圖

 

  經過對比推薦和非推薦的url的a標籤,能夠找出一個特徵就是,非推薦的url的a標籤的class屬性都是 "titlelnk",所以咱們

能夠基於此構造正則表達式,python示例代碼

#coding:utf8
import re
import requests
      
url = 'http://www.cnblogs.com/'

response = requests.get(url)
response.encoding = 'utf8'
html = response.text

urls = re.findall('class=\"titlelnk\"\s*[^>]*?href=\"([^\"]+)', html)

for url in urls:
    print url
         
# 輸出
''' 
http://www.cnblogs.com/olivers/p/6073506.html
http://www.cnblogs.com/-free/p/6073496.html
...
'''

 

  固然使用正則表達式還有其餘不少的形式,使用XPath也有不少其餘的形式,但經過這個例子,咱們能夠體會到在採集文

章列表時,使用XPath是比較方便的。而後讓咱們改一下需求,如今咱們須要採集文章的發佈時間,見圖

 

  這個很明顯咱們僅使用普通的XPath是沒法取到時間的,會帶上不少額外的字符,但太複雜的XPath寫起來挺麻煩的,因

此咱們能夠經過先取到帶額外字符的時間,再對數據作清洗來獲得時間。而後再看一下使用正則表達式,因爲時間的格式很固

定,而且網頁中不存在會干擾到咱們的時間,所以正則寫起來就比較簡單了,並且還不用過濾。python示例代碼

#coding:utf8
import re
from lxml import etree 
import requests

url = 'http://www.cnblogs.com/'

response = requests.get(url)
response.encoding = 'utf8'
html = response.text

# XPath 用法
root = etree.HTML(html)
time_node_list = root.xpath("//div[@class='post_item_foot']/text()")
# 這個XPath匹配到的數據也是不對的,存在不少空白節點
# 咱們來處理一下
# 刪除空白節點 並去除空格
time_node_list = [node.strip() for node in time_node_list if node.strip()]
# 提取時間字符串
time_list = [' '.join(node.split()[1:]) for node in time_node_list]

for time_str in time_list:
    print time_str
               
               
# 輸出
'''
2016-11-17 15:23
2016-11-17 15:10
2016-11-17 14:34
2016-11-17 11:58
...
'''

# 正則用法
time_list = re.findall('(\d+-\d+-\d+\s*\d+:\d+)', html)
# 這樣寫是不對的,由於在源碼中還有不少時間格式存在於標籤中,所以對正則優化以下
time_list = re.findall(u'發佈於\s*(\d+-\d+-\d+\s*\d+:\d+)', html)

for time_str in time_list:
    print time_str

# 輸出
'''
2016-11-17 15:23
2016-11-17 15:10
2016-11-17 14:34
2016-11-17 11:58
...
'''

 

  在這個例子中咱們其實就能夠體會到XPath的一些侷限性了,下面咱們再來看一個XPath基本作不到的例子。

  需求改成獲取頁面中全部的英文單詞。首先分析一下,這個需求能夠說和頁面的結構徹底不要緊,XPath這種基於頁面層

次結構的語法能夠說徹底沒用,但使用正則的話卻能夠輕易達到目的。python示例代碼

#coding:utf8
import re
from lxml import etree 
import requests

url = 'http://www.cnblogs.com/'

response = requests.get(url)
response.encoding = 'utf8'
html = response.text

# 首先替換掉源碼中的各類標籤
html = re.sub('<[^>]+/?>', '', html)

# 匹配英文單詞
words = re.findall("[a-z]+", html, re.I)

print len(words)
print words[:10]

# 輸出
'''
303
[u'DDD', u'Connect', u'Connect', u'Mac', u'Visual', u'Studio', u'MSSQL', u'Server', u'on', u'Linux']
'''

 

  在這三個例子中,第一個和第二個都會涉及到XPath和正則表達式的選擇問題,第三個也會涉及到選擇,但幾乎立馬就放

棄了XPath,下面咱們來總結一下 XPath 和正則表達式到底該如何選用。

  第一個例子中,目標數據所在的位置能夠很方便的用 XPath 語法標示,而且不會產生誤匹配。而使用正則表達式的時候

卻可能會發生誤匹配,須要收集更多的特徵來保證匹配的準確性,這無疑就增長了複雜度,所以推薦使用XPath。第二個例子

中,目標數據所在位置使用XPath來不能徹底匹配,存在多餘數據,須要進一步的處理。但使用正則表達式的時候,卻能夠一

步到位,節省了工做量,所以推薦使用正則表達式。

  也就是說,首先要分清目標數據是層次結構明顯仍是特徵明顯,這個分清楚了,該選什麼也就基本肯定了,後續要思考的

其實算是優化的步驟。

  簡單來講,在XPath和正則表達式均可以使用的狀況下,選擇的標準有這麼幾個(按優先級排序):

    1. 匹配準確度
    2. 語法複雜度(思考花費時間長短)
    3. 後續處理複雜度
    4. 可維護性
    5. 可讀性

  標準只是死的,它不會告訴你該如何去應用,其實通常狀況下我是這麼作的:

      1. 首先明白目的是什麼(拿到準確的目標數據),而後想一下使用XPath的話,加上後續處理須要寫幾行代碼?大概

    估計一下,再估計一下使用正則須要寫幾行代碼,這時候你差很少就有個判斷了

      2.而後再想一下萬一目標網站改了一下格式,我須要維護的話,怎麼改起來方便?(這個推薦使用XPath,可讀性和

    可維護行都是比較好的,正則表達式的可讀性並不怎麼好)

  其實在真正的工做中,一個網頁通常須要提取不少個字段,所以XPath和正則表達式混用是很常常的,這個選擇只是針對

匹配具體字段而言的。

  若是想讓代碼達到最優,須要考慮的東西還有不少,好比XPath和正則表達式的執行效率,通常狀況下,正則表達式的效

率是比較高的,這個前提是不太複雜的正則表達式,有些特別複雜的正則表達式可能嚴重減慢執行效率。

 

 

 

參考文章:

《用 python 寫的爬蟲,有哪些提升的技能?》 https://www.zhihu.com/question/36832667

【Python爬蟲】入門知識 http://www.jianshu.com/p/74b94eadae15

《xpath與正則表達式抽取網頁信息的速度比較》http://pcliuyang.blog.51cto.com/8343567/1341117

相關文章
相關標籤/搜索