左手用R右手Python系列17——CSS表达式与网页解析

时间:2022-05-08
本文章向大家介绍左手用R右手Python系列17——CSS表达式与网页解析,主要内容包括R语言:、Python版:、基本概念、基础应用、原理机制和需要注意的事项等,并结合实例形式分析了其使用技巧,希望通过本文能帮助到大家理解应用这部分内容。

上一篇着重讲解了网页解析中的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中的绝对路径(/)
  • “*”匹配所有元素
  • “,”或条件,同时符合两个条件
  • “+”右侧相邻元素
  • “~”兄弟节点

以上是CSS表达式中几个最为常用的特殊符号,这些特殊符号在路径定位中都有着特殊意义,接下来一个一个进行解释。

“.”/“#”(class属性与id属性)

“.”和“#”分别代表标签内class属性和id属性的连接符。

<div style="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 style="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 style="line-height: 28px;">
     <a style="" target="_blank" href="/blog/datamofang/9485">
        <b>balabalabala</b>
     离散颜色标度连续化的最佳方案
     </a>
     <span style="margin-left: 21px;"> 56次阅读/0条评论</span>
     <span style="margin-left: 5px;"> (2017-08-22)</span>
     <span style="margin-left: 21px;"> </span>
</li>
xml

myhtml<-'<li style="line-height: 28px;">
     <a style="" target="_blank" href="/blog/datamofang/9485">
        <b>balabalabala</b>
     离散颜色标度连续化的最佳方案
     </a>
     <span style="margin-left: 21px;"> 56次阅读/0条评论</span>
     <span style="margin-left: 5px;"> (2017-08-22)</span>
     <span style="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] "nbalabalabalan离散颜色标度连续化的最佳方案n" " 56次阅读/0条评论"                     
[3] " (2017-08-22)"          " "         
read_html(myhtml,encoding="UTF-8")%>% html_nodes(" *[style]") %>% html_text()
[1] "n     nbalabalabalan离散颜色标度连续化的最佳方案nn 56次阅读/0条评论n (2017-08-22)n n"
[2] "nbalabalabalan离散颜色标度连续化的最佳方案n"                                              
[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] "nbalabalabalan离散颜色标度连续化的最佳方案n"
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] "nbalabalabalan离散颜色标度连续化的最佳方案n" " 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 style="line-height: 28px;">
                       <a style="" target="_blank" href="/blog/datamofang/9027">精美炫酷数据分析地图——简单几步轻松学会</a>
                       <span style="margin-left: 21px;"> 235次阅读/0条评论</span>
                       <span style="margin-left: 5px;">  (2017-07-27)</span>
                       <span style="margin-left: 21px;"> </span>
         </li>
         <li style="line-height: 28px;">
                       <a style="" target="_blank" href="/blog/datamofang/9000">不用编程,教你轻松搞定数据地图</a>
                       <span style="margin-left: 21px;"> 178次阅读/0条评论</span>
                       <span style="margin-left: 5px;">  (2017-07-26)</span>
                       <span style="margin-left: 21px;"> </span>
         </li>
         <li style="line-height: 28px;">
                       <a style="" target="_blank" href="/blog/datamofang/8996">Excel 有哪些可能需要熟练掌握而很多人不会的技能?</a>
                       <span style="margin-left: 21px;"> 142次阅读/0条评论</span>
                       <span style="margin-left: 5px;">  (2017-07-26)</span>
                       <span style="margin-left: 21px;"> </span>
         </li>
         <li style="line-height: 28px;">
                       <a style="" target="_blank" href="/blog/datamofang/8763">一般人不知道的几个excel制图技巧 </a>
                       <span style="margin-left: 21px;"> 199次阅读 / 0条评论</span>
                       <span style="margin-left: 5px;">  (2017-07-05)</span>
                       <span style="margin-left: 21px;"> </span>
        </li>
        <li style="line-height: 28px;">
                       <a style="" target="_blank" href="/blog/datamofang/8654">那些培训师都不曾告诉你的关于Excel图表的秘密~</a>
                       <span style="margin-left: 21px;"> 424次阅读/0条评论</span>
                       <span style="margin-left: 5px;">  (2017-06-20)</span>
                       <span style="margin-left: 21px;"> </span>
        </li>
        <li style="line-height: 28px;">
                       <a style="" target="_blank" href="/blog/datamofang/8595">Excel依然是一款强大的数据可视化利器~</a>
                       <span style="margin-left: 21px;"> 671次阅读/3条评论</span>
                       <span style="margin-left: 5px;">  (2017-06-15)</span>
                       <span style="margin-left: 21px;"> </span>
        </li></ul>
