左手用R右手Python系列17——CSS表達式與網頁解析

上一篇著重講解了網頁解析中的XPath表達式,今天這一篇主要講解另一套網頁解析語法——CSS路徑表達式。

R語言與Python中都有支持CSS表達式的解析庫,R語言中以rvest包為主進行講解,Python中為BeautifulSoup為主進行講解。

本篇講解內容實戰網頁時我的天善社區博客主頁,網址如下:

https://ask.hellobi.com/blog/datamofang/sitemap/

R語言:

R語言中,rvest中的默認解析語法即為css路徑表達式,當然rvest也是支持XPath,只是XPath並非首選語法,而是備選語法,怎麼知道呢,列印一下rvest的html_nodes函數參數內容即可得知。

library("rvest")url<-"https://ask.hellobi.com/blog/datamofang/sitemap/"content<-read_html(url,encoding="UTF-8")

1、特殊符號:

  • 「.」表示節點的class(class屬性值內含有空格,以.替代)
  • 「#」表示節點的id
  • 「 」空格也表示所有後代元素,相當於xpath中的相對路徑(//)
  • 「>」表示子元素,相當於XPath中的絕對路徑(/)
  • 「*」匹配所有元素
  • 「,」或條件,用於分割不同的路徑,相當於「或」,與XPath中的「|」異曲同工
  • 「+」右側相鄰元素
  • 「~」兄弟節點

以上是CSS表達式中幾個最為常用的特殊符號,這些特殊符號在路徑定位中都有著特殊意義,接下來一個一個進行解釋。

「.」/「#」(class屬性與id屬性)

「.」和「#」分別代表標籤內class屬性和id屬性的連接符。

<div stylex="margin-top: 25px;" id="raindu" class="btn-group btn-group-justified blog-menu" role="group"> <a href="/blog/datamofang" class="btn btn-default">我的首頁</a> <a href="/blog/datamofang/sitemap/" class="btn btn-default">博客地圖</a></div>myhtml<-"<div stylex="margin-top: 25px;" id="raindu" class="btn-group btn-group-justified blog-menu" role="group"><a href="/blog/datamofang" class="btn btn-default">我的首頁</a><a href="/blog/datamofang/sitemap/" class="btn btn-default">博客地圖</a></div>"read_html(myhtml,encoding="UTF-8")%>% html_nodes("div.btn-group a") %>% html_text()#[1] "我的首頁" "博客地圖"read_html(myhtml,encoding="UTF-8")%>% html_nodes("div#raindu a") %>% html_text()#[1] "我的首頁" "博客地圖"

可以看到以上兩句表達式都可以完美匹配出來div標籤節點內部a節點內的文本,這裡的定位主要是靠『.』和』#』兩個連接符實現的,這是相對比較規範的寫法。

以上表達式寫法中還有一個細節性的小知識點,就是class屬性值倘若特別長,可以截取其前幾個字元(可以作為唯一辨識就可以),倘若內部有空格,空格可以以「.」號替代,否則可能引起表達式匹配錯誤。

「>」和「 」(右尖括弧和空格)

右尖括弧和空格在css表達式中起著重要作用,相信看過前一篇文章的一定記得我在解釋XPath路徑表達式的時候講過絕對路徑和相對路徑,其詳細內含這裡就不解釋了,如果你感興趣可以查看前文,這裡的「>」和」「 」就扮演了css表達式中絕對路徑和相對路徑的角色。

左手用R右手Python系列16——XPath與網頁解析庫

<li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/9485"> <b>balabalabala</b> 離散顏色標度連續化的最佳方案 </a> <span stylex="margin-left: 21px;"> 56次閱讀/0條評論</span> <span stylex="margin-left: 5px;"> (2017-08-22)</span> <span stylex="margin-left: 21px;"> </span></li>xmlmyhtml<-"<li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/9485"> <b>balabalabala</b> 離散顏色標度連續化的最佳方案 </a> <span stylex="margin-left: 21px;"> 56次閱讀/0條評論</span> <span stylex="margin-left: 5px;"> (2017-08-22)</span> <span stylex="margin-left: 21px;"> </span></li>"

絕對路徑:

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li>a>b") %>% html_text()[1] "balabalabala"

相對路徑:

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a b") %>% html_text()[1] "balabalabala"read_html(myhtml,encoding="UTF-8")%>% html_nodes("li b") %>% html_text()[1] "balabalabala"

從以上三個輸出可以很明確的發現,所有的輸出結果都是一樣的,第一句函數執行的功能是在文檔中查找li節點內的子節點a節點內的子節點b,並輸出其文本內容;第二句函數執行的功能是查找文檔中li節點內的所有節點為a(相對路徑)的節點內所有節點為b的節點(相對路徑),並輸出其文本。第三句函數執行功能為在文檔中查找所有li節點內所有節點為b的節點並輸出其內容。因為myhtml文檔中只有一個b節點,所有三者輸出的內容是一樣的。

「>」和「 」(右尖括弧和空格)的區別非常明顯,也非常重要,請慎用「>」(絕對路徑),只有在有100%把握的時候再用,一般來說使用「 」(空格:相對路徑)的css表達式比較穩健,但是在同一個文檔中同名節點較多的情況下,因為相對路徑需要遍歷的路徑較多,耗時長,可能匹配出沒有價值的內容,所以在實際使用時還是要隨機應變。

「*」和「,」星號和單引號:

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li *[style]") %>% html_text()[1] "
balabalabala
離散顏色標度連續化的最佳方案
" " 56次閱讀/0條評論" [3] " (2017-08-22)" " " read_html(myhtml,encoding="UTF-8")%>% html_nodes(" *[style]") %>% html_text()[1] "

balabalabala
離散顏色標度連續化的最佳方案

56次閱讀/0條評論
(2017-08-22)

"[2] "
balabalabala
離散顏色標度連續化的最佳方案
" [3] " 56次閱讀/0條評論" [4] " (2017-08-22)" [5] " "

以上第一句執行的功能是在li節點中查詢所有包含style屬性的節點,並輸出對應節點文本內容,第二個匹配的範圍更大一些,匹配了文檔中所有包含style屬性的節點並輸出對應節點文本內容。可以看到li這個頂層節點內的所有文本被拼接在一起作為li的文本對象被輸出了。

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a[target]") %>% html_text()[1] "
balabalabala
離散顏色標度連續化的最佳方案
"read_html(myhtml,encoding="UTF-8")%>% html_nodes("li span") %>% html_text()[1] " 56次閱讀/0條評論" " (2017-08-22)" " " read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a[target],li span") %>% html_text()[1] "
balabalabala
離散顏色標度連續化的最佳方案
" " 56次閱讀/0條評論" [3] " (2017-08-22)" " "

