13-下载网页
[toc]
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.重试下载
下载时遇到的错误经常是临时性的,比如服务器过载时返回的 503 Service 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了。我们的下载函数可以在后续代码中得到复用,该函数能够捕获异常、在可能的情况下重试网站以及设置用户代理。
在本书中,我们将假设你在文件中编写代码,而不是使用提示符的方式(如上述代码所示)。当你发现代码以Python提示符 >>> 或IPython提示符 `In [1]:` 开始时,你需要将其输入到正在使用的主文件中,或是保存文件后,在Python解释器中导入这些函数和类。