Python使用Beautiful Soup(BS4)库解析HTML和XML


Posted in Python onJune 05, 2022

一、Beautiful Soup概述:

Beautiful Soup支持从HTML或XML文件中提取数据的Python库;

它支持Python标准库中的HTML解析器,还支持一些第三方的解析器lxml。

Beautiful Soup自动将输入文档转换为Unicode编码,输出文档转换为utf-8编码。

安装:

pip install beautifulsoup4

可选择安装解析器

  • pip install lxml
  • pip install html5lib

二、BeautifulSoup4简单使用

假设有这样一个Html,具体内容如下:

<!DOCTYPE html>
<html>
<head>
    <meta content="text/html;charset=utf-8" http-equiv="content-type" />
    <meta content="IE=Edge" http-equiv="X-UA-Compatible" />
    <meta content="always" name="referrer" />
    <link href="https://ss1.bdstatic.com/5eN1bjq8AAUYm2zgoY3K/r/www/cache/bdorz/baidu.min.css" rel="external nofollow"  rel="stylesheet" type="text/css" />
    <title>百度一下,你就知道 </title>
</head>
<body link="#0000cc">
  <div id="wrapper">
    <div id="head">
        <div class="head_wrapper">
          <div id="u1">
            <a class="mnav" href="http://news.baidu.com" rel="external nofollow"  name="tj_trnews">新闻 </a>
            <a class="mnav" href="https://www.hao123.com" rel="external nofollow"  name="tj_trhao123">hao123 </a>
            <a class="mnav" href="http://map.baidu.com" rel="external nofollow"  name="tj_trmap">地图 </a>
            <a class="mnav" href="http://v.baidu.com" rel="external nofollow"  name="tj_trvideo">视频 </a>
            <a class="mnav" href="http://tieba.baidu.com" rel="external nofollow"  rel="external nofollow"  name="tj_trtieba">贴吧 </a>
            <a class="bri" href="//www.baidu.com/more/" rel="external nofollow"  name="tj_briicon" style="display: block;">更多产品 </a>
          </div>
        </div>
    </div>
  </div>
</body>
</html>

创建beautifulsoup4对象:

from bs4 import BeautifulSoup

file = open('./aa.html', 'rb')
html = file.read()
bs = BeautifulSoup(html, "html.parser")  # 缩进格式

print(bs.prettify())  # 格式化html结构
print(bs.title)  # 
print(bs.title.name)  # 获取title标签的名称 :title
print(bs.title.string)  # 获取title标签的文本内容 :   百度一下,你就知道
print(bs.head)  # 获取head标签的所有内容 :
print(bs.div)  # 获取第一个div标签中的所有内容   :
print(bs.div["id"])  # 获取第一个div标签的id的值      :    wrapper
print(bs.a)  # 获取第一个a标签中的所有内容    :       <a href="http://news.baidu.com/" rel="external nofollow"   target="_blank">新闻 </a>
print(bs.find_all("a"))  # 获取所有的a标签中的所有内容     :   [....]
print(bs.find(id="u1"))  # 获取id="u1"的所有内容 :
for item in bs.find_all("a"):  # 获取所有的a标签,并遍历打印a标签中的href的值    :
    print(item.get("href"))
for item in bs.find_all("a"):  # 获取所有的a标签,并遍历打印a标签的文本值:
    print(item.get_text())

三、BeautifulSoup4四大对象种类

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

1、Tag:标签

Tag通俗点讲就是HTML中的一个个标签,例如:

print(bs.title) # 获取title标签的所有内容
print(bs.head) # 获取head标签的所有内容
print(bs.a) # 获取第一个a标签的所有内容
print(type(bs.a))# 类型

我们可以利用 soup 加标签名轻松地获取这些标签的内容,这些对象的类型是bs4.element.Tag。但是注意,它查找的是在所有内容中的第一个符合要求的标签。

对于 Tag,它有两个重要的属性,是 name 和 attrs:

print(bs.name) # [document] #bs 对象本身比较特殊,它的 name 即为 [document]
print(bs.head.name) # head #对于其他内部标签,输出的值便为标签本身的名称
print(bs.a.attrs) # 在这里,我们把 a 标签的所有属性打印输出了出来,得到的类型是一个字典。
print(bs.a['class']) ##还可以利用get方法,传入属性的名称,二者是等价的,等价 bs.a.get('class')

