高性能HTML内容解析
-
用正则表达式从网页中提取数据虽然可行。但是,网页的源代码是一种结构化的数据,如果仅仅使用正则表达式,那么这种结构化的优势就没有被很好地利用起来。现在把正则表达式中举的那个例子再做一下演绎:
-
有一个人,长得非常特别,身高3米,皮肤是绿色。如果他在你眼前,你必定可以一眼认出他。可是,现在只知道他在地球上,应该如何找到他?到全世界的每个地方都去看一下,直到遇到他为止。这种做法理论上当然没有问题,但却费时费力,而且人生苦短,可能一辈子也碰不到。但如果现在知道了他的地址“中国,北京,海淀区,xx路,xx号,第3层楼”,要找到他就易如反掌了。
HTML基础结构
-
HTML
也就是前面章节提到的网页源代码,是一种结构化的标记语言。HTML
可以描述一个网页的结构信息。 -
HTML
与CSS
(Cascading Style Sheets
,层叠样式表)、JavaScript
一起构成了现代互联网的基石。 -
HTML
中的标签都是成对出现的,标签的一般形式就如下图所示
<标签名>
文本
</标签名>
-
不加斜杠,表示标签开始;加上斜杠,表示标签结束。它们中间的部分,就是标签里面的元素。标签里面可以是另一个标签,也可以是一段文本。标签可以并列,也可以嵌套。
-
不论谁在谁旁边,不论谁包含了谁,通过HTML 的这种表示方法,都可以轻易将不同标签的相对关系表现出来。
下面是一段真实是HTML
代码的结构
<html>
<head>
<title>测试</title>
</head>
<body>
<div class="useful">
<ul>
<li class="info">我需要的信息1</li>
<li class="info">我需要的信息2</li>
<li class="info">我需要的信息3</li>
</ul>
</div>
<div class="useless">
<ul>
<li class="info">垃圾1</li>
<li class="info">垃圾2</li>
</ul>
</div>
</body>
</html>
- 每个标签除了标签名以外,还有“属性”。一个标签可以有0个、1个或者多个属性,所以一个真正的HTML标签应该是下面这样的:
<标签名 属性1="属性1的值" 属性2="属性2的值">
显示在网页上的文本
</标签名>
- 可以表示成一个倒立的树形结构,如下所示:
HTML
就是通过这样一种一层套一层的结构来描述一个网页各个部分的相对关系的。这里的<html></html>
、<div></div>
等都是HTML
的标签。如果把HTML
最外层的标签<html>
当作树根,从树根上面分出了两个树枝<head>
和<body>,<body>
里面又分出了class分
别为useful
和useless
的两个树枝
实现XPath从HTML源代码中提取有用的信息
XPath的介绍
-
XPath(XML Path)
是一种查询语言,它能在XML
(Extensible Markup Language
,可扩展标记语言)和HTML
的树状结构中寻找结点。 -
形象一点来说,
XPath
就是一种根据“地址”来“找人”的语言。 -
用正则表达式来提取信息,经常会出现不明原因的无法提取想要内容的情况。最后即便绞尽脑汁终于把想要的内容提取了出来,却发现浪费了太多的时间。
-
需要寻找的内容越复杂,构造正则表达式所需要花费的时间也就越多。
-
而
XPath
却不一样,熟练使用XPath
以后,构造不同的XPath
,所需要花费的时间几乎是一样的,所以用XPath
从HTML
源代码中提取信息可以大大提高效率。 -
在
Python
中,为了使用XPath
,需要安装一个第三方库:lxml
。
库的安装
在Mac OS
下安装lxml
- 如果操作系统为
Mac OS
,可以直接使用pip:pip install lxml
在Ubuntu
下安装lxml
- 如果操作系统为
Ubuntu
,可以使用如下命令安装:lxml:sudo apt-get install python-lxml
在Windows
下安装lxml
- 如果操作系统为
Windows
,那么安装lxml
的过程比较麻烦。如果直接使用pip install lxml
命令安装,大概率会出现问题,因为lxml
底层使用的是C语言来实现的,所以计算机需要安装C++
的运行库 - 首先下载对应版本的
whl
,点击跳转,下载完成后再存放这个包的文件夹打开CMD
,执行下面代码:pip install lxml‑3.7.3‑cp36‑cp36m‑win_amd64.whl
- 若上面的方法不行,那么则可以将下载好的
whl
文件的后缀改成zip
,然后解压缩,就会得到两个文件夹,如下所示:
- 把这两个文件夹复制到,
Python
安装文件夹下的Lib\site-packages
即可 - 打开
Python
交互环境,输入Import lxml
,如果不报错,就说明安装成功
XPath语法讲解
<html>
<head>
<title>测试</title>
</head>
<body>
<div class="useful">
<ul>
<li class="info">我需要的信息1</li>
<li class="info">我需要的信息2</li>
<li class="info">我需要的信息3</li>
</ul>
</div>
<div class="useless">
<ul>
<li class="info">垃圾1</li>
<li class="info">垃圾2</li>
</ul>
</div>
</body>
</html>
如何获取以下的信息
我需要信息1
我需要信息2
我需要信息3
selector = lxml.html.fromstring(source)
info = selector.xpath('//div[@class="useful"]/ul/li/text()')
print(info)
XPath语句的格式
XPath
的使用格式如下所示,其中网页源代码可以使用requests
来获取
import lxml.html
selector = lxml.fromstring('网页源代码')
info = selector.xpath('一段XPath语句')
XPath
的核心就是写地址
获取文本:
//标签1[@属性1="属性值1"]/标签2[@属性2="属性值2"]/..../text()
获取属性
//标签1[@属性1="属性值1"]/标签2[@属性2="属性值2"]/..../@属性n
注:[@属性="属性值"]
不是必需的。它的作用是帮助过滤相同的标签。在不需要过滤相同标签的情况下可以省略。
标签1的选取
标签可以直接从html
这个最外层的标签开始,一层一层往下找,这个时候XPath
语句是这样的
/html/body/div[@class="useful"]/ul/li/text()
当以html
开头的时候,它前面是单斜线。这样写虽然也可以达到目的,但是却多此一举。正如在淘宝买东西时,没有人会把收货地址的形式写为“地球,亚洲,中国,北京,海淀区,××路,××号”一样。地址前面的“地球,亚洲,中国”写了虽然也没错,但却没有必要。谁都知道全世界只有一个北京。而北京必定在中国,中国必定在亚洲,亚洲必定在地球上。所以,写收货地址的时候,直接写北京就可以了,前面的“地球,亚洲,中国”可以省略。XPath
也是同样的道理。在XPath
里面找到一个标志性的“地标”,然后从这个标志性的“地标”开始往下找就可以了。标志性的“地标”前面的标签都可以省略。
可以省略的属性
<div class="useful">
<ul>
<li class="info">我需要的信息1</li>
<li class="info">我需要的信息2</li>
<li class="info">我需要的信息3</li>
</ul>
</div>
< ul >
标签本身就没有属性,则写XPath
的时候,其属性可以省略。
标签有属性,但是如果这个标签的所有属性值都相同,则可以省略属性,例如 < li class ="info" >
,所有的 < li >
标签都有一个class
属性,值都为info
,所以属性可以省略。
XPath的特殊情况
以相同的字符串开头,如下面的HTML
代码
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8" />
<title></title>
</head>
<body>
<div id="test-1">需要的内容1</div>
<div id="test-2">需要的内容2</div>
<div id="testfault">需要的内容3</div>
<div id="useless">这是我不需要的内容</div>
</body>
</html>
selector = lxml.html.fromstring(source)
content = selector.xpath('//div[starts-with(@id, "test")]/text()')
for each in content:
print(each)
属性值包含相同的字符串
- 寻找属性值包含某些字符串的元素时,
XPath
的写法格式和以某些字符串开头的写法格式是相同的,只不过关键字从“starts-with
”变成了“contains
”。例如提取所有属性值中包含“-key
”的标签中的文本信息
source = '''
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8" />
<title></title>
</head>
<body>
<div id="test-key">需要的内容1</div>
<div id="test-key-2">需要的内容2</div>
<div id="key-testfault">需要的内容3</div>
<div id="useless">这是我不需要的内容</div>
</body>
</html>
'''
selector = lxml.html.fromstring(source)
content = selector.xpath('//div[contains(@id, "key")]/text()')
对XPath
返回的对象执行XPath
<html>
<head>
<title>测试</title>
</head>
<body>
<div class="useful">
<ul>
<li class="info">我需要的信息1</li>
<li class="info">我需要的信息2</li>
<li class="info">我需要的信息3</li>
</ul>
</div>
<div class="useless">
<ul>
<li class="info">垃圾1</li>
<li class="info">垃圾2</li>
</ul>
</div>
</body>
</html>
useful = selector.xpath('//div[@class="useful"]')
info_list = useful[0].xpath('ul/li/text()')
注:在对XPath
返回的对象再次执行XPath
是不需要添加斜线,直接以标签名开始即可
不同标签下的文字
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8" />
<title></title>
</head>
<body>
<div id="test3">
aaaa,
<span id="test4">
bbbb,
<ul>
ccc,
<li>dddd,</li>
</ul>
eeee,
</span>
ffff
</div>
</body>
</html>
selector = lxml.html.fromstring(source)
list_data = selector.xpath('//div[@id="test3"]/text()')
for data in list_data:
print(data)
为什么只有两个字符串,是因为只有这两句是真正的属于div
标签的,XPath
不会自动把子标签的内容提取出来。若是要全部提取,只需要按照下面的写法
data_all = selector.xpath('string(//div[@id="test3"])')
使用谷歌浏览器来辅助构造XPath
借助Google来协助分析网页的结构,可以大大提高分析效率
我们在需要分析的页面右击然后点击检查就可以查看网页的代码,或者可以直接使用快捷键F12
查看
网页的代码如上图所示,但是我们一个个的去找我们所需要的也很麻烦,所以我们还是可以通过浏览器来快速查找
先选择红色的光标,然后点击自己想要查找的地方,就会直接跳转到所需要的代码处
此时点击复制即可
我们还可以借助浏览器来验证自己的XPath
是否书写正确,Ctrl + F
即可快速查询
测试:爬取豆瓣一周口碑排行榜
import requests
from lxml import etree
if __name__ == '__main__':
headers = {
'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/99.0.4844.51 Safari/537.36'
}
url = 'https://movie.douban.com/'
page_text = requests.get(url=url, headers=headers).text
# print(page_text)
# 数据解析
tree = etree.HTML(page_text)
title_list = tree.xpath('//div[@class="billboard-bd"]/table/tr/td[2]/a/text()')
print(title_list)