BeautifulSoup 需要很长时间,这可以更快地完成吗?
BeautifulSoup takes forever, can this be done faster?
我正在使用 Raspberry Pi 1B+ w/ Debian Linux:
Linux rbian 3.18.0-trunk-rpi #1 PREEMPT Debian 3.18.5-1~exp1+rpi16 (2015-03-28) armv6l GNU/Linux
作为更大的 Python 程序的一部分,我正在使用此代码:
#!/usr/bin/env python
import time
from urllib2 import Request, urlopen
from bs4 import BeautifulSoup
_url="http://xml.buienradar.nl/"
s1 = time.time()
req = Request(_url)
print "Request = {0}".format(time.time() - s1)
s2 = time.time()
response = urlopen(req)
print "URLopen = {0}".format(time.time() - s2)
s3 = time.time()
output = response.read()
print "Read = {0}".format(time.time() - s3)
s4 = time.time()
soup = BeautifulSoup(output)
print "Soup (1) = {0}".format(time.time() - s4)
s5 = time.time()
MSwind = str(soup.buienradarnl.weergegevens.actueel_weer.weerstations.find(id=6350).windsnelheidms)
GRwind = str(soup.buienradarnl.weergegevens.actueel_weer.weerstations.find(id=6350).windrichtinggr)
ms = MSwind.replace("<"," ").replace(">"," ").split()[1]
gr = GRwind.replace("<"," ").replace(">"," ").split()[1]
print "Extracting info = {0}".format(time.time() - s5)
s6 = time.time()
soup = BeautifulSoup(urlopen(_url))
print "Soup (2) = {0}".format(time.time() - s6)
s5 = time.time()
MSwind = str(soup.buienradarnl.weergegevens.actueel_weer.weerstations.find(id=6350).windsnelheidms)
GRwind = str(soup.buienradarnl.weergegevens.actueel_weer.weerstations.find(id=6350).windrichtinggr)
ms = MSwind.replace("<"," ").replace(">"," ").split()[1]
gr = GRwind.replace("<"," ").replace(">"," ").split()[1]
print "Extracting info = {0}".format(time.time() - s5)
当我 运行 它时,我得到这个输出:
Request = 0.00394511222839
URLopen = 0.0579500198364
Read = 0.0346400737762
Soup (1) = 23.6777830124
Extracting info = 0.183892965317
Soup (2) = 36.6107468605
Extracting info = 0.382317781448
因此,BeautifulSoup 命令需要大约半分钟来处理 _url
。
如果这可以在 10 秒内完成,我真的很喜欢它。
任何能显着加快代码速度(至少 -60%)的建议都将非常受欢迎。
安装lxml
库;安装后 BeautifulSoup 将使用它作为默认解析器。
lxml
使用 libxml2
C 库解析页面,这比默认 html.parser
后端快得多,在纯 Python.[=25= 中实现]
您还可以将页面解析为 XML 而不是 HTML:
soup = BeautifulSoup(output, 'xml')
使用 lxml
解析给定页面应该会更快;我每秒可以解析页面近 50 次:
>>> timeit("BeautifulSoup(output, 'xml')", 'from __main__ import BeautifulSoup, output', number=50)
1.1700470447540283
不过,我想知道您是否缺少其他一些 Python 加速库,因为即使使用内置解析器我也肯定无法重现您的结果:
>>> timeit("BeautifulSoup(output, 'html.parser')", 'from __main__ import BeautifulSoup, output', number=50)
1.7218239307403564
也许您的内存受限,大文件导致您的 OS 大量交换内存?内存交换(将页面写入磁盘并从磁盘加载其他页面)甚至可以使最快的程序停止运行。
请注意,不是在标签元素上使用 str()
并拆分标签,您只需使用 .string
attribute:
即可从标签中获取值
station_6350 = soup.buienradarnl.weergegevens.actueel_weer.weerstations.find(id=6350)
ml = station_6350.windsnelheidMS.string
gr = station_6350.windrichtingGR.string
如果您使用 XML 解析器,请注意标记名必须匹配大小写(HTML 是一种不区分大小写的标记语言)。
由于这是一个 XML 文档,另一种选择是使用 lxml
ElementTree 模型;您可以使用 XPath 表达式来提取数据:
from lxml import etree
response = urlopen(_url)
for event, elem in etree.iterparse(response, tag='weerstation'):
if elem.get('id') == '6350':
ml = elem.find('windsnelheidMS').text
gr = elem.find('windrichtingGR').text
break
# clear elements we are not interested in, adapted from
#
elem.clear()
for ancestor in elem.xpath('ancestor-or-self::*'):
while ancestor.getprevious() is not None:
del ancestor.getparent()[0]
这应该只构建所需的最小对象树,清除您在文档中不需要的气象站。
演示:
>>> from lxml import etree
>>> from urllib2 import urlopen
>>> _url = "http://xml.buienradar.nl/"
>>> response = urlopen(_url)
>>> for event, elem in etree.iterparse(response, tag='weerstation'):
... if elem.get('id') == '6350':
... ml = elem.find('windsnelheidMS').text
... gr = elem.find('windrichtingGR').text
... break
... # clear elements we are not interested in
... elem.clear()
... for ancestor in elem.xpath('ancestor-or-self::*'):
... while ancestor.getprevious() is not None:
... del ancestor.getparent()[0]
...
>>> ml
'4.64'
>>> gr
'337.8'
使用 requests
和正则表达式可以更短更快。对于这种相对简单的数据收集,正则表达式工作正常。
#!/usr/bin/env python
from __future__ import print_function
import re
import requests
import time
_url = "http://xml.buienradar.nl/"
_regex = '<weerstation id="6391">.*?'\
'<windsnelheidMS>(.*?)</windsnelheidMS>.*?'\
'<windrichtingGR>(.*?)</windrichtingGR>'
s1 = time.time()
br = requests.get(_url)
print("Request = {0}".format(time.time() - s1))
s5 = time.time()
MSwind, GRwind = re.findall(_regex, br.text)[0]
print("Extracting info = {0}".format(time.time() - s5))
print('wind speed', MSwind, 'm/s')
print('wind direction', GRwind, 'degrees')
在我的桌面上(虽然不是覆盆子 :-))运行速度非常快;
Request = 0.0723416805267334
Extracting info = 0.0009412765502929688
wind speed 2.35 m/s
wind direction 232.6 degrees
当然,如果颠倒 windsnelheidMS
和 windrichtingGR
标签,这个特定的正则表达式会 失败 。但是考虑到 XML 很可能是计算机生成的,这似乎不太可能。
并且有一个解决方案。首先使用正则表达式捕获 <weerstation id="6391">
和 </weerstation>
之间的文本,然后使用另外两个正则表达式来查找风速和风向。
我正在使用 Raspberry Pi 1B+ w/ Debian Linux:
Linux rbian 3.18.0-trunk-rpi #1 PREEMPT Debian 3.18.5-1~exp1+rpi16 (2015-03-28) armv6l GNU/Linux
作为更大的 Python 程序的一部分,我正在使用此代码:
#!/usr/bin/env python
import time
from urllib2 import Request, urlopen
from bs4 import BeautifulSoup
_url="http://xml.buienradar.nl/"
s1 = time.time()
req = Request(_url)
print "Request = {0}".format(time.time() - s1)
s2 = time.time()
response = urlopen(req)
print "URLopen = {0}".format(time.time() - s2)
s3 = time.time()
output = response.read()
print "Read = {0}".format(time.time() - s3)
s4 = time.time()
soup = BeautifulSoup(output)
print "Soup (1) = {0}".format(time.time() - s4)
s5 = time.time()
MSwind = str(soup.buienradarnl.weergegevens.actueel_weer.weerstations.find(id=6350).windsnelheidms)
GRwind = str(soup.buienradarnl.weergegevens.actueel_weer.weerstations.find(id=6350).windrichtinggr)
ms = MSwind.replace("<"," ").replace(">"," ").split()[1]
gr = GRwind.replace("<"," ").replace(">"," ").split()[1]
print "Extracting info = {0}".format(time.time() - s5)
s6 = time.time()
soup = BeautifulSoup(urlopen(_url))
print "Soup (2) = {0}".format(time.time() - s6)
s5 = time.time()
MSwind = str(soup.buienradarnl.weergegevens.actueel_weer.weerstations.find(id=6350).windsnelheidms)
GRwind = str(soup.buienradarnl.weergegevens.actueel_weer.weerstations.find(id=6350).windrichtinggr)
ms = MSwind.replace("<"," ").replace(">"," ").split()[1]
gr = GRwind.replace("<"," ").replace(">"," ").split()[1]
print "Extracting info = {0}".format(time.time() - s5)
当我 运行 它时,我得到这个输出:
Request = 0.00394511222839
URLopen = 0.0579500198364
Read = 0.0346400737762
Soup (1) = 23.6777830124
Extracting info = 0.183892965317
Soup (2) = 36.6107468605
Extracting info = 0.382317781448
因此,BeautifulSoup 命令需要大约半分钟来处理 _url
。
如果这可以在 10 秒内完成,我真的很喜欢它。
任何能显着加快代码速度(至少 -60%)的建议都将非常受欢迎。
安装lxml
库;安装后 BeautifulSoup 将使用它作为默认解析器。
lxml
使用 libxml2
C 库解析页面,这比默认 html.parser
后端快得多,在纯 Python.[=25= 中实现]
您还可以将页面解析为 XML 而不是 HTML:
soup = BeautifulSoup(output, 'xml')
使用 lxml
解析给定页面应该会更快;我每秒可以解析页面近 50 次:
>>> timeit("BeautifulSoup(output, 'xml')", 'from __main__ import BeautifulSoup, output', number=50)
1.1700470447540283
不过,我想知道您是否缺少其他一些 Python 加速库,因为即使使用内置解析器我也肯定无法重现您的结果:
>>> timeit("BeautifulSoup(output, 'html.parser')", 'from __main__ import BeautifulSoup, output', number=50)
1.7218239307403564
也许您的内存受限,大文件导致您的 OS 大量交换内存?内存交换(将页面写入磁盘并从磁盘加载其他页面)甚至可以使最快的程序停止运行。
请注意,不是在标签元素上使用 str()
并拆分标签,您只需使用 .string
attribute:
station_6350 = soup.buienradarnl.weergegevens.actueel_weer.weerstations.find(id=6350)
ml = station_6350.windsnelheidMS.string
gr = station_6350.windrichtingGR.string
如果您使用 XML 解析器,请注意标记名必须匹配大小写(HTML 是一种不区分大小写的标记语言)。
由于这是一个 XML 文档,另一种选择是使用 lxml
ElementTree 模型;您可以使用 XPath 表达式来提取数据:
from lxml import etree
response = urlopen(_url)
for event, elem in etree.iterparse(response, tag='weerstation'):
if elem.get('id') == '6350':
ml = elem.find('windsnelheidMS').text
gr = elem.find('windrichtingGR').text
break
# clear elements we are not interested in, adapted from
#
elem.clear()
for ancestor in elem.xpath('ancestor-or-self::*'):
while ancestor.getprevious() is not None:
del ancestor.getparent()[0]
这应该只构建所需的最小对象树,清除您在文档中不需要的气象站。
演示:
>>> from lxml import etree
>>> from urllib2 import urlopen
>>> _url = "http://xml.buienradar.nl/"
>>> response = urlopen(_url)
>>> for event, elem in etree.iterparse(response, tag='weerstation'):
... if elem.get('id') == '6350':
... ml = elem.find('windsnelheidMS').text
... gr = elem.find('windrichtingGR').text
... break
... # clear elements we are not interested in
... elem.clear()
... for ancestor in elem.xpath('ancestor-or-self::*'):
... while ancestor.getprevious() is not None:
... del ancestor.getparent()[0]
...
>>> ml
'4.64'
>>> gr
'337.8'
使用 requests
和正则表达式可以更短更快。对于这种相对简单的数据收集,正则表达式工作正常。
#!/usr/bin/env python
from __future__ import print_function
import re
import requests
import time
_url = "http://xml.buienradar.nl/"
_regex = '<weerstation id="6391">.*?'\
'<windsnelheidMS>(.*?)</windsnelheidMS>.*?'\
'<windrichtingGR>(.*?)</windrichtingGR>'
s1 = time.time()
br = requests.get(_url)
print("Request = {0}".format(time.time() - s1))
s5 = time.time()
MSwind, GRwind = re.findall(_regex, br.text)[0]
print("Extracting info = {0}".format(time.time() - s5))
print('wind speed', MSwind, 'm/s')
print('wind direction', GRwind, 'degrees')
在我的桌面上(虽然不是覆盆子 :-))运行速度非常快;
Request = 0.0723416805267334
Extracting info = 0.0009412765502929688
wind speed 2.35 m/s
wind direction 232.6 degrees
当然,如果颠倒 windsnelheidMS
和 windrichtingGR
标签,这个特定的正则表达式会 失败 。但是考虑到 XML 很可能是计算机生成的,这似乎不太可能。
并且有一个解决方案。首先使用正则表达式捕获 <weerstation id="6391">
和 </weerstation>
之间的文本,然后使用另外两个正则表达式来查找风速和风向。