[数据采集笔记04]——网页解析——lxml、bs4、正则

2.网页解析

2.1.网页解析概述

除了学会向服务器发出请求、下载HTML源码,要获取结构化的数据还面临一个最常见的任务,就是从HTML源码中提取数据


三种方法:

  • 正则表达式
  • lxml库
  • BeautifulSoup

针对文本的解析, 用正则表达式

针对html/xml的解析, 有Xpath、 BeautifulSoup、 正则表达式

针对JSON的解析, 有JSONPath

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DXFDplie-1589126552640)(D:\笔记图片集\1588768761168.png)]


几种解析网页技术的区别

  • 正则表达式基于文本的特征来匹配或查找指定的数据,可以处理任何格式的字符串文档,类似于模糊匹配的效果(re模块支持正则表达式语法的使用)
  • XPath和BeautifulSoup基于HTML/XML文档的层次结构来确定到达指定节点的路径,更适合出层级比较明显的数据
  • JSONPath专门用来JSON文档的数据解析
  • lxml库支持XPath语法的使用
  • json模块支持JSONPath语法的使用
  • BeautifulSoup本身就是一个Python库,是官方推荐的使用方法

2.2. XPath语法和lxml模块

  • xpath(XML Path Language)是一门在XML和HTML文档中查找信息的语言,可用来在XML和HTML文档中对元素和属性进行遍历。

  • 选取节点

    XPath 使用路径表达式来选取 XML 文档中的节点或者节点集。这些路径表达式和我们在常规的电脑文件系统中看到的表达式非常相似,代表着从一个节点到另一个或者一组节点的顺序,并以“/”字符进行分隔


2.2.1. XPath语法

  1. 选取节点

    节点是按照路径选取的,既可以从根节点开始,也可以从任意位置开始

    表达式说明
    nodename选取此节点的所有子节点
    /从根节点选取
    //从匹配选择的当前节点选取文档中的节点,而不用考虑它们的位置
    .选取当前节点
    . .选取当前节点的父节点
    @选取属性
  2. 谓词

    谓词指的是路径表达式的附加条件,用来查找某个特定的节点或者包含某个指定的值的节点,被嵌在方括号中,表示对节点进行进一步筛选,用于查找某个特定节点或者包含某个指定值的节点。


    格式:

    元素[表达式]


    表达式说明
    /bookstore/book[1]选取bookstore子元素的第一个book元素
    /bookstore/book[last()]选取bookstore子元素的最后一个book元素。
    bookstore/book[position() < 3]选取最前面的两个属于bookstore元素的子元素的book元素。
    //book[@price]选取拥有price属性的book元素
    //book[@price=10]选取所有属性price等于10的book元素
  3. 选取未知节点

    表示通配符,Xpath用选取未知的节点

    表达式说明
    *匹配任何元素节点
    @*匹配节点中的任何属性
  4. 选取若干路径

    通过在路径表达式中使用“|”运算符,可以选取若干个路径。

    如:

    //bookstore/book | //book/title  # 选取所有book元素以及book元素下所有的title元素
    

2.2.2. 插件的下载

  • XPath Helper : Chrome浏览器插件
  • Try Xpath : FireFox 插件

2.3. lxml 库

lxml 是 一个HTML/XML的解析器,主要的功能是如何解析和提取 HTML/XML 数据。


lxml和正则一样,也是用 C 实现的,是一款高性能的 Python HTML/XML 解析器,我们可以利用之前学习的XPath语法,来快速的定位特定元素以及节点信息。



  • 文档:lxml python 官方文档:http://lxml.de/index.html

  • 依赖:需要安装C语言库

  • 安装方式:可使用 pip 安装:pip install lxml



2.3.1. 基本使用

  • 我们可以利用他来解析HTML代码,并且在解析HTML代码的时候,如果HTML代码不规范,他会自动的进行补全

  • 使用 lxml 的 etree 库 from lxml import etree

  • python 3.5后的lxml模块中不能再直接引入etree模块,需要使用以下方法引用

    from lxml import html

    etree=html.etree

2.3.2. 从字符串或文件中解析XML

