目录
通过前面的学习,我们已经掌握了使用正则表达式从网页的源代码中提取数据的方法。这一章,我们将利用网页的结构化特点,学习使用更加高效的方法提取页面信息。
学习重点:
(1)HTML基础结构
(2)使用XPath从HTML源代码中提取有用信息
一、HTML基础结构
1.什么是HTML?
HTML指的是超文本标记语言 (Hyper Text Markup Language),它不是一种编程语言,而是一种标记语言。HTML可以描述一个网页的结构信息。
HTML与CSS、JavaScript分别代表了网页中的结构、样式与行为,他们构成了丰富多彩的互联网世界。
2.HTML标签
- HTML 标签是由尖括号包围的关键词,比如 <html>
- HTML 标签通常是成对出现的,比如 <b> 和 </b>
- 标签对中的第一个标签是开始标签,第二个标签是结束标签
- 开始和结束标签也被称为开放标签和闭合标签
<标签名>
正文
</标签名>
不带斜杠,表示标签的开始;加上斜杠,表示标签的结束。它们中间的部分,就是标签里面的元素。标签里面可以是另一个标签,也可以是一段文本。标签可以并列,也可以嵌套,但是不能交叉。
示例:
<html>
<head>
<title>My First Page.</title> # 这是网页的名字
<body>
<h1>My First Heading.</h1> # 这是一个标题
<p>My First Paragraph.</p> # 这是一个段落
</body>
</html>
在元素的起始标签中, 还可以包含“属性”来设置元素的其他特性。 一个标签可以有多个属性,每个属性之间用空格隔开。
<div 属性名="属性值">显示在网页上的文本</div> # 拥有单个属性的标签
<div 属性名1="属性1的值" 属性名2="属性2的值">显示在网页上的文本</div> # 拥有多个属性的标签
拥有多个属性的标签
<div id="wrapper" class="wrapper_new wrapper_s">显示在网页上的文本</div>
3.网页的结构化特点
HTML标签的层级关系就像一棵树,html是树根,head和body是树枝,title是从head分出来的树枝,div是从body分出来的树枝。按照特定的标签,我们就可以一步步的找到想要的信息。
二、XPath
1.XPath介绍
XPath(XML Path) 是一门在 XML (Extensible Markup Language,可拓展标记语言)文档中查找信息的语言。XPath 用于在 XML 文档中通过元素和属性进行导航。
XPath 使用路径表达式在 XML 文档和HTML的树状结构中追踪目标节点或节点集。
XPath的详细语法可参考w3school。
在Python中,为了使用XPath,需要使用lxml库。
2.lxml介绍
lxml XML工具箱是C库libxml2和libxslt的Python绑定 。它的独特之处在于它将这些库的速度和XML功能的完整性与本机Python API的简单性结合在一起,该Python API大多数都兼容,但优于著名的 ElementTree API。最新版本适用于2.7到3.9的所有CPython版本。
lxml是功能最丰富且易于使用的库,用于处理Python语言中的XML和HTML。想要阅读lxml的完整文档可访问lxml官网。
在python中我们主要使用lxml下的子库etree进行网页内容的解析。
3.lxml的安装
windows下安装
pip install lxml
进入python的交互环境,输入import lxml,如没有错误提示则lxml安装成功。
4.lxml.etree的使用
使用下面代码导入etree库
>>from lxml import etree
etree解析文本的四种方式:
- fromstring() 解析字符串
- HTML()解析HTML对象
- XML()解析XML对象
- parse()解析文件类型的对象
(1)将字符串格式的网页代码转换为Element对象
使用etree.HTML将字符串格式的网页代码转换为_Element对象,调用HTML方法将自动把缺省的主要节点补齐。
from lxml import etree
html = '''
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Title</title>
</head>
'''
ele = etree.HTML(html) # 将字符串格式的网页代码转换为_Element对象
print(etree.tostring(ele)) # .tostring方法将_Element对象转换为字符串
# 输出
'<html><head>\n <meta charset="UTF-8"/>\n <title>Title</title>\n</head>\n</html>'
(2)使用etree.xpath表达式获取标签中的文本
content = ele.xpath('//title/text()') # 括号内的字符串是我们下面要重点了解的xpath语句
print(content)
# 输出
['Title']
(3)XPath语句格式
上面括号内的字符串就是一段XPath语句。XPath语句的核心就是写地址。
获取文本:
//标签1[@属性1=“属性值1”]/标签2[@属性2=“属性值2”]/……/text()
获取属性:
//标签1[@属性1=“属性值1”]/标签2[@属性2=“属性值2”]/……/@属性n
其中[@属性=“属性值”]不是必需的,它的主要作用是帮助过滤相同的标签。在不需要过滤相同标签的时候可以省略。
实例:从下面的网页源码中提取html、css、javascript三段信息,我们该怎么做?
<!DOCTYPE html>
<html lang="zh-en">
<head>
<meta charset="UTF-8"/>
<title>网络爬虫</title>
</head>
<body>
<div id='useful'>
<ul>有用的信息
<li>html</li>
<li>css</li>
<li>javascript</li>
</ul>
</div>
<div id='useless'>
<ul>无用的信息
<li>python</li>
<li>java</li>
<li>c#</li>
</ul>
</div>
</body>
</html>
XPath语句可以写成下面这样。
'//div[@id="useful"]/ul/li/text()'
如果是要获得charset的属性值,则可以写成:
'//meta/@charset'
(4)标签构造技巧
从目标开始倒着找
从需要提取的内容往上找标签,直到找到一个拥有“标志性属性值”的标签为止。在上面的例子中,我们要找的是html、css、javascript三段信息,往上找我们找到了li标签,但是我们知道无效的信息也是在另外3个li标签下;我们接着往上找到了ul标签,但这依然无法将有效信息和无效信息区别开,再接着往上,我们找到了<div id = 'useful'>,注意到无效的信息是在<div id = 'useless'>下,明显不同。<div id = 'useful'>就是拥有标志性属性值的标签,我们可以开始从这个标签开始定位。
使用Google Chrome浏览器辅助构造XPath
在Chrome的检查页面,右键点击想要追踪的内容,
复制出来的结果:
//*[@id="maincontent"]/h1
由于我们需要查找的是<h1>HTML 实例</h1>中的内容,我们改造一下上面的XPath就可以了。
//*[@id="maincontent"]/h1/text()
刚刚我们找到的是1级标题,接下来我们试试找到所有二级标题。
复制出来的xpath
//*[@id="maincontent"]/div[3]/h2
我们稍微解释下这一段:该目标对象从id为"maincontent"的标签开始,位于其后第3个div子标签下的h2标签下。另外值得一提的是div[3]方括号中的3代表的是id为"maincontent"标签下面的第3个<div>
标签,这里的编号从1开始,与编程语言中从0开始不一样。
按照以下写法,我们只能得到这一个标签下的内容。
url = 'https://www.w3school.com.cn/tags/tag_meta.asp'
response = requests.get(url).content
page = etree.HTML(response)
content = page.xpath('//*[@id="maincontent"]/div[3]/h2/text()')
# 输出
['浏览器支持']
为了获得所有二级标题的内容,我们将[3]删除即可。
url = 'https://www.w3school.com.cn/tags/tag_meta.asp'
response = requests.get(url).content
page = etree.HTML(response)
content = page.xpath('//*[@id="maincontent"]/div/h2/text()')
print(content)
# 输出
['定义和用法', '浏览器支持', 'HTML 与 XHTML 之间的差异', '提示和注释:', '必需的属性', '可选的属性', '全局属性', 'TIY 实例', '相关页面']
(5)文本提取技巧
- 提取某个节点下的所有文本
实例:将<div id = 'useful'>/ul下的所有文本提取出来。
我们期望的结果是把“有用的信息”“html”"css""javascript"四个字符串找出来,我们尝试使用之前的方法匹配内容。
page = etree.parse(url)
content = page.xpath('//div[@id = "useful"]/ul/text()')
print(content)
# 输出
['有用的信息\n ', '\n ', '\n ', '\n ']
结果发现它只匹配到“有用的信息”和另外3个换行符‘\n’,这是因为xpath不会自动把自标签的文字提取出来。试试下面的方法将更有效。
page = etree.parse(url)
content = page.xpath('//div[@id = "useful"]/ul')
print(content[0].xpath('(string(.))'))
# 输出
有用的信息
html
css
javascript
说明:此处采用了正则表达式中提到的先抓大后抓小原则。page.xpath('//div[@id = "useful"]/ul')将ul节点下的内容作为一个整体提取出来,接着对这个节点再使用一次xpath,string(.)关键字的作用是将当前整个节点中的所有字符串提取出来。
排除以相同字符串开头的无效文本
<html> <body> <div id="data-jobid">6575906</div> <div id="data-positionid">6575906</div> <div id="data-salary">20k-30k·16薪</div> <div id="data-company">耀天游戏</div> <div id="data-positionname">用户产品经理</div> <div id="data-companyid">10723</div> <div id="useless">无效的信息<div> </body> </html>
我们需要从以上信息中将id属性值以“data”开头的所有标签下的内容提取出来,如果只是指定div标签 ,则势必包含了“无效信息”。
此种情况我们可以使用starts-with指定属性值的开头内容。
text = '''
<html>
<body>
<div id="data-jobid">6575906</div>
<div id="data-positionid">6575906</div>
<div id="data-salary">20k-30k·16薪</div>
<div id="data-company">耀天游戏</div>
<div id="data-positionname">用户产品经理</div>
<div id="data-companyid">10723</div>
<div id="useless">无效的信息<div>
</body>
</html>
'''
page = etree.HTML(text)
content = page.xpath('//div[starts-with(@id,"data")]/text()')
print(content)
# 输出
['6575906', '6575906', '20k-30k·16薪', '耀天游戏', '用户产品经理', '10723']
- 包含或不包含某些信息
如果想提取id属性值只包含“position”标签下的内容,可以使用contains
page = etree.HTML(text)
content = page.xpath('//div[contains(@id,"position")]/text()')
print(content)
# 输出
['6575906', '用户产品经理']
如果是不包含,则可增加not关键词
content = page.xpath('//div[not(contains(@id,"position"))]/text()')
三、练习:爬取豆瓣Top250数据
爬取目标网站:豆瓣Top250:https://movie.douban.com/top250
爬取内容:电影名称、电影排名、电影评分、导演姓名、主演1姓名、上映时间、制片国家、电影类型
变量名称:movie_title、movie_rank、rating_score、director_name、actor_1_name、released_year、country、genres
爬取技术:requests、XPath
提交资料:代码文件、数据文件csv
技术提示:1.需要装饰请求头;2.先抓大后抓小,即先抓到每部电影代码段,再从中分别抓取需要的信息;3.有些数据是合并在一起的,需要分割,导演姓名和主演1姓名在一个text中。