1.背景
Python爬虫的主要套路就是使用requests库获取网页源代码,然后再从网页源代码中提取自己所需的信息。之前的案例中从网页提取信息主要使用BeautifulSoup------美味汤,确实很美味,个人觉得是对新手最友好。当然除了美味汤还有另外两种主流方法:正则表达式和Lxml。正则表达式对新人不太友好,而且在网页比较复杂的时候,比较难用,适合在特定的情况下使用,优点:速度较快。
BeautifulSoup和Lxml在网页解析和信息提取方面都是重量级的选手,而且两种方法的学习成本都不高。借用一本教材里对三种方法的对比:
lxml底层语言是C,BeautifulSoup是Python,速度方面lxml应该是占优势的。不过截图中的作者说BeautifulSoup采用'lxml'解析网页,速度就和直接使用Lxml库差不多了;还有一些知乎大佬说Lxml速度要明显快于BeautifulSoup,口说无凭,本文就来做个实验,对这两个库的性能做一个简单对比。
2.Lxml库的Xpath语法介绍
2.1节点关系介绍:
通常通过requests库获取的源代码都是HTML,HTML 中的所有内容都是节点,也称为节点树,例如:
是不是有点眼熟,对,第一篇爬虫介绍文章里就用过这张图介绍过HTML。
节点树中的节点彼此拥有层级关系。用父(parent)、子(child)和同胞(sibling)等术语用于描述这些关系。父节点拥有子节点。同级的子节点被称为同胞(兄弟或姐妹)。
- 在节点树中,顶端节点被称为根(root)
- 每个节点都有父节点、除了根(它没有父节点)
- 一个节点可拥有任意数量的子
- 同胞是拥有相同父节点的节点
看完基本介绍,用一段示例代码再来介绍:
<html>
<head>
<title>DOM 教程</title>
</head>
<body>
<h1>DOM 第一课</h1>
<p>Hello world!</p>
</body>
</html>
从上面的示例 HTML 代码中:
- <html> 节点没有父节点;它是根节点
- <head> 和 <body> 的父节点是 <html> 节点
- 文本节点 "Hello world!" 的父节点是 <p> 节点
并且:
- <html> 节点拥有两个子节点:<head> 和 <body>
- <head> 节点拥有一个子节点:<title> 节点
- <title> 节点也拥有一个子节点:文本节点 "DOM 教程"
- <h1> 和 <p> 节点是同胞节点,同时也是 <body> 的子节点
并且:
- <head> 元素是 <html> 元素的首个子节点
- <body> 元素是 <html> 元素的最后一个子节点
- <h1> 元素是 <body> 元素的首个子节点
- <p> 元素是 <body> 元素的最后一个子节点
其实上面的概念介绍不看都可以,只要清楚每个节点的结构是这种:<标签>内容</标签> 结构就好了。看到一个**<>** 就是一个节点,而且都是成对出现的,看到**</>**就表示这个节点结束。
节点里面也是可以包含其他节点的,同时被包含的同级别节点之间就是同胞关系,包含这些节点的那个节点就是他们的父节点。当然,这些同胞节点里面也可以再包含别的节点,最里面的节点就是最外面的后代节点,最外面的节点就是最里面节点的先辈节点。 如果把节点树比作一个大家庭,节点间的每层包含关系就代表隔了一个辈分,形象的比喻出了父节点、子节点、同胞节点、先辈节点和后代节点。
2.2 lxml库的Xpath语法介绍和使用
参考博客: https://blog.csdn.net/a417197457/article/details/81143112
只要能理解这张表就可以了:
继续以示例代码介绍:
- html 表示选取元素html的所有子节点
- /html 表示选取根元素html
- /html/head 表示选取属于html的子元素的head元素
- //head 表示选取所有head子元素,而不管它们在什么地方,属不属于html
- /html//head 表示选取属于html元素的后代中的所有head元素,位于html下任何位置
- //@attribute 表示选取名为attribute的所有属性
其实主要区分"/"和"//"就好了,"/"代表只能选取直系后代元素,不能跨代选取。而"//"表示选取所有的后代元素,可以跨代选取。
3.Lxml库应用案例
继续以上篇文章的海投网为例,爬取陕西所有高校宣讲会公司信息来进行性能对比。
首先,导入需要的模块:
import requests
from lxml import etree
requests是用来获取网页源代码,lxml.etree是用来解析网页的,和beautifulsoup中的html.parser或者lxml解析是一个作用。
别看Xpath的语法那么繁琐,实战中可以偷懒的,xpath路径不用人工一句一句的敲,谷歌浏览器提供了傻瓜式获取Xpath路径信息,大大减轻了使用lxml模块爬虫的工作量。比如可以这样获取公司xpath信息:
①鼠标放到想要提取信息的位置,右击,选择检查。
②在网页源代码位置右击所选元素。
③然后依次选择copy,copy xpath。
粘贴复制的内容://*[@id="w0"]/table/tbody/tr[1]/td[2]/a/div
试着打印一下刚刚获取公司名称,代码如下:
import requests
from lxml import etree
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'
}
url ='https://xjh.haitou.cc/'
response = requests.get(url,headers = headers)
selector = etree.HTML(response.text)
company = selector.xpath('//*[@id="w0"]/table/tbody/tr[1]/td[2]/a/div/text()')#后面加的/text()是获取标签中的文本信息
print(id)
运行上面的代码:
发现以列表形式返回了公司名称,说明抓取数据成功。不过这只有一条公司信息,怎么获取整个页面的所有公司信息呢?继续获取其他的公司名称xpath路径,前三条依次为:
发现只有tr[X]中的X会发生变化,可以修改代码使其抓取全部公司信息:
//*[@id="w0"]/table/tbody/tr/td[2]/a/div,直接去掉tr后面的序号,可以获得通用的xpath信息。剩下的套路很熟悉,用for循环取出返回列表中的数据就可以了。
import requests
import time
from lxml import etree
for i in range(1,21):
url = 'https://xjh.haitou.cc/xa/after/page-{}'.format(i)'
response = requests.get(url,headers = headers)
selector = etree.HTML(response.text)
time.sleep(1)
companys = selector.xpath('//*[@id="w0"]/table/tbody/tr/td[2]/a/div/text()')#后面加的/text()是获取标签中的文本信息
for company in companys:
print(company)
4.性能对比
写如下代码来对性能进行比较:
import requests
import time
from lxml import etree
from bs4 import BeautifulSoup
headers = {
'User-Agent':'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/74.0.3729.131 Safari/537.36'
}
urls =['https://xjh.haitou.cc/xa/after/page-{}'.format(i) for i in range(1,21)]
def lxml_get_xuanjiang(url):#lxml爬虫函数
response = requests.get(url,headers = headers)
selector = etree.HTML(response.text)
time.sleep(1)
companys = selector.xpath('//*[@id="w0"]/table/tbody/tr/td[2]/a/div/text()')#后面加的/text()是获取标签中的文本信息
for company in companys:
return company #不用存储数据,只返回
def bs_get_xuanjiang(url):#beautifulsoup爬虫函数
response = requests.get(url,headers = headers)
html = response.text
soup = BeautifulSoup(html,'lxml')
time.sleep(1)
companys = soup.find_all('div',class_="text-success company pull-left")
for co in companys:
company = co.get_text()
return company
if __name__ == '__main__':#主程序入口
for name,get_xuanjiang in [('lxml',lxml_get_xuanjiang),('beautifulsoup',bs_get_xuanjiang)]:
start = time.time()
for url in urls:
get_xuanjiang(url)
end = time.time()
print(name,end-start)
为了排除网速等其他因素的干扰,运行了三次程序,结果如下:
lxml三局两胜,比起美味汤有微弱的优势,不过也有可能与网页结构或其他未知因素干扰导致结果有一定的偏差,还需要对不同网页进行多次对比实验,得出结果才有说服力。
5.结论
通过分别使用两种方法爬取海投网信息速度做了一个对比,基本验证了文章开头某作者的观点,在使用'lxml'解析网页时,beautifulsoup和lxml的xpath速度相差不大。在以后的实战中可以根据个人喜好来选择,不用太纠结两种方法的性能了,个人还是对美味汤比较熟悉些,也推荐新手使用。