为了能够将XML文件解析为树结构,etree模块提供了如下3个函数:

  • fromstring()
  • XML()
  • HTML()
  • XML()函数的行为类似于fromstring()函数,通常用于将XML字面量直接写入到源码中,HTML()函数可以自动补全缺少的<html><body>标签

还可以调用parse()函数从XML文件中直接解析,在调用函数时,如果没有提供解析器,则使用默认的XML解析器,函数会返回一个ElementTree类的对象。


HTML文件不规范时,则需要自己指定解析器parser=etree.HTMLParser() htmlEle=etree.parse(文件名,parser=parser)


对于Element对象,使用tostring()函数,将元素序列化为XML树的编码字符串表示形式

2.3.3.lxml库的相关类

  • Element类: XML的结点
  • ElementTree类: 一个完整的XML文档树
  • ElementPath类: 可以理解为Xpath, 用于搜索和定位节点
①Element类
  • 节点操作

    root=etree.Element(“book”)

  • 节点属性操作

    a.root=etree.Element(“book”, interesting=‘totally’)

    b. root.set(‘age’,‘30’)

  • 节点内文本的操作:通过text、tail属性或者xpath()方法来访问文本内容

②ElementPath类
  • 在ElementTree或者Element中,使用如下3个方法,可以满足搜索和查询需求,这3个方法的参数都是XPath语句
    • find()方法:返回匹配的第一个子元素
    • findall()方法:以列表形式返回所有匹配的子元素
    • iterfind()方法:返回一个所有匹配元素的迭代器html=etree.HTML(text)alist=html.findall(".//a[@href]")for a in alist: print(a.text)

