SKYue

解决BeautifulSoup中同时返回Tag和NavigalbeString对象的问题

在BeautifulSoup中,用contents或children遍历子节点的时候,如果节点下存在字符串,则会同时获取TagNavigalbeString对象。这是一个非常坑爹的特性,一方面通常获取子节点主要是得到Tag,另一方面,bs已经提供了stringsstripped_strings单独获取节点下的字符串,这里就是多此一举。

下面以contents为例,来看看这个问题的具体情况并给出两种解决方案。

一个例子

假设有下面这个xml:

<tab>
<tabletitle>
<title name="公司名称"/>
<title name="所属行业"/>
<title name="主营业务"/>
<title name="董事长"/>
<title name="最终控制人"/>
</tabletitle>
<tablelist>
<col con="许继电气股份有限公司"/>
<col con="电气设备"/>
<col con="从事电力系统二次设备和一次设备的研制、销售"/>
<col con="许继集团有限公司"/>
<col con="国务院国有资产监督管理委员会"/>
</tablelist>
<tablelist>
<col con="中国南方航空股份有限公司"/>
<col con="机场航运"/>
<col con="提供国内、港澳台地区及国际航空客运、货运及邮运服务"/>
<col con="中国南方航空集团公司"/>
<col con="国务院国有资产监督管理委员会"/>
</tablelist>
</tab>

现在需要把数据转成普通的表格,方便存入excel中,代码如下:

from bs4 import BeautifulSoup

from_str = '''
<tab>
<tabletitle>
<title name="公司名称"/>
<title name="所属行业"/>
<title name="主营业务"/>
<title name="董事长"/>
<title name="最终控制人"/>
</tabletitle>
<tablelist>
<col con="许继电气股份有限公司"/>
<col con="电气设备"/>
<col con="从事电力系统二次设备和一次设备的研制、销售"/>
<col con="许继集团有限公司"/>
<col con="国务院国有资产监督管理委员会"/>
</tablelist>
<tablelist>
<col con="中国南方航空股份有限公司"/>
<col con="机场航运"/>
<col con="提供国内、港澳台地区及国际航空客运、货运及邮运服务"/>
<col con="中国南方航空集团公司"/>
<col con="国务院国有资产监督管理委员会"/>
</tablelist>
</tab>'''

soup = BeautifulSoup(from_str, 'lxml-xml')
tablelist = soup.findAll('tablelist')
for i in tablelist:
    for j in i.contents:
        print(j['con'], end='\t')

运行上面代码会发现print语句报错:

TypeError: string indices must be integers

提示string类型的索引,必须是整数。于是将print语句改为

print(type(j))

看下j的类型,发现同时有TagNavigableString。表面上看xml中没有字符串,但其实在每个标签之间有个换行符(只有换行符时也获取了NavigalbeString就更坑爹了),所以contents遍历的时候,获取了NavigableString类型的对象。

解决这个问题,有两个比较常见的方法。

两种方法

方法一:使用isinstance判断类型

在循环中加一个类型判断,过滤掉NavigableString类型的子节点,即可输出正常的结果,代码如下:

for i in tablelist:
    for j in i.contents:
        if not isinstance(j, NavigableString):  ## 类型判断
            print(j['con'], end='\t')

注意这种方法需要在前面加上from bs4 import NavigableString导入相应的模块。

方法二:使用findAll(True)

另一种是用findAll()方法,只要加上True参数,就会返回所有Tag类型的子节点:

for i in tablelist:
    for j in i.findAll(True):  ## True参数只返回Tag子节点
        print(j['con'], end='\t')

两种方法都可以实现只获取Tag类型子节点,LZ更喜欢第2种,少写一层缩进,少写一个import,更重要的是可以少一些循环次数。感觉以后完全不需要contentschildren两个属性了。

Code

共有7篇文章

Trade

共有25篇文章

Read

共有8篇文章

Movie

共有22篇文章

Diary

共有50篇文章

Travel

共有6篇文章

Tech

共有27篇文章

Review

共有9篇文章

Poetry

共有2篇文章

2018

共有26篇文章

2017

共有23篇文章

2016

共有89篇文章

2015

共有18篇文章