python爬虫之数据解析
第五章 数据解析
- 针对文本的解析,有正则表达式
- 针对HTML/XML解析有XPath、Beautiful Soup、正则表达式
- 针对JSON的解析,有jsonpath
一、正则表达式
1. 导入re模块,用re.search()方法和re.findall()方法
re.search(想找的内容,一个整体)这是找第一个,后面的找不到
1 | import re |
re.findall(正则语句,查找的文件)返回所有的符合条件的
1 | import re |
从字符串中提取中文,当中文不连接时,返回的是两个元素的列表
1 | import re |
二、XPath和lxml库
1. XPath语法
a)选取节点
表达式 | 说明 |
---|---|
nodename | 选取此节点的所有子节点 |
/ | 从根节点选取 |
// | 从匹配选择的当前节点选取文档中的节点,而不用考虑它们的位置(重要) |
. | 选取当前节点(类似于Linux) |
.. | 选取当前节点的父节点 |
@ | 选取属性 |
b)谓语
表达式 | 说明 |
---|---|
/bookstore/book[1] | 选取属于bookstore子元素的第一个book元素 |
/bookstore/book[last()] | 选取属于bookstore子元素的最后一个book元素 |
/bookstore/book[last()-1] | 选取属于bookstore子元素的倒数第二个book元素 |
/bookstore/book[position()<3] | 选取最前面的两个属于bookstore元素的子元素的book元素 |
//title[@lang] | 选取所有的title元素,且这些元素的拥有名称为lang的属性 |
//title[@lang=’eng’] | 选取所有的title元素,且这些元素的拥有值为eng的lang属性 |
/bookstore/book[price>35.00] | 选取bookstore元素的所有book元素,且其中的price元素的值大于35.00 |
/bookstore/book[price>35.00]/title | 选取bookstore元素中book元素的所有title元素,且其中的price元素值必须大于35.00 |
2. lxml库概述(需要导入lxml.etree模块)
- Element类:可以理解为XML的节点
- ElementTree类:可以理解为一个完整的XML文档树
- ElementPath类:可以理解为XPath,用于搜索和定位节点
a)Element类简介
Element类是XML处理的核心类,可以直观的理解为XML节点,大部分XML节点的处理都是围绕篇Element类进行的
所以,我们要创建一个节点对象
1 | #导入模块etree |
上述示例中,参数root表示节点的名称
关于Element类的相关操作,主要可分为三部分,分别是节点操作、节点属性的操作、节点内文本的操作
节点操作;若要获取节点的名称,可以通过tag属性获取
1
2
3
4
5print(root.tag)
#root
print(etree.tostring(root))
#b'<root/>'(我感觉这是节点的显示吧。。)
#该函数将元素序列化为XML树的编码字符串表示形式
节点属性的操作:在创建节点的同时,可以为节点增加属性。节点中的属性是以键值对的形式进行存储的,类似于字典的存储方式。通过构造方法创建节点时,可以在该方法中以参数的形式设置属性,其中参数的名称表示属性的名称,参数的值表示为属性的值。
1
2
3
4
5#2.给节点增加属性
#在创建的同时添加属性
root = etree.Element('root',name='zhang')
print(etree.tostring(root))
#b'<root name="zhang"/>'还可以用set()方法,把属性键值对增加进已有的节点
1
2
3
4#2.2增加属性 set
root.set('age','18')
print(etree.tostring(root))
#b'<root name="zhang" age="18"/>'节点内文本的操作:一般情况下,可以通过text、tail属性或者xpath()方法来访问文本内容
1
2
3
4#3.添加文本
root.text='hello,world!'
print(etree.tostring(root))
#b'<root name="zhang" age="18">hello,world!</root>'
b)从字符串或文件中解析XML
为了能够将XML文件解析为树结构,etree模块中提供了如下3个函数
1. fromstring()函数:从字符串中解析XML文档或片段,返回根节点 2. XML()函数:从字符串常量中解析XML文档或片段,返回根节点 3. HTML()函数:从字符串常量中解析HTML文档或片段,返回根节点
其中,XML函数的行为类似于fromstring函数;HTML()函数自动补全缺少的和
标签
1 | import lxml from etree |
1 | #方法2:用xml函数 |
1 | #方法3:html函数,他会自动修正html |
从文件中读取
1 | element = etree.parse('hello.html')#读取文件 |
c)ElementPath类简介
ElementTree类中附带了一个类似于XPath路径语言的ElementPath类。现提供以下三个常用的函数:
find()方法:返回匹配的第一个子元素
- findall()方法:以列表的形式返回所有匹配的子元素
- iterfind()方法:返回一个所有匹配元素的迭代器
1 | #三。查找与搜索元素 |
3.lxml库的基本使用
这里有一个测试用例文件,hello.html
1
2
3
4
5
6
7
8
9 <div>
<ul>
<li class="item-0"><a href="link1.html">first item</a></li>
<li class="item-1"><a href="link2.html">second item</a></li>
<li class="item-inactive"><a href="link3.html"><span class="bold">third item</span></a></li>
<li class="item-1"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
首先导入lxml.etree模块
1 | from lxml import etree |
读取文件,发现返回的是elementTree,不是element节点
1 | element = etree.parse('hello.html')#读取文件 |
用xpath()方法,将hello.html文件中与该路径表达式匹配到的列表返回
1 | #1.获取所有的li标签 |
1 | #2. 获取所有li元素的class属性 |
1 | #3. 获取li标签下的所有a标签 |
1 | #4. 获取倒数第二个li标签下的a标签的文本 |
三、Beautiful Soup
Beautiful Soup 和lxml库功能相似,但是Beautiful Soup 使用起来更加简洁方便
需安装beautifulsoup4和bs4
1. 导入bs4.beautifuSoup
1 | from bs4 import BeautifulSoup |
2. 测试用例(‘’‘三个点表示原样式写入)
1 | html = """ |
3. 构造beautifulSoup对象
BeautifulSoup(html,’lxml’),html是页面的代码(表示要解析的文档字符串或文件对象),lxml是解析的的解析器,自动补全标签
1 | #构造beautifulsoup对象 |
可以用本地的HTML文件来构造beautifulsoup对象
1 | bs = BeautifulSoup(open('index.html'),'lxml') |
格式化输出,用的是prettify()方法,输出的形式就类似于页面代码的格式,方便查看
1 | print(bs.prettify())#美观显示 |
4. 三个获取(获取节点、获取文本字符串、获取注释)
获取节点,bs.属性标签(如果有多个标签,只取第一个)
1
2
3
4
5
6
7
8print(bs.p) #如果有多个标签,只取第一个
#<p class="title" name="dormouse"><b>The Dormouse's story</b></p>
print(bs.a.name)#获取标签名称
#a
print(bs.a.attrs)#获取标签的所有属性
#{'href': 'http://example.com/elsie', 'class': ['sister'], 'id': 'link1'}- 获取文本字符串,用的.string
1
2
3title = bs.title.string
print(title)
#The Dormouse's story1
2print(title.find_parent()) #获取父节点
#<title>The Dormouse's story</title>1
2
3
4
5
6
7
8
9
10
11
12
13
14#如果没有下一节点,就会找父亲的下一节点,如果还是没有,会找爷爷的下一节点
print(title.find_next()) #获取下一个节点(下一节点的意思是“兄弟”)
#<body>
#<p class="title" name="dormouse"><b>The Dormouse's story</b></p>
#<p class="sotry">Once upon a time there were three little sisters;and their names were
#<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>
#<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
#<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
#and they lived at the bottom of a well.</p>
#<p class="story">...</p>
#</body>
#因为title的下一节点没有,就去找了head标签的下一个节点,head标签下也没有下一节点,就去html节点下找,然后找到了body1
2print(title.find_previous())#获取上一节点(父节点)
##<title>The Dormouse's story</title>- 获取注释的内容(也就是说.string方法不规避注释)
1
2
3
4#3. 注释
print(bs.a.string)
# Elsie
#这里获取的是第一个a标签,而第一个a标签中是注释的内容,正好获取了注释的内容
5. 通过操作方法进行解读搜索
1 | from bs4 import BeautifulSoup |
实际上,网页中有用的信息都存在于网页中的文本或者各种不同的标签的属性值,为了能够得到这些有用的网页信息,可以通过一些查找方法获取文本或者标签属性。因此,bs4库内置了一些方法,常用的有这两个方法:
(1) find()方法:用于查找符合查询条件的第一个标签节点。
(2) find_all()方法:查找所有符合查询条件的标签节点,并返回一个列表
1 | def find_all(self, name=None, attrs={}, recursive=True, text=None, |
name参数,查找所有名字为name的标签,但字符串会被自动忽略。
1
2
3
4
5
6
7#1.1 标签名
print(bs.find_all('a'))
'''
[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
'''1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20#1.2 正则 必须是编译后的正则
# 查找以b开头的标签
print(bs.find_all(re.compile('^b')))
'''
[<body>
<div data-foo="value">foo!</div>
<p class="title" name="dormouse">
<b>The Dormouse's story</b>
</p>
<p class="sotry">Once upon a time there were three little sisters;and their names were
<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a> and
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>;
and they lived at the bottom of a well.
</p>
<p class="story">...</p>
</body>,
<b>The Dormouse's story</b>]
'''1
2
3
4
5
6
7
8
9
10# 1.3 列表
# 查找a标签和b标签
print(bs.find_all(['a','b']))
'''
[<b>The Dormouse's story</b>,
<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
'''kwargs:根据属性进行查找
1
2
3#2.1 直接传入属性值
print(bs.find_all(id='link2'))
#[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]1
2
3
4#2.2 传入编译后的正则
#查找href属性包含elsie的标签
print(bs.find_all(href=re.compile('elsie')))
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]1
2
3
4
5
6
7#2.3 查找class属性'sister'的标签 class是关键字,写成class_
print(bs.find_all(class_='sister'))
'''
[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
'''attrs:如果某个指定名字的参数不是搜索方法中内置的参数名,那么在进行搜索时,会把该参数当作指定名称中的属性来搜索
传入的是字典
1
2
3
4
5
6
7
8#2.4 查找data-foo为'value'的标签
!!!print(bs.find_all(data-foo='value')) #错,参数不能有中划线
#3。 attrs:根据属性进行查找,参数是字典
print(bs.find_all(attrs={
'data-foo':'value'
}))
#[<div data-foo="value">foo!</div>]text:搜索文档中的字符串内容,可以接受字符串、正则表达式和列表。此方法不查找注释的内容
1
2
3
4
5
6#4。 text:根据文本进行查找,可以传入字符串,正则,列表
#4.1 传入字符串 查找内容为'Lacie'的标签
print(bs.find_all(text='Lacie'))
print(bs.find_all(text='Lacie')[0].find_parent())
#['Lacie']
#<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>1
2
3#4.2 传入正则
print(bs.find_all(text=re.compile('story')))
#["The Dormouse's story", "The Dormouse's story"]1
2
3#4.3 传入列表 text参数不查找注释
print(bs.find_all(text=['Elsie','Lacie','Tillie']))
#['Lacie', 'Tillie']limit参数:限制查找的个数
1
2
3
4
5
6#5 limit:用于限制最多查几个
print(bs.find_all('a',limit=2))
'''
[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
'''recursive参数:调用tag的 find_all() 方法时,Beautiful Soup会检索当前tag的所有子孙节点,如果只想搜索tag的直接子节点,可以使用参数 recursive=False
1
2
3
4
5#6 recursive:是否要递归查找,默认是True,如果指定为False 只能查找直接子标签
print(bs.find_all('title'))
print(bs.find_all('title',recursive=False))
#[<title>The Dormouse's story</title>]
#[]
6. 通过CSS选择器进行搜索
为了使用CSS选择器达到筛选节点的目的,在bs4库的BeautifulSoup类中提供了一个select()方法,该方法会将搜索到的结果放入列表。
1 | from bs4 import BeautifulSoup |
通过标签查找
1
2print(bs.select('title'))
#[<title>The Dormouse's story</title>]- 通过类名查找
1
2
3
4
5
6print(bs.select('.sister'))
'''
[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
'''- 通过id查找
1
2print(bs.select('#link1'))
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]- 组合查找
1
2
3
4
5
6
7
8
9
10
11
12print(bs.select('p #link2'))#查找p标签下的link2的标签
#[<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>]
print(bs.select('head > title'))#查找head下的title
#[<title>The Dormouse's story</title>]
print(bs.select('body > title'))#查找body下的title
#[<title>The Dormouse's story</title>]
print(bs.select('body .sister'))#查找body下class为title的标签
'''
[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>,
<a class="sister" href="http://example.com/lacie" id="link2">Lacie</a>,
<a class="sister" href="http://example.com/tillie" id="link3">Tillie</a>]
'''- 通过属性查找,查找href=’http://example.com/elsie'的a标签
1
2print(bs.select('a[href="http://example.com/elsie"]'))
#[<a class="sister" href="http://example.com/elsie" id="link1"><!-- Elsie --></a>]
四、JSONPath和json模块
从Python 2.6 开始加入了json模块,使用import json导入就可以使用。json模块提供了Python 对象的序列化和反序列化功能。
(1) 序列化:将一个python对象编码转换为JSON字符串的过程,dump()和dumps()
(2)反序列化:将以给JSON字符串解码转换为python对象的过程,load()和loads()
1. json模块基本运用
函数 | 作用 |
---|---|
loads() | 将json字符串转换为python对象 |
load() | 将json文件转换为python对象 |
dumps() | 将python类型转换为json字符串 |
dump() | 将python类型转换为json文件 |
(1) loads() json字符串->python
1 | import json |
(2) jumps() python->json字符串 !!注意:jumps()方法默认使用ascii码,禁用,使用utf-8编码
1 | json_obj = json.dumps(dic) |
这里有个小技巧,可以设置缩进,让json格式看起来更爽洁
格式化输出,用indent参数设定缩进的空格数,可以设置为2,或者4
1
2
3
4
5
6
7
8 print(json.dumps(dic,ensure_ascii=False,indent=4))
'''
{
"name": "张三",
"age": 16,
"gender": "男"
}
'''
(3) dump() python->json文件对象
打开文件,如没有此文件就创建,如果有就写,with可以自动关闭
1 | with open('person.json','w',encoding='utf-8') as f: |
(4) load() json文件->python
1 | with open('person.json','r',encoding='utf-8') as f: |
2. JSONPath简介
JSONPath是一种信息抽取类库,是从JSON文档中抽取指定信息的工具。
要提前安装jsonpath库
1 | pip install jsonpath |
使用要导入josnpath模块
1 | import jsonpath |
3. JSONPath语法对比
JSON结构清晰,可读性高,复杂度低,非常容易匹配。JSONPath的语法和XPath类似。
XPath | JSONPath | 描述 | |
---|---|---|---|
/ | $ | 根节点 | |
. | @ | 现行节点 | |
/ | .or[] | 取子节点 | |
.. | n/a | 取父节点,JSONPath未支持 | |
// | .. | 不管位置,选择所有符合条件的节点 | |
* | * | 匹配所有元素节点 | |
@ | n/a | 根据属性访问,JSON不支持,因为JSON是键值对的结构,不需要属性访问 | |
[] | [] | 迭代器表示(可以在里面做简单的迭代操作,如数组下标、根据内容选值等) | |
\ | [,] | 支持迭代器多选 | |
[] | ?() | 过滤操作 |
首先获得一个json文件,导入json和jsonpath
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25import json
import jsonpath
json_str = '''
{
"store": {
"book": [
{ "category":"reference",
"author":"Nigel Rees",
"title":"Sayings of the Century",
"price":8.95
},
{ "category":"fiction",
"author":"J. R. R. Tolkien",
"title":"The Lord of the Rings",
"isbn":"0-395-19395-8",
"price":22.99
}
],
"bicycle":{
"color":"red",
"price":19.95
}
}
}
'''将json格式转换为python对象
1
2
3json_param = json.loads(json_str)
print(type(json_param))
#<class 'dict'>进行jsonpath对文件进行数据解析
查看json_param下的bicycle的color属性
1
2
3
4
5
6
7
8check_url = '$.store.bicycle.color'
print(jsonpath.jsonpath(json_param,check_url))
#['red']
#或者直接用..进行定位(最好在最前面加个$表示一下在根节点内进行查找)
check_url = '$..color'
print(jsonpath.jsonpath(json_param,check_url))
#['red']输出所有的book
1
2
3
4
5check_url = '$.store.book[*]'
print(jsonpath.jsonpath(json_param,check_url))
'''
[{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95}, {'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}]
'''输出第一本book,注意:这里第一个索引是从0开始
1
2
3check_url = '$.store.book[0]'
print(jsonpath.jsonpath(json_param,check_url))
#[{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95}]输出所有的书名
1
2
3check_url = '$.store.book[*].title'
print(jsonpath.jsonpath(json_param,check_url))
#['Sayings of the Century', 'The Lord of the Rings']过滤 输出book中的price为22.99的所有对象
?() ?就是那个要找的对象,()是要满足的要求,@要加,表示在book里找。点表示在下一个节点了,也主要要加
1
2
3check_url = '$.store.book[?(@.price==22.99)]'
print(jsonpath.jsonpath(json_param,check_url))
#[{'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}]输出所有价格小于10的book
1
2
3check_url = '$.store.book[?(@.price<10)]'
print(jsonpath.jsonpath(json_param,check_url))
#[{'category': 'reference', 'author': 'Nigel Rees', 'title': 'Sayings of the Century', 'price': 8.95}]输出所有含有isbn的book
1
2
3check_url = '$.store.book[?(@.isbn)]'
print(jsonpath.jsonpath(json_param,check_url))
#[{'category': 'fiction', 'author': 'J. R. R. Tolkien', 'title': 'The Lord of the Rings', 'isbn': '0-395-19395-8', 'price': 22.99}]