几种数据解析对比
解析工具 | 解析速度 | 使用难度 |
---|---|---|
BeautifulSoup | 最慢 | 最简单 |
lxml | 快 | 简单 |
正则表达式 | 最快 | 最难 |
lxml—-xpath
Xpath
xpath(XML Path Language):一门在XML和HTML文档中查找信息的语言,可用来在XML和HTML文档中对元素和属性进行遍历。Xpath有七种类型节点:元素、属性、文本、命名空间、处理指令、注释以及文档根节点。
- xpath浏览器常用工具:
Xpath谓语
路径表达式 | 结果 |
---|---|
/bookstore/book[1] | 选第一个book |
/bookstore/book[last()] | 选最后一个book |
/bookstore/book[last()-1] | 选倒数第二个book |
/bookstore/book[position()<3] | 选取前两个book |
//title[@lang] | 选取拥有lang属性的title |
//title[@lang=’en’] | 选取lang属性等于en的title |
/bookstore/book[price>35.00] | 选取price值大于35的book |
/bookstore/book[price>35.00]/title | 选取price值大于35的book的title |
Xpath通配符
通配符 | 描述 |
---|---|
* | 匹配任意节点 |
@* | 匹配节点中的任何属性 |
Xpath运算符
运算符 | 描述 | 实例 |
---|---|---|
| | 一次选中多个集合 | //book | //cd |
+ | 加法 | 6 + 4 |
- | 减法 | 6 - 4 |
* | 乘法 | 6 * 4 |
div | 除法 | 8 div 4 |
mod | 计算除法余数 | 5 mod 2 |
还有一些比较运算符返回的是布尔型的值:=、!=、<、<=、>、>=、or、and
lxml库
lxml是一个HTML/XML的解析器。解析HTML代码的时候,如果HTML代码不规范,他会自动进行补全。
lxml库基本使用
from lxml import etree
text = """
<div>
<ul>
<li class="item-0"><a href="index.html"></a></li>
<li class="item-1"><a href="index.html"></a></li>
<li class="item-2"><a href="index.html"></a>
</ul>
</div>
"""
# 将字符串解析为HTML文档
html = etree.HTML(text)
print(html)
# 将字符串系列化html
result = etree.tostring(html).decode('utf-8')
print(result)
# 从文件中读取html,如果不能因为html文件的规范问题不能直接读取,那我们需要加入第一行,指定编码方式
parse = etree.HTMLParser(encoding='utf-8')
html = etree.parse('Xpath-test.html', parser=parse)
print(etree.tostring(html).decode('utf-8'))
lxml使用Xpath语法
如下是一个html文件的简单例子:
<!-- hello.thml -->
<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-0"><a href="link4.html">fourth item</a></li>
<li class="item-0"><a href="link5.html">fifth item</a></li>
</ul>
</div>
代码使用xpath例子如下:
from lxml import etree
html = etree.parse('hello.html')
# 获取所有li标签
result = html.xpath('//li')
for i in result:
print(etree.tostring(i))
# 获取所有li标签下所有class属性值
result = html.xpath('//li/@class')
# 获取li标签下href为www.baidu.com的a标签,找不到会返回空列表
result = html.xpath('//li/a[@href="www.baidu.com"]')
# 获取li下所有span标签
result = html.xpath('//li//span')
# 获取li下所有a标签里的class
result = html.xpath('//li/a//@class')
# 获取最后一个li的a标签的href值
result = html.xpath('//li[last()]/a/@href')
# 获取倒数第二个li元素的内容
result0 = html.xpath('//li[last()-1]/a')
print(result0[0].text)
result1 = html.xpath('//li[last()-1]/a/text()')
print(result1)
BeautifulSoup4
安装
pip install bs4
和lxml一样,Beautiful Soup也是一个HTML/XML的解析器,主要功能也是解析和提取数据。lxml只会局部遍历,而Beautiful Soup是基于HTML DOM(Document Object Model)的,会载入整个文档,解析整个DOM树,因此时间和内存开销会大很多,所以性能低于lxml。
使用BeautifulSoup4也要安装lxml,所以我不是很喜欢,这里不做记录,需要使用的话自己百度吧!
re模块—-正则表达式
正则表达式
单字符串匹配规则
字符 | 描述 |
---|---|
任意字符 | 输入谁,匹配谁 |
.(点) | 匹配任意的字符(除了’\n’) |
\d | 匹配任意的数字 |
\D | 匹配任意的非数字 |
\s | 匹配空白字符(包括:\n,\t,\r和空格) |
\S | 匹配非空白字符 |
\w | 匹配的是a-z和A-Z以及数字和下划线 |
\W | 匹配的和’\w’相反 |
[] | 组合的方式,只要满足中括号中的某一项都算匹配成功(如:[a-zA-Z0-9],[^a-zA-Z0-9]) |
多字符匹配
字符 | 匹配 |
---|---|
* | 匹配前一个字符0次或者无限次 |
+ | 匹配前一个字符1次或者无限次 |
? | 匹配前一个字符0次或者1次 |
{m}/{m, n} | 匹配前一个字符m次或者m到n次 |
开始、结束、贪婪、非贪婪
字符 | 描述 |
---|---|
^ | 以…开始 |
$ | 以…结束 |
? | 默认为贪婪匹配,加上’?’为非贪婪匹配(这个’?’与表示次数的’?’不同,这个必须放在表示次数的符号后,如: *? +? ??。) |
转义字符和原生字符串
Python中的转义字符是‘\‘,原生字符串通过在字符串之前加字母‘r’表示,如:r”abc\n”。
正则表达式中,转义字符也是‘\‘。
通过Python使用正则表达式时,有时会因为转义而出现一些问题,如下例子:
import re
text = "\cab c"
res = re.match("\\c", text)
print(res.group())
观察上述代码可以发现,我们的意图是想匹配text开头的’\c’字符串,但在执行时却报错了。这是因为使用Python执行上述代码时,”\\c”经过Python的转义,变为了”\c”,之后再进入正则表达式的转义,这时已经没有转义字符了,自然解析就出了问题。解决办法有两个,如下:
# 解决办法一:计算两层转义该写几个'\'
res = re.match("\\\\c", text)
# 解决办法二:使用原生字符
res = re.match(r"\\c", text)
使用原生字符后,就没有Python转义这一步骤了。所以,在应用过程中,我们建议多使用原生字符。
分组——正则表达式中加入括号
如果想具体的分组提取,如下例子:
import re
text = "apple price is $99, orange price is $88"
result = re.search('.+(\$\d+).+(\$\d+)', text)
print(result.group()) # 输出:apple price is $99, orange price is $88
print(result.group(0)) # group()/group(0)都匹配整个分组
print(result.group(1)) # 输出:$99
print(result.group(2)) # 输出:$88
print(result.groups()) # 输出:('$99', '$88')
re模块常用方法
方法 | 描述 |
---|---|
compile() | 将正则表达式提前编译好,返回一个对象,提高程序运行效率 |
match() | 在字符串开始的位置匹配,若比配失败,返回None |
search() | 找到第一个匹配的值返回,找不到返回None |
findall() | 返回所有满足条件的匹配项,找不到返回None |
finditer() | 搜索字符串,返回一个Match对象的迭代器 |
split() | 按照匹配的子串,将string分割后返回列表 |
sub() | 替换字符串中每一个匹配的子串后,返回替换后的字符串 |
import re
text = "apple price is $99, orange price is $88"
rr = re.compile(r'\$\d+')
res1 = rr.findall(text)
print(res1) # 输出:['$99', '$88']
res2 = re.match(r'\w+ \w+', text)
print(res2.group()) # 输出:apple price
res3 = re.search(r'\$\d+', text)
print(res3.group()) # 输出:$99
res4 = re.findall(r'\$\d+', text)
print(res4) # 输出:['$99', '$88']
res5 = re.finditer(r'\$\d+', text)
print('----------------------')
for i in res5:
print(i.group()) # 输出:$99\n$88
print('----------------------')
res6 = re.split(r' |,', text)
print(res6) # 输出:['apple', 'price', 'is', '$99', '', 'orange', 'price', 'is', '$88']
res7 = re.sub(r' |,', '*', text)
print(res7) # 输出:apple*price*is*$99**orange*price*is*$88
# -----------------------------------------------------------------------------
# ------如果要加注释,使用的方法的最后加上re.VERBOSE
# ------如果要 . 能表示所有字符,包括 '\n',那使用的方法的最后加上re.DOTALL
#---------------------------具体例子如下---------------------------------
houses = re.findall(r"""
<li.*?house-cell.*?<a.*?strongbox.*?>(.*?)</a> # 房源标题
.*?<p.*?room.*?>(.*?)</p> # 户型大小
.*?money.*?strongbox.*?>(.*?</b>.*?)</div> # 价格
""", text, re.VERBOSE|re.DOTALL)
# -----------------------------------------------------------------------------