从网页抓取数据(三种抓取网页数据的方法-2.Beautiful)

优采云 发布时间: 2022-01-15 04:15

  从网页抓取数据(三种抓取网页数据的方法-2.Beautiful)

  下面我们将介绍三种抓取网页数据的方法,首先是正则表达式,然后是流行的 BeautifulSoup 模块,最后是强大的 lxml 模块。

  1. 正则表达式

  如果您是正则表达式的新手,或者需要一些提示,请查看正则表达式 HOWTO 以获得完整的介绍。

  当我们使用正则表达式抓取国家/地区数据时,我们首先尝试匹配元素的内容,如下所示:

  >>> import re

>>> import urllib2

>>> url = 'http://example.webscraping.com/view/United-Kingdom-239' >>> html = urllib2.urlopen(url).read() >>> re.findall('(.*?)', html) ['/places/static/images/flags/gb.png', '244,820 square kilometres', '62,348,447', 'GB', 'United Kingdom', 'London', 'EU', '.uk', 'GBP', 'Pound', '44', '@# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA', '^(([A-Z]\\d{2}[A-Z]{2})|([A-Z]\\d{3}[A-Z]{2})|([A-Z]{2}\\d{2}[A-Z]{2})|([A-Z]{2}\\d{3}[A-Z]{2})|([A-Z]\\d[A-Z]\\d[A-Z]{2})|([A-Z]{2}\\d[A-Z]\\d[A-Z]{2})|(GIR0AA))$', 'en-GB,cy-GB,gd', 'IE '] >>>

  从以上结果可以看出,标签用于多个国家属性。要隔离 area 属性,我们只需选择其中的第二个元素,如下所示:

  >>> re.findall('(.*?)', html)[1]