bs.a['class'] = "newClass"# 可以对这些属性和内容等等进行修改
print(bs.a) 

del bs.a['class'] # 还可以对这个属性进行删除
print(bs.a)

2、NavigableString:标签内部的文字

既然我们已经得到了标签的内容,那么问题来了,我们要想获取标签内部的文字怎么办呢?很简单,用 .string 即可,例如:

print(bs.title.string)  # 百度一下,你就知道 
print(type(bs.title.string))  #

3、BeautifulSoup:文档的内容

BeautifulSoup对象表示的是一个文档的内容。大部分时候,可以把它当作 Tag 对象,是一个特殊的 Tag,我们可以分别获取它的类型,名称,以及属性,例如:

print(type(bs.name))  # 
print(bs.name)  # [document]
print(bs.attrs)  # {}

4、Comment:注释

Comment 对象是一个特殊类型的 NavigableString 对象,其输出的内容不包括注释符号。

print(bs.a)
# 此时不能出现空格和换行符,a标签如下:
# 
print(bs.a.string) # 新闻
print(type(bs.a.string)) #

四、遍历文档树所用属性

  • .contents:获取Tag的所有子节点,返回一个list
print(bs.head.contents)     # tag的.contents属性可以将tag的子节点以列表的方式输出:[...]
print(bs.head.contents[1])  # 用列表索引来获取它的某一个元素:
  • .children:获取Tag的所有子节点,返回一个生成器
for child in bs.body.children:
    print(child)
  • .descendants:获取Tag的所有子孙节点
  • .parent:获取Tag的父节点
  • .parents:递归得到父辈元素的所有节点,返回一个生成器
  • .previous_sibling:获取当前Tag的上一个节点,属性通常是字符串或空白,真实结果是当前标签与上一个标签之间的顿号和换行符
  • .next_sibling:获取当前Tag的下一个节点,属性通常是字符串或空白,真是结果是当前标签与下一个标签之间的顿号与换行符
  • .previous_siblings:获取当前Tag的上面所有的兄弟节点,返回一个生成器
  • .next_siblings:获取当前Tag的下面所有的兄弟节点,返回一个生成器
  • .previous_element:获取解析过程中上一个被解析的对象(字符串或tag),可能与previous_sibling相同,但通常是不一样的
  • .next_element:获取解析过程中下一个被解析的对象(字符串或tag),可能与next_sibling相同,但通常是不一样的
  • .previous_elements:返回一个生成器,可以向前访问文档的解析内容
  • .next_elements:返回一个生成器,可以向后访问文档的解析内容
  • .strings:如果Tag包含多个字符串,即在子孙节点中有内容,可以用此获取,而后进行遍历
  • .stripped_strings:与strings用法一致,只不过可以去除掉那些多余的空白内容
  • .has_attr:判断Tag是否包含属性

五、搜索文档树

1、find_all():过滤器

find_all(name, attrs, recursive, text, **kwargs):

find_all过滤器可以被用在tag的name中,节点的属性等。

(1)name参数:

字符串过滤:会查找与字符串完全匹配的内容

a_list = bs.find_all("a")
print(a_list)

正则表达式过滤:如果传入的是正则表达式,那么BeautifulSoup4会通过search()来匹配内容

import re 

t_list = bs.find_all(re.compile("a")) 
for item in t_list: 
   print(item)

列表:如果传入一个列表,BeautifulSoup4将会与列表中的任一元素匹配到的节点返回

t_list = bs.find_all(["meta","link"])
for item in t_list:
    print(item)

方法:传入一个方法,根据方法来匹配

def name_is_exists(tag): 
    return tag.has_attr("name") 
t_list = bs.find_all(name_is_exists) 
for item in t_list: 
    print(item)

(2)kwargs参数:

t_list = bs.find_all(id="head")  # 查询id=head的Tag
t_list = bs.find_all(href=re.compile(http://news.baidu.com))  # 查询href属性包含ss1.bdstatic.com的Tag
t_list = bs.find_all(class_=True) # 查询所有包含class的Tag(注意:class在Python中属于关键字,所以加_以示区别)
for item in t_list: 
    print(item)

(3)attrs参数:

并不是所有的属性都可以使用上面这种方式进行搜索,比如HTML的data-*属性:

t_list = bs.find_all(data-foo="value")

如果执行这段代码,将会报错。我们可以使用attrs参数,定义一个字典来搜索包含特殊属性的tag:

t_list = bs.find_all(attrs={"data-foo":"value"})
for item in t_list:
    print(item)

(4)text参数:

通过text参数可以搜索文档中的字符串内容,与name参数的可选值一样,text参数接受 字符串,正则表达式,列表

t_list = bs.find_all(text="hao123") 
t_list = bs.find_all(text=["hao123", "地图", "贴吧"]) 
t_list = bs.find_all(text=re.compile("\d"))

当我们搜索text中的一些特殊属性时,同样也可以传入一个方法来达到我们的目的:

def length_is_two(text):
    return text and len(text) == 2
t_list = bs.find_all(text=length_is_two)

(5)limit参数

可以传入一个limit参数来限制返回的数量,当搜索出的数据量为5,而设置了limit=2时,此时只会返回前2个数据

t_list = bs.find_all("a",limit=2)

find_all除了上面一些常规的写法,还可以对其进行一些简写:

# 下面两者是相等的
t_list = bs.find_all("a") 
t_list = bs("a") 

# 下面两者是相等的
t_list = bs.a.find_all(text="新闻") 
t_list = bs.a(text="新闻")

2、find()

find()将返回符合条件的第一个Tag,有时我们只需要或一个Tag时,我们就可以用到find()方法了。当然了,也可以使用find_all()方法,传入一个limit=1,然后再取出第一个值也是可以的,不过未免繁琐。

t_list = bs.find_all("title",limit=1) # 返回只有一个结果的列表
t = bs.find("title") # 返回唯一值
t = bs.find("abc") # 如果没有找到,则返回None

从结果可以看出find_all,尽管传入了limit=1,但是返回值仍然为一个列表,当我们只需要取一个值时,远不如find方法方便。但是如果未搜索到值时,将返回一个None。

在上面介绍BeautifulSoup4的时候,我们知道可以通过bs.div来获取第一个div标签,如果我们需要获取第一个div下的第一个div,我们可以这样:

t = bs.div.div
# 等价于
t = bs.find("div").find("div")

六、CSS选择器:select()方法

BeautifulSoup支持部分的CSS选择器,在Tag获取BeautifulSoup对象的.select()方法中传入字符串参数,即可使用CSS选择器的语法找到Tag:

print(bs.select('title'))  # 1、通过标签名查找               
print(bs.select('a'))                                
print(bs.select('.mnav'))  # 2、通过类名查找                
print(bs.select('#u1'))  # 3、通过id查找                  
print(bs.select('div .bri'))  # 4、组合查找               
print(bs.select('a[class="bri"]'))  # 5、属性查找         
print(bs.select('a[href="http://tieba.baidu.com" rel="external nofollow"  rel="external nofollow" ]')) 
print(bs.select("head > title"))  # 6、直接子标签查找        
print(bs.select(".mnav ~ .bri"))  # 7、兄弟节点标签查找       
print(bs.select('title')[0].get_text())  # 8、获取内容

七、综合实例:

from bs4 import BeautifulSoup
import requests,re
req_obj = requests.get('https://www.baidu.com')
soup = BeautifulSoup(req_obj.text,'lxml')

'''标签查找'''
print(soup.title)              #只是查找出第一个
print(soup.find('title'))      #效果和上面一样
print(soup.find_all('div'))    #查出所有的div标签

'''获取标签里的属性'''
tag = soup.div
print(tag['class'])   #多属性的话,会返回一个列表
print(tag['id'])      #查找标签的id属性
print(tag.attrs)      #查找标签所有的属性,返回一个字典(属性名:属性值)

'''标签包的字符串'''
tag = soup.title
print(tag.string)                 #获取标签里的字符串
tag.string.replace_with("哈哈")    #字符串不能直接编辑,可以替换

'''子节点的操作'''
tag = soup.head
print(tag.title)     #获取head标签后再获取它包含的子标签

'''contents 和 .children'''
tag = soup.body
print(tag.contents)        #将标签的子节点以列表返回
print([child for child in tag.children])      #输出和上面一样


'''descendants'''
tag = soup.body
[print(child_tag) for child_tag in tag.descendants]    #获取所有子节点和子子节点

'''strings和.stripped_strings'''
tag = soup.body
[print(str) for str in tag.strings]             #输出所有所有文本内容
[print(str) for str in tag.stripped_strings]    #输出所有所有文本内容,去除空格或空行

'''.parent和.parents'''
tag = soup.title
print(tag.parent)                 #输出便签的父标签
[print(parent) for parent in tag.parents]  #输出所有的父标签

'''.next_siblings 和 .previous_siblings
    查出所有的兄弟节点
'''

'''.next_element 和 .previous_element
    下一个兄弟节点
'''

'''find_all的keyword 参数'''
soup.find_all(id='link2')                   #查找所有包含 id 属性的标签
soup.find_all(href=re.compile("elsie"))     #href 参数,Beautiful Soup会搜索每个标签的href属性:
soup.find_all(id=True)                       #找出所有的有id属性的标签
soup.find_all(href=re.compile("elsie"), id='link1')         #也可以组合查找
soup.find_all(attrs={"属性名": "属性值"})  #也可以通过字典的方式查找

八、BeautifulSoup 和lxml(Xpath)对比

# test.py
# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup, SoupStrainer
import traceback
import json
from lxml import etree
import re
import time

def getHtmlText(url):
    try:
        r = requests.get(url, headers=headers)
        r.raise_for_status()
        if r.encoding == 'ISO-8859-1':
            r.encoding = r.apparent_encoding
        return r.text
    except:
        traceback.print_exc()

# ----------使用BeautifulSoup解析------------------------
def parseWithBeautifulSoup(html_text):
    soup = BeautifulSoup(html_text, 'lxml') 
    content = []
    for mulu in soup.find_all(class_='mulu'): # 先找到所有的 div class=mulu 标记
        # 找到div_h2 标记
        h2 = mulu.find('h2')
        if h2 != None:
            h2_title = h2.string # 获取标题
            lst = []
            for a in mulu.select('div.box a'):
                href = a.get('href') # 找到 href 属性
                box_title = a.get('title')  # 找到 title 属性
                pattern = re.compile(r'\s*\[(.*)\]\s+(.*)') # (re) 匹配括号内的表达式,也表示一个组
                match = pattern.search(box_title)
                if match != None:
                    date = match.group(1)
                    real_title = match.group(2)
                    lst.append({'href':href,'title':real_title,'date':date})
            content.append({'title':h2_title,'content':lst})
    with open('dmbj_bs.json', 'w') as fp:
        json.dump(content, fp=fp, indent=4)

# ----------使用Xpath解析------------------------
def parseWithXpath(html_text):
    html = etree.HTML(html_text)
    content = []
    for div_mulu in html.xpath('.//*[@class="mulu"]'): # 先找到所有的 div class=mulu 标记
        # 找到所有的 div_h2 标记
        div_h2 = div_mulu.xpath('./div[@class="mulu-title"]/center/h2/text()')
        if len(div_h2) > 0:
            h2_title = div_h2[0] # 获取标题
            a_s = div_mulu.xpath('./div[@class="box"]/ul/li/a')
            lst = []
            for a in a_s:
                href = a.xpath('./@href')[0] # 找到 href 属性
                box_title = a.xpath('./@title')[0] # 找到 title 属性
                pattern = re.compile(r'\s*\[(.*)\]\s+(.*)') # (re) 匹配括号内的表达式,也表示一个组
                match = pattern.search(box_title)
                if match != None:
                    date = match.group(1)
                    real_title = match.group(2)
                    lst.append({'href':href,'title':real_title,'date':date})
            content.append({'title':h2_title,'content':lst})
    with open('dmbj_xp.json', 'w') as fp:
        json.dump(content, fp=fp, indent=4)

def main():
    html_text = getHtmlText('http://www.seputu.com')
    print(len(html_text))
    start = time.clock()
    parseWithBeautifulSoup(html_text)
    print('BSoup cost:', time.clock()-start)
    start = time.clock()
    parseWithXpath(html_text)
    print('Xpath cost:', time.clock()-start)

if __name__ == '__main__':
    user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36'
    headers={'User-Agent': user_agent}
    main()

到此这篇关于Python使用Beautiful Soup(BS4)库解析HTML和XML的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持三水点靠木。


Tags in this post...

Python 相关文章推荐
Python实现小数转化为百分数的格式化输出方法示例
Sep 20 Python
python实现图书管理系统
Mar 12 Python
Python中str.join()简单用法示例
Mar 20 Python
python微信公众号之关注公众号自动回复
Oct 25 Python
Python2和Python3.6环境解决共存问题
Nov 09 Python
python处理两种分隔符的数据集方法
Dec 12 Python
Python操作MySQL数据库的两种方式实例分析【pymysql和pandas】
Mar 18 Python
Python3内置模块pprint让打印比print更美观详解
Jun 02 Python
django 自定义filter 判断if var in list的例子
Aug 20 Python
Python OpenCV读取显示视频的方法示例
Feb 20 Python
详解anaconda离线安装pytorchGPU版
Sep 08 Python
如何使用pdb进行Python调试
Jun 30 Python
Python四款GUI图形界面库介绍
Python序列化模块JSON与Pickle
Jun 05 #Python
python 判断字符串当中是否包含字符(str.contain)
Python测试框架pytest高阶用法全面详解
Python使用Web框架Flask开发项目
Jun 01 #Python
numpy array找出符合条件的数并赋值的示例代码
Jun 01 #Python
 python中的元类metaclass详情
May 30 #Python
You might like
PHP分页显示制作详细讲解
2006/10/09 PHP
php 中include()与require()的对比
2006/10/09 PHP
PHP和JavaScrip分别获取关联数组的键值示例代码
2013/09/16 PHP
分享一个超好用的php header下载函数
2014/01/31 PHP
php+ajax实现仿百度查询下拉内容功能示例
2017/10/20 PHP
php处理多图上传压缩代码功能
2018/06/13 PHP
实例化php类时传参的方法分析
2020/06/05 PHP
jQuery的学习步骤
2011/02/23 Javascript
使用变量动态设置js的属性名
2014/10/19 Javascript
JS逆序遍历实现代码
2014/12/02 Javascript
input点击后placeholder中的提示消息消失
2016/01/15 Javascript
jQuery实现元素拖拽并cookie保存顺序的方法
2016/02/20 Javascript
完美实现八种js焦点轮播图(下篇)
2020/04/20 Javascript
省市区三级联动jquery实现代码
2020/04/15 Javascript
微信小程序 wx.uploadFile在安卓手机上面the same task is working问题解决
2016/12/14 Javascript
详解微信小程序开发之城市选择器 城市切换
2017/01/17 Javascript
使用JavaScript触发过渡效果的方法
2017/01/19 Javascript
HTML5+Canvas调用手机拍照功能实现图片上传(下)
2017/04/21 Javascript
基于input动态模糊查询的实现方法
2017/12/12 Javascript
javascript将扁平的数据转为树形结构的高效率算法
2020/02/27 Javascript
vue iview实现动态新增和删除
2020/06/17 Javascript
wepy--用vantUI 实现上弹列表并选择相应的值操作
2020/11/03 Javascript
[52:52]DOTA2上海特级锦标赛C组资格赛#1 OG VS LGD第三局
2016/02/27 DOTA
[54:56]DOTA2上海特级锦标赛主赛事日 - 5 总决赛Liquid VS Secret第三局
2016/03/06 DOTA
python中使用smtplib和email模块发送邮件实例
2014/04/22 Python
Python之pandas读写文件乱码的解决方法
2018/04/20 Python
用python wxpy管理微信公众号并利用微信获取自己的开源数据
2019/07/30 Python
解决margin 外边距合并问题
2019/07/03 HTML / CSS
英国户外服装、鞋类和设备的领先零售商:Millets
2020/10/12 全球购物
string = null 和string = ''的区别
2013/04/28 面试题
竞选部门副经理的自荐书范文
2014/02/11 职场文书
公司合作意向书
2014/04/01 职场文书
中华美德颂演讲稿
2014/05/20 职场文书
邻里守望志愿服务活动方案
2014/08/15 职场文书
创业开店,这样方式更合理
2019/08/26 职场文书
Pygame游戏开发之太空射击实战敌人精灵篇
2022/08/05 Python