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

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

  网页抓取qq(三种抓取网页数据的方法-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('.*?(.*?)',html)['244,820 square kilometres']

  这种正则表达式虽然更容易适应未来的变化,但存在构建困难、可读性差的问题。此外,还有一些细微的布局变化会使正则表达式不尽人意,例如在标签中添加标题属性。

  从这个例子可以看出,正则表达式为我们提供了一种快速抓取数据的方式,但是这种方式过于脆弱,在网页更新后容易出现问题。好在还有一些更好的解决方案,后面会介绍。

  2. 美汤

  Beautiful Soup 是一个非常流行的 Python 模块。该模块可以解析网页并提供方便的界面来定位内容。如果您还没有安装该模块,可以使用以下命令安装其最新版本(需要先安装pip,请自行百度):

  pip install beautifulsoup4

  使用 Beautiful Soup 的第一步是将下载的 HTML 内容解析成一个 Soup 文档。由于大多数网页没有好的 HTML 格式,Beautiful Soup 需要确定其实际格式。例如,在下面的简单网页列表中,存在属性值周围缺少引号和未关闭标签的问题。

  

Area

Population

  如果将 Population 列表项解析为 Area 列表项的子元素,而不是两个并排的列表项,我们在爬行时会得到错误的结果。我们来看看Beautiful Soup是如何处理的。

  >>> 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> 标签: a

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

选择 class="link" 的 <a> 标签: a.link

选择 id="home" 的 <a> 标签: a#home

选择父元素为 <a> 标签的所有 子标签: a > span

选择 <a> 标签内部的所有 标签: a span

选择 title 属性为"Home"的所有 <a> 标签: 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 = (&#39;area&#39;, &#39;population&#39;, &#39;iso&#39;, &#39;country&#39;, &#39;capital&#39;, &#39;continent&#39;, &#39;tld&#39;, &#39;currency_code&#39;, &#39;currency_name&#39;, &#39;phone&#39;, &#39;postal_code_format&#39;, &#39;postal_code_regex&#39;, &#39;languages&#39;, &#39;nei*敏*感*词*ours&#39;)

def regex_scraper(html):

results = {}

for field in FIELDS:

results[field] = re.search(&#39;.*?(.*?)&#39;.format(field), html).groups()[0]

return results

def beautiful_soup_scraper(html):

soup = BeautifulSoup(html, &#39;html.parser&#39;)

results = {}

for field in FIELDS:

results[field] = soup.find(&#39;table&#39;).find(&#39;tr&#39;, id=&#39;places_{}__row&#39;.format(field)).find(&#39;td&#39;, class_=&#39;w2p_fw&#39;).text

return results

def lxml_scraper(html):

tree = lxml.html.fromstring(html)

results = {}

for field in FIELDS:

results[field] = tree.cssselect(&#39;table > tr#places_{}__row > td.w2p_fw&#39;.format(field))[0].text_content()

return results

def main():

times = {}

html = urllib2.urlopen(&#39;http://example.webscraping.com/view/United-Kingdom-239&#39;).read()

NUM_ITERATIONS = 1000 # number of times to test each scraper

for name, scraper in (&#39;Regular expressions&#39;, regex_scraper), (&#39;Beautiful Soup&#39;, beautiful_soup_scraper), (&#39;Lxml&#39;, 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[&#39;area&#39;] == &#39;244,820 square kilometres&#39;)

times[name].append(time.time() - start)

# record end time of scrape and output the total

end = time.time()

print &#39;{}: {:.2f} seconds&#39;.format(name, end - start)

writer = csv.writer(open(&#39;times.csv&#39;, &#39;w&#39;))

header = sorted(times.keys())

writer.writerow(header)

for row in zip(*[times[scraper] for scraper in header]):

writer.writerow(row)

if __name__ == &#39;__main__&#39;:

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人工客服


线