一、介绍
这篇文章并不是为了比较出结论,因为结论是显而易见的,Xpath 必然比 BeautifulSoup 在时间和空间上都要性能更好一些。一个很明显的理由是,BeautifulSoup 在构建一个对象的时候需要传入一个参数以指定解析器,而在它支持的众多的解析器中,lxml 是性能最佳的,BeautifulSoup 对象的各种方法可以理解为是对 lxml 的封装,换句话说,BeautifulSoup 本质上并没有创造出自己的解析方式,而是建立在各种解析器的基础上。考虑到其他一些内部耗时因素,BeautifulSoup 注定会比 lxml 甚至是任何一个构建对象时使用的解析器要慢,要更耗费空间。正如在算法领域有“牺牲部分空间换时间”这样的思想,工程领域也经常有“牺牲部分性能换易用性”的实践。只有付出这样子的代价才能够换来它的简洁、优美与用户友好性。
因此,本文只是通过一个爬虫例子来简单验证一下这个结论,以及对它们之间的差距有一个数量上的认识。
二、测试过程
2.1 测试程序
# test.py
# -*- coding: utf-8 -*-
import requests
from bs4 import BeautifulSoup, SoupStrainer
import traceback
import json
from lxml import etree
import re
import timedef getHtmlText(url):try:r = requests.get(url, headers=headers)r.raise_for_status()if r.encoding == 'ISO-8859-1':r.encoding = r.apparent_encodingreturn r.textexcept:traceback.print_exc()def parseWithBeautifulSoup(html_text):soup = BeautifulSoup(html_text, 'html.parser') # 后改为 'lxml'content = []for mulu in soup.find_all(class_='mulu'):h2 = mulu.find('h2')if h2 != None:h2_title = h2.string # 获取标题lst = []for a in mulu.select('div.box a'):href = a.get('href')box_title = a.get('title')pattern = re.compile(r'\s*\[(.*)\]\s+(.*)') # (re) 匹配括号内的表达式,也表示一个组match = pattern.search(box_title)if match != None:date = match.group(1)real_title = match.group(2)lst.append({
'href':href,'title':real_title,'date':date})content.append({
'title':h2_title,'content':lst})with open('dmbj_bs.json', 'w') as fp:json.dump(content, fp=fp, indent=4)def parseWithXpath(html_text):html = etree.HTML(html_text)div_mulus = html.xpath('.//*[@class="mulu"]') # 先找到所有的 div class=mulu 标记content = []for div_mulu in div_mulus:# 找到所有的 div_h2 标记div_h2 = div_mulu.xpath('./div[@class="mulu-title"]/center/h2/text()')if len(div_h2) > 0:h2_title = div_h2[0]a_s = div_mulu.xpath('./div[@class="box"]/ul/li/a')lst = []for a in a_s:# 找到 href 属性href = a.xpath('./@href')[0]# 找到 title 属性box_title = a.xpath('./@title')[0]pattern = re.compile(r'\s*\[(.*)\]\s+(.*)') # (re) 匹配括号内的表达式,也表示一个组match = pattern.search(box_title)if match != None:date = match.group(1)real_title = match.group(2)lst.append({
'href':href,'title':real_title,'date':date})content.append({
'title':h2_title,'content':lst})with open('dmbj_xp.json', 'w') as fp:json.dump(content, fp=fp, indent=4)def main():html_text = getHtmlText('http://www.seputu.com')print(len(html_text))start = time.clock()parseWithBeautifulSoup(html_text)print('BSoup cost:', time.clock()-start)start = time.clock()parseWithXpath(html_text)print('Xpath cost:', time.clock()-start)if __name__ == '__main__':user_agent = 'Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/66.0.3359.117 Safari/537.36'headers={
'User-Agent': user_agent}main()
2.2 运行截图
html.parser
lxml
2.3 结果分析
从运行截图的对比中可以看出,当我们使用 html.parser 作为解析器时,BeautifulSoup 解析的耗时平均是 Xpath 的 1.8 倍;当我们使用 lxml 作为解析器时,BeautifulSoup 解析的耗时虽有减少,但平均仍是 Xpath 的 1.5 倍+。意味着在解析耗时方面,Be autifulSoup(lxml)略大于 BeautifulSoup(html.parser),远大于 XPath(lxml)。
三、总结
BeautifulSoup 这碗美味汤确实是美味可口,但是一碗好汤的煲制时间和用料上面都更加花费,这无可厚非。虽然 Xpath 相对来说可能语义性没有前者强,但总体也是 user-friendly,也很好用,功能十分强大,最重要的是它的底层解析器 lxml 是用 C 编写的,速度自然就不必说了。如果你十分追求高效率和资源节约,Xpath 是非常好的选择(如果你能熟练使用的话)。