「,」這裡的逗號相當於XPath中的「|」號,功能是分割條件表達式,即將逗號兩邊的內容作為單獨的表達式,輸出符合所有表達式匹配模式的內容。

「+」「~」右側相鄰元素,兄弟節點

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a[target]+span") %>% html_text()[1] " 56次閱讀/0條評論"read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a[target]~span") %>% html_text()[1] " 56次閱讀/0條評論" " (2017-08-22)" " "

以上兩句函數功能類似,但是有細微區別,第一句「+」輸出現有節點的右側相鄰節點,而「~」則是輸出現有節點的所有兄弟節點(同輩節點)。

2、謂語表達:

通常我們提取內容要按照標籤內屬性名稱或者屬性值進行條件限定來提取,這時候我們需要在表達式中對標籤節點進行條件限定。

<ul class="blog-sitemap"> <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/9027">精美炫酷數據分析地圖——簡單幾步輕鬆學會</a> <span stylex="margin-left: 21px;"> 235次閱讀/0條評論</span> <span stylex="margin-left: 5px;"> (2017-07-27)</span> <span stylex="margin-left: 21px;"> </span> </li> <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/9000">不用編程,教你輕鬆搞定數據地圖</a> <span stylex="margin-left: 21px;"> 178次閱讀/0條評論</span> <span stylex="margin-left: 5px;"> (2017-07-26)</span> <span stylex="margin-left: 21px;"> </span> </li> <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/8996">Excel 有哪些可能需要熟練掌握而很多人不會的技能?</a> <span stylex="margin-left: 21px;"> 142次閱讀/0條評論</span> <span stylex="margin-left: 5px;"> (2017-07-26)</span> <span stylex="margin-left: 21px;"> </span> </li> <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/8763">一般人不知道的幾個excel製圖技巧 </a> <span stylex="margin-left: 21px;"> 199次閱讀 / 0條評論</span> <span stylex="margin-left: 5px;"> (2017-07-05)</span> <span stylex="margin-left: 21px;"> </span> </li> <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/8654">那些培訓師都不曾告訴你的關於Excel圖表的秘密~</a> <span stylex="margin-left: 21px;"> 424次閱讀/0條評論</span> <span stylex="margin-left: 5px;"> (2017-06-20)</span> <span stylex="margin-left: 21px;"> </span> </li> <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/8595">Excel依然是一款強大的數據可視化利器~</a> <span stylex="margin-left: 21px;"> 671次閱讀/3條評論</span> <span stylex="margin-left: 5px;"> (2017-06-15)</span> <span stylex="margin-left: 21px;"> </span> </li></ul>read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a[target]") %>% html_text()[1] "
balabalabala
離散顏色標度連續化的最佳方案
"

