异步社区

异步社区是国内领先的IT专业图书社区,由人民邮电出版社主办,致力于优质学习内容的出版和分享。

34篇博客

网络爬虫简介(3)— 编写第一个网络爬虫

异步社区2018-12-20 11:56

1.5 编写第一个网络爬虫 
为了抓取网站,我们首先需要下载包含有感兴趣数据的网页,该过程一般
称为爬取(crawling)。爬取一个网站有很多种方法,而选用哪种方法更加合适,则取决于目标网站的结构。本章中,我们首先会探讨如何安全地下载网页,然后会介绍如下3
种爬取网站的常见方法:
爬取网站地图;
使用数据库ID遍历每个网页;
跟踪网页链接。
到目前为止,我们交替使用了抓取和爬取这两个术语,接下来让我们先来定义这两种方法的相似点和不同点。


1.5.1 抓取与爬取的对比
根据你所关注的信息以及站点内容和结构的不同,你可能需要进行网络抓取或是网站爬取。那么它们有什么区别呢?网络抓取通常针对特定网站,并在这些站点上获取指定信息。网络抓取用于访问这些特定的页面,如果站点发生变化或者站点中的信息位置发生变化的话,则需要进行修改。例如,你可能想要通过网络抓取查看你喜欢的当地餐厅的每日特色菜,为了实现该目的,你需要抓取其网站中日常更新该信息的部分。与之不同的是,网络爬取通常是以通用的方式构建的,其目标是一系列顶级域名的网站或是整个网络。爬取可以用来收集更具体的信息,不过更常见的情况是爬取网络,从许多不同的站点或页面中获取小而通用的信息,然后跟踪链接到其他页面中。


除了爬取和抓取外,我们还会在第8章中介绍网络爬虫。爬虫可以用来爬取指定的一系列网站,或是在多个站点甚至整个互联网中进行更广泛的爬取。一般来说,我们会使用特定的术语反映我们的用例。在你开发网络爬虫时,可能会注意到它们在你想要使用的技术、库和包中的区别。在这些情况下,你对不同术语的理解,可以帮助你基于所使用的术语选择适当的包或技术(例如,是否只用于抓取?是否也适用于爬虫?)。


1.5.2 下载网页
要想抓取网页,我们首先需要将其下载下来。下面的示例脚本使用Python的urllib模块下载URL。
import urllib.request
def download(url):
return urllib.request.urlopen(url).read()


当传入URL参数时,该函数将会下载网页并返回其HTML。不过,这个代码片段存在一个问题,即当下载网页时,我们可能会遇到一些无法控制的错误,比如请求的页面可能不存在。此时,urllib会抛出异常,然后退出脚本。安全起见,下面再给出一个更稳建的版本,可以捕获这些异常。
import urllib.request
from urllib.error import URLError, HTTPError, ContentTooShortError
def download(url):
print('Downloading:', url)
try:
html = urllib.request.urlopen(url).read()
except (URLError, HTTPError, ContentTooShortError) as e:
print('Download error:', e.reason)
html = None
return html


现在,当出现下载或URL错误时,该函数能够捕获到异常,然后返回None。
在本书中,我们将假设你在文件中编写代码,而不是使用提示符的方式(如上述代码所示)。当你发现代码以Python提示符>>>或IPython
提示符In[1]:开始时,你需要将其输入到正在使用的主文件中,或是保存文件后,在Python解释器中导入这些函数和类。
1.重试下载
下载时遇到的错误经常是临时性的,比如服务器过载时返回的503Service Unavailable错误。对于此类错误,我们可以在短暂等待后尝试重新下载,因为这个服务器问题现在可能已经解决。不过,我们不需要对所有错误都尝试重新下载。如果服务器返回的是404 Not Found这种错误,则说明该网页目前并不存在,再次尝试同样的请求一般也不会出现不同的结果。互联网工程任务组(Internet Engineering Task Force
)定义了HTTP错误的完整列表,从中可以了解到4xx错误发生在请求存在问题时,而5xx错误则发生在服务端存在问题时。所以,我们只需要确保download函数在发生5xx错误时重试下载即可。下面是支持重试下载功能的新版本代码。
def download(url, num_retries=2):
print('Downloading:', url)
try:
html = urllib.request.urlopen(url).read()
except (URLError, HTTPError, ContentTooShortError) as e:
print('Download error:', e.reason)
html = None
if num_retries > 0:
if hasattr(e, 'code') and 500 <= e.code < 600:
# recursively retry 5xx HTTP errors
return download(url, num_retries - 1)
return html


现在,当download函数遇到5xx错误码时,将会递归调用函数自身进行重试。此外,该函数还增加了一个参数,用于设定重试下载的次数,其默认值为两次。我们在这里限制网页下载的尝试次数,是因为服务器错误可能暂时还没有恢复。想要测试该函数,可以尝试下载http://httpstat.us/500,该网址会始终返回500错误码。>>> download('http://httpstat.us/500')
Downloading: http://httpstat.us/500
Download error: Internal Server Error
Downloading: http://httpstat.us/500
Download error: Internal Server Error
Downloading: http://httpstat.us/500
Download error: Internal Server Error


从上面的返回结果可以看出,download函数的行为和预期一致,先尝试下载网页,在接收到500错误后,又进行了两次重试才放弃。
2.设置用户代理
默认情况下,urllib使用Python-urllib/3.x作为用户代理下载网页内容,其中3.x是环境当前所用Python的版本号。如果能使用可辨识的用户代
理则更好,这样可以避免我们的网络爬虫碰到一些问题。此外,也许是因为曾经历过质量不佳的Python网络爬虫造成的服务器过载,一些网站还会封禁这个默认的用户代理。因此,为了使下载网站更加可靠,我们需要控制用户代理的设定。下面的代码对download函数进行了修改,设定了一个默认的用户代理‘wswp’(即Web Scraping with Python的首字母缩写)。
def download(url, user_agent='wswp', num_retries=2):
print('Downloading:', url)
request = urllib.request.Request(url)
request.add_header('User-agent', user_agent)
try:
html = urllib.request.urlopen(request).read()
except (URLError, HTTPError, ContentTooShortError) as e:
print('Download error:', e.reason)
html = None
if num_retries > 0:
if hasattr(e, 'code') and 500 <= e.code < 600:
# recursively retry 5xx HTTP errors
return download(url, num_retries - 1)
return html


现在,如果你再次尝试访问meetup.com,就能够看到一个合法的HTML了。我们的下载函数可以在后续代码中得到复用,该函数能够捕获异常、在可能的情况下重试网站以及设置用户代理。


原文网址:https://www.epubit.com/book/detail/33225
内容来源:异步社区;版权属【人民邮电出版社 异步社区】所有,转载已获得授权;未经授权,不得以任何方式复制和传播本书内容,如需转载请联系异步社区。