Python网络爬虫 第二章 数据解析


一、数据解析概述

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

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

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

二、正则表达式

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

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

常?元字符:

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

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

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

贪婪匹配和惰性匹配

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

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

str: 玩?吃鸡游戏, 晚上?起上游戏, ?嘛呢? 打游戏啊
reg: 玩?.*?游戏
此时匹配的是: 玩?吃鸡游戏
reg: 玩?.*游戏 
此时匹配的是: 玩?吃鸡游戏, 晚上?起上游戏, ?嘛呢? 打游戏 

str: 
胡辣汤
reg: <.*> 结果:
胡辣汤
str:
胡辣汤
reg: <.*?> 结果:
str:
胡辣汤
饭团 reg:
.*?
结果:
胡辣汤

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

三、re模块

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

lst = re.findall("m", "mai le fo len, mai ni mei!")
print(lst) # ['m', 'm', 'm']
#'r'是防止字符转义的 如果路径中出现'\t'的话 不加r的话\t就会被转义 而加了'r'之后'\t'就能保留原有的样子
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 = """
郭麒麟
宋铁
大聪明
范思哲
胡说八道
""" # (?P<分组名字>正则) 可以单独从正则匹配的内容中进一步提取内容 obj = re.compile(r"
(?P.*?)
", 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'
  • .*?
    .*?(?P.*?)' r'.*?

    .*?
    (?P.*?) .*?' r'class="rating_num" property="v:average">(?P.*?).*?' r'(?P.*?)人评价', 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'
  • .*?
    .*?(?P.*?)' r'.*?

    .*?
    (?P.*?) .*?' r'class="rating_num" property="v:average">(?P.*?).*?' r'(?P.*?)人评价', 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这个标签括起来了. 这个时候. 浏览器在展示的时候就会让我爱你变粗变?. 俗称标题, 所以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属性.

    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>1id>
     <name>野花遍地?name>
     <price>1.23price>
     <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 = """
    
        1
        野花遍地香
        1.23
        臭豆腐
        
            周大强
            周芷若
            周杰伦
            蔡依林
            
    热热热热热1
    热热热热热2
    胖胖陈 胖胖不陈
    """ 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>Titletitle>
        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

    右击 检查