以上語句限制了我們查找的對象是li內所有含target屬性的節點(這裡僅有一個)

2、元素限定:

p[attr] #包含attr屬性p[attr="value"] #attr【屬性名】為「value」【屬性值】的元素p[href^="https"] #選擇所有href屬性值以https開頭的a元素p[href$=".pdf"] #選擇所有href屬性值以.pdf結尾的a元素p[href*="w3schools"] #選擇所有href屬性值包含w3schools的a元素p[title~="flower"] #title的屬性值包含單詞「flower」的節點(用於屬性值為句子的場景)css=button.attr:contains("OK") #:contains是個Pseudo-class,用冒號開頭,括弧里是內容。

元素限定可能是我們在css表達式中運用到頻率僅次於特殊符號的功能元素了,因為通常解析的目標網頁體系和內容都非常龐大,如果不加以限定的話,肯定會輸出很多對我們沒有任何價值的信息。

以上文中幾篇評論文章為例,我們來講解以上所有元素限定的用法:

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li[style]") %>% html_text()[1] "
精美炫酷數據分析地圖——簡單幾步輕鬆學會
235次閱讀/0條評論
(2017-07-27)

" [2] "
不用編程,教你輕鬆搞定數據地圖
178次閱讀/0條評論
(2017-07-26)

" [3] "
Excel 有哪些可能需要熟練掌握而很多人不會的技能?
142次閱讀/0條評論
(2017-07-26)

"[4] "
一般人不知道的幾個excel製圖技巧
199次閱讀 / 0條評論
(2017-07-05)

" [5] "
那些培訓師都不曾告訴你的關於Excel圖表的秘密~
424次閱讀/0條評論
(2017-06-20)

" [6] "
Excel依然是一款強大的數據可視化利器~
671次閱讀/3條評論
(2017-06-15)

"

這裡限定了li標籤的屬性為style,所有上述語句輸出了ul內部所有li標籤中含有style屬性的對應節點並輸出文本內容。

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a[target="_blank"]") %>% html_text()[1] "精美炫酷數據分析地圖——簡單幾步輕鬆學會" "不用編程,教你輕鬆搞定數據地圖" [3] "Excel 有哪些可能需要熟練掌握而很多人不會的技能?" "一般人不知道的幾個excel製圖技巧 " [5] "那些培訓師都不曾告訴你的關於Excel圖表的秘密~" "Excel依然是一款強大的數據可視化利器~"

這裡我限定了li內所有屬性值為』_blank』的a節點並輸出其文本內容,輸出了所有博客文章名稱。

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a[href^="/blog"]") %>% html_text()[1] "精美炫酷數據分析地圖——簡單幾步輕鬆學會" "不用編程,教你輕鬆搞定數據地圖" [3] "Excel 有哪些可能需要熟練掌握而很多人不會的技能?" "一般人不知道的幾個excel製圖技巧 " [5] "那些培訓師都不曾告訴你的關於Excel圖表的秘密~" "Excel依然是一款強大的數據可視化利器~"