'244,820 square kilometres'

  虽然这个方案现在可用,但如果页面发生变化,它很可能会失败。例如,该表已更改为删除第二行中的土地面积数据。如果我们现在只抓取数据,我们可以忽略这种未来可能发生的变化。但是,如果我们以后想再次获取这些数据,我们需要一个更健壮的解决方案,尽可能避免这种布局更改的影响。为了使正则表达式更加健壮,我们也可以添加它的父元素。由于元素具有 ID 属性,因此它应该是唯一的。

  >>> re.findall('Area: (.*?)', html) ['244,820 square kilometres']

  这个迭代版本看起来好一点,但是网页更新还有很多其他的方式也会让这个正则表达式不令人满意。例如,将双引号更改为单引号,在标签之间添加额外的空格,或者更改 area_label 等。下面是一个尝试支持这些可能性的改进版本。

  >>> re.findall('.*?>> from bs4 import BeautifulSoup

>>> broken_html = 'AreaPopulation' >>> # parse the HTML >>> soup = BeautifulSoup(broken_html, 'html.parser') >>> fixed_html = soup.prettify() >>> print fixed_html Area Population

  从上面的执行结果可以看出,Beautiful Soup 能够正确解析缺失的引号并关闭标签。现在我们可以使用 find() 和 find_all() 方法来定位我们需要的元素。

  >>> ul = soup.find('ul', attrs={'class':'country'})

>>> ul.find('li') # return just the first match

AreaPopulation >>> ul.find_all('li') # return all matches [AreaPopulation, Population]

  注意:由于不同版本的Python内置库的容错能力存在差异,处理结果可能与上述不同。详情请参阅: 。想知道所有的方法和参数,可以参考 Beautiful Soup 的官方文档

  以下是使用此方法提取样本国家地区数据的完整代码。

  >>> from bs4 import BeautifulSoup

>>> import urllib2 >>> url = 'http://example.webscraping.com/view/United-Kingdom-239' >>> html = urllib2.urlopen(url).read() >>> # locate the area row >>> tr = soup.find(attrs={'id':'places_area__row'}) >>> # locate the area tag >>> td = tr.find(attrs={'class':'w2p_fw'}) >>> area = td.text # extract the text from this tag >>> print area 244,820 square kilometres

  此代码虽然比正则表达式代码更复杂,但更易于构建和理解。此外,布局中的一些小变化,例如额外的空白和制表符属性,我们不再需要担心它了。

  3. Lxml

  Lxml 是基于 XML 解析库 libxml2 的 Python 包装器。这个模块是用C语言编写的,解析速度比Beautiful Soup快,但是安装过程比较复杂。最新安装说明可以参考。**

  与 Beautiful Soup 一样,使用 lxml 模块的第一步是将可能无效的 HTML 解析为统一格式。以下是使用此模块解析不完整 HTML 的示例:

  >>> import lxml.html

>>> broken_html = 'AreaPopulation' >>> # parse the HTML >>> tree = lxml.html.fromstring(broken_html) >>> fixed_html = lxml.html.tostring(tree, pretty_print=True) >>> print fixed_html Area Population

  同样,lxml 正确解析属性周围缺少的引号并关闭标签,但模块不会添加和标签。

  解析输入后,进入选择元素的步骤。此时,lxml 有几种不同的方法,例如类似于 Beautiful Soup 的 XPath 选择器和 find() 方法。但是,将来我们会使用 CSS 选择器,因为它们更简洁,可以在解析动态内容时重复使用。此外,一些有 jQuery 选择器经验的读者会更熟悉它。

  以下是使用 lxml 的 CSS 选择器提取区域数据的示例代码:

  >>> import urllib2

>>> import lxml.html

>>> url = 'http://example.webscraping.com/view/United-Kingdom-239' >>> html = urllib2.urlopen(url).read() >>> tree = lxml.html.fromstring(html) >>> td = tree.cssselect('tr#places_area__row > td.w2p_fw')[0] # *行代码 >>> area = td.text_content() >>> print area 244,820 square kilometres

  *行代码会先找到ID为places_area__row的表格行元素,然后选择类为w2p_fw的表格数据子标签。

  CSS 选择器表示用于选择元素的模式。以下是一些常用选择器的示例:

  选择所有标签: *

选择 标签: a

选择所有 class="link" 的元素: .link

选择 class="link" 的 标签: a.link 选择 id="home" 的 标签: a#home 选择父元素为 标签的所有 子标签: a > span 选择 标签内部的所有 标签: a span 选择 title 属性为"Home"的所有 标签: a[title=Home]

  W3C 在

  Lxml 已经实现了大部分 CSS3 属性,其不支持的功能可以在:.

  注意:lxml 的内部实现实际上将 CSS 选择器转换为等效的 XPath 选择器。

  4. 性能比较

  在下面的代码中,每个爬虫会执行1000次,每次执行都会检查爬取结果是否正确,然后打印总时间。

  # -*- coding: utf-8 -*-

import csv

import time

import urllib2

import re import timeit from bs4 import BeautifulSoup import lxml.html FIELDS = ('area', 'population', 'iso', 'country', 'capital', 'continent', 'tld', 'currency_code', 'currency_name', 'phone', 'postal_code_format', 'postal_code_regex', 'languages', 'nei*敏*感*词*ours') def regex_scraper(html): results = {} for field in FIELDS: results[field] = re.search('.*?(.*?)'.format(field), html).groups()[0] return results def beautiful_soup_scraper(html): soup = BeautifulSoup(html, 'html.parser') results = {} for field in FIELDS: results[field] = soup.find('table').find('tr', id='places_{}__row'.format(field)).find('td', class_='w2p_fw').text return results def lxml_scraper(html): tree = lxml.html.fromstring(html) results = {} for field in FIELDS: results[field] = tree.cssselect('table > tr#places_{}__row > td.w2p_fw'.format(field))[0].text_content() return results def main(): times = {} html = urllib2.urlopen('http://example.webscraping.com/view/United-Kingdom-239').read() NUM_ITERATIONS = 1000 # number of times to test each scraper for name, scraper in ('Regular expressions', regex_scraper), ('Beautiful Soup', beautiful_soup_scraper), ('Lxml', lxml_scraper): times[name] = [] # record start time of scrape start = time.time() for i in range(NUM_ITERATIONS): if scraper == regex_scraper: # the regular expression module will cache results # so need to purge this cache for meaningful timings re.purge() # *行代码 result = scraper(html) # check scraped result is as expected assert(result['area'] == '244,820 square kilometres') times[name].append(time.time() - start) # record end time of scrape and output the total end = time.time() print '{}: {:.2f} seconds'.format(name, end - start) writer = csv.writer(open('times.csv', 'w')) header = sorted(times.keys()) writer.writerow(header) for row in zip(*[times[scraper] for scraper in header]): writer.writerow(row) if __name__ == '__main__': main()

  请注意,我们在 * 行代码中调用了 re.purge() 方法。默认情况下,正则表达式会缓存搜索结果,公平起见,我们需要使用这种方法来清除缓存。

  这是在我的计算机上运行脚本的结果:

  

  由于硬件条件的不同,不同计算机的执行结果也会有一定的差异。但是,每种方法之间的相对差异应该具有可比性。从结果可以看出,Beautiful Soup 在爬取我们的示例网页时比其他两种方法慢 7 倍以上。事实上,这个结果是意料之中的,因为 lxml 和 regex 模块是用 C 编写的,而 Beautiful Soup 是用纯 Python 编写的。一个有趣的事实是 lxml 的性能与正则表达式差不多。由于 lxml 必须在搜索元素之前将输入解析为内部格式,因此会产生额外的开销。当捕获同一个网页的多个特征时,这种初始解析的开销会降低,lxml 会更有竞争力。所以,

  5. 总结

  三种网页抓取方式的优缺点:

  抓取方式 性能 使用难度 安装难度

  正则表达式

  快的

  困难

  简单(内置模块)

  美丽的汤

  慢的

  简单的

  简单(纯 Python)

  lxml

  快的

  简单的

  比较困难

  如果您的爬虫的瓶颈是下载页面,而不是提取数据,那么使用较慢的方法(如 Beautiful Soup)不是问题。正则表达式在一次性提取中非常有用,除了可以避免解析整个网页的开销,如果只需要抓取少量数据并想避免额外的依赖,那么正则表达式可能更适合. 但是,一般来说,lxml 是抓取数据的最佳选择,因为它不仅速度更快,而且功能更多,而正则表达式和 Beautiful Soup 仅在某些特定场景下才有用。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线