• 自学Python第十五天-爬虫解析工具 RE 、BS4 和 xpath



    使用 python 编写爬虫一个重要的步骤就是在爬到的网页内容中,抓取所需要的数据。这里就需要使用各种解析工具了。

    解析工具

    解析工具用来分析爬取到的内容,常用的工具有:

    • 正则表达式
      其实对于解析工具多数都会首推正则表达式,因为它最好用,但是也最难用。由于正则表达式的使用范围广,用法复杂,所以单独进行研究。这里只研究 python 中如何使用正则表达式。
    • BeautifulSoup
      BeautifulSoup 是个第三方库,它用起来最简单,这里使用版本4。
    • lxml(xpath)
      lxml 也是个第三方库,如果想使用的好,还需要 xpath 的相关知识。

    re 库

    re 库是 python 用来操作正则表达式的一个内置模块,直接引用就可以使用了。

    注,很多时候返回的是一个 Match 对象,可以使用 group 方法获取对象中的匹配结果

    常用的方法

    re 模块常用的方法有:

    • findall :查找所有,返回 list,re.findall(regexp, string)->List
    lst = re.findall('m', 'mai le fo len, mai ni mei!')
    print(lst)		# ['m', 'm', 'm']
    lst = re.findall(r"\d+", '5点之前,你要给我5000万')
    print(lst)		# ['5', '5000']
    
    • 1
    • 2
    • 3
    • 4
    • search :查找匹配,返回第一个查找结果的Match对象,如果没有匹配则返回 None
    ret = re.search(r"\d", '5点之前,你要给我5000万').group()
    print(ret)		# 5
    
    • 1
    • 2
    • match :从字符串的开始进行匹配,返回Match对象
    • finditer :类似findall,但是返回是Match对象的迭代器对象。
    • compile :预加载正则表达式,返回正则对象 re.compile(regexp)->Re
    obj = re.complie(r'\d')
    ret = obj.search('5点之前,你要给我5000万')
    # complie 方法的参数可以对正则表达式进行扩展,例如
    obj = re.complie(r'11.*?11', re.S)	# 参数2 re.S 可以让正则表达式中 . 可以匹配换行符
    
    • 1
    • 2
    • 3
    • 4

    分组

    当匹配结果有很多,而我们只想要匹配结果其中的一部分时,可以使用分组

    import re
    
    s = """
    <div class='class1'><span id='1'>孙悟空</span></div>
    <div class='class2'><span id='2'>猪八戒</span></div>
    <div class='class3'><span id='3'>唐僧</span></div>
    <div class='class4'><span id='4'>沙和尚</span></div>
    <div class='class5'><span id='5'>白龙马</span></div>
    """
    
    obj = re.compile(r"<div class='.*?'><span id='\d+'>(?P<gp1>.*?)</span></div>", re.S)
    
    res = obj.finditer(s)
    
    for it in res:
        print(it.group('gp1'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    此例中,(?P ) 内为分组,分组名称为 <> 内的 gp1 ,分组内容就是括号中的 .*? 所匹配的内容。使用 group 方法调用分组信息,如果此方法没有参数则是返回全部匹配信息,有参数则返回参数指定的分组信息。

    BS4 解析

    BS4 是一个第三方库,全称 beautiful soup 版本4。它可以根据 HTML 的标签来进行分析并获取需要的数据

    BS4 的安装和引用

    使用 pip install bs4 进行安装,使用 from bs4 import BeautifulSoup 引用 BS4

    使用bs4进行解析的步骤

    使用 bs4 解析的步骤如下:

    1. 获取需要解析的内容
    2. 将解析内容交给 bs 处理,生成 bs 对象
    3. 从 bs 对象查找需要的数据

    获取解析内容就是正常的获取页面源代码

    import requests
    
    url ='https://movie.douban.com/top250'
    headers = {
        "User-Agent": "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/95.0.4638.69 Safari/537.36"
    }
    resp = requests.get(url, headers=headers)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    生成 bs 对象

    from bs4 import BeautifulSoup
    page = BeautifulSoup(resp.text, "html.parser")
    
    • 1
    • 2

    参数2是指定了传到 bs4 对象里的数据格式。

    从 bs 对象查找数据

    使用 find(标签名, 属性=值)find_all(标签名, 属性=值) 这两个方法来获取需要的对象

    # 查找并获取第一个 ol 标签
    ol = page.find("ol", class_='grid_view')		# class 是关键字,所以使用 class_
    
    • 1
    • 2

    如果要避免使用关键字或常用字产生冲突,可以这样使用:

    ol = page.find("ol", attrs={"class": "grid_view"})
    
    • 1

    这两者是等同的。获取所有数据:

    lis = ol.find_all("li")		# 获取所有 li 标签,生成一个列表
    
    • 1

    根据获取的标签,取得标签的文本内容:

    for li in lis:
        title = li.find("span", attrs={"class": "title"})
        print(title.text)       # .text 为被标签的文本内容
    
    • 1
    • 2
    • 3

    获取标签的属性

    使用另一个实例演示如何获取标签属性

    import requests, time
    from bs4 import BeautifulSoup
    
    url = 'https://www.umei.cc/bizhitupian/weimeibizhi/'
    resp = requests.get(url)
    resp.encoding = 'utf-8'
    # 获取主页面代码
    main_page = BeautifulSoup(resp.text, "html.parser")
    # 查找子页面超链接
    a_list = main_page.find("div", attrs={"class": "pic-box"}).find("ul", attrs={"class", "pic-list after"}).find_all("a")
    for a in a_list:
        herf = 'https://www.umei.cc/' + a.get('href').strip('/')  # 通过 get 方法获取标签属性的值
        # 获取子页面源代码
        resp_child = requests.get(herf)
        resp_child.encoding = 'utf-8'
        # 获取子页面需要的数据
        child_page = BeautifulSoup(resp_child.text, 'html.parser')
        img = child_page.find("section", attrs={"class": "img-content"}).find('img')	# 获取 img 标签
        src = img.get('src')	# 获取 img 标签的 src 属性值
        img_name = src.split('/')[-1]  # 以 / 进行切割,取最后一部分,作为图片名称
        img_resp = requests.get(src)  # 请求图片
        with open("./download/" + img_name, mode='wb') as f:
            f.write(img_resp.content)  # 图片内容写入文件
        print('Over!!', img_name)
        time.sleep(1)		# 设置间隔时间,防止请求速度太快
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    即,使用 BeautifulSoup.get(attrs_name)->String 来获取标签内的属性值

    xpath 解析

    xpath 解析是一种新的解析方式,比 re 简单,比 bs 高效,所以会经常用到。xpath 是在 xml 文档中搜索内容的一种语言,类似正则表达式。

    xpath 解析使用第三方库 lxml

    lxml 的安装和引用

    使用 pip install lxml 安装 lxml库,使用 from lxml import etree 来引用

    生成 xpath 对象

    xpath 可以根据输入的数据类型不同指定处理格式,例如:

    from lxml import etree
    
    xml = """..."""	# 定义一段 xml 文本
    tree_xml = etree.XML(xml)           # 生成基于 xml 的对象
    tree_file = etree.parse('b.html')	# 基于文件生成对象
    
    • 1
    • 2
    • 3
    • 4
    • 5

    查找数据

    这里先定义一段 xml 文本

    xml = """
    <book>
        <id>1</id>
        <name>野花遍地香</name>
        <price>1.23</price>
        <nick>臭豆腐</nick>
        <author>
            <nick id="10086">周大强</nick>
            <nick id="10010">周芷若</nick>
            <nick class="joy">周杰伦</nick>
            <nick class="jolin">蔡依林</nick>
            <div>
                <nick>热热热热</nick>
            </div>
            <div>
                <nick>特别热</nick>
                <div>
                    <nick>好热好热</nick>
                </div>
            </div>
            <span>
                <nick>span的热</nick>
            </span>
        </author>
        
        <partner>
            <nick id="ppc">胖胖陈</nick>
            <nick id="ppbc">胖胖不陈</nick>
        </partner>
    </book>
    """
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    tree = etree.XML(xml)           # 生成基于 xml 的对象
    # result = tree.xpath('/book')    # / 表示层级关系,第一个 / 是根节点
    # 获取 name 的文本内容,使用 text()
    # result = tree.xpath('/book/name/text()')        # ['野花遍地香']
    # 获取的节点不是唯一,输出的是同胞节点列表
    # result = tree.xpath('/book/author/nick/text()')     # ['周大强', '周芷若', '周杰伦', '蔡依林']
    # 跨层级查找同名节点,使用 // 即跨层级查找所有后代节点,查找节点 nick
    # result = tree.xpath('/book/author//nick/text()')    # ['周大强', '周芷若', '周杰伦', '蔡依林', '热热热热', '特别热', '好热好热', 'span的热']
    # 跨节点(任意节点)查找节点,使用通配符 *
    result = tree.xpath('/book/author/*/nick/text()')       # ['热热热热', '特别热', 'span的热']
    
    print(result)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    xpath 的筛选定位

    除了按照层级和节点来获取需要的数据,还可以根据情况,进行具体的筛选定位。假设筛选对象为 b.html 文件

    <!DOCTYPE html>
    <html lang="en">
        <head>
            <meta charset="UTF-8"/>
            <title>Title</title>
        </head>
        <body>
            <ul>
                <li><a href="http://www.baidu.com">百度</a></li>
                <li><a href="http://www.google.com">谷歌</a></li>
                <li><a href="http://www.sogou.com">搜狗</a></li>
            </ul>
            <ol>
                <li><a href="feiji">飞机</a></li>
                <li><a href="dapao">大炮</a></li>
                <li><a href="huoche">火车</a></li>
            </ol>
            <div class="job">李嘉诚</div>
            <div class="common">胡辣汤</div>
        </body>
    </html>
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    from lxml import etree
    
    tree = etree.parse("b.html")    # 以文件创建 xpath 对象
    # 获取节点文本列表
    # result = tree.xpath('/html/body/ul/li/a/text()')    # ['百度', '谷歌', '搜狗']
    # 获取第一个 li 节点里的 a 标签的文本内容,使用 [1] ,注意从1开始而不是0
    # result = tree.xpath('/html/body/ul/li[1]/a/text()')     # ['百度']
    # 使用属性筛选定位标签,使用 [@属性名=属性值]
    # result = tree.xpath('/html/body/ol/li/a[@href="dapao"]/text()')     # ['大炮']
    
    # print(result)
    
    # 分次查找
    ol_li_list = tree.xpath('/html/body/ol/li')
    for li in ol_li_list:
        # 从每一个 li 中提取文本信息
        result = li.xpath('./a/text()')     # 在li中继续寻找, ./ 表示当前节点 (相对路径)
        result2 = li.xpath('./a/@href')     # 获取 a 标签的 href 属性的值
        print(result)
        print(result2)
    
    # 快速获取属性值列表
    print(tree.xpath('/html/body/ul/li/a/@href'))
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    xpath 的一个应用实例

    通常爬网页上的数据时,可以将相同类的数据做为一个块(div 的概念),然后使用循环获取每个块的需要的数据。

    import requests
    from lxml import etree
    
    # 获取页面代码
    url = 'https://beijing.zbj.com/search/f/?kw=saas&r=1'
    resp = requests.get(url)
    # 解析
    html = etree.HTML(resp.text)
    # 获取每一个块内容
    divs = html.xpath('//div[@id="__nuxt"]//div[@class="search-content"]//div[@class="search-result-list-service"]/div')
    # 从块内中获取需要的数据
    for div in divs:
        com_name = div.xpath('./a[@class="name-address"]//div[@class="shop-detail"]/div/text()')[0]
        price = div.xpath('.//div[@class="price"]/span/text()')[0].strip('¥')
        title = div.xpath('.//div[@class="bot-content"]/a/text()')
        location = div.xpath('.//div[@class="price"]/div/text()')[0].strip()
        print(com_name, price, title, location)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
  • 相关阅读:
    Python函数传递参数
    Linux:动静态库介绍
    2023-python-解释器是什么东西?
    神经网络图像识别技术,神经网络指纹识别
    左神:中级提升班5
    asp.net人事管理信息系统VS开发sqlserver数据库web结构c#编程Microsoft Visual Studio
    Android ROM 常见debug方法
    『牛客|每日一题』岛屿数量
    成员变量为动态数据时不可轻易使用
    实战 - Restful APi 格式规范
  • 原文地址:https://blog.csdn.net/runsong911/article/details/125453453