本次限定了li節點內所有含有href屬性值以「/blog」開頭的a節點並輸出這些節點的文本。(因為所有a節點的href屬性值都是以/blog開頭的,所有輸出了所有文章名稱)。

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a[href$="54"]") %>% html_text()[1] "那些培訓師都不曾告訴你的關於Excel圖表的秘密~"

與上面那句類似,這裡限定的是href屬性值以54結尾的a節點,並輸出其文本內容,僅有一個符合條件。

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a[href*="datamofang"]") %>% html_text()[1] "精美炫酷數據分析地圖——簡單幾步輕鬆學會" "不用編程,教你輕鬆搞定數據地圖" [3] "Excel 有哪些可能需要熟練掌握而很多人不會的技能?" "一般人不知道的幾個excel製圖技巧 " [5] "那些培訓師都不曾告訴你的關於Excel圖表的秘密~" "Excel依然是一款強大的數據可視化利器~"

這裡的「*」代表包含關係,即限定了href屬性值內容包含字元串「datamofang」的所有節點a並輸出其文本對象。

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a[href~="datamofang"]") %>% html_text()character(0)

以上代碼中的「~」也是代表包含關係,但是這裡的包含關係與上一條的包含關係有所不同,這裡的「~」專門用於匹配屬性值為句子(帶有單詞邊界【一般為空格】),所有本案例情形無法匹配到。

mycontent<-"<div class="ba"><ul id="myid" class="myclass" target="raindu is the blog of lityduyu">raindu"s blog</ul></div>"read_html(mycontent,encoding="UTF-8")%>% html_nodes("div.ba ul[target~="blog"]") %>% html_text()[1] "raindu"s blog"read_html(mycontent,encoding="UTF-8")%>% html_nodes("div.ba ul[target*="blog"]") %>% html_text()[1] "raindu"s blog"

這裡可以看到,「~」的適用範圍僅限於匹配句子中有單詞邊界的目標單詞,而「」因為指代的包含關係限制較少,所以其匹配範圍更廣,也就是說「」的匹配操作可以涵蓋所有「~」適用的匹配情形,但是如果明確了你的匹配目標是有單詞邊界的句子的話,適用「~」匹配可以避免輸出無效內容,更為精確。

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a[href]:contains("Excel")") %>% html_text()[1] "Excel 有哪些可能需要熟練掌握而很多人不會的技能?" "那些培訓師都不曾告訴你的關於Excel圖表的秘密~" [3] "Excel依然是一款強大的數據可視化利器~"

以上的contains是一個匹配函數,跟XPath中的匹配函數及其類似,但是這裡限定的是節點文本內包含的字元串,之前的操作都是基於屬性值包含關係,以上匹配輸出了所有含有href屬性的a節點中文本內容包含字元串「Excel」的目標節點的文本對象。

3、Pseudo Classes偽類:nth-child/nth-of-type

nth-child p:nth-child(2) #選擇作為第二個子元素的p元素p:nth-child(2n) #選擇作為偶數個子元素的p元素p:nth-last-child(2) #選擇作為倒數第二個p元素p:first-child #選擇作為第一個元素的p元素 p:last-child #選擇作為倒數第一個元素的p元素 nth-of-typep:nth-of-type(2) #選擇第二個p元素p:nth-of-type(2n) #選擇第偶數個p元素p:nth-last-of-type(2) #選擇倒數第二個p元素p:first-of-type #選擇作為第一個元素的p元素 p:last-of-type #選擇作為倒數第一個元素的p元素

首先給大家解釋Pseudo Classes偽類中nth-child/nth-of-type的區別,對於nth-child,你可以理解為限定第n個位置必須是p元素,而nth-of-type的限定條件較為寬鬆,僅限定第二出現的p元素,會自動忽略那些非p元素,而前者則將所有元素放在一起排列位置。

mycontent<- "<li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/8595">Excel依然是一款強大的數據可視化利器~</a> <span stylex="margin-left: 21px;"> 671次閱讀/3條評論</span> <span stylex="margin-left: 5px;"> (2017-06-15)</span> <span stylex="margin-left: 21px;"> </span> </li>"read_html(mycontent,encoding="UTF-8")%>% html_nodes("li span:nth-child(1)") %>% html_text()character(0)read_html(mycontent,encoding="UTF-8")%>% html_nodes("li span:nth-of-type(1)") %>% html_text()[1] " 671次閱讀/3條評論"

