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

优采云 发布时间: 2021-10-21 18:12

  c 抓取网页数据(三种抓取网页数据的方法-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 是一个基于 libxml2(一个 XML 解析库)的 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有几种不同的方法,比如XPath选择器和类似于Beautiful Soup的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 提出了 CSS3 规范,其网站是

  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和正则表达式模块都是用C语言写的,而Beautiful Soup是用纯Python写的。一个有趣的事实是 lxml 的性能几乎与正则表达式一样好。由于 lxml 必须在搜索元素之前将输入解析为内部格式,因此会产生额外的开销。当抓取同一个网页的多个特征时,这个初步分析的开销会减少,lxml会更有竞争力。

  5. 总结

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

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

  正则表达式

  快的

  困难

  简单(内置模块)

  美汤

  减缓

  简单的

  简单(纯 Python)

  xml文件

  快的

  简单的

  比较难

  如果你的爬虫的瓶颈是下载网页而不是提取数据,那么使用较慢的方法(比如Beautiful Soup)不是问题。正则表达式在一次性提取中非常有用,还可以避免解析整个网页的开销。如果你只需要爬取少量数据,又想避免额外的依赖,那么正则表达式可能更合适。但是,一般情况下,lxml 是捕获数据的最佳选择,因为它不仅速度更快,而且用途更广,而正则表达式和 Beautiful Soup 仅在某些场景下有用。

0 个评论

要回复文章请先登录注册


官方客服QQ群

微信人工客服

QQ人工客服


线