几种数据解析对比
| 解析工具 | 解析速度 | 使用难度 |
|---|---|---|
| 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 etreetext = """<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)# 将字符串系列化htmlresult = 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 etreehtml = 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标签里的classresult = 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 retext = "\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 retext = "apple price is $99, orange price is $88"result = re.search('.+(\$\d+).+(\$\d+)', text)print(result.group()) # 输出:apple price is $99, orange price is $88print(result.group(0)) # group()/group(0)都匹配整个分组print(result.group(1)) # 输出:$99print(result.group(2)) # 输出:$88print(result.groups()) # 输出:('$99', '$88')
re模块常用方法
| 方法 | 描述 |
|---|---|
| compile() | 将正则表达式提前编译好,返回一个对象,提高程序运行效率 |
| match() | 在字符串开始的位置匹配,若比配失败,返回None |
| search() | 找到第一个匹配的值返回,找不到返回None |
| findall() | 返回所有满足条件的匹配项,找不到返回None |
| finditer() | 搜索字符串,返回一个Match对象的迭代器 |
| split() | 按照匹配的子串,将string分割后返回列表 |
| sub() | 替换字符串中每一个匹配的子串后,返回替换后的字符串 |
import retext = "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 priceres3 = re.search(r'\$\d+', text)print(res3.group()) # 输出:$99res4 = 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$88print('----------------------')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)# -----------------------------------------------------------------------------
