zoukankan      html  css  js  c++  java
  • 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 限制时, 就会停止搜索返回结果
  • 相关阅读:
    leetcode 29-> Divide Two Integers without using multiplication, division and mod operator
    ros topic 发布一次可能会接收不到数据
    python中的print()、str()和repr()的区别
    python 部分函数
    uiautomatorviewer错误 unable toconnect to adb
    pyqt 不规则形状窗口显示
    appium 计算器demo
    Spring 3.0 注解注入详解
    Spring Autowire自动装配
    restful 学习地址
  • 原文地址:https://www.cnblogs.com/gxfaxe/p/15264817.html
Copyright © 2011-2022 走看看