read_html(myhtml,encoding="UTF-8")%>% html_nodes("li a[target]") %>% html_text()
[1] "nbalabalabalan离散颜色标度连续化的最佳方案n"

以上语句限制了我们查找的对象是li内所有含target属性的节点(这里仅有一个)

2、元素限定:

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表达式中运用到频率仅次于特殊符号的功能元素了,因为通常解析的目标网页体系和内容都非常庞大,如果不加以限定的话,肯定会输出很多对我们没有任何价值的信息。

以上文中几篇评论文章为例,我们来讲解以上所有元素限定的用法:

read_html(myhtml,encoding="UTF-8")%>% html_nodes("li[style]") %>% html_text()
[1] "n精美炫酷数据分析地图——简单几步轻松学会n 235次阅读/0条评论n  (2017-07-27)n n"          
[2] "n不用编程,教你轻松搞定数据地图n 178次阅读/0条评论n  (2017-07-26)n n"                  
[3] "nExcel 有哪些可能需要熟练掌握而很多人不会的技能?n 142次阅读/0条评论n  (2017-07-26)n n"
[4] "n一般人不知道的几个excel制图技巧 n 199次阅读 / 0条评论n  (2017-07-05)n n"              
[5] "n那些培训师都不曾告诉你的关于Excel图表的秘密~n 424次阅读/0条评论n  (2017-06-20)n n"    
[6] "nExcel依然是一款强大的数据可视化利器~n 671次阅读/3条评论n  (2017-06-15)n n"

这里限定了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-type
p:nth-of-type(2)           #选择第二个p元素
p:nth-of-type(2)           #选择第偶数个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 style="line-height: 28px;">
              <a style="" target="_blank" href="/blog/datamofang/8595">Excel依然是一款强大的数据可视化利器~</a>
              <span style="margin-left: 21px;"> 671次阅读/3条评论</span>
              <span style="margin-left: 5px;">  (2017-06-15)</span>
              <span style="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 style="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 style="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 BeautifulSoup
soup = 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 style="line-height: 28px;">
     <a style="" target="_blank" href="/blog/datamofang/9485">
      <b>balabalabala</b>
     离散颜色标度连续化的最佳方案
     </a>
     <span style="margin-left: 21px;"> 56次阅读/0条评论</span>
     <span style="margin-left: 5px;"> (2017-08-22)</span>
     <span style="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)
['nbalabalabalan     离散颜色标度连续化的最佳方案n     ', ' 56次阅读/0条评论', ' (2017-08-22)', ' ']         " "         
text=[]
for mytext in soup.select("[style]"):
    text.append(mytext.get_text())
print(text)
['nbalabalabalan     离散颜色标度连续化的最佳方案n     ', ' 56次阅读/0条评论', ' (2017-08-22)', ' ', 'nnbalabalabalan     离散颜色标度连续化的最佳方案n     n 56次阅读/0条评论n (2017-08-22)n n', 'nbalabalabalan     离散颜色标度连续化的最佳方案n     ', ' 56次阅读/0条评论', ' (2017-08-22)', ' ']

以上第一句执行的功能是在li节点中查询所有包含style属性的节点,并输出对应节点文本内容,第二个匹配的范围更大一些,匹配了文档中所有包含style属性的节点并输出对应节点文本内容。可以看到li这个顶层节点内的所有文本被拼接在一起作为li的文本对象被输出了。

soup.select("li a[target]")[0].get_text()
'nbalabalabalan     离散颜色标度连续化的最佳方案n     '
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)
['nbalabalabalan     离散颜色标度连续化的最佳方案n     ', ' 56次阅读/0条评论', ' (2017-08-22)', ' ']                                " "

“,”这里的逗号相当于XPath中的“|”号,功能是分割条件表达式,即将逗号两边的内容作为单独的表达式,输出符合所有表达式匹配模式的内容。

