Python学习第二十三天
数据解析
主要的作用是解析HTML、XML,urllib是用来爬取数据我们将数据爬取后,我们需要将爬取的数据部分进行解析功能。
分类:xpath、jsonpath、bs4(beautiful soup)
xpath
官网概念
xpath没有官网是由一些社区维护发布的、官网目前可以认定为W3C(万维网)在维护,也可以通过其他的学习网站进行学习。
安装
# lxml支持了xpath
pip install lxml
作用
-
主要用于 XML 和 HTML 文档的节点定位。
-
语法强大,支持复杂的查询和条件过滤。
-
适用于需要精确查找的场景。
使用
from lxml import etree
# 解析本地文件 如果报错 需要看下meta是否有结尾 检查自己的html中是不是有没有结尾的<meta charset="utf-8" /> 注意必须要有结尾/>
tree = etree.parse('test_xpath.html')
# 用法
# / 从根节点开始选取
value = tree.xpath("/html/body/ul/li")
print(f"/从根节点开始选取:{value}") # [<Element li at 0x22cf8c3df80>, <Element li at 0x22cf8c3dfc0>, <Element li at 0x22cf8c3e000>]
# //从当前节点开始,递归选取所有匹配的节点,无论它们在文档中的位置
value = tree.xpath("//ul")
print(f"//从当前节点开始,递归选取所有匹配的节点,无论它们在文档中的位置:{value}") # [<Element ul at 0x23ccd56dc40>]
# . 选取当前节点
value = tree.xpath(".")
print(f".选取当前节点:{value}") # [<Element html at 0x19bff2afcc0>]
# .. 选取当前节点的父节点
value = tree.xpath("..//ul")
print(f"..选取当前节点的父节点:{value}") # [<Element ul at 0x19bff39d980>]
# @ 选取属性
value = tree.xpath("//ul/li/@style")
print(f"@选取属性:{value}") # ['font-size: 10px', 'font-size: 20px', 'font-size: 30px']
# * 匹配任何元素节点
value = tree.xpath("//ul/*")
print(f"*匹配任何元素节点:{value}") # [<Element li at 0x1a20b761500>, <Element li at 0x1a20b761540>, <Element li at 0x1a20b7616c0>, <Element a at 0x1a20b761640>]
# @*匹配任何属性节点
value = tree.xpath("//ul/li/@*")
print(f"@*匹配任何属性节点:{value}") # ['font-size: 10px', 'font-size: 20px', 'font-size: 30px']
# node()匹配任何类型的节点(元素、属性、文本等)
value = tree.xpath("//ul/li/node()")
print(f"node()匹配任何类型的节点(元素、属性、文本等):{value}") # ['python', 'java', 'js']
# text()选取节点的文本内容
value = tree.xpath("//ul/li/text()")
print(f"text()选取节点的文本内容:{value}") # ['python', 'java', 'js']
# []用于过滤节点,括号内为条件
value = tree.xpath("//li[@id='java']")
print(f"[]用于过滤节点,括号内为条件:{value}") # [<Element li at 0x1ee12a8d780>]
# | 用于组合多个路径,返回所有匹配的节点
value = tree.xpath("//ul/li/text()|//ul/a/text()")
print(f"| 用于组合多个路径,返回所有匹配的节点:{value}") # ['python', 'java', 'js', 'test_a']
# position()返回节点在节点集中的位置
value = tree.xpath("//ul/li[position()<2]")
print(f"position() 返回节点在节点集中的位置:{value}") # [<Element li at 0x206623c2180>] 返回第一个li
# last()返回节点集中最后一个节点
value = tree.xpath("//ul/li[last()]") # [<Element li at 0x134ce211bc0>]
print(f"last()返回节点集中最后一个节点:{value}") # [<Element li at 0x13428cd1e40>] li最后一个
# contains(str1, str2) 判断str1是否包含str2,返回布尔值
value = tree.xpath("//ul/li[contains(text(),'java')]")
print(f"contains(str1, str2)判断str1是否包含str2,返回布尔值:{value}") # [<Element li at 0x1e48dbc1c00>]
# starts-with(str1, str2) 判断str1是否以str2开头,返回布尔值
value = tree.xpath("//ul/li[starts-with(text(),'p')]")
print(f"starts-with(str1, str2)判断str1是否以str2开头,返回布尔值:{value}") # [<Element li at 0x230bb061c80>]
# not(expression)对表达式取反
value = tree.xpath("//ul/li[not(text()='java')]/text()")
print(f"not(expression)对表达式取反:{value}") # ['python', 'js']
# and逻辑与操作
value = tree.xpath("//ul/li[text()='java' and position()<3 ]")
print(f"and逻辑与操作:{value}") # [<Element li at 0x1946f761ec0>]
# or逻辑或操作
value = tree.xpath("//ul/li[text()='java' or text()='python' ]/text()")
print(f"or逻辑或操作:{value}") # ['python', 'java']
语法
语法 | 描述 |
---|---|
/ | 从根节点开始选取。 |
// | 从当前节点开始,递归选取所有匹配的节点,无论它们在文档中的位置。 |
. | 选取当前节点。 |
.. | 选取当前节点的父节点。 |
@ | 选取属性。 |
* | 匹配任何元素节点。 |
@* | 匹配任何属性节点。 |
node() | 匹配任何类型的节点(元素、属性、文本等)。 |
text() | 选取节点的文本内容。 |
[] | 用于过滤节点,括号内为条件。 |
| | 用于组合多个路径,返回所有匹配的节点。 |
position() | 返回节点在节点集中的位置。 |
last() | 返回节点集中最后一个节点。 |
contains(str1, str2) | 判断 str1 是否包含 str2 ,返回布尔值。 |
starts-with(str1, str2) | 判断 str1 是否以 str2 开头,返回布尔值。 |
not(expression) | 对表达式取反。 |
and | 逻辑与操作。 |
or | 逻辑或操作。 |
jsonpath
官网概念
其实jsonpath主要是对于xpath的一个补充、因为xpath处理的是html或者xml,所以当需要解析json时来使用jsonpath。
安装
# 支持一般的语法
pip install jsonpath
# 支持函数 length()、min()等
pip install jsonpath-ng
作用
-
主要用于 JSON 数据的查询和提取。
-
语法简单,适合处理嵌套的 JSON 结构。
-
适用于 API 返回的 JSON 数据处理。
使用
import jsonpath,json
from django.template.defaultfilters import length
# 官网:https://pypi.org/project/jsonpath-ng/ 注意了 必须要使用ext下才可以使用函数 否则会一直报错 这里说的是 表达式内直接写函数 `len`|`sub(/foo\\+(.*)/, \\1)`|`split(+, 2, -1)`|`sorted`
from jsonpath_ng.ext import parse
# 将json加载成jsonobject对象 之后对于jsonobject进行处理
obj = json.load(open("test_jsonpath.json",'r',encoding='utf-8'))
# $ 根节点
value = jsonpath.jsonpath(obj,"$.store")
print(f"$根节点:{value}") # $.store输出全部 $.clothing不是根目录 输出false
# @ 当前节点 通常于过滤连用[]
value = jsonpath.jsonpath(obj,"$.store.clothing[?(@.price > 10)]")
print(f"@当前节点:{value}") # [{'name': '绿色衣服', 'color': 'green', 'remark': '一件漂亮的绿色衣服', 'price': 179.99}]
# .key 选择当前节点的key子节点
value = jsonpath.jsonpath(obj,"$.store.clothing")
print(f".key选择当前节点的key子节点:{value}") # 太长了
# ['key'] 选择当前节点的key子节点(支持特殊字符)
value = jsonpath.jsonpath(obj,"$.store['food']")
print(f"['key'] 选择当前节点的key子节点(支持特殊字符):{value}") # [[{'name': '大面包', 'price': 1.5}, {'name': '烤鸡', 'price': 20}]]
# ..key 递归查找所有key节点
value = jsonpath.jsonpath(obj,"$.store..['food']")
print(f"..key 递归查找所有key节点:{value}") # [[{'name': '大面包', 'price': 1.5}, {'name': '烤鸡', 'price': 20}]]
# 通配符 *
value = jsonpath.jsonpath(obj,"$.store.clothing[*].name")
print(f"通配符 *:{value}") # ['红色衣服', '蓝色衣服', '绿色衣服']
# 数组索引 [n] 选择数组中的第n个元素(从 0 开始)
value = jsonpath.jsonpath(obj,"$.store.clothing[0].name")
print(f"数组索引 [n] 选择数组中的第n个元素(从 0 开始):{value}") # ['红色衣服']
# [start:end:step] 选择数组中的切片(支持 Python 切片语法)
value = jsonpath.jsonpath(obj,"$.store.clothing[0:1,1].name")
print(f"[start:end:step]选择数组中的切片(支持 Python 切片语法):{value}") # ['红色衣服', '蓝色衣服']
# [?(表达式)]使用过滤表达式筛选数组元素
value = jsonpath.jsonpath(obj,"$.store.clothing[?(@.name == '绿色衣服')]")
print(f" [?(表达式)]使用过滤表达式筛选数组元素:{value}") # [{'name': '绿色衣服', 'color': 'blue', 'remark': '一件漂亮的绿色衣服', 'price': 179.99}]
# =~ 正则表达式匹配
# len 返回数组或字符串的长度 函数这些需要jsonpath-ng库才可以支持 否则会返回False 安装pip install jsonpath-ng
try:
jsonpath_expr = parse('$.store.clothing.`len`')
except Exception as e:
raise e
# 下面的部分为 jsonpath_ng模块中的规定匹配写法
matches = [match.value for match in jsonpath_expr.find(obj)]
#或者 print(f"length()返回数组或字符串的长度:{length(matches)}") 这种方式就不用引入ext那个包了 引入 jsonpath_ng即可
print(f"`len`返回数组或字符串的长度:{matches}")
# min 返回数组中的最小值
try:
jsonpath_expr = parse('$.store.clothing[?(@.price < 10)].price')
except Exception as e:
raise e
# 下面的部分为 jsonpath_ng模块中的规定匹配写法
matches = [match.value for match in jsonpath_expr.find(obj)]
price = min(matches)
print(f"min()返回数组或字符串的长度:{price}")
# max() 返回数组中的最大值
price = max(matches)
print(f"max()返回数组中的最大值:{price}")
# sum() 返回数组中的总和
price = sum(matches)
print(f"sum()返回数组中的总和:{price}")
# sorted 排序
try:
jsonpath_expr = parse('$.store.clothing[?(@.price < 10)].`sorted`')
except Exception as e:
raise e
matches = [match.value for match in jsonpath_expr.find(obj)]
print(f"`sorted`排序:{matches}")
语法
语法 | 描述 |
---|---|
根节点 | |
$ | 根节点。 |
当前节点 | |
@ | 当前节点。 |
子节点 | |
.key | 选择当前节点的 key 子节点。 |
['key'] | 选择当前节点的 key 子节点(支持特殊字符)。 |
递归查找 | |
..key | 递归查找所有 key 节点。 |
通配符 | |
* | 匹配任何属性名或数组索引。 |
数组索引 | |
[n] | 选择数组中的第 n 个元素(从 0 开始)。 |
[start:end:step] | 选择数组中的切片(支持 Python 切片语法)。 |
[?(表达式)] | 使用过滤表达式筛选数组元素。 |
组合路径 | |
key1.key2 | 选择 key1 下的 key2 子节点。 |
key1[*].key2 | 选择 key1 下所有元素的 key2 子节点。 |
过滤表达式 | |
== | 等于。 |
!= | 不等于。 |
< | 小于。 |
<= | 小于等于。 |
> | 大于。 |
>= | 大于等于。 |
=~ | 正则表达式匹配。 |
in | 包含于。 |
nin | 不包含于。 |
&& | 逻辑与。 |
| | 逻辑或 |
函数 | |
length() | 返回数组或字符串的长度。 |
min() | 返回数组中的最小值。 |
max() | 返回数组中的最大值。 |
sum() | 返回数组中的总和。 |
对比
功能 | JSONPath | XPath |
---|---|---|
根节点 | $ | / |
当前节点 | @ | . |
父节点 | 无直接支持 | .. |
子节点 | .key 或 ['key'] | /nodename 或 //nodename |
递归查找 | ..key | //nodename |
通配符 | * | * |
属性选择 | 无直接支持(需通过过滤表达式) | @attr |
数组索引 | [n] | [n] |
数组切片 | [start:end:step] | 无直接支持 |
过滤表达式 | [?(表达式)] | [表达式] |
逻辑运算符 | && ,| | and , or |
比较运算符 | == , != , < , <= , > , >= | = , != , < , <= , > , >= |
正则表达式 | =~ | matches() |
函数 | length() , min() , max() , avg() , sum() | text() , contains() , starts-with() , position() , last() |
组合路径 | key1.key2 或 key1[*].key2 | /path1/path2 或 //path1//path2 |
文本内容 | 无直接支持 | text() |
Beautiful Soup
官网概念
Beautiful Soup 是一个 可以从 HTML 或 XML 文件中提取数据的 Python 库。它能用你喜欢的解析器和习惯的方式实现 文档树的导航、查找、和修改。它会帮你节省数小时甚至数天的工作时间。(主要的作用是解析HTML,获取指定的标签内容)。
安装
# 需要安装requests 联合使用
pip install beautifulsoup,requests
使用
import requests,re
from bs4 import BeautifulSoup
#
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="story1">...</p>
<p class="story2">2222</p>
<div data-foo="value">foo!</div>
"""
soup = BeautifulSoup(html_doc, 'html.parser')
# 获取title标签
print(soup.title)
# 获取title标签的名称 不是标签内部的
print(soup.title.name)
# 获取title标签的名称 是标签内部的
print(soup.title.string)
# parent的名称
print(soup.title.parent.name)
# 获取 第一个p标签
print(soup.p)
# 获取第一个p标签的class['title']
print(soup.p['class'])
# 获取第一个a标签
print(soup.a)
# 获取所有的a标签 find_all(标签名)
print(soup.find_all("a"))
# 获取所有的p标签
print(soup.find_all("p"))
# 使用多个引入正则re
print(soup.find_all(href=re.compile("elsie"), id='link1'))
# 获取自定义标签内容
print(soup.find_all(attrs={"data-foo": "value"}))
# 按css来搜索
print(soup.find_all("a", class_="sister"))
语法
方法或属性 | 描述 |
---|---|
初始化 | |
BeautifulSoup(html, 'html.parser') | 创建 BeautifulSoup 对象,解析 HTML 文档。 |
BeautifulSoup(html, 'lxml') | 使用 lxml 解析器创建 BeautifulSoup 对象(需安装 lxml 库)。 |
BeautifulSoup(html, 'html5lib') | 使用 html5lib 解析器创建 BeautifulSoup 对象(需安装 html5lib 库)。 |
查找元素 | |
soup.find(tag) | 查找第一个匹配的标签。 |
soup.find_all(tag) | 查找所有匹配的标签,返回列表。 |
soup.select(css_selector) | 使用 CSS 选择器查找元素,返回列表。 |
soup.find(tag, attrs={}) | 根据标签和属性查找元素。 |
soup.find_all(tag, attrs={}) | 根据标签和属性查找所有匹配的元素。 |
获取内容 | |
tag.text | 获取标签的文本内容。 |
tag.get_text() | 获取标签的文本内容(与 tag.text 相同)。 |
tag.string | 获取标签的文本内容(仅当标签内没有子标签时有效)。 |
tag['attr'] | 获取标签的属性值。 |
tag.get('attr') | 获取标签的属性值(推荐使用,避免属性不存在时报错)。 |
tag.contents | 获取标签的直接子节点列表。 |
tag.children | 获取标签的直接子节点迭代器。 |
tag.descendants | 获取标签的所有后代节点迭代器。 |
导航文档树 | |
tag.parent | 获取标签的父节点。 |
tag.parents | 获取标签的所有祖先节点迭代器。 |
tag.next_sibling | 获取标签的下一个兄弟节点。 |
tag.previous_sibling | 获取标签的上一个兄弟节点。 |
tag.next_element | 获取文档中的下一个节点(不一定是兄弟节点)。 |
tag.previous_element | 获取文档中的上一个节点(不一定是兄弟节点)。 |
修改文档 | |
tag.name = 'new_tag' | 修改标签的名称。 |
tag['attr'] = 'value' | 修改标签的属性值。 |
tag.append(new_tag) | 在标签内追加新标签或字符串。 |
tag.insert(index, new_tag) | 在标签内插入新标签或字符串。 |
tag.replace_with(new_tag) | 替换当前标签。 |
tag.decompose() | 删除当前标签及其内容。 |
tag.clear() | 删除当前标签的内容,保留标签本身。 |
其他方法 | |
soup.prettify() | 格式化输出 HTML 文档。 |
soup.encode() | 将文档编码为字节字符串。 |
soup.decode() | 将字节字符串解码为文档。 |
对比
特性/工具 | XPath | JSONPath | BeautifulSoup |
---|---|---|---|
用途 | 用于在 XML 和 HTML 文档中定位元素 | 用于在 JSON 数据中定位元素 | 用于解析和操作 HTML/XML 文档 |
语法 | 基于路径表达式 | 基于路径表达式 | 基于 Python 对象的 API |
数据格式 | XML、HTML | JSON | HTML、XML |
定位元素 | 通过节点路径、属性、文本等 | 通过键、索引、条件等 | 通过标签名、属性、CSS 选择器等 |
示例 | /bookstore/book[1]/title | $.store.book[0].title | soup.find('div', class_='title') |
灵活性 | 高,支持复杂查询 | 中,适合 JSON 结构 | 高,支持多种查找方式 |
学习曲线 | 中 | 低 | 低 |
适用场景 | XML/HTML 文档解析 | JSON 数据解析 | HTML/XML 文档解析和操作 |
跨语言支持 | 是(多种编程语言支持) | 是(多种编程语言支持) | 仅限 Python |
性能 | 高 | 高 | 中 |
扩展性 | 中 | 中 | 高 |
选择建议
-
如果需要处理 XML/HTML 文档并需要复杂的查询,选择 XPath。
-
如果需要处理 JSON 数据,选择 JSONPath。
-
如果需要解析和操作 HTML/XML 文档,并且使用 Python,选择 BeautifulSoup。