2.3.4. lxml中使用XPath语法

  • 使用lxml库中的路径表达式技巧,通过调用xpath()方法匹配选取的节点

  • 1.获取所有li标签html.xpath(“//li”)

    2.获取所有li元素下的所有class属性的值

    aattr=html.xpath("//li/@class")

  • 3.获取li标签下href为www.baidu.com的a标签

    alist=html.xpath("//li/a[@href=‘www.baidu.com’]")

  • 4.获取最后一个li的a的href属性对应的值

    aattr=html.xpath("//li[last()]/a/@href")

  • 5.获取倒数第二个li元素的内容

    (1)t=html.xpath("//li[last()-1]/a") t[0].text

    (2)t=html.xpath("//li[last()-1]/a/text()")

2.3.5. 案例: 使用requests+lxml 爬取电影天堂

from lxml import etree
import time
import requests

HEADERS = {
       'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/61.0.3163.100 Safari/537.36'
}

Base_Domain = "https://www.dytt8.net"


def get_detail_urls(url):
    """获取该页的全部url"""
    resp = requests.get(url, headers= HEADERS)
    resp.encoding = resp.apparent_encoding
    # text = resp.content.decode("gbk", "ignore")
    text = resp.text
    html = etree.HTML(text)

    details_urls = html.xpath("//div[@class='co_content8']//table//a/@href")
    #
    details_urls= map(lambda urlID:Base_Domain+urlID, details_urls)

    return details_urls


def paser_detail(detailurl):
    movie = {}

    resp = requests.get(detailurl, headers=HEADERS)
    text = resp.content.decode("gbk", "ignore")
    html = etree.HTML(text)
    title=html.xpath("//div[@class='title_all']//font[@color='#07519a']/text()")[0]
    # title = html.find("//div[@class='title_all']//font[@color='#07519a']/text()")
    movie["title"] = title
    zoomE = html.xpath("//div[@id='Zoom']")[0]
    image= zoomE.xpath(".//img/@src")
    movie["image"] = image
    infos = zoomE.xpath(".//text()")

    def parse_info(info, rule):
        return info.replace(rule, "").strip()

    for index, info in enumerate(infos):
        if info.startswith("◎年  代"):
            info = parse_info(info, "◎年  代")
            movie["year"] = info
        elif info.startswith("◎豆瓣评分"):
            info = parse_info(info, "◎豆瓣评分")
            movie["score"] = info

        elif info.startswith("◎片  长"):
            info = parse_info(info, "◎片  长")
            movie["score"] = info
        elif info.startswith("◎导  演"):
            info = parse_info(info, "◎导  演")
            movie["director"] = info
        elif info.startswith("◎主  演"):
            info = parse_info(info, "◎主  演")
            actors = [info]
            for x in range(index + 1, len(infos)):
                actor = infos[x].strip()
                actors.append(actor)
                if actor.startswith("◎"):
                    break
            movie["actor"] = actors
        elif info.startswith("◎简  介"):
            info = parse_info(info, "◎简  介")
            for x in range(index + 1, len(infos)):
                profile = infos[x].strip()
                if profile.startswith("【下载地址】"):
                    break
                movie["profile"] = profile
    downurl = html.xpath("//td[@bgcolor='#fdfddf']/a/@href")[0]
    movie["downurl"] = downurl

    return movie



def spider(start, end):
    base_url = "https://www.dytt8.net/html/gndy/dyzz/list_23_{}.html"

    movies = []

    #循环页数
    for page in range(start, end):
        print("---" * 15)
        print("开始第{}页".format(page))
        #构建新的带有页数的url
        url = base_url.format(page)
        #获取该页的详细url列表
        detailurls = get_detail_urls(url)
        #循环获取列表中的每一个电影
        for x, detailurl in enumerate(detailurls):

            print("     第{0}页, 第{1}个url".format(page,x))
            movie = paser_detail(detailurl)
            print("     ",movie)
            movies.append(movie)
            time.sleep(1)
        time.sleep(1)
if __name__ == "__main__":
    spider(1,2)


2. 网页解析

2.3. BeautifulSoup4库

和 lxml 一样,Beautiful Soup 也是一个HTML/XML的解析器,主要的功能也是如何解析和提取 HTML/XML 数据。

lxml 只会局部遍历,而Beautiful Soup 是基于HTML DOM(Document Object Model)的,会载入整个文档,解析整个DOM树,因此时间和内存开销都会大很多,所以性能要低于lxml。

BeautifulSoup 用来解析 HTML 比较简单,API非常人性化,支持CSS选择器、Python标准库中的HTML解析器,也支持 lxml 的 XML解析器。

2.3.1. 安装和文档

在命令行中用一下命令。

pip install bs4

中文文档:https://www.crummy.com/software/BeautifulSoup/bs4/doc/index.zh.html

2.3.2. 几大解析工具的对比

解析工具解析速度使用难度
BeautifulSoup最慢最简单
lxml简单
正则表达式最快最难

2.3.3. DOM文档对象模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Cg2B7VfO-1589126735537)(C:\Users\12505\AppData\Local\Temp\1589100422158.png)]

2.3.4. bs4支持的解析器

解析器使用方法优势劣势
Python标准库BeautifulSoup(markup, "html.parser")Python的内置标准库执行速度适中文档容错能力强Python 2.7.3 or 3.2.2)前 的版本中文档容错能力差
lxml HTML 解析器BeautifulSoup(markup, "lxml")速度快文档容错能力强需要安装C语言库
lxml XML 解析器BeautifulSoup(markup, ["lxml-xml"])
BeautifulSoup(markup, "xml")
速度快唯一支持XML的解析器需要安装C语言库
html5libBeautifulSoup(markup, "html5lib")最好的容错性以浏览器的方式解析文档生成HTML5格式的文档速度慢不依赖外部扩展

2.3.5. bs4四个常用的对象

Beautiful Soup将复杂HTML文档转换成一个复杂的树形结构,每个节点都是Python对象,所有对象可以归纳为4种:

  • Tag:Tag 通俗点讲就是 HTML 中的一个。个标签。利用 soup 加标签名轻松地获取这些标签的内容,这些对象的类型是bs4.element.Tag。但是注意,它查找的是在所有内容中的第一个符合要求的标签。对于Tag,它有两个重要的属性,分别是name和attrs。
  • NavigatableString:NavigatableString:如果拿到标签后,还想获取标签中的内容。那么可以通过tag.string获取标签中的文字。
  • BeautifulSoup:BeautifulSoup 对象表示的是一个文档的全部内容.大部分时候,可以把它当作 Tag 对象,它支持 遍历文档树 和 搜索文档树 中描述的大部分的方法.
  • Comment: Comment对象是一种特殊类型的NavigableString对象

