Python爬虫之三种网页抓取方法性能比较

<div class="markdown_views"> <p> &nbsp;下面咱们将介绍三种抓取网页数据的方法,首先是<strong>正则表达式</strong>,而后是流行的 <strong>BeautifulSoup</strong> 模块,最后是强大的 <strong>lxml</strong> 模块。</p>css

<p><strong>1. 正则表达式</strong></p>html

<p> &nbsp;若是你对正则表达式还不熟悉,或是须要一些提示时,能够查阅<a href="https://docs.python.org/2/howto/regex.html" rel="nofollow" target="_blank">Regular Expression HOWTO</a> 得到完整介绍。</p>python

<p> &nbsp;当咱们使用正则表达式抓取国家面积数据时,首先要尝试匹配元素中的内容,以下所示:</p>css3

<pre class="prettyprint" name="code"><code class="hljs python has-numbering"><span class="hljs-prompt">&gt;&gt;&gt; </span><span class="hljs-keyword">import</span> re <span class="hljs-prompt">&gt;&gt;&gt; </span><span class="hljs-keyword">import</span> urllib2</br> <span class="hljs-prompt">&gt;&gt;&gt; </span>url = <span class="hljs-string">'http://example.webscraping.com/view/United-Kingdom-239'</span></br> <span class="hljs-prompt">&gt;&gt;&gt; </span>html = urllib2.urlopen(url).read()</br> <span class="hljs-prompt">&gt;&gt;&gt; </span>re.findall(<span class="hljs-string">'&lt;td class="w2p_fw"&gt;(.*?)&lt;/td&gt;'</span>, html)</br> [<span class="hljs-string">'&lt;img src="/places/static/images/flags/gb.png" /&gt;'</span>, <span class="hljs-string">'244,820 square kilometres'</span>, <span class="hljs-string">'62,348,447'</span>, <span class="hljs-string">'GB'</span>, <span class="hljs-string">'United Kingdom'</span>, <span class="hljs-string">'London'</span>, <span class="hljs-string">'&lt;a href="/continent/EU"&gt;EU&lt;/a&gt;'</span>, <span class="hljs-string">'.uk'</span>, <span class="hljs-string">'GBP'</span>, <span class="hljs-string">'Pound'</span>, <span class="hljs-string">'44'</span>, <span class="hljs-string">'@# #@@|@## #@@|@@# #@@|@@## #@@|@#@ #@@|@@#@ #@@|GIR0AA'</span>, <span class="hljs-string">'^(([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))$'</span>, <span class="hljs-string">'en-GB,cy-GB,gd'</span>, <span class="hljs-string">'&lt;div&gt;&lt;a href="/iso/IE"&gt;IE &lt;/a&gt;&lt;/div&gt;'</span>] <span class="hljs-prompt">&gt;&gt;&gt; </span></code></pre>web

<p> &nbsp; 从上述结果看出,多个国家眷性都使用了&lt; td class=”w2p_fw” &gt;标签。要想分离出面积属性,咱们能够只选择其中的第二个元素,以下所示:</p>正则表达式

<pre class="prettyprint" name="code"><code class="hljs vbnet has-numbering">&gt;&gt;&gt; re.findall(<span class="hljs-comment">'<span class="hljs-xmlDocTag">&lt;td class="w2p_fw"&gt;</span>(.*?)<span class="hljs-xmlDocTag">&lt;/td&gt;</span>', html)[1]</span> <span class="hljs-comment">'244,820 square kilometres'</span></code></pre>express

<p> &nbsp; 虽然如今可使用这个方案,可是若是网页发生变化,该方案极可能就会失效。好比表格发生了变化,去除了第二行中的国土面积数据。若是咱们只在如今抓取数据,就能够忽略这种将来可能发生的变化。可是,若是咱们但愿将来还能再次抓取该数据,就须要给出更加健壮的解决方案,从而尽量避免这种布局变化所带来的影响。想要该正则表达式更加健壮,咱们能够将其父元素&lt; tr &gt;也加入进来。因为该元素具备ID属性,因此应该是惟一的。</p>api

<pre class="prettyprint" name="code"><code class="hljs xml has-numbering">&gt;&gt;&gt; re.findall('<span class="hljs-tag">&lt;<span class="hljs-title">tr</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"places_area__row"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-title">td</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">"w2p_fl"</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-title">label</span> <span class="hljs-attribute">for</span>=<span class="hljs-value">"places_area"</span> <span class="hljs-attribute">id</span>=<span class="hljs-value">"places_area__label"</span>&gt;</span>Area: <span class="hljs-tag">&lt;/<span class="hljs-title">label</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-title">td</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-title">td</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">"w2p_fw"</span>&gt;</span>(.*?)<span class="hljs-tag">&lt;/<span class="hljs-title">td</span>&gt;</span>', html)</br> ['244,820 square kilometres']</code></pre>缓存

