• 爬虫之Beautifulsoup及xpath


    1.BeautifulSoup (以 Python 风格的方式来对 HTML 或 XML 进行迭代,搜索和修改)

    1.1 介绍 

      Beautiful Soup提供一些简单的、python式的函数用来处理导航、搜索、修改分析树等功能。它是一个工具箱,通过解析文档为用户提供需要抓取的数据,因为简单,所以不需要多少代码就可以写出一个完整的应用程序。

    1.2 解析器

      Beautiful Soup支持Python标准库中的HTML解析器,还支持一些第三方的解析器,如果我们不安装它,则 Python 会使用 Python默认的解析器,lxml 解析器更加强大,速度更快,推荐安装。

    官方文档

    1.3 使用

      借用官方文档提供的爱丽丝梦游仙境文档内容

    html_doc = """
    <html><head><title>The Dormouse's story</title></head>
    <body>
    <p class="title"><b>The Dormouse's story</b></p>
    
    <p class="story">Once upon a time there were three little sisters; and their names were
    <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
    <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
    <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
    and they lived at the bottom of a well.</p>
    
    <p class="story">...</p>
    """

     1.标签获取

    from bs4 import BeautifulSoup
    
    soup=BeautifulSoup(html_doc,'html.parser')
    
    # 获取a标签的所有内容
    print(soup.a)       # <a class="sister 123" href="http://example.com/elsie" id="link1">Elsie</a>
    print(type(soup.a))     # <class 'bs4.element.Tag'>
    
    
    # 获取title节点的所有内容
    print(soup.head.title)  # <title>The Dormouse's story</title>
    
    
    print(soup.find_all("a"))   # 获取所有符合条件的标签对象集合,以列表形式展示

    2.标签对象的属性,名称,文本

    from bs4 import BeautifulSoup
    
    soup=BeautifulSoup(html_doc,'html.parser')
    
    for link in soup.find_all('a'):
        # print(link.name)       # 获取标签名称
    
        # print(link.get('href'))     # 获取a标签中的所有href
        # print(link["href"])
        # print(link.get("id"))   # 获取标签的id
        # print(link.get("class"))    # 获取标签的class值
        # print(link.attrs)       # 获取a标签中的所有属性
        # del link["id"]
        # print(link.attrs)   # 获取除了a标签中除id属性外的所有属性
    
        print(link.text)    # 获取a标签下的文本信息
        print(link.string)
        print(link.get_text())

    # text和string的区别

    print(soup.p.string)    # None
    print(soup.p.text)  # The Dormouse's story      123

    1.4 文档树信息获取

    print(soup.head.title.string)   #连续跨节点获取文本信息
    print(soup.body.a.string)       # 获取a标签下的第一个文本信息
    
    # 子节点,子孙节点
    print(soup.p.contents)      # 获取第一个p标签下的所有文本信息,最终在一个列表内
    # ['
    ', <b>The Dormouse's story</b>, '
    ', <span alex="dsb" class="123">123</span>, '
    ']
    
    print(soup.p.children)  # 包含p下所有子节点的生成器
    for child in soup.p.children:
        print(child)    # 获取p下的所有子节点
    
    print(soup.p.descendants)   # 获取子孙节点,p下所有的标签都会选择出来
    for child in soup.p.descendants:
        print(child)
    
    # 父节点,祖先节点
    print(soup.p.parent)      # 获取p标签的父节点,得到整个body
    print(soup.p.parents)     # 一个生成器,找到p标签所有的祖先节点
    
    # 兄弟节点
    print(soup.a.next_sibling)  # a节点的下一个兄弟,得到一个逗号
    print(soup.a.next_siblings) # 一个生成器,下面的兄弟们
    
    print(soup.a.previous_sibling)  # 上一个兄弟,即得到了上面的文本信息
    print(soup.a.previous_siblings) # 一个生成器,上面的兄弟们

    搜索文档树下的几种过滤器(结合find_all)  

    还是借用官方文档提供的内容

    html_doc = """
    <html><head><title>The Dormouse's story</title></head>
    <body>
    <p class="title"><b>The Dormouse's story</b></p>
    
    <p class="story">Once upon a time there were three little sisters; and their names were
    <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
    <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
    <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
    and they lived at the bottom of a well.</p>
    
    <p class="story">...</p>
    """

    第一种:字符串

    ret = soup.find_all(name="a")   # a标签的所有节点

    第二种:正则

    import re
    tmp = re.compile("^h")
    rep = soup.find_all(name=tmp)   # 获取所有以h开头的标签节点,包含html标签和head标签

    第三种:列表

    ret = soup.find_all(name=["a","b"])     # 获取所有的a标签和b标签

    第四种:方法

    def has_class_but_no_id(tag):
        return tag.has_attr('class') and not tag.has_attr('id')
        
    for tag in soup.find_all(name=has_class_but_no_id):
        print(tag)  # 获取具有class属性但不具有id属性的标签

    关于limit参数:

      如果我们不需要全部结果,可以使用 limit参数限制返回结果的数量

    print(soup.find_all('a',limit=2))

    关于recursive参数:

      调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False

    print(soup.html.find_all('a',recursive=False))

     find的使用(只返回一个):

      find_all() 方法的返回结果是值包含一个元素的列表,而 find() 方法直接返回结果

    print(soup.find('a'))
    # <a class="sister" href="http://example.com/elsie" id="link1">Elsie</a>

      soup.head.title 是 tag的名字 方法的简写.这个简写的原理就是多次调用当前tag的 find() 方法:

    soup.head.title
    # <title>The Dormouse's story</title>
    soup.find("head").find("title")
    # <title>The Dormouse's story</title>

    1.5 css选择器

       这里其实通过名称就可以知道,它是通过css属性来进行查找的

    ret=soup.select("a")          # 标签名查找
    ret=soup.select("#link1")     # 通过id查找
    ret=soup.select(".sister")    # 通过类名查找
    ret=soup.select(".c1 p,a")    # 组合查找
    ret = soup.select("a[href='http://example.com/tillie']")  # 通过属性查找

    更多介绍可以查看官方文档

    2.xpath (快速,简单易用,功能齐全的库,用来处理 HTML 和 XML)

       xpath全称为XML Path Language, 一种小型的查询语言,实现的功能与re以及bs一样,但是大多数情况会选择使用xpath

      由于XPath属于lxml库模块,所以首先要安装库lxml

    调用方法:

    from lxml import etree
    
    selector=etree.HTML('源码')   # 将源码转化为能被XPath匹配的格式
    # <Element html at 0x29b7fdb6708>
    ret = selector.xpath('表达式')     # 返回为一列表

    2.1 查询语法

    原文

    html_doc = """
    <html><head><title>The Dormouse's story</title></head>
    <body>
    <p class="title"><b>The Dormouse's story</b></p>
    
    <p class="story">Once upon a time there were three little sisters; and their names were
    <a href="http://example.com/elsie" class="sister" id="link1">Elsie</a>,
    <a href="http://example.com/lacie" class="sister" id="link2">Lacie</a> and
    <a href="http://example.com/tillie" class="sister" id="link3">Tillie</a>;
    and they lived at the bottom of a well.</p>
    
    <p class="story">...</p>
    """

    这里首先需要将它转换成xpath匹配的格式

    from lxml import etree
    selector=etree.HTML(html_doc)   # 将源码转化为能被XPath匹配的格式

     节点选取

    nodename     选取nodename节点的所有子节点         xpath(‘//div’)         选取了所有div节点
    /            从根节点选取                        xpath(‘/div’)          从根节点上选取div节点
    //           选取所有的当前节点,不考虑他们的位置    xpath(‘//div’)         选取所有的div节点
    .            选取当前节点                        xpath(‘./div’)         选取当前节点下的div节点
    ..           选取当前节点的父节点                 xpath(‘..’)            回到上一个节点
    @            选取属性                           xpath(’//@calss’)     选取所有的class属性

      用法

    from lxml import etree
    selector = etree.HTML(html_doc)
    
    ret=selector.xpath("//p")
    # [<Element p at 0x2a6126569c8>, <Element p at 0x2a612656a08>, <Element p at 0x2a612656a48>]
    ret=selector.xpath("//p/text()")   # 打印当中的文本信息,包括换行符
    
    ret=selector.xpath("/p")     # []
    
    ret=selector.xpath("//a[@id='link1']")     # [<Element a at 0x1c541e43808>]
    ret=selector.xpath("//a[@id='link1']/text()")     # ['Elsie']

     谓语用法(返回的都是element对象)

    表达式                                         结果
    xpath(‘/body/div[1]’)                     选取body下的第一个div节点
    xpath(‘/body/div[last()]’)                选取body下最后一个div节点
    xpath(‘/body/div[last()-1]’)              选取body下倒数第二个div节点
    xpath(‘/body/div[positon()<3]’)           选取body下前两个div节点
    xpath(‘/body/div[@class]’)                选取body下带有class属性的div节点
    xpath(‘/body/div[@class=”main”]’)         选取body下class属性为main的div节点
    xpath(‘/body/div[price>35.00]’)           选取body下price元素值大于35的div节点

    通配符

    表达式                 结果
    xpath(’/div/*’)     选取div下的所有子节点
    xpath(‘/div[@*]’)    选取所有带属性的div节点

    多个路径的选取

    表达式                         结果
    xpath(‘//div|//table’)    选取所有的div和table节点

    代码

    from lxml import etree
    selector = etree.HTML(html_doc)
    
    ret = selector.xpath('//title/text()|//a/text()')
    # ["The Dormouse's story", 'Elsie', 'Lacie', 'Tillie']

    2.2 xpath轴

      轴可以定义相对于当前节点的节点集

    轴名称                      表达式                                  描述
    ancestor                xpath(‘./ancestor::*’)              选取当前节点的所有先辈节点(父、祖父)
    ancestor-or-self        xpath(‘./ancestor-or-self::*’)      选取当前节点的所有先辈节点以及节点本身
    attribute               xpath(‘./attribute::*’)             选取当前节点的所有属性
    child                   xpath(‘./child::*’)                 返回当前节点的所有子节点
    descendant              xpath(‘./descendant::*’)            返回当前节点的所有后代节点(子节点、孙节点)
    following               xpath(‘./following::*’)             选取文档中当前节点结束标签后的所有节点
    following-sibing        xpath(‘./following-sibing::*’)      选取当前节点之后的兄弟节点
    parent                  xpath(‘./parent::*’)                选取当前节点的父节点
    preceding               xpath(‘./preceding::*’)             选取文档中当前节点开始标签前的所有节点
    
    preceding-sibling       xpath(‘./preceding-sibling::*’)     选取当前节点之前的兄弟节点
    self                    xpath(‘./self::*’)                  选取当前节点

    用法

    ret = selector.xpath('//a/ancestor::*')
    # [<Element html at 0x168a62717c8>, <Element body at 0x168a6271748>, <Element p at 0x168a6271708>]
    
    ret = selector.xpath('//a/parent::*/text()')
    # ['Once upon a time there were three little sisters; and their names were
    ', ',
    ', ' and
    ',
    #  ';
    and they lived at the bottom of a well.']
    
    ret = selector.xpath('//a/attribute::*')
    # ['http://example.com/elsie', 'sister', 'link1', 'http://example.com/lacie', 'sister',
    #  'link2', 'http://example.com/tillie', 'sister', 'link3']

    2.3 功能函数

      使用功能函数能够进行模糊搜索

    函数                  用法                                                               解释
    starts-with         xpath(‘//div[starts-with(@id,”ma”)]‘)                        选取id值以ma开头的div节点
    contains            xpath(‘//div[contains(@id,”ma”)]‘)                           选取id值包含ma的div节点
    and                 xpath(‘//div[contains(@id,”ma”) and contains(@id,”in”)]‘)    选取id值包含ma和in的div节点
    text()              xpath(‘//div[contains(text(),”ma”)]‘)                        选取节点文本包含ma的div节点

    用法

    from lxml import etree
    selector = etree.HTML(html_doc)
    
    # p标签class属性为story,在它下面的a标签id属性以link开头的文本信息
    ret=selector.xpath("//p[@class='story']/a[starts-with(@id,'link')]/text()")
    # ['Elsie', 'Lacie', 'Tillie']
    
    # p标签class属性为story,在它下面的a标签id属性包含k的文本信息
    ret=selector.xpath("//p[@class='story']/a[contains(@id,'k')]/text()")
    # ['Elsie', 'Lacie', 'Tillie']
    
    # p标签class属性为story,在它下面的a标签class属性包含is的文本信息
    ret=selector.xpath("//p[@class='story']/a[contains(@class,'is')]/text()")
    # ['Elsie', 'Lacie']
    
    # 选取p标签class属性为story,在它下面的a标签文本信息包含ie的文本信息
    ret=selector.xpath("//p[@class='story']/a[contains(text(),'ie')]/text()")
    # ['Elsie', 'Lacie', 'Tillie']

    更多介绍可以参考w3c

    2.4 链家二手房信息地的抓取

      打开链家网,选取我们需要的信息,点击右键在copy中点击copy xpath

    得到如下内容:

    //*[@id="leftContent"]/ul/li[1]/div/div[1]/a

    代码:

    import requests
    from lxml import etree
    
    response = requests.get("https://bj.lianjia.com/ershoufang/changping/pg1/",
            headers={
                'Referer':'https://bj.lianjia.com/ershoufang/changping/',
                'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/70.0.3534.4 Safari/537.36',
                        })
    
    selector=etree.HTML(response.content) # 将html源码转化为能被XPath匹配的格式
    
    ret = selector.xpath("//*[@id='leftContent']/ul/li[1]/div/div[1]/a/text()")
    print(ret)      # ['商品房满五年唯一 有电梯高楼层 东南2居室 已留钥匙']

    这里我们要获取首页所有该房源名称呢

    ret = selector.xpath("//*[@id='leftContent']/ul/li[1]//div/div[1]/a/text()")

    注意两个的区别,这里我们不考虑它的位置

    3.总结

      几种获取节点的库比较:

    抓取方式  

    性能

    使用难度

    re正则

    困难

    BeautifulSoup

    简单  

    Xpath

      通常情况下,lxml 是抓取数据的最好选择,它不仅速度快(结合谷歌浏览器),功能也更加丰富,而正则表达式和 Beautiful Soup只在某些特定场景下有用

  • 相关阅读:
    团队的展示以及规划
    基于上次数独基础优化,并添加GUI界面
    利用程序随机构造N个已解答的数独棋盘
    本学期高级软件工程课程的实践项目的自我目标
    本学期高级软件工程课程的实践项目的自我目标
    案列分析
    编程作业
    构建之法
    软工一
    小黄衫获奖感言
  • 原文地址:https://www.cnblogs.com/LearningOnline/p/9721092.html
Copyright © 2020-2023  润新知