看吧,區別立馬呈現出來,兩者皆有其適用場景,前者更適合子節點全部為同一類型時,後者則適合子節點中混雜有不同類型的節點。

read_html(mycontent,encoding="UTF-8")%>% html_nodes("li span:first-child") %>% html_text()character(0)read_html(mycontent,encoding="UTF-8")%>% html_nodes("li span:first-of-type") %>% html_text()[1] " 671次閱讀/3條評論"

所以以上兩句的區別仍然是在於元素類型是否相同,因為li的子節點中第一個節點是a而非span,所以適用span:first-child限定了第一個節點必須是span,自然輸出內容為空,而span:first-of-type則輸出子節點中的第一個span,限定較少,完成了匹配。

read_html(mycontent,encoding="UTF-8")%>% html_nodes("li span:last-child") %>% html_text()[1] " "read_html(mycontent,encoding="UTF-8")%>% html_nodes("li span:last-of-type") %>% html_text()[1] " "

當使用last來匹配的時候,因為li內的後三個節點都是span節點,也就是last-child是有符合條件的,所以返回最後一個span內容,內容為空。同理,span:last-of-type也匹配出來了,內容也為空。

read_html(mycontent,encoding="UTF-8")%>% html_nodes("li span:nth-child(2n)") %>% html_text()[1] " 671次閱讀/3條評論" " " read_html(mycontent,encoding="UTF-8")%>% html_nodes("li span:nth-of-type(2n)") %>% html_text()[1] " (2017-06-15)"

這裡的區別更加顯著,使用span:nth-child(2n)匹配的是li的第2個子節點,但是剛好符合span處於偶數位置的條件,所以匹配出了節點內容,而span:nth-of-type(2n)則匹配出了所有子節點中的span節點的偶數位置節點。因而二者匹配輸出的內容是不同的。

綜上所述,span:nth-child限定比較嚴格,nth-of-type限定較為寬鬆,所以實際使用時一定要分清適用場景。

Python版:

這裡我使用Python的BeautifulSoup包的解析器重現以上內容。

1、特殊符號:

「.」/「#」(class屬性與id屬性)

「.」和「#」分別代表標籤內class屬性和id屬性的連接符。

<div stylex="margin-top: 25px;" id="raindu" class="btn-group btn-group-justified blog-menu" role="group"> <a href="/blog/datamofang" class="btn btn-default">我的首頁</a> <a href="/blog/datamofang/sitemap/" class="btn btn-default">博客地圖</a></div>html=""" <div stylex="margin-top: 25px;" id="raindu" class="btn-group btn-group-justified blog-menu" role="group"> <a href="/blog/datamofang" class="btn btn-default">我的首頁</a> <a href="/blog/datamofang/sitemap/" class="btn btn-default">博客地圖</a></div>"""from bs4 import BeautifulSoupsoup = BeautifulSoup(html,"lxml")text=[]for mytext in soup.select("div.btn-group a"): text.append(mytext.get_text())print(text)["我的首頁", "博客地圖"]text=[]for mytext in soup.select("div#raindu a"): text.append(mytext.get_text())print(text)["我的首頁", "博客地圖"]

可以看到以上兩句表達式都可以完美匹配出來div標籤節點內部a節點內的文本,這裡的定位主要是靠『.』和』#』兩個連接符實現的,這是相對比較規範的寫法。

「>」和「 」(右尖括弧和空格)

myhtml=""" <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/9485"> <b>balabalabala</b> 離散顏色標度連續化的最佳方案 </a> <span stylex="margin-left: 21px;"> 56次閱讀/0條評論</span> <span stylex="margin-left: 5px;"> (2017-08-22)</span> <span stylex="margin-left: 21px;"> </span></li>"""#絕對路徑:soup = BeautifulSoup(myhtml,"lxml")soup.select("li > a > b")[0].get_text()"balabalabala"#相對路徑soup.select("li a b")[0].get_text()"balabalabala"soup.select("li b")[0].get_text()"balabalabala"

