python爬虫BeautifulSoup和Lxml性能对比

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速度相差不大。在以后的实战中可以根据个人喜好来选择,不用太纠结两种方法的性能了,个人还是对美味汤比较熟悉些,也推荐新手使用。

相关推荐
明月看潮生几秒前
青少年编程与数学 02-003 Go语言网络编程 15课题、Go语言URL编程
开发语言·网络·青少年编程·golang·编程与数学
雷神乐乐4 分钟前
File.separator与File.separatorChar的区别
java·路径分隔符
小刘|9 分钟前
《Java 实现希尔排序:原理剖析与代码详解》
java·算法·排序算法
南宫理的日知录11 分钟前
99、Python并发编程:多线程的问题、临界资源以及同步机制
开发语言·python·学习·编程学习
jjyangyou13 分钟前
物联网核心安全系列——物联网安全需求
物联网·算法·安全·嵌入式·产品经理·硬件·产品设计
coberup20 分钟前
django Forbidden (403)错误解决方法
python·django·403错误
逊嘘28 分钟前
【Java语言】抽象类与接口
java·开发语言·jvm
xinghuitunan28 分钟前
蓝桥杯顺子日期(填空题)
c语言·蓝桥杯
van叶~30 分钟前
算法妙妙屋-------1.递归的深邃回响:二叉树的奇妙剪枝
c++·算法
Half-up30 分钟前
C语言心型代码解析
c语言·开发语言