• python3 之 bs4 BeautifulSoup 简单使用


    python3 bs4

    Beautiful Soup

    • Beautiful Soup 是一个可以从HTML或XML文件中提取数据的Python库。它能够通过你喜欢的转换器实现惯用的文档导航,查找,修改文档的方式
    • 官方文档

    解析器

    • 对网页进行析取时,若未规定解析器,此时使用的是python内部默认的解析器“html.parser”。
    • 官方文档上多次提到推荐使用"lxml"和"html5lib"解析器,因为默认的"html.parser"自动补全标签的功能很差,经常会出问题。
    • 解析器是什么呢?
      • BeautifulSoup做的工作就是对html标签进行解释和分类,不同的解析器对相同html标签会做出不同解释。
    解析器 使用方法 优势 劣势
    python 标准库 BeautifulSoup(markup, "html.parser") 1、python 内置的标准库 python2.7.2 or python3.2.2 前的文档容错性差
    2、执行速度适中
    3、文档容错能力强
    lxml HTML 解析 BeautifulSoup(markup, "lxml") 1、速度快 需要安装C语言库
    2、文档容错能力强
    lxml XML 解析器 BeautifulSoup(markup, ["lxml","xml"]) 1、速度快 需要安装C语言库
    BeautifulSoup(markup, "xml") 2、唯一支持 XML 的解析器
    html5lib BeautifulSoup(markup, "html5lib") 1、最好的容错性 1、需要安装C语言库
    2、以浏览器的方式解析文档 2、不依赖外部扩展
    3、生成 HTML5 格式的文档

    安装及基本使用

    • Windows下安装
    # 安装 BeautifulSoup
    pip install beautifulsoup4
    
    # 安装解析器
    # Beautiful Soup 支持 python 标准库中 HTML 解析器, 还支持一些第三方的解析器, 其中一个是 lxml
    # 安装 lxml
    pip install lxml
    # 另一个可供选择的解析器是纯 python 实现的 html5lib, html5lib与浏览器相同, 可以选择下列方法来安装
    pip install html5lib
    

    BeautifulSoup的使用

    实例化

    html_file_path = os.path.join(os.getcwd(), '../html_dir', 'test_lxml.html')
    
    html_file = ''
    with open(html_file_path, 'r') as f:
    	lines  = f.readlines()
    	for line in lines:
    		html_file += line
    
    # 初始化时自动更正格式, 输出结果中包含 html 和 body 节点, 不会自动缩进
    # "lxml": 指定解析器, 优先使用 lxml 解析器
    soup = BeautifulSoup(html_file, 'lxml')     # 传入字符串格式的 HTML
    soup = BeautifulSoup(open(html_file_path))      # 传入一个文件对象
    
    • HTML 文档的内容
    <head><title>The Dormouse's story</title></head>
    
    <p class="story">
    	this is P label
    	<a href="http://www.baidu.com" class="baidu" id="link1"><span>baidu</span></a><span>this is span</span>
    	<a href="http://www.cnblogs.com" class="cnblogs" id="link2"><span>cnblogs</span></a>
    </p>
    <div>
    	<ul class="ul1">
    		<li class="item-0 li" name="item0"><a href="link1.html">first item</a></li>
    		<li class="item-1"><a href="link2.html">second item</a></li>
    		<li class="item-inactive"><a href="link3.html">third item</a></li>
    		<li class="item-1"><a href="link4.html">fourth item</a></li>
    		<li class="item-0"><a href="link5.html">fifth item</a></li>
    		<li class="aaa li-aaa"><a href="link6.html">aaaaa item</a></li>
    		<li class="li li-first" name="item6"><a href="link6.html"><span>six item</span></a></li>
    		<li class="li li-first" name="item7"><a href="link7.html"><span>seven item</span></a></li>
    	</ul>
    	
    	<ul class="ul2">
    		<li class="item-10 li" name="item10"><a href="link10.html">10 item</a></li>
    		<li class="item-11 li" name="item11"><a href="link11.html">11 item</a></li>
    		<li class="item-12 li" name="item12"><a href="link12.html">12 item</a></li>
    		<li class="item-13 li" name="item13"><a href="link13.html">13 item</a></li>
    		<li class="item-14 li" name="item14"><a href="link14.html">14 item</a></li>
    		<li class="item-15 li" name="item15"><a href="link15.html">15 item</a></li>
    		<li class="item-16 li" name="item16"><a href="link16.html">16 item</a></li>
    	</ul>
    </div>
    

    Tag

    • name
      • 每一个标签都有自己的名字, 通过 tag.name 的方式获取
      • tag.name = "tag_new_name": 修改标签的名字, 后面在获取该标签信息需要使用新名字, tag_new_name.name
    print(f'通过 .name 获取标签名: {soup.p.name}')
    soup.p.name = 'p_tag'   # 修改 p 标签名
    print(f'需要通过修改后的标签名 p_tag.name 获取标签名: {soup.p_tag.name}')
    print(f'通过 .name 获取标签名: {soup.ul.name}')
    soup.ul.name = 'new_ul'   # 修改 ul 标签名
    print(f'需要通过修改后的标签名 p_tag.name 获取标签名: {soup.new_ul.name}')
    
    • attributes
      • 一个标签可能有很多属性, 比如: class、name、id..., 标签属性的操作方法和字典相同
      • tag.attrs: 获取标签所有属性, 返回一个字典格式的 {属性: 属性值} 键值对
      • 标签属性的操作方法和字典一样, 增删改查
    print(f"需要通过修改后的标签名 p_tag.name 获取 class 属性: {soup.p_tag['class']}")
    soup.p_tag['id'] = 'p1'     # p 标签增加 id 属性
    soup.p_tag['class'] = 'story'     # p 标签修改 class 属性
    print(f"需要通过修改后的标签名 p_tag.name 获取所有属性: {soup.p_tag.attrs}")
    print(f"需要通过修改后的标签名 p_tag.name 获取 id 属性: {soup.p_tag['id']}")
    
    • 多值属性
      • HTML4 定义了一系列可以包含多个值的属性。在 HTML5 中移除了一些,却增加更多.最常见的多值的属性是 class (一个tag可以有多个CSS的class). 还有一些属性 rel , rev , accept-charset , headers , accesskey . 在Beautiful Soup中多值属性的返回类型是list
    print(f"获取 li 标签的所有属性, class 是多值属性, value 是列表格式的两个属性值: {soup.li.attrs}")
    
    • 如果某个属性看起来好像有多个值, 但在任何版本的 HTML 定义中都没有被定义为多值属性, 那么 Beautiful Soup 会将这个属性作为字符串返回
    id_soup = BeautifulSoup("<p id='my id1'></p>")
    print(f"HTML未定义过的多值属性, 将两个值返回成一个字符串: {id_soup.p['id']}")
    
    • 如果转换的文档是XML格式,那么tag中不包含多值属性
    id_soup = BeautifulSoup("<p id='my id1'></p>", 'xml')
    print(f"xml 格式中的多值属性, 将两个值返回成一个字符串: {id_soup.p['id']}")
    
    • 可遍历的字符串
    print(f'可遍历的字符串: {soup.a.string}, type: {type(soup.a.string)}')
    soup.a.string.replace_with('No longer bold')    # 标签的 字符串不能编辑, 但是可以替换
    print(f"可遍历的字符串, 替换后的字符串: {soup.a.string}, type: {type(soup.a.string)}")
    

    子节点

    • Tag 的名字
      • 操作文档树最简单的方法就是告诉它你想获取的 tag 的 name。如果想获取 标签,只要用 soup.head :
      • 可以在文档树的tag中多次调用这个方法
    print(f'Tag 的名字, 将会打印包括 head 标签及其内的所有内容: {soup.head}')
    print(f'获取 title: {soup.head.title}')
    print(f'获取 ul 标签下 li 标签下 a 标签的名字: {soup.ul.li.a}')
    
    • find_all()
      • 查找所有符合条件的标签
    print(f"查找所有的 a 标签数量: {len(soup.find_all('a'))}, 结果: {soup.find_all('a')}")
    
    • contents()
      • 将 tag 的子节点以列表的方式输出
      • .contents 属性仅包含tag的直接子节点
    print(f"查找所有的 ul 标签下的第二个 li 标签下的 a 标签: {soup.ul.contents[3].a}")
    print(f'contents 将子节点以列表的形式输出: 数量: {len(soup.ul.contents)}, 结果: {soup.ul.contents}')
    
    • children
      • 返回对象是一个生成器
      • children 属性仅包含tag的直接子节点
    li_list = soup.ul
    for item in li_list.children:
    	if item != '
    ':    # 去掉换行符
    		print(f'ul 下的 li 标签下的 a 标签的文本: {item.a.string}')
    
    • descendants
      • 返回对象是一个生成器
      • descendants 属性可以对所有 tag 的子孙节点进行递归循环
    li_list = soup.ul
    print(f'descendants 对象是一个生成器: {len(list(li_list.descendants))}, 结果: {li_list.descendants}')
    for item in li_list.descendants:
    	if item != '
    ':    # 去掉换行符
    		print(f'descendants 递归循环 ul 下的所有子孙节点: {item}')
    
    • string
      • 如果tag只有一个 NavigableString 类型子节点,那么这个tag可以使用 .string 得到子节点
      • 如果一个tag仅有一个子节点,那么这个tag也可以使用 .string 方法,输出结果与当前唯一子节点的 .string 结果相同
      • 如果tag包含了多个子节点,tag就无法确定 .string 方法应该调用哪个子节点的内容, .string 的输出结果是 None
    print(f"head 只有一个 title 子节点: {soup.head.string}")
    print(f"title 只有一个文本子节点: {soup.head.title.string}")
    print(f"ul 有多个子节点: {soup.ul.string}")
    
    • strings 和 stripped_strings
      • 返回的是一个生成器
      • 如果 tag 中包含多个字符串, 可以使用 .strings 来循环获取, 但是会包含空白内容或换行符等
      • 使用 .stripped_strings 可以去除多余空白内容, 全部是空格的行会被忽略掉,段首和段末的空白会被删除
    print('使用 strings 获取 ul 下的多个子节点')
    for item in soup.ul.strings:
    	if item != '
    ':
    		print(item)
    
    print('使用 stripped_strings 获取 ul 下的多个子节点')
    for item in soup.ul.stripped_strings:
    	if item != '
    ':
    		print(item)
    

    父节点

    • parent
      • 通过 parent 属性来获取某个元素的父节点
    print(f'获取 title 的父节点 head: {soup.title.parent}')
    print(f'获取 title 文本的父节点 title: {soup.title.string.parent}')
    print(f'获取 html 顶层节点的父节点是整个 HTML, 返回 bs4.BeautifulSoup 对象: {type(soup.html.parent)}')
    print(f'soup 的 parent 是 None: {soup.parent}')
    
    • parents
      • 返回对象是一个生成器
      • 通过元素的 .parents 属性可以递归得到元素的所有祖先节点
    print(f'获取 title 的所有的祖先节点, 返回对象是一个生成器: {soup.title.parents}')
    for item in soup.title.parents:
    	if item != '
    ':
    		print(item, end='
    ')
    

    兄弟节点

    • next_sibling【下一个兄弟节点】 和 previous_sibling【上一个兄弟节点】
      • 实际文档中的tag的 .next_sibling 和 .previous_sibling 属性通常是字符串或空白
      • 如果以为第一个
      • 标签的 .next_sibling 结果是第二个
      • 标签,那就错了,真实结果是第一个
      • 标签和第二个
      • 标签之间的换行符
    print(f'ul 节点下的 li 节点: {list(soup.ul.children)}')
    # 注意: 我下面选择的元素都是换行符, 所以打印的结果是标签
    print(f'ul 节点下的 li 节点的下一个兄弟节点: {list(soup.ul.children)[0].next_sibling}')
    print(f'ul 节点下的 li 节点的下一个兄弟节点: {list(soup.ul.children)[2].next_sibling}')
    print(f'ul 节点下的 li 节点的上一个兄弟节点: {list(soup.ul.children)[4].previous_sibling}')
    print(f'ul 节点下的 li 节点的上一个兄弟节点: {list(soup.ul.children)[2].previous_sibling}')
    
    • 通过 .next_siblings 和 .previous_siblings 属性可以对当前节点的兄弟节点迭代输出
      • .next_siblings 和 .previous_siblings: 返回结果是生成器
    print(f'ul 节点下的 li 节点: {list(soup.ul.children)}')
    print(f'next_siblings : {list(soup.ul.children)[0].next_siblings}')
    print(f'previous_siblings : {list(soup.ul.children)[4].previous_siblings}')
    print('迭代 next_siblings 的结果: ')
    # 这次循环打印会有换行
    for item in list(soup.ul.children)[0].next_siblings:
    	print(item)
    
    print('迭代 previous_siblings 的结果: ')
    # 这次循环打印会有换行
    for item in list(soup.ul.children)[4].previous_siblings:
    	print(item)
    

    回退和前进

    • .next_element 和 .previous_element
      • next_element:
        • 指向解析过程中下一个被解析的对象(字符串或tag),结果可能与 .next_sibling 相同,但通常是不一样的
        • next_element 解析的内容当前标签内的内容, 而不是当前标签结束后的下一个标签
        • 例如: <li class="item-10 li" name="item10"><a href="link10.html">10 item</a></li>
          • 解析器先进入 <li> 标签, 然后是 <a> 标签, 然后是字符串 10 item, 然后关闭 </a> 标签, 关闭 </li> 标签
          • next_element 解析的就是 <li> 标签后面一个对象 <a> 标签
      • previous_element: 与 next_element 正好相反, 当前对象的上一个解析对象
    print(f'ul 节点下的 li 节点: {list(soup.ul.children)}')
    print(f'ul 节点下的 li 节点下的所有子节点 第三个 li: {list(soup.ul.children)[3]}')
    print(f'ul 节点下的 li 节点下的的所有子节点 第三个 li 的中的标签 a: {list(soup.ul.children)[3].next_element}')
    print(f'ul 节点下的 li 节点下的的所有子节点 第三个 li 的中的标签 a 的上一个解析标签: {list(soup.ul.children)[3].next_element.previous_element}')
    
    
    • .next_elements 和 .previous_elements
      • 返回的是生成器
      • 通过 .next_elements 和 .previous_elements 的迭代器就可以向前或向后访问文档的解析内容,就好像文档正在被解析一样
    print(f'ul 节点下的 li 节点: {list(soup.ul.children)}')
    print(f'ul 节点下的 li 节点下的所有子节点 第三个 li: {list(soup.ul.children)[3]}')
    print(f'ul 节点下的 li 节点下的的所有子节点 第三个 li 的中的标签 a: next_elements')
    for item in list(soup.ul.children)[3].next_elements:
    	print(item, end='
    ==========
    ')
    
    print(f'ul 节点下的 li 节点下的的所有子节点 第三个 li 的中的标签 a 的上一个解析标签: previous_elements')
    for item in list(soup.ul.children)[3].next_element.previous_elements:
    	print(item, end='
    ==========
    ')
    

    搜索文档树

    • find()

      • 获取匹配的第一个标签
      • find(name, attrs, recursive, text, **kwargs)
      • 唯一的区别是 find_all() 方法的返回结果是值包含一个元素的列表, 而 find() 方法直接返回结果
      • find_all() 方法没有找到目标是返回空列表, find() 方法找不到目标时, 返回 None
      • soup.head.title 是标签的名字方法的简写, 这个简写的原理就是多次调用当前标签的 find() 方法
        • soup.head.title 和 soup.find('head').find('title') 实际一样
    • find_all()

      • find_all(): 方法搜索当前tag的所有tag子节点,并判断是否符合过滤器的条件
      • find_all(name, attrs, recursive, text, **kwargs )
        • name:

          • name 参数可以查找所有名字为 name 的标签, 字符串对象会被自动忽略掉
          • name 参数可以是任意类型过滤器, 字符串, 正则表达式, 列表, True
        • recursive:

          • recursive=False: 只搜索标签的直接子节点
        • keyword:

          • 如果指定名字的参数不是搜索内置参数名, 搜索时会把该参数当做指定名字标签的属性来搜索, 如果包含一个名字为 id 的参数, Beautiful Soup 会搜索每个标签的 id 属性
          • 如果传入 href 参数, Beautiful Soup 会搜索每个标签的 href 属性
          • 搜索指定名字的属性时可以使用的参数包括: 字符串, 正则表达式, 列表, True
          • 有些标签属性搜索不能使用, 比如: HTML5 中的 data-* 属性, 可以通过 find_all() 方法的 attr 参数定义一个字典参数来搜索包括含特殊属性的标签
    print(f"两个方法等价: {soup.title.find_all(text=True)}, {soup.title(text=True)}")
    print(f"定义一个字典参数来搜索包含特殊属性的标签: {soup.find_all(attrs={'data-foo': 'value'})}")
    
    • 字符串: 在搜索方法中传入一个字符串参数, Beautiful Soup 会查找与字符串完整匹配的内容
      • 如果传入字节码参数, Beautiful Soup 会当做 UTF-8 编码, 可以传入一段 Unicode 编码来避免 Beautiful Soup 解析编码出错
    print(f"查找所有的 a 标签: {soup.find_all('a')}")
    print(f"查找所有的 title 标签: {soup.find_all('title')}")
    
    • 正则表达式: 如果传入正则表达式作为参数, Beautiful Soup 会通过正则表达式的 match() 来匹配内容
    print(f"查找所有的 p 开头的标签: {soup.find_all(re.compile('^p'))}")
    print(f"查找所有的 ul 开头的标签: {soup.find_all(re.compile('^ul'))}")
    print(f"查找所有包含的 l 标签: 数量: {len(soup.find_all(re.compile('l')))}, 结果: {soup.find_all(re.compile('l'))}")
    
    • 列表: 如果传入列表参数, Beautiful Soup 会将与列表中任意一元素匹配的内容返回
    print(f"查找所有的 a、title、form 标签: {soup.find_all(['a', 'title', 'form'])}")
    
    • True: 可以匹配任何值, 查找所有的标签, 但是不会返回字符串节点
    print(f"查找所有的标签: {soup.find_all(True)}")
    
    • 方法传参
      • 如果没有合适的过滤器, 那么还可以自定义一个方法, 方法只接受一个参数, 如果这个方法返回 True 表示当前元素匹配并且被找到, 如果不是则返回 None
    print(f"查找所有包含 class 和 id 属性: {soup.find_all(lambda tag: tag.has_attr('class') and tag.has_attr('id'))}")
    
    • 按 CSS 搜索

      • 标识CSS类名的关键字 class 在 Python中是保留字, 使用 class 做参数会导致语法错误, 从 Beautiful Soup 的 4.1.1 版本开始, 可以通过 class_ 参数搜索有指定 CSS 类名的标签
      • class_ 参数同样接受不同类型的过滤器, 字符串, 正则表达式, 方法, True
      • 标签的 class 属性是多值属性, 按照 CSS 类名搜索标签时, 可以分别搜索标签中的每个 CSS 类名
      • 搜索 class 属性时也可以通过 CSS 值完全匹配
      • 完全匹配时, 如果 CSS 的类名的顺序与实际不符, 将搜索不到结果
    • text 参数

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

      • find_all() 方法返回所有的搜索结果, 如果文档树很大搜索结果会很慢, 如果我们不需要全部结果, 可以使用 limit 参数限制返回结果的数量, 效果与 SQL 中的 limit 关键字类似, 当搜索到的结果达到 limit 限制时, 就会停止搜索返回结果
  • 相关阅读:
    npm常用命令
    React进阶
    ant按需加载、配置configoverrides.js文件、项目中引入less、解决TypeError: this.getOptions is not a function错误
    数组常用api
    react项目实战简单登录注册
    Hoot新特性
    win10全局安装插件却不能全局用,https:/go.microsoft.com/ fwlink/?LinkID=135170禁止运行脚本
    Ant Design编辑表格(类组件)
    反射解决类的复制
    基于Jquery和Ajax的多选框
  • 原文地址:https://www.cnblogs.com/gxfaxe/p/15264817.html
Copyright © 2020-2023  润新知