從以上三個輸出可以很明確的發現,所有的輸出結果都是一樣的,第一句函數執行的功能是在文檔中查找li節點的子節點a節點到的子節點b,並輸出其文本內容;第二句函數執行的功能是查找文檔中li節點中的所有節點為a(相對路徑)的節點內所有節點為b的節點(相對路徑),並輸出其文本內容。第三句函數執行功能為在文檔中查找所有li節點內的所有節點為b的節點並輸出其內容。因為myhtml文檔中只有一個b節點,所有三者輸出的內容是一樣的。

所以「>」和「 」(右尖括弧和空格)的區別非常明顯,也非常重要,請慎用「>」(絕對路徑),只有在有100%把握的時候再用,一般來說使用「 」(空格:相對路徑)的css表達式比較穩健,但是在同一個文檔中同名節點較多的情況下,因為相對路徑需要遍歷的路徑較多,耗時長、可能匹配出沒有價值的內容,所以在實際使用時還是要隨機應變。

「*」和「,」星號和單引號:

text=[]for mytext in soup.select("li [style]"): text.append(mytext.get_text())print(text)["
balabalabala
離散顏色標度連續化的最佳方案
", " 56次閱讀/0條評論", " (2017-08-22)", " "] " " text=[]for mytext in soup.select("[style]"): text.append(mytext.get_text())print(text)["
balabalabala
離散顏色標度連續化的最佳方案
", " 56次閱讀/0條評論", " (2017-08-22)", " ", "

balabalabala
離散顏色標度連續化的最佳方案

56次閱讀/0條評論
(2017-08-22)

", "
balabalabala
離散顏色標度連續化的最佳方案
", " 56次閱讀/0條評論", " (2017-08-22)", " "]

以上第一句執行的功能是在li節點中查詢所有包含style屬性的節點,並輸出對應節點文本內容,第二個匹配的範圍更大一些,匹配了文檔中所有包含style屬性的節點並輸出對應節點文本內容。可以看到li這個頂層節點內的所有文本被拼接在一起作為li的文本對象被輸出了。

soup.select("li a[target]")[0].get_text()"
balabalabala
離散顏色標度連續化的最佳方案
"text=[]for mytext in soup.select("li span"): text.append(mytext.get_text())print(text)[" 56次閱讀/0條評論", " (2017-08-22)", " "]text=[]for mytext in soup.select("li a[target],li span"): text.append(mytext.get_text())print(text)["
balabalabala
離散顏色標度連續化的最佳方案
", " 56次閱讀/0條評論", " (2017-08-22)", " "] " "

「,」這裡的逗號相當於XPath中的「|」號,功能是分割條件表達式,即將逗號兩邊的內容作為單獨的表達式,輸出符合所有表達式匹配模式的內容。

2、謂語表達:

通常我們提取內容要按照標籤內屬性名稱或者屬性值進行條件限定來提取,這時候我們需要在表達式中對標籤節點進行身份限定。

<ul class="blog-sitemap"> <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/9027">精美炫酷數據分析地圖——簡單幾步輕鬆學會</a> <span stylex="margin-left: 21px;"> 235次閱讀/0條評論</span> <span stylex="margin-left: 5px;"> (2017-07-27)</span> <span stylex="margin-left: 21px;"> </span> </li> <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/9000">不用編程,教你輕鬆搞定數據地圖</a> <span stylex="margin-left: 21px;"> 178次閱讀/0條評論</span> <span stylex="margin-left: 5px;"> (2017-07-26)</span> <span stylex="margin-left: 21px;"> </span> </li> <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/8996">Excel 有哪些可能需要熟練掌握而很多人不會的技能?</a> <span stylex="margin-left: 21px;"> 142次閱讀/0條評論</span> <span stylex="margin-left: 5px;"> (2017-07-26)</span> <span stylex="margin-left: 21px;"> </span> </li> <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/8763">一般人不知道的幾個excel製圖技巧 </a> <span stylex="margin-left: 21px;"> 199次閱讀 / 0條評論</span> <span stylex="margin-left: 5px;"> (2017-07-05)</span> <span stylex="margin-left: 21px;"> </span> </li> <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/8654">那些培訓師都不曾告訴你的關於Excel圖表的秘密~</a> <span stylex="margin-left: 21px;"> 424次閱讀/0條評論</span> <span stylex="margin-left: 5px;"> (2017-06-20)</span> <span stylex="margin-left: 21px;"> </span> </li> <li stylex="line-height: 28px;"> <a stylex="" target="_blank" href="/blog/datamofang/8595">Excel依然是一款強大的數據可視化利器~</a> <span stylex="margin-left: 21px;"> 671次閱讀/3條評論</span> <span stylex="margin-left: 5px;"> (2017-06-15)</span> <span stylex="margin-left: 21px;"> </span> </li></ul>soup = BeautifulSoup(myhtml,"lxml")soup.select("li a[target]")[0].get_text()"精美炫酷數據分析地圖——簡單幾步輕鬆學會"

以上語句限制了我們查找的對象是li內所有含target屬性的節點(這裡僅有一個)

###元素限定:p[attr] #包含什麼屬性p[attr="value"] #target為blank的元素p[href^="subtring"] #選擇所有href屬性值以https開頭的a元素p[href$=".pdf"] #選擇所有href屬性值以.pdf結尾的a元素p[href*="w3schools"] #選擇所有href屬性值包含w3schools的a元素p[title~="flower"] #包含關係css=button.attr:contains("OK") #:contains是個Pseudo-class,用冒號開頭,括弧里是內容。

元素限定可能是我們在css表達式中運用到頻率僅次於特殊符號的功能元素了,因為通常解析的目標網頁體系和內容都非常龐大,如果不加以限定的話,肯定會輸出很多對我們沒有任何用處的內容信息。

以上文中幾篇評論文章為例,我們來講解以上所有元素限定的用法:

text=[]for mytext in soup.select("li[style]"): text.append(mytext.get_text())print(text)["
精美炫酷數據分析地圖——簡單幾步輕鬆學會
235次閱讀/0條評論
(2017-07-27)

", "
不用編程,教你輕鬆搞定數據地圖
178次閱讀/0條評論
(2017-07-26)

", "
Excel 有哪些可能需要熟練掌握而很多人不會的技能?
142次閱讀/0條評論
(2017-07-26)

", "
一般人不知道的幾個excel製圖技巧
199次閱讀 / 0條評論
(2017-07-05)

", "
那些培訓師都不曾告訴你的關於Excel圖表的秘密~
424次閱讀/0條評論
(2017-06-20)

", "
Excel依然是一款強大的數據可視化利器~
671次閱讀/3條評論
(2017-06-15)

"]

這裡我們限定了li標籤的屬性為style,所有上述語句輸出了ul內部所有li標籤中含有style屬性的節點對應並輸出其文本內容。

text=[]for mytext in soup.select("li a[target="_blank"]"): text.append(mytext.get_text())print(text)

[『精美炫酷數據分析地圖——簡單幾步輕鬆學會』, 『不用編程,教你輕鬆搞定數據地圖』, 『Excel 有哪些可能需要熟練掌握而很多人不會的技能?』, 『一般人不知道的幾個excel製圖技巧 『, 『那些培訓師都不曾告訴你的關於Excel圖表的秘密~』, 『Excel依然是一款強大的數據可視化利器~』]

這裡我限定了li內所有屬性值為』_blank』的a節點並輸出其文本內容,輸出了所有博客文章名稱。

text=[]for mytext in soup.select("li a[href^="/blog"]"): text.append(mytext.get_text())print(text)["精美炫酷數據分析地圖——簡單幾步輕鬆學會", "不用編程,教你輕鬆搞定數據地圖", "Excel 有哪些可能需要熟練掌握而很多人不會的技能?", "一般人不知道的幾個excel製圖技巧 ", "那些培訓師都不曾告訴你的關於Excel圖表的秘密~", "Excel依然是一款強大的數據可視化利器~"]

本次限定了li節點內所有含有href屬性值以「/blog」開頭的a節點並輸出這些節點的文本內容。(因為所有a節點的href屬性值都是以/blog開頭的,所有輸出了所有文章名稱)。

text=[]for mytext in soup.select("li a[href$="54"]"): text.append(mytext.get_text())print(text)["那些培訓師都不曾告訴你的關於Excel圖表的秘密~"]

與上面那句類似,這裡限定的是href屬性值以54結尾的a節點,並輸出其文本內容,僅有一個符合條件。

text=[]for mytext in soup.select("li a[href*="datamofang"]"): text.append(mytext.get_text())print(text)["精美炫酷數據分析地圖——簡單幾步輕鬆學會", "不用編程,教你輕鬆搞定數據地圖", "Excel 有哪些可能需要熟練掌握而很多人不會的技能?", "一般人不知道的幾個excel製圖技巧 ", "那些培訓師都不曾告訴你的關於Excel圖表的秘密~", "Excel依然是一款強大的數據可視化利器~"]

這裡的「*」代表包含關係,即限定了href屬性值內容包含字元串「datamofang」的所有節點a並輸出其文本對象。

text=[]for mytext in soup.select("li a[href~="datamofang"]"): text.append(mytext.get_text())print(text)[]

以上代碼中的「~」也是代表包含關係,但是這裡的包含關係與上一條的包含關係有所不同,這裡的「~」專門用於匹配屬性值為句子(帶有單詞邊界【一般為空格】),所有本案例情形無法匹配到。

mycontent="<div class="ba"><ul id="myid" class="myclass" target="raindu is the blog of lityduyu">raindu"s blog</ul></div>"soup = BeautifulSoup(mycontent,"lxml")soup.select("div.ba ul[target~="blog"]")[0].get_text()"raindu"s blog"soup.select("div.ba ul[target*="blog"]")[0].get_text()"raindu"s blog"

這裡可以看到,「~」的適用範圍僅限於匹配句子中有單詞邊界的目標單詞,而「」因為指代的包含關係限制較少,所以其匹配範圍更廣,也就是說「」的匹配操作可以涵蓋所有「~」適用的匹配情形,但是如果明確了你的匹配目標是有單詞邊界的句子的話,適用「~」匹配可以避免輸出無效內容,更為精確。

3、Pseudo Classes偽類:nth-child/nth-of-type

發現BeautifuSoup暫時還不支持css路徑表達式中的Pseudo Classes偽類偽類,不過BeautifuSoup中可選的解析器有很多,這一點兒並不會對網頁解析造成太大困擾,即便是適用以上這些已經支持的CSS表達式同樣可以完成大部分解析工作。

最後使用BeautifuSoup的css解析工具完成博客文章信息的解析工作。

from bs4 import BeautifulSoupurl="https://ask.hellobi.com/blog/datamofang/sitemap/"import requestsheader={"User-Agent":"Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.79 Safari/537.36"}response = requests.get(url,headers=header)soup = BeautifulSoup(response.text,"lxml")#文章名稱:myclass=[]for i in soup.select("ul li a[href*="datamofang"]"): myclass.append(i.get_text(strip=True))print(myclass)#文章鏈接:Pagelinks=[]for i in soup.select("ul li a[href*="datamofang"]"): Pagelinks.append("https://ask.hellobi.com"+i.get("href"))print(Pagelinks)#文章閱讀量、閱讀日期:Pageviews=[];Pagedate=[]for i in soup.select("ul li[style]"): Pageviews.append(i.find_all("span")[0].get_text(strip=True)) Pagedate.append(i.find_all("span")[1].get_text(strip=True))print(Pageviews,Pagedate)import pandas as pdmycontent=pd.DataFrame({"myclass":myclass,"Pagelinks":Pagelinks,"Pageviews":Pageviews,"Pagedate":Pagedate})print(mycontent)

本文參考文獻:

w3schools.com/cssref/tr

tutorials.jenkov.com/cs

zhangxinxu.com/wordpres

lxml.de/xpathxslt.html

cuiqingcai.com/1319.htm

在線課程請點擊文末原文鏈接:

Hellobi Live | 9月12日 R語言可視化在商務場景中的應用

往期案例數據請移步本人GitHub: github.com/ljtyduyu/Dat 歡迎關注數據小魔方qq交流群

推薦閱讀:

現在的網路爬蟲的研究成果和存在的問題有哪些?
python2.7爬蟲中decode("utf-8")出錯該如何解決?

TAG:R编程语言 | Python | 网页爬虫 |