HTML解析

Posted on 2016-08-04

网页的组织是有序化的,我们都知道可以利用标签来挖掘想要的内容,然而如果内容隐藏的很深呢?

你也许这样写过:

bsObj.findAll("table")[4].findAll("tr")[2].find("td").findAll("div")[1].find("a")

这看起来又冗长,况且一旦网页稍有变动,你的爬虫就失效了。

事实上,现在我们每天访问的网站都存在样式表。如果说HTML标签定义了网页的结构,那么CSS则定义了网页的外观。CSS为不同的标签定义了属性样式,这也是可以利用的特点之一。 比如

<span class="green"></span>
<span class="red"></span>

这样我们是不是就很容易提取“红”,“绿”不同的区块呢?

以這个网站为例

再看下源码部分:image

我们发现,红色部分是《战争与和平》中任人物的台词,绿色部分是出现的人物姓名。

尝试如下代码:

from urllib.request import urlopen
from bs4 import BeautifulSoup

html = urlopen("http://www.pythonscraping.com/pages/warandpeace.html")
bsObj = BeautifulSoup(html,"html.parser")
nameList = bsObj.findAll("span", {"class":"green"})

for name in nameList:
print(name.get_text())

结果是什么呢? image

利用beautifulsoup对象,我们调用findall()方法提取了人物的姓名,当然,是根据其pan class属性提取的。

对比之前的

bsObj.tagname

(参照上一篇教程中的bsObj.h1) bsObj.tagname语句只能获得首次出现的标签。

对比现在的:

bsObj.findAll(tagname,tagAttributes)

我们可以获取指定标签下,指定属性下的所有内容。就像這里的姓名列表。

find()与findAll()

BeautifulSoup中的这两个方法可能会成为你最喜欢调用的方法之一,它轻松过滤了html中你不需要的内容,指哪儿打哪儿。

這两种方法看起来十分相似,然而让我们来看一下文档中对他们的定义:

findAll(tag, attributes, recursive, text, limit, keywords)
find(tag, attributes, recursive, text, keywords)

大多数时候,我们只用到tag与attributes这两个参数,所以用哪一种方法都没有差别。下面我们深入细节的来比较一下。

.findAll({"h1","h2","h3","h4","h5","h6"})

这会返回所有的不同大小的标题内容。

.findAll("span", {"class":"green", "class":"red"})

显然,red span 与green span中的内容都会返回,find_all() 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件.


recursive参数是一个bool值,True时,BeautifulSoup会检索当前tag的所有子孙节点,如果你只想检索当前tag的首层节点,那就设为False。


Text参数用来搜索文档中的字符串内容 不妨试试

nameList = bsObj.findAll(text="the prince")
print(len(nameList))

以上统计了“the prince”出现的次数,欢迎大家评论中告诉我是多少。


Limit参数

find_all() 方法返回全部的搜索结构,如果文档树很大那么搜索会很慢.如果我们不需要全部结果,可以使用 limit 参数限制返回结果的数量.显然,find方法就等同于limit=1的结果。


Keyword参数则允许你选择具有特定属性的标签。例如:

allText = bsObj.findAll(id="text")
print(allText[0].get_text())

其它BeautifulSoup对象

目前为止,你应该已经见过两种对象了: 第一种,BeautifulSoup对象

bsObj.div.h1

以及Tag对象

bsObj.findAll(tagname,tagAttributes)

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种: Tag , NavigableString , BeautifulSoup , Comment .

下面再介绍一些也许你不常用,但是依旧十分重要的对象:

NavigableString对象,用来保护没有标签的文本。

Comment对象用来寻找html中在comment标签下的注释内容。比如:

<!--like this one-->

HTML导航树

我们都知道html的结构被映射成一棵树。

戳我一下举个栗子

html
    |-body
      |-div.wapper
        |-h1
        |-div.content
        |-table#giftList
          |-tr
            |-th
            |-th
            |-th
            |-th
          |-tr.gift#gift1
            |-td
            |-td
              |-span.excitingNote
            |-td
            |-td
              |-img
        |-...table rows continue...
      |-div.footer

一般来说

  • 子标签, 即一个父标签的下一级;
  • 后代标签, 指一个父标签下面所有级别的标签.
  • 所有子标签都是后代标签, 但不是所有的后代标签都是子标签

一般, bs函数总是处理当前标签的后代标签, 比如.findAll就是递归地在所有后代标签中查找

from urllib.request import urlopen
from bs4 import BeautifulSoup

html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bsObj = BeautifulSoup(html,"html.parser")

for child in bsObj.find("table",{"id":"giftList"}).children:
print(child) 使用.children将只获取当前标签的子标签, 使用descendants将获取所有后代标签

from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bsObj = BeautifulSoup(html,"html.parser")

for sibling in bsObj.find("table",{"id":"giftList"}).tr.next_siblings:
print(sibling) 

结果第一个标题行都没有了 使用.next_siblings将获得当前标签之后,所有的兄弟标签. 注意, 对象本身不能作为自己的兄弟标签; 从名字也可以看出, 是返回之后的兄弟标签. 相应的有previous_siblings,next_sibling和previous_sibling


parent和parents分别用于获取标签对象的直属父标签和所有父辈标签。

from urllib.request import urlopen
from bs4 import BeautifulSoup

html = urlopen("http://www.pythonscraping.com/pages/page3.html")
bsObj = BeautifulSoup(html,"html.parser")
print(bsObj.find("img",{"src":"../img/gifts/img1.jpg"}).parent.previous_sibling.get_text())

结果呢是15美元,我们来看看是怎么一回事: image

仔细看:

print(bsObj.find("img",{"src":"../img/gifts/img1.jpg"}).parent.previous_sibling.get_text())
  1. 首先我们定位的是src=”../img/gifts/img1.jpg”父级标签<td>
  2. 我们通过.partnt选择了
  3. 我们通过.previous_sibling再次选择了<td>标签的前一个同层子节点。

更多使用还是建议大家阅读文档。


蚊子再小也是肉~
本站总访问量 本站访客数