2.3.6. 遍历文档树

  1. contents和children(返回某个标签下的直接子元素,包括字符串。contents返回来的是列表,children返回的是迭代器)

    #示例
    head_tag = soup.head
    
    #返回所有子节点的列表
    print(head_tag.children)
    #返回所有子节点的迭代器
    for child in head_tag.children:
        print(child)
    
    
  2. strings 和stripped_strings

    如果tag中包含多个字符串,可以使用.string来循环获取

2.3.7. 搜索文档树

  1. find和find_all方法

  2. 搜索文档树,一般用得比较多的就是两个方法,一个是find,一个是find_all。

  3. find方法是找到第一个满足条件的标签后就立即返回,只返回一个元素。

  4. find_all方法是把所有满足条件的标签都选到,然后返回回去。使用这两个方法,最常用的用法是传入name以及attr参数找出符合要求的标签。

    • name参数

      • 传入字符串:soup.find_all(“a”)
      • 传入正则表达式:soup.find_all(re.compile("^b"))
      • 传入列表:soup.find_all([“a”,“b”])
    • attrs参数

      • 如果某个指定名字的参数不是搜索方法中内置的参数名,那么在进行搜索时,会把该参数当做指定名称的标签中的属性来搜索
      • bl=soup.find_all(id=“link1”)
      • bl=soup.find_all(href=re.compile(“elsie”),id=“link1”)
      • 如果要搜索的标签名称为class,由于class是Python关键字,所以可以在class后面加一个下划线_
      • soup.find_all(“a”,class_=‘sister’)
      • 有些标签的属性名称是不能使用的,比如html5中的“data-”属性,在程序中使用时,会出现SyntaxError异常信息。这时可以通过find_all()方法的attrs参数传入一个字典来搜索包含特殊属性的标签
      • soup.find_all(attrs={‘data-foo’:‘value’})
    • text参数

      通过在find_all()方法中传入text参数,可以搜索文档中的字符串内容,与name参数一样,text参数可以接收字符串,正则表达式和列表等

    • limit参数

      find_all方法接收limit参数,限制返回结果的数量,避免DOM树非常大时,搜素速度变慢

    • recursive参数

      在调用find_all方法时,BeautifulSoup对象会检索当前节点的所有子节点。如果想检索当前节点的直接子节点,使用参数recursive=False即可

    • 获取某个属性的值

      通过下标方式获取 al=soup.find_all(“a”) for a in al: href=a[“href”]

      通过attrs属性的方式al=soup.find_all(“a”) for a in al: href=a.attrs[“href”]

  5. string、strings、stripped_strings、get_text()区别

    • string:获取某个标签下的非标签字符串,返回的是字符串,如果标签下有多行字符,则获取不到数据,可以使用contents获取
    • strings:获取某个标签下的子孙非标签字符串,并以生成器对象形式返回,可以直接把生成器转换为列表
    • stripped_strings:获取某个标签下的子孙非标签字符串,会去掉空白字符
  6. select方法(使用css选择器的方式解析数据)

    • 通过标签名查找:soup.select(‘a’)
    • 通过类名查找:应该在类的前面加一个.,soup.select(’.sister’)
    • 通过id查找:应该在id的名字前面加一个#号soup.select(’#link1
    • 组合查找:组合查找即和写 class 文件时,标签名与类名、id名进行的组合原理是一样的,例如查找 p 标签中,id 等于 link1的内容,二者需要用空格分开soup.select(“p #link1”);直接子标签查找,则使用 > 分隔:print(soup.select(“head > title”))
    • 通过属性查找:查找时还可以加入属性元素,属性需要用中括号括起来,注意属性和标签属于同一节点,所以中间不能加空格,否则会无法匹配到soup.select(‘a[href=“http://example.com/elsie”]’)
    • 获取内容: select 方法返回的结果都是列表形式,可以遍历形式输出,然后用 get_text() 方法来获取它的内容

参考

  • bs4的示例博客https://www.cnblogs.com/amou/p/9184614.html
  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值