<p> &nbsp;这个迭代版本看起来更好一些,可是网页更新还有不少其余方式,一样可让该正则表达式没法知足。好比,将双引号变为单引号,&lt; td &gt;标签之间添加多余的空格,或是变动area_label等。下面是尝试支持这些可能性的改进版本。</p>markdown

<pre class="prettyprint" name="code"><code class="hljs scilab has-numbering">&gt;&gt;&gt; <span class="hljs-transposed_variable">re.</span>findall(<span class="hljs-string">'&lt;tr id="</span>places_area__row<span class="hljs-string">"&gt;.*?&lt;td\s*class=["</span>\<span class="hljs-string">']w2p_fw["</span>\<span class="hljs-string">']&gt;(.*?)&lt;/td&gt;'</span>,html)<span class="hljs-matrix">[<span class="hljs-string">'244,820 square kilometres'</span>]</span></code></pre>

<p> &nbsp;虽然该正则表达式更容易适应将来变化,但又存在难以构造、可读性差的问题。此外,还有一些微小的布局变化也会使该正则表达式没法知足,好比在&lt; td &gt;标签里添加title属性。 <br>  &nbsp;从本例中能够看出,正则表达式为咱们提供了抓取数据的快捷方式,可是,该方法过于脆弱,容易在网页更新后出现问题。幸亏还有一些更好的解决方案,后期将会介绍。</p>

<p><strong>2. Beautiful Soup</strong></p>

<p> &nbsp;<strong>Beautiful Soup</strong>是一个很是流行的 <strong>Python</strong> 模块。该模块能够解析网页,并提供定位内容的便捷接口。若是你尚未安装该模块,可使用下面的命令安装其最新版本(须要先安装 <strong>pip</strong>,请自行百度):</p>

<pre class="prettyprint" name="code"><code class="hljs cmake has-numbering">pip <span class="hljs-keyword">install</span> beautifulsoup4</code></pre>

<p> &nbsp;使用 <strong>Beautiful Soup</strong> 的第一步是将已下载的 <strong>HTML</strong> 内容解析为 <strong>soup</strong> 文档。因为大多数网页都不具有良好的 <strong>HTML</strong> 格式,所以 <strong>Beautiful Soup</strong> 须要对其实际格式进行肯定。例如,在下面这个简单网页的列表中,存在属性值两侧引号缺失和标签未闭合的问题。</p>

<pre class="prettyprint" name="code"><code class="hljs xml has-numbering"><span class="hljs-tag">&lt;<span class="hljs-title">ul</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">country</span>&gt;</span></br> <span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span>Area</br> <span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span>Population</br> <span class="hljs-tag">&lt;/<span class="hljs-title">ul</span>&gt;</span></code></pre>

<p> &nbsp;若是 Population 列表项被解析为 Area 列表项的子元素,而不是并列的两个列表项的话,咱们在抓取时就会获得错误的结果。下面让咱们看一下 <strong>Beautiful Soup</strong> 是如何处理的。</p>

<pre class="prettyprint" name="code"><code class="hljs xml has-numbering">&gt;&gt;&gt; from bs4 import BeautifulSoup &gt;&gt;&gt; broken_html = '<span class="hljs-tag">&lt;<span class="hljs-title">ul</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">country</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</br></span>Area<span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span>Population<span class="hljs-tag">&lt;/<span class="hljs-title">ul</span>&gt;</span>'</br> &gt;&gt;&gt; # parse the HTML</br> &gt;&gt;&gt; soup = BeautifulSoup(broken_html, 'html.parser')</br> &gt;&gt;&gt; fixed_html = soup.prettify()</br> &gt;&gt;&gt; print fixed_html</br> <span class="hljs-tag">&lt;<span class="hljs-title">ul</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">"country"</span>&gt;</span></br> <span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span></br> Area</br> <span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span></br> Population</br> <span class="hljs-tag">&lt;/<span class="hljs-title">li</span>&gt;</span></br> <span class="hljs-tag">&lt;/<span class="hljs-title">li</span>&gt;</span></br> <span class="hljs-tag">&lt;/<span class="hljs-title">ul</span>&gt;</span></code></pre>

<p> &nbsp;从上面的执行结果中能够看出,<strong>Beautiful Soup</strong> 可以正确解析缺失的引号并闭合标签。如今可使用 <strong>find()</strong> 和 <strong>find_all()</strong> 方法来定位咱们须要的元素了。</p>

<pre class="prettyprint" name="code"><code class="hljs xml has-numbering">&gt;&gt;&gt; ul = soup.find('ul', attrs={'class':'country'})</br> &gt;&gt;&gt; ul.find('li') # return just the first match</br> <span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span>Area<span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span>Population<span class="hljs-tag">&lt;/<span class="hljs-title">li</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-title">li</span>&gt;</span></br> &gt;&gt;&gt; ul.find_all('li') # return all matches</br> [<span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span>Area<span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span>Population<span class="hljs-tag">&lt;/<span class="hljs-title">li</span>&gt;</span><span class="hljs-tag">&lt;/<span class="hljs-title">li</span>&gt;</span>, <span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span>Population<span class="hljs-tag">&lt;/<span class="hljs-title">li</span>&gt;</span>]</code></pre>

<p><strong>Note: 因为不一样版本的Python内置库的容错能力有所区别,可能处理结果和上述有所不一样,具体请参考: <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser" rel="nofollow" target="_blank">https://www.crummy.com/software/BeautifulSoup/bs4/doc/#installing-a-parser</a>。想了解所有方法和参数,能够查阅 Beautiful Soup 的 <a href="https://www.crummy.com/software/BeautifulSoup/bs4/doc/" rel="nofollow" target="_blank">官方文档</a></strong></p>

<p> &nbsp;下面是使用该方法抽取示例国家面积数据的完整代码。</p>

<pre class="prettyprint" name="code"><code class="hljs python has-numbering"><span class="hljs-prompt">&gt;&gt;&gt; </span><span class="hljs-keyword">from</span> bs4 <span class="hljs-keyword">import</span> BeautifulSoup</br> <span class="hljs-prompt">&gt;&gt;&gt; </span><span class="hljs-keyword">import</span> urllib2</br> <span class="hljs-prompt">&gt;&gt;&gt; </span>url = <span class="hljs-string">'http://example.webscraping.com/view/United-Kingdom-239'</span></br> <span class="hljs-prompt">&gt;&gt;&gt; </span>html = urllib2.urlopen(url).read()</br> <span class="hljs-prompt">&gt;&gt;&gt; </span><span class="hljs-comment"># locate the area row</span></br> <span class="hljs-prompt">&gt;&gt;&gt; </span>tr = soup.find(attrs={<span class="hljs-string">'id'</span>:<span class="hljs-string">'places_area__row'</span>})</br> <span class="hljs-prompt">&gt;&gt;&gt; </span><span class="hljs-comment"># locate the area tag</span></br> <span class="hljs-prompt">&gt;&gt;&gt; </span>td = tr.find(attrs={<span class="hljs-string">'class'</span>:<span class="hljs-string">'w2p_fw'</span>})</br> <span class="hljs-prompt">&gt;&gt;&gt; </span>area = td.text <span class="hljs-comment"># extract the text from this tag</span></br> <span class="hljs-prompt">&gt;&gt;&gt; </span><span class="hljs-keyword">print</span> area</br> <span class="hljs-number">244</span>,<span class="hljs-number">820</span> square kilometres</code></pre>

<p> &nbsp;这段代码虽然比正则表达式的代码更加复杂,但更容易构造和理解。并且,像多余的空格和标签属性这种布局上的小变化,咱们也无需再担忧了。</p>

<p><strong>3. Lxml</strong></p>

<p> &nbsp;<strong>Lxml</strong> 是基于 <strong>libxml2</strong> 这一 <strong>XML</strong> 解析库的 <strong>Python</strong> 封装。该模块使用 C语言 编写,解析速度比 <strong>Beautiful Soup</strong> 更快,不过安装过程也更为复杂。最新的安装说明能够参考 <a href="http://lxml.de/installation.html" rel="nofollow" target="_blank">http://lxml.de/installation.html</a> .**</p>

<p> &nbsp;和 <strong>Beautiful Soup</strong> 同样,使用 <strong>lxml</strong> 模块的第一步也是将有可能不合法的 <strong>HTML</strong> 解析为统一格式。下面是使用该模块解析一个不完整 <strong>HTML</strong> 的例子:</p>

<pre class="prettyprint" name="code"><code class="hljs xml has-numbering">&gt;&gt;&gt; import lxml.html</br> &gt;&gt;&gt; broken_html = '<span class="hljs-tag">&lt;<span class="hljs-title">ul</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">country</span>&gt;</span><span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</br></span>Area<span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span>Population<span class="hljs-tag">&lt;/<span class="hljs-title">ul</span>&gt;</span>' &gt;&gt;&gt; # parse the HTML</br> &gt;&gt;&gt; tree = lxml.html.fromstring(broken_html)</br> &gt;&gt;&gt; fixed_html = lxml.html.tostring(tree, pretty_print=True)</br> &gt;&gt;&gt; print fixed_html</br> <span class="hljs-tag">&lt;<span class="hljs-title">ul</span> <span class="hljs-attribute">class</span>=<span class="hljs-value">"country"</span>&gt;</span></br> <span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span>Area<span class="hljs-tag">&lt;/<span class="hljs-title">li</span>&gt;</span></br> <span class="hljs-tag">&lt;<span class="hljs-title">li</span>&gt;</span>Population<span class="hljs-tag">&lt;/<span class="hljs-title">li</span>&gt;</span></br> <span class="hljs-tag">&lt;/<span class="hljs-title">ul</span>&gt;</span></code></pre>

<p> &nbsp;一样地,<strong>lxml</strong> 也能够正确解析属性两侧缺失的引号,并闭合标签,不过该模块没有额外添加 &lt; html &gt; 和 &lt; body &gt; 标签。</p>

<p> &nbsp;解析完输入内容以后,进入选择元素的步骤,此时 <strong>lxml</strong> 有几种不一样的方法,好比 <strong>XPath</strong> 选择器和相似 <strong>Beautiful Soup</strong> 的 <strong>find()</strong> 方法。不过,后续咱们将使用 <strong>CSS</strong> 选择器,由于它更加简洁,而且可以在解析动态内容时得以复用。此外,一些拥有 <strong>jQuery</strong> 选择器相关经验的读者会对其更加熟悉。</p>

<p> &nbsp;下面是使用 <strong>lxml</strong> 的 <strong>CSS</strong> 选择器抽取面积数据的示例代码:</p>

<pre class="prettyprint" name="code"><code class="hljs python has-numbering"><span class="hljs-prompt">&gt;&gt;&gt; </span><span class="hljs-keyword">import</span> urllib2</br> <span class="hljs-prompt">&gt;&gt;&gt; </span><span class="hljs-keyword">import</span> lxml.html</br> <span class="hljs-prompt">&gt;&gt;&gt; </span>url = <span class="hljs-string">'http://example.webscraping.com/view/United-Kingdom-239'</span></br> <span class="hljs-prompt">&gt;&gt;&gt; </span>html = urllib2.urlopen(url).read()</br> <span class="hljs-prompt">&gt;&gt;&gt; </span>tree = lxml.html.fromstring(html)</br> <span class="hljs-prompt">&gt;&gt;&gt; </span>td = tree.cssselect(<span class="hljs-string">'tr#places_area__row &gt; td.w2p_fw'</span>)[<span class="hljs-number">0</span>] <span class="hljs-comment"># *行代码</span></br> <span class="hljs-prompt">&gt;&gt;&gt; </span>area = td.text_content()</br> <span class="hljs-prompt">&gt;&gt;&gt; </span><span class="hljs-keyword">print</span> area <span class="hljs-number">244</span>,<span class="hljs-number">820</span> square kilometres</code></pre>

<p> &nbsp; <strong>*行代码</strong>首先会找到 ID 为 <strong>places_area__row</strong> 的表格行元素,而后选择 <strong>class</strong> 为 <strong>w2p_fw</strong> 的表格数据子标签。</p>

<p> &nbsp; <strong>CSS</strong> 选择器表示选择元素所使用的模式,下面是一些经常使用的选择器示例:</p>

<pre class="prettyprint" name="code"><code class="hljs livecodeserver has-numbering">选择全部标签: *</br> 选择 &lt;<span class="hljs-operator">a</span>&gt; 标签: <span class="hljs-operator">a</span></br> 选择全部 class=<span class="hljs-string">"link"</span> 的元素: .link</br> 选择 class=<span class="hljs-string">"link"</span> 的 &lt;<span class="hljs-operator">a</span>&gt; 标签: <span class="hljs-operator">a</span>.link</br> 选择 id=<span class="hljs-string">"home"</span> 的 &lt;<span class="hljs-operator">a</span>&gt; 标签: <span class="hljs-operator">a</span><span class="hljs-comment">#home</span></br> 选择父元素为 &lt;<span class="hljs-operator">a</span>&gt; 标签的全部 &lt;span&gt; 子标签: <span class="hljs-operator">a</span> &gt; span</br> 选择 &lt;<span class="hljs-operator">a</span>&gt; 标签内部的全部 &lt;span&gt; 标签: <span class="hljs-operator">a</span> span </br> 选择 title 属性为<span class="hljs-string">"Home"</span>的全部 &lt;<span class="hljs-operator">a</span>&gt; 标签: <span class="hljs-operator">a</span>[title=Home]</code></pre>

<p> &nbsp; <strong>W3C</strong> 已提出 <strong>CSS3</strong> 规范,其网址为 <a href="https://www.w3.org/TR/2011/REC-css3-selectors-20110929/" rel="nofollow" target="_blank">https://www.w3.org/TR/2011/REC-css3-selectors-20110929/</a></p>

<p> &nbsp;<strong>Lxml</strong> 已经实现了大部分 <strong>CSS3</strong> 属性,其不支持的功能能够参见: <a href="https://cssselect.readthedocs.io/en/latest/" rel="nofollow" target="_blank">https://cssselect.readthedocs.io/en/latest/</a> .</p>

<p><strong>Note: lxml在内部的实现中,其实是将 CSS 选择器转换为等价的 XPath 选择器。</strong></p>

<p><strong>4. 性能对比</strong></p>

<p> &nbsp; 在如下这段代码中,每一个爬虫都会执行 1000 次,每次执行都会检查抓取结果是否正确,而后打印总用时。</p>

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

<p><br> &nbsp; 注意,咱们在 <strong>*行代码</strong> 中调用了 <strong>re.purge()</strong> 方法。默认状况下,正则表达式会缓存搜索结果,为了公平起见,咱们须要使用该方法清除缓存。</p>

<p>下面是个人电脑运行该脚本的结果:</p>

<p><img src="https://img-blog.csdn.net/20170419131832120?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvT3NjZXIyMDE2/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="这里写图片描述" title=""> </p>

<p><br> &nbsp; 因为硬件条件的区别,不一样电脑的执行结果也会存在必定差别。不过,每种方法之间的相对差别应当是至关的。从结果中能够看出,在抓取咱们的示例网页时,<strong>Beautiful Soup</strong> 比其余两种方法慢了超过 7 倍之多。实际上这一结果是符合预期的,由于 <strong>lxml</strong> 和正则表达式模块都是 <strong>C</strong> 语言编写的,而 <strong>Beautiful Soup</strong> 则是纯 <strong>Python</strong> 编写的。一个有趣的事实是,<strong>lxml</strong> 表现的和正则表达式差很少好。因为 <strong>lxml</strong> 在搜索元素以前,必须将输入解析为内部格式,所以会产生额外的开销。而当抓取同一网页的多个特征时,这种初始化解析产生的开销就会下降,<strong>lxml</strong> 也就更具竞争力,因此说,<strong>lxml</strong> 是一个强大的模块。<br></p>

<p><strong>5. 总结</strong></p>

<p>三种网页抓取方法优缺点:<br><br></p>

<table> <thead> <tr> <th align="center">   &nbsp;&nbsp; &nbsp;抓取方法</th> <th align="center"> &nbsp; &nbsp;性能</th> <th align="center"> &nbsp; &nbsp; &nbsp;使用难度</th> <th align="center"> &nbsp; &nbsp; &nbsp;安装难度</th> </tr> </thead> <tbody><tr> <td align="center">正则表达式</td> <td align="center">快</td> <td align="center">困难</td> <td align="center">简单(内置模块)</td> </tr> <tr> <td align="center">Beautiful Soup</td> <td align="center">慢</td> <td align="center">简单</td> <td align="center">简单(纯Python)</td> </tr> <tr> <td align="center">Lxml</td> <td align="center">快</td> <td align="center">简单</td> <td align="center">相对困难</td> </tr> </tbody></table>

<p><br><br> &nbsp; 若是你的爬虫瓶颈是下载网页,而不是抽取数据的话,那么使用较慢的方法(如 <strong>Beautiful Soup</strong>)也不成问题。正则表达式在一次性抽取中很是有用,此外还能够避免解析整个网页带来的开销,若是只需抓取少许数据,而且想要避免额外依赖的话,那么正则表达式可能更加适合。不过,一般状况下,<strong>lxml</strong> 是抓取数据的最好选择,这是由于它不只速度快,功能也更加丰富,而正则表达式和 <strong>Beautiful Soup</strong>只在某些特定场景下有用。</p> </div>

相关文章
相关标签/搜索