Pyhon+lxml+xpath快速实现网页爬虫(比BeautifulSoup好用)
背景
最近因为工作需要写爬虫,以前用过BeautifulSoup,所以很自然的无脑上BeautifulSoup了,不过使用过程中发现BeautifulSoup有一个致命的缺陷,就是不能支持XPath。XPath可以快速在结构化的文档(如XML,HTML)中查找、访问元素的语言,语法比正则表达式还要简单,非常容易使用。
在浏览器中其中可以方便地获取任何目标元素的XPath,简单来说XPath和文件路径很像,通过文件路径可以快速定位文件,通过XPath可以快速定位网页中的元素。这里网页和元素的关系类似文件系统和文件的关系。当然XPath提供了更多的能力。下面就是本文的重点——lxml了。lxml提供了很多功能强大的子库,本文主要用到xlml.etree(支持XPath!支持XPath!支持XPath!,重要的事说三遍)。
先看一个简单的小例子
#!env python3
from urllib.request import urlopen
from lxml import etree
sHtml = """
<!DOCTYPE html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
<div>
<p>This is the first paragraph</p>
</div>
<div>
<p>This is the second paragraph</p>
<p>This is the third paragraph</p>
</div>
</body>
</html>
"""
#解析html
eleRoot = etree.HTML(sHtml)
#使用xpath获取元素
listP = eleRoot.xpath("/html/body/div/p")
#打印title信息
for eleP in listP:
print(eleP.text)
运行结果如下:
This is the first paragraph
This is the second paragraph
This is the third paragraph
eleRoot是lxml.etree._Element类型,该类型提供了众多接口: |方法或者属性|功能| |—-|—-| |_Element.text|获取标签的文本| |_Element.tag |获取标签名字(如head,div)| |_Element.xpath(path)|根据XPath获取元素对象|
详情参照官方文档。
xpath()返回的是一个列表,列表中包含所有符合XPath的元素。上面的例子给定的XPath是”/html/body/div/p”。
可以看到两个div的共3个p标签都符合这个路径,所以最后返回了三个p标签元素,如果想获得指定的标签,可以在给定XPath的时候添加索引,比如上图想获得second paragraph,那么path应该为”/html/body/div[2]/p[1]“,没错XPath的索引是从1开始的,而不是0。这样返回的是一个只有一个_Element元素的列表。
xpath()除了可以指定绝对路径(以”/“开头的路径),还可以在元素上使用相对路径。修改上面的代码中获取和打印元素的代码为如下:
#获取body元素,然后通过相对路径访问子元素
eleBody = eleRoot.xpath("/html/body")[0]
eleP = eleBody.xpath("div[2]/p[1]")[0]
print(eleP.text)
首先获取body元素,然后通过xpath获取body元素的第二个div的第一个p,运行结果如下:
This is the second paragraph
调用etree.HTML()获得的元素为根元素,对根元素只能使用绝对路径,不能使用相对路径,其他元素均可以使用相对路径调用xpath()。
如何快速获取网页中元素的XPath
在Chrome浏览器中(其他浏览器应该也都支持这个功能),鼠标放在目标元素的上面右键 -> 点击”检查元素“,就会跳转到指定元素的标签代码上了,对选中的标签右键 -> 点击Copy -> 点击”Copy full XPath”,即可获得该元素的XPath了。
XPath语法示例
注意:返回的是etree._Element的list,一个元素也会以list的格式返回
获得所有xxx元素
eleRoot.xpath("//xxx")
获得/html/body/div的所有子元素
eleRoot.xpath("html/body/div/*")
获取class为yyy的标签
eleRoot.xpath("//*[contains(@class, "yyy")]"
获取属性zzz的值为yyy的标签
eleRoot.xpath("//*[contains(@zzz, "yyy")])
如果解析中文网页出现乱码乱码的问题
请看移步另一篇文章 —— 使用lxml.etree解析中文网页时出现乱码问题的解决办法