zoukankan      html  css  js  c++  java
  • Python网络爬虫 第二章 数据解析

    一、数据解析概述

    在上⼀章中, 我们基本上掌握了抓取整个⽹⻚的基本技能. 但是呢, ⼤多数情况下, 我们并不需要整个⽹⻚的内容, 只是需要那么⼀⼩部分.
    怎么办呢? 这就涉及到了数据提取的问题.
    本课程中, 提供三种解析⽅式:

    • 1. re解析
    • 2. bs4解析
    • 3. xpath解析

    这三种⽅式可以混合进⾏使⽤, 完全以结果做导向, 只要能拿到你想要的数据. ⽤什么⽅案并不重要. 当你掌握了这些之后. 再考虑性能的问题

    二、正则表达式

    Regular Expression, 正则表达式, ⼀种使⽤表达式的⽅式对字符串进⾏匹配的语法规则.
    我们抓取到的⽹⻚源代码本质上就是⼀个超⻓的字符串, 想从⾥⾯提取内容.⽤正则再合适不过了.
    正则的优点: 速度快, 效率⾼, 准确性⾼ 正则的缺点: 新⼿上⼿难度有点⼉⾼.
    不过只要掌握了正则编写的逻辑关系, 写出⼀个提取⻚⾯内容的正则其实并不复杂

    https://github.com/cdoco/learn-regex-zh
    正则的语法: 使⽤元字符进⾏排列组合⽤来匹配字符串 在线测试正则表达式https://tool.oschina.net/regex/
    元字符: 具有固定含义的特殊符号

    常⽤元字符:

    . 匹配除换⾏符以外的任意字符
    w 匹配字⺟或数字或下划线
    s 匹配任意的空⽩符
    d 匹配数字
    
     匹配⼀个换⾏符
    	 匹配⼀个制表符
    ^ 匹配字符串的开始
    $ 匹配字符串的结尾
    W 匹配⾮字⺟或数字或下划线
    D 匹配⾮数字
    S 匹配⾮空⽩符
    a|b 匹配字符a或字符b
    () 匹配括号内的表达式,也表示⼀个组
    [...] 匹配字符组中的字符
    [^...] 匹配除了字符组中字符的所有字符

    量词: 控制前⾯的元字符出现的次数

    * 重复零次或更多次
    + 重复⼀次或更多次
    ? 重复零次或⼀次
    {n} 重复n次
    {n,} 重复n次或更多次
    {n,m} 重复n到m次

    贪婪匹配和惰性匹配

    .* 贪婪匹配
    .*? 惰性匹配

    这两个要着重的说⼀下. 因为我们写爬⾍⽤的最多的就是这个惰性匹配.
    先看案例

    str: 玩⼉吃鸡游戏, 晚上⼀起上游戏, ⼲嘛呢? 打游戏啊
    reg: 玩⼉.*?游戏
    此时匹配的是: 玩⼉吃鸡游戏
    reg: 玩⼉.*游戏 
    此时匹配的是: 玩⼉吃鸡游戏, 晚上⼀起上游戏, ⼲嘛呢? 打游戏 
    
    str: <div>胡辣汤</div>
    reg: <.*>
    结果: <div>胡辣汤</div>
    
    str: <div>胡辣汤</div>
    reg: <.*?>
     结果:
    <div>
    </div>
    
    str: <div>胡辣汤</div><span>饭团</span>
    reg: <div>.*?</div>
    结果:
    <div>胡辣汤</div>

    所以我们能发现这样⼀个规律: .? 表示尽可能少的匹配, .表示尽可能多的匹配, 暂时先记住这个规律. 后⾯写爬⾍会⽤到的哦

    三、re模块

    那么接下来的问题是, 正则我会写了, 怎么在python程序中使⽤正则呢? 答案是re模块
    re模块中我们只需要记住这么⼏个功能就⾜够我们使⽤了.
    1. findall 查找所有. 返回list

    lst = re.findall("m", "mai le fo len, mai ni mei!")
    print(lst) # ['m', 'm', 'm']
    #'r'是防止字符转义的 如果路径中出现'	'的话 不加r的话	就会被转义 而加了'r'之后'	'就能保留原有的样子
    lst = re.findall(r"d+", "5点之前. 你要给我5000万")
    print(lst) # ['5', '5000']

    2. search 会进⾏匹配. 但是如果匹配到了第⼀个结果. 就会返回这个结果. 如果匹配不上search返回的则是None

    ret = re.search(r'd', '5点之前. 你要给我5000万').group()
    print(ret) # 5

    3. match 只能从字符串的开头进⾏匹配

    ret = re.match('a', 'abc').group()
    print(ret) # a

    4. finditer, 和findall差不多. 只不过这时返回的是迭代器(重点)

    it = re.finditer("m", "mai le fo len, mai ni mei!")
    for el in it: 
        print(el.group()) # 依然需要分组

    5. compile() 可以将⼀个⻓⻓的正则进⾏预加载. ⽅便后⾯的使⽤

    obj = re.compile(r'd{3}') # 将正则表达式编译成为⼀个正则表达式对象, 规则要匹配的是3个数字
    ret = obj.search('abc123eeee') # 正则表达式对象调⽤search, 参数为待匹配的字符串
    print(ret.group()) # 结果: 123

    6. 正则中的内容如何单独提取?
    单独获取到正则中的具体内容可以给分组起名字

    import re
    s = """ <div class='jay'><span id='1'>郭麒麟</span></div> <div class='jj'><span id='2'>宋铁</span></div> <div class='jolin'><span id='3'>大聪明</span></div> <div class='sylar'><span id='4'>范思哲</span></div> <div class='tory'><span id='5'>胡说八道</span></div> """ # (?P<分组名字>正则) 可以单独从正则匹配的内容中进一步提取内容 obj = re.compile(r"<div class='.*?'><span id='(?P<id>d+)'>(?P<wahaha>.*?)</span></div>", re.S) # re.S: 让.能匹配换行符 result = obj.finditer(s) for it in result: print(it.group("wahaha")) print(it.group("id"))

    这⾥可以看到我们可以通过使⽤分组. 来对正则匹配到的内容进⼀步的进⾏筛选.
    关于正则, 还有⼀个重要的⼩点, 也⾮常的简单, 在本节中就不继续扩展了. 下⼀⼩节的案例中会把这个⼩点进⾏简单的介绍.

    四、⼿刃⾖瓣TOP250电影信息

    终于可以放开⼿脚⼲⼀番事业了. 今天我们的⽬标是⾖瓣电影
    TOP250排⾏榜. 没别的意思, 练⼿⽽已
    先看需求:

    ⽬标: 抓取"电影名称","上映年份","评分","评分⼈数"四项内容.
    怎么做呢? ⾸先, 先看⼀下⻚⾯源代码. 数据是否是直接怼在源代码上的?

    很明显, 我们想要的数据全部都在⻚⾯源代码中体现了. 所以, 我们不需要考虑js动态加载数据的情况了. 那么接下来就是编写爬⾍代码的
    第⼀步了. 拿到⻚⾯源代码:

    import requests
    headers = {
    "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/87.0.4280.88 Safari/537.36"
    } url = "https://movie.douban.com/top250?start=0&filter=" resp = requests.get(url, headers=headers) print(resp.text)

    然后呢. 从⻚⾯源代码中提取我们需要的内容. 这时候我们就可以去写正则了

    # 解析数据
    obj = re.compile(r'<li>.*?<div class="item">.*?<span class="title">(?P<name>.*?)'
                     r'</span>.*?<p class="">.*?<br>(?P<year>.*?)&nbsp.*?<span '
                     r'class="rating_num" property="v:average">(?P<score>.*?)</span>.*?'
                     r'<span>(?P<num>.*?)人评价</span>', re.S)

    开始匹配, 将最终完整的数据按照⾃⼰喜欢(需要)的⽅式写⼊⽂件.

    # 拿到页面源代码.   requests
    # 通过re来提取想要的有效信息  re
    import requests
    import re
    import csv
    
    url = "https://movie.douban.com/top250"
    headers = {
        "user-agent": "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/88.0.4324.192 Safari/537.36"
    }
    resp = requests.get(url, headers=headers)
    page_content = resp.text
    
    # 解析数据
    obj = re.compile(r'<li>.*?<div class="item">.*?<span class="title">(?P<name>.*?)'
                     r'</span>.*?<p class="">.*?<br>(?P<year>.*?)&nbsp.*?<span '
                     r'class="rating_num" property="v:average">(?P<score>.*?)</span>.*?'
                     r'<span>(?P<num>.*?)人评价</span>', re.S)
    # 开始匹配
    result = obj.finditer(page_content)
    f = open("data.csv", mode="w")
    #创建csv文件写入工具,也可以直接f.write()
    csvwriter = csv.writer(f)
    for it in result:
        # print(it.group("name"))
        # print(it.group("score"))
        # print(it.group("num"))
        # print(it.group("year").strip())
        dic = it.groupdict()
        # strip() 方法用于移除字符串头尾指定的字符(默认为空格)
        dic['year'] = dic['year'].strip()
        csvwriter.writerow(dic.values())
    f.close()
    print("over!")

    五、bs4解析-HTML语法

    bs4解析⽐较简单, 但是呢, ⾸先你需要了解⼀丢丢的html知识. 然后再去使⽤bs4去提取, 逻辑和编写难度就会⾮常简单和清晰
    HTML(Hyper Text Markup Language)超⽂本标记语⾔, 是我们编写⽹⻚的最基本也是最核⼼的⼀种语⾔. 其语法规则就是⽤不同的标签对⽹⻚上的内容进⾏标记, 从⽽使⽹⻚显示出不同的展示效果.

    <h1>
     我爱你
    </h1> 

    上述代码的含义是在⻚⾯中显示"我爱你"三个字, 但是我爱你三个字被"<h1>"和"</h1>"标记了. ⽩话就是被括起来了. 被H1这个标签括起来了. 这个时候. 浏览器在展示的时候就会让我爱你变粗变⼤. 俗称标题, 所以HTML的语法就是⽤类似这样的标签对⻚⾯内容进⾏标记.不同的标签表现出来的效果也是不⼀样的.

    h1: ⼀级标题
    h2: ⼆级标题
    p: 段落
    font: 字体(被废弃了, 但能⽤)
    body: 主体

    这⾥只是给⼩⽩们简单科普⼀下, 其实HTML标签还有很多很多的.
    我们不需要⼀⼀列举(这是爬⾍课, 不是前端课).
    OK~ 标签我们明⽩了, 接下来就是属性了.

    <h1>
    我爱你
    </h1>
    <h1 align='right'>
    我爱你妹
    </h1>

    有意思了. 我们发现在标签中还可以给出xxx=xxx这样的东⻄. 那么它⼜是什么呢? ⼜该如何解读呢?
    ⾸先, 这两个标签都是h1标签, 都是⼀级标题, 但是下⾯这个会显示在右边. 也就是说, 通过xxx=xxx这种形式对h1标签进⼀步的说明了.
    那么这种语法在html中被称为标签的属性. 并且属性可以有很多个.
    例如:

     <body text="green" bgcolor="#eee"> 你看我的颜⾊. 贼健康
    </body>

    总结, html语法:

    <标签 属性="值" 属性="值">
    被标记的内容
    </标签>

    有了这些知识, 我们再去看bs4就会得⼼应⼿了. 因为bs4就是通过标签和属性去定位⻚⾯上的内容的。

    六、bs4模块安装和使⽤

    bs4模块安装
    在python中我⼀般只推荐⽤pip进⾏安装. 原因: 简单!!!!

    pip install bs4

    如果安装的速度慢, 建议更换国内源(推荐阿⾥源或者清华源)
    如何使⽤bs4

    pip install -i https://pypi.tuna.tsinghua.edu.cn/simple bs4

    bs4在使⽤的时候就需要参照⼀些html的基本语法来进⾏使⽤了. 我们直接上案例哈. 案例是最能直观的展现出bs4的便捷效果的.
    我们来尝试抓取北京新发地市场的农产品价格. http://www.xinfadi.com.cn/marketanalysis/0/list/1.shtml

    ⽼规矩, 先获取⻚⾯源代码. 并且确定数据就在⻚⾯源代码中~

    import requests
    from bs4 import BeautifulSoup
    resp =requests.get("http://www.xinfadi.com.cn/marketanalysis/0/list/1.shtml")
    print(resp.text)

    将⻚⾯源代码丢给BeautifulSoup, 然后我们就可以通过bs对象去检索⻚⾯源代码中的html标签了

    page = BeautifulSoup(resp.text)

    BeautifulSoup对象获取html中的内容主要通过两个⽅法来完成
    find()
    find_all()

    基本上有这两个⽅法就够⽤了. 其他的可以⾃⾏进⾏英⽂翻译就知道啥意思了.
    不论是find还是find_all 参数⼏乎是⼀致的.
    语法:
    find(标签, 属性=值)
    意思是在⻚⾯中查找 xxx标签, 并且标签的xxx属性必须是xxx值 例:
    find('div', age=18) 含义: 在⻚⾯中查找div标签, 并且属性age必须是18的这个标签.
    find_all()的⽤法和find()⼏乎⼀致.

    • find()查找1个.
    • find_all()查找⻚⾯中所有的.

    但是这种写法会有些问题. ⽐如html标签中的class属性.

    <div class="honor">
    page.find("div", class="honor")
    # 注意, python中class是关键字. 会报错的. 怎么办呢? 可以在class后⾯加个下划线
    page.find("div", class_="honor")
    #我们可以使⽤第⼆种写法来避免这类问题出现
    page.find("div", attrs={"class": "honor"})

    好了, ⽤法说完了. 接下来就回来看怎么抓取新发地的价格吧

    table = page.find("table", class_="hq_table")  
    print(table)

    接下来就可以进⼀步去提取数据了. 后⾯的直接给出完整代码.
    因为逻辑都是⼀样的. 并没有多么的复杂, 过程就省略了.

    最后代码

    # 安装
    # pip install bs4 -i 清华
    
    # 1. 拿到页面源代码
    # 2. 使用bs4进行解析. 拿到数据
    import requests
    from bs4 import BeautifulSoup
    import csv
    
    url = "http://www.xinfadi.com.cn/marketanalysis/0/list/1.shtml"
    resp = requests.get(url)
    
    f = open("菜价.csv", mode="w")
    csvwriter = csv.writer(f)
    
    # 解析数据
    # 1. 把页面源代码交给BeautifulSoup进行处理, 生成bs对象
    # html.parser 告诉解析器这是html文件
    page = BeautifulSoup(resp.text, "html.parser")  # 指定html解析器
    # 2. 从bs对象中查找数据
    # find(标签, 属性=值)
    # find_all(标签, 属性=值)
    # table = page.find("table", class_="hq_table")  # class是python的关键字
    table = page.find("table", attrs={"class": "hq_table"})  # 和上一行是一个意思. 此时可以避免class
    # 拿到所有数据行tr
    # 行tr 列td
    print(table)
    #做切片 从第一个开始切 排除了第0个表头 获得纯数据
    trs = table.find_all("tr")[1:]
    for tr in trs:  # 每一行
        tds = tr.find_all("td")  # 拿到每行中的所有td
        name = tds[0].text  # .text 表示拿到被标签标记的内容
        low = tds[1].text  # .text 表示拿到被标签标记的内容
        avg = tds[2].text  # .text 表示拿到被标签标记的内容
        high = tds[3].text  # .text 表示拿到被标签标记的内容
        tp = tds[4].text  # .text 表示拿到被标签标记的内容
        kind = tds[5].text  # .text 表示拿到被标签标记的内容
        date = tds[6].text  # .text 表示拿到被标签标记的内容
        csvwriter.writerow([name, low, avg, high, tp, kind, date])
    f.close()
    print("over1!!!!")

    七、bs4抓取图片

    为了视频和⽂档能够正常投放在市⾯上, 这里抓取的图⽚都是唯美桌⾯系
    https://www.umei.cc/bizhitupian/weimeibizhi/

    注意我选中的这个区域, 我们想要的图⽚就在这⾥. 但是, 绝对不是现在你看到的样⼦. 为什么呢? 不够⾼清⼤图~
    真正的⾼清⼤图在⼦⻚⾯中, ⽐如, 我点击第⼀个图⽚

    这才是我想要的⼤图~
    也就是说, 我需要在⽹站的⾸⻚中, 找到⼦⻚⾯的链接, 然后请求到⼦⻚⾯, 才能看到这张⼤图~ 不明⽩的, 把上⾯的内容重新梳理⼀下!!!!!!!
    也就是说, 想要下载该⽹站图⽚(⾼清⼤图), 需要三步,

    • 第⼀步, 在主⻚⾯中拿到每⼀个图⽚的⼦⻚⾯链接
    • 第⼆步, 在⼦⻚⾯中找到真正的图⽚下载地址
    • 第三步, 下载图⽚
    # 1.拿到主页面的源代码. 然后提取到子页面的链接地址, href
    # 2.通过href拿到子页面的内容. 从子页面中找到图片的下载地址 img -> src
    # 3.下载图片
    import requests
    from bs4 import BeautifulSoup
    import time
    
    url = "https://www.umei.cc/bizhitupian/weimeibizhi/"
    resp = requests.get(url)
    resp.encoding = 'utf-8'  # 处理乱码
    
    # print(resp.text)
    # 把源代码交给bs
    main_page = BeautifulSoup(resp.text, "html.parser")
    alist = main_page.find("div", class_="TypeList").find_all("a")
    # print(alist)
    for a in alist:
        href = a.get('href')  # 直接通过get就可以拿到属性的值
        # 拿到子页面的源代码
        # 从子页面中拿到图片的下载路径
        child_page_resp = requests.get(href)
        child_page_resp.encoding = 'utf-8'
        child_page_text = child_page_resp.text
        child_page = BeautifulSoup(child_page_text, "html.parser")
        p = child_page.find("p", align="center")
        img = p.find("img")
        # 图片url
        src = img.get("src")
        # 下载图片
        img_resp = requests.get(src)
        # img_resp.content  # 这里拿到的是字节
        # http://kr.shanghai-jiuxin.com/file/2020/1031/6b72c57a1423c866d2b9dc10d0473f27.jpg
        # 6b72c57a1423c866d2b9dc10d0473f27.jpg
        img_name = src.split("/")[-1]  # 拿到url中的最后一个/以后的内容
        with open("img/"+img_name, mode="wb") as f:
            f.write(img_resp.content)  # 图片内容写入文件
    
        print("over!!!", img_name)
        time.sleep(1)
    
    
    print("all over!!!")

     八、Xpath解析

    XPath是⼀⻔在 XML ⽂档中查找信息的语⾔. XPath可⽤来在 XML⽂档中对元素和属性进⾏遍历. ⽽我们熟知的HTML恰巧属于XML的⼀个⼦集. 所以完全可以⽤xpath去查找html中的内容.

    详细说明见这篇博客https://www.jianshu.com/p/85a3004b5c06
    ⾸先, 先了解⼏个概念.在上述html中,

    <book>
     <id>1</id>
     <name>野花遍地⾹</name>
     <price>1.23</price>
     <author>
     <nick>周⼤强</nick>
     <nick>周芷若</nick>
     </author>
    </book> 
    • book, id, name, price....都被称为节点.
    • Id, name, price, author被称为book的⼦节点
    • book被称为id, name, price, author的⽗节点
    • id, name, price,author被称为同胞节点

    OK~ 有了这些基础知识后, 我们就可以开始了解xpath的基本语法了
    在python中想要使⽤xpath, 需要安装lxml模块

    pip install lxml

    ⽤法:
    1. 将要解析的html内容构造出etree对象.
    2. 使⽤etree对象的xpath()⽅法配合xpath表达式来完成对数据的提取

    xpath语法

    表达式描述
    nodename 选取此节点的所有子节点。
    / 从根节点选取。
    // 从匹配选择的当前节点选择文档中的节点,而不考虑它们的位置。
    . 选取当前节点。
    .. 选取当前节点的父节点。
    @ 选取属性。
    from lxml import etree
    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>热热热热热1</nick>
            </div>
            <span>
                <nick>热热热热热2</nick>
            </span>
        </author>
    
        <partner>
            <nick id="ppc">胖胖陈</nick>
            <nick id="ppbc">胖胖不陈</nick>
        </partner>
    </book>
    """
    
    tree = etree.XML(xml)
    # result = tree.xpath("/book")  # /表示层级关系. 第一个/是根节点
    # result = tree.xpath("/book/name")
    # result = tree.xpath("/book/name/text()")  # text() 拿文本
    # result = tree.xpath("/book/author//nick/text()")  # // 后代
    # result = tree.xpath("/book/author/*/nick/text()")  # * 任意的节点. 通配符(会儿)
    result = tree.xpath("/book//nick/text()")
    print(result)

    xpath如何提取属性信息. 我们上⼀段真实的HTML来给各位讲解⼀下
    准备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>
    from lxml import etree
    
    
    tree = etree.parse("b.html")
    # result = tree.xpath('/html')
    # result = tree.xpath("/html/body/ul/li/a/text()")
    # result = tree.xpath("/html/body/ul/li[1]/a/text()")  # xpath的顺序是从1开始数的, []表示索引
    
    # result = tree.xpath("/html/body/ol/li/a[@href='dapao']/text()")  # [@xxx=xxx] 属性的筛选
    
    # print(result)
    
    # ol_li_list = tree.xpath("/html/body/ol/li")
    #
    # for li in ol_li_list:
    #     # 从每一个li中提取到文字信息
    #     result = li.xpath("./a/text()")  # 在li中继续去寻找. 相对查找
    #     print(result)
    #     result2 = li.xpath("./a/@href")  # 拿到属性值: @属性
    #     print(result2)
    #
    # print(tree.xpath("/html/body/ul/li/a/@href"))
    
    print(tree.xpath('/html/body/div[1]/text()'))
    print(tree.xpath('/html/body/ol/li/a/text()'))

    如果页面过于复杂,但想要要获取xpath,可以借助谷歌浏览器的功能直接获取xpath

    右击 检查

     

    作者:王陸

    -------------------------------------------

    个性签名:罔谈彼短,靡持己长。做一个谦逊爱学的人!

    本站使用「署名 4.0 国际」创作共享协议,转载请在文章明显位置注明作者及出处。鉴于博主处于考研复习期间,有什么问题请在评论区中提出,博主尽可能当天回复,加微信好友请注明原因

  • 相关阅读:
    hash算法
    2020/9/30计算机硬件组成day3
    NIO与IO区别
    Collection.toArray()方法使用的坑&如何反转数组
    Arrays.asList()使用指南
    JDK8的LocalDateTime用法
    Linux 删除文件夹和文件的命令
    list集合为空或为null的区别
    easyExcel使用
    java Object 转换为 Long
  • 原文地址:https://www.cnblogs.com/wkfvawl/p/14725960.html
Copyright © 2011-2022 走看看