2、谓语表达:

通常我们提取内容要按照标签内属性名称或者属性值进行条件限定来提取,这时候我们需要在表达式中对标签节点进行身份限定。

<ul class="blog-sitemap">
         <li style="line-height: 28px;">
                       <a style="" target="_blank" href="/blog/datamofang/9027">精美炫酷数据分析地图——简单几步轻松学会</a>
                       <span style="margin-left: 21px;"> 235次阅读/0条评论</span>
                       <span style="margin-left: 5px;">  (2017-07-27)</span>
                       <span style="margin-left: 21px;"> </span>
         </li>
         <li style="line-height: 28px;">
                       <a style="" target="_blank" href="/blog/datamofang/9000">不用编程,教你轻松搞定数据地图</a>
                       <span style="margin-left: 21px;"> 178次阅读/0条评论</span>
                       <span style="margin-left: 5px;">  (2017-07-26)</span>
                       <span style="margin-left: 21px;"> </span>
         </li>
         <li style="line-height: 28px;">
                       <a style="" target="_blank" href="/blog/datamofang/8996">Excel 有哪些可能需要熟练掌握而很多人不会的技能?</a>
                       <span style="margin-left: 21px;"> 142次阅读/0条评论</span>
                       <span style="margin-left: 5px;">  (2017-07-26)</span>
                       <span style="margin-left: 21px;"> </span>
         </li>
         <li style="line-height: 28px;">
                       <a style="" target="_blank" href="/blog/datamofang/8763">一般人不知道的几个excel制图技巧 </a>
                       <span style="margin-left: 21px;"> 199次阅读 / 0条评论</span>
                       <span style="margin-left: 5px;">  (2017-07-05)</span>
                       <span style="margin-left: 21px;"> </span>
        </li>
        <li style="line-height: 28px;">
                       <a style="" target="_blank" href="/blog/datamofang/8654">那些培训师都不曾告诉你的关于Excel图表的秘密~</a>
                       <span style="margin-left: 21px;"> 424次阅读/0条评论</span>
                       <span style="margin-left: 5px;">  (2017-06-20)</span>
                       <span style="margin-left: 21px;"> </span>
        </li>
        <li style="line-height: 28px;">
                       <a style="" target="_blank" href="/blog/datamofang/8595">Excel依然是一款强大的数据可视化利器~</a>
                       <span style="margin-left: 21px;"> 671次阅读/3条评论</span>
                       <span style="margin-left: 5px;">  (2017-06-15)</span>
                       <span style="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)
['n精美炫酷数据分析地图——简单几步轻松学会n 235次阅读/0条评论n  (2017-07-27)n n', 'n不用编程,教你轻松搞定数据地图n 178次阅读/0条评论n  (2017-07-26)n n', 'nExcel 有哪些可能需要熟练掌握而很多人不会的技能?n 142次阅读/0条评论n  (2017-07-26)n n', 'n一般人不知道的几个excel制图技巧 n 199次阅读 / 0条评论n  (2017-07-05)n n', 'n那些培训师都不曾告诉你的关于Excel图表的秘密~n 424次阅读/0条评论n  (2017-06-20)n n', 'nExcel依然是一款强大的数据可视化利器~n 671次阅读/3条评论n  (2017-06-15)n n']

这里我们限定了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 BeautifulSoup
url="https://ask.hellobi.com/blog/datamofang/sitemap/"
import requests
header={"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 pd
mycontent=pd.DataFrame({"myclass":myclass,"Pagelinks":Pagelinks,"Pageviews":Pageviews,"Pagedate":Pagedate})
print(mycontent)

本文参考文献: https://www.w3schools.com/cssref/trysel.asp http://tutorials.jenkov.com/css/selectors.html#universal-selector http://www.zhangxinxu.com/wordpress/2011/06/css3%E9%80%89%E6%8B%A9%E5%99%A8nth-child%E5%92%8Cnth-of-type%E4%B9%8B%E9%97%B4%E7%9A%84%E5%B7%AE%E5%BC%82/ http://lxml.de/xpathxslt.html http://cuiqingcai.com/1319.html

往期案例数据请移步本人GitHub: https://github.com/ljtyduyu/DataWarehouse/tree/master/File