python网络数据采集3(译者:哈雷)

第一部分 创建爬虫

这部分内容重点介绍网络数据采集的基本原理:如何用 Python 从网络服务器请求信息,如何对服务器的响应进行基本处理,以及如何以自动化手段与网站进行交互。最终,你将轻松游弋于网络空间,创建出具有域名切换、信息收集以及信息存储功能的爬虫。

说实话,如果你想以较少的预先投入获取较高的回报,网络数据采集肯定是一个值得踏入的神奇领域。大体上,你遇到的 90% 的网络数据采集项目使用的都是接下来的六章里介绍的技术。这部分内容涵盖了一般人(也包括技术达人)在思考“网络爬虫”时通常的想法:

  • 通过网站域名获取 HTML 数据

  • 根据目标信息解析数据

  • 存储目标信息

  • 如果有必要,移动到另一个网页重复这个过程

这将为你学习本书第二部分中更复杂的项目奠定坚实的基础。不要天真地认为这部分内容没有第二部分里的一些比较高级的项目重要。其实,当你写自己的网络爬虫时,几乎每天都要用到第一部分的所有内容。

第 1 章 初见网络爬虫

一旦你开始采集网络数据,就会感受到浏览器为我们做的所有细节。网络上如果没有 HTML 文本格式层、CSS 样式层、JavaScript 执行层和图像渲染层,乍看起来会有点儿吓人,但是在这一章和下一章,我们将介绍如何不通过浏览器的帮助来格式化和理解数据。

本章将首先向网络服务器发送 GET 请求以获取具体网页,再从网页中读取 HTML 内容,最后做一些简单的信息提取,将我们要寻找的内容分离出来。

1.1 网络连接

如果你没在网络或网络安全上花过太多时间,那么互联网的原理可能看起来有点儿神秘。准确地说,每当打开浏览器连接 http://google.com 的时候,我们不会思考网络正在做什么,而且如今也不必思考。实际上,我认为很神奇的是,计算机接口已经如此先进,让大多数人上网的时候完全不思考网络是如何工作的。

但是,网络数据采集需要抛开一些接口的遮挡,不仅是在浏览器层(它如何解释所有的 HTML、CSS 和 JavaScript),有时也包括网络连接层。

我们通过下面的例子让你对浏览器获取信息的过程有一个基本的认识。Alice 有一台网络服务器。Bob 有一个台式机正准备连接 Alice 的服务器。当一台机器想与另一台机器对话时,下面的某个行为将会发生。

1. Bob 的电脑发送一串 1 和 0 比特值,表示电路上的高低电压。这些比特构成了一种信息,包括请求头和消息体。请求头包含当前 Bob 的本地路由器 MAC 地址和 Alice 的 IP 地址。消息体包含 Bob 对 Alice 服务器应用的请求。

2. Bob 的本地路由器收到所有 1 和 0 比特值,把它们理解成一个数据包(packet),从 Bob 自己的 MAC 地址“寄到”Alice 的 IP 地址。他的路由器把数据包“盖上”自己的 IP 地址作为“发件”地址,然后通过互联网发出去。

3. Bob 的数据包游历了一些中介服务器,沿着正确的物理 / 电路路径前进,到了 Alice 的服务器。

4. Alice 的服务器在她的 IP 地址收到了数据包。

5. Alice 的服务器读取数据包请求头里的目标端口(通常是网络应用的 80 端口,可以理解成数据包的“房间号”,IP 地址就是“街道地址”),然后把它传递到对应的应用——网络服务器应用上。

6. 网络服务器应用从服务器处理器收到一串数据,数据是这样的:

  • 这是一个 GET 请求

  • 请求文件 index.html

7. 网络服务器应用找到对应的 HTML 文件,把它打包成一个新的数据包发送给 Bob,然后通过它的本地路由器发出去,用同样的过程回传到 Bob 的机器上。

瞧!我们就这样实现了互联网。

那么,在这场数据交换中,网络浏览器从哪里开始参与的?完全没有参与。其实,在互联网的历史中,浏览器是一个比较年轻的发明,始于 1990 年的 Nexus 浏览器。

的确,网络浏览器是一个非常有用的应用,它创建信息的数据包,发送它们,然后把你获取的数据解释成漂亮的图像、声音、视频和文字。但是,网络浏览器就是代码,而代码是可以分解的,可以分解成许多基本组件,可重写、重用,以及做成我们想要的任何东西。网络浏览器可以让服务器发送一些数据,到那些对接无线(或有线)网络接口的应用上,但是许多语言也都有实现这些功能的库文件。

让我们看看 Python 是如何实现的:

from urllib.request import urlopen
html = urlopen("http://pythonscraping.com/pages/page1.html")
print(html.read())

你可以把这段代码保存为 scrapetest.py,然后在终端里运行如下命令:

$python scrapetest.py

注意,如果你的设备上安装了 Python 2.x,可能需要直接指明版本才能运行 Python 3.x 代码:

$python3 scrapetest.py

这将会输出 http://pythonscraping.com/pages/page1.html 这个网页的全部 HTML 代码。更准确地说,这会输出在域名为 http://pythonscraping.com 的服务器上 < 网络应用根地址 >/pages 文件夹里的 HTML 文件 page1.html 的源代码。

有什么区别?现在大多数网页需要加载许多相关的资源文件。可能是图像文件、JavaScript 文件、CSS 文件,或你需要连接的其他各种网页内容。当网络浏览器遇到一个标签时,比如 <img src="cuteKitten.jpg">,会向服务器发起另一个请求,以获取 cuteKitten.jpg 文件中的数据为用户充分渲染网页。但是,我们的 Python 程序没有返回并向服务器请求多个文件的逻辑,它只能读取我们已经请求的单个 HTML 文件。

那么它是怎样做的呢?幸好 Python 语法接近正常英文,下面这行代码

from urllib.request import urlopen

其实已经显示了它的含义:它查找 Python 的 request 模块(在 urllib 库里面),只导入一个urlopen 函数。

 urllib 还是 urllib2 ?

如果你用过 Python 2.x 里的 urllib2 库,可能会发现 urllib2 与 urllib 有些不同。在 Python 3.x 里,urllib2 改名为 urllib,被分成一些子模块:urllib.requesturllib.parse 和urllib.error。尽管函数名称大多和原来一样,但是在用新的 urllib 库时需要注意哪些函数被移动到子模块里了。

urllib 是 Python 的标准库(就是说你不用额外安装就可以运行这个例子),包含了从网络请求数据,处理 cookie,甚至改变像请求头和用户代理这些元数据的函数。我们将在本书中广泛使用urllib,所以建议你读读这个库的 Python 文档(https://docs.python.org/3/library/urllib.html)。

urlopen 用来打开并读取一个从网络获取的远程对象。因为它是一个非常通用的库(它可以轻松读取 HTML 文件、图像文件,或其他任何文件流),所以我们将在本书中频繁地使用它。

1.2 BeautifulSoup简介

“美味的汤,绿色的浓汤,
在热气腾腾的盖碗里装!
谁不愿意尝一尝,这样的好汤?
晚餐用的汤,美味的汤!”

BeautifulSoup 库的名字取自刘易斯 ·卡罗尔在《爱丽丝梦游仙境》里的同名诗歌。在故事中,这首诗是素甲鱼 1 唱的。

1Mock Turtle,它本身是一个双关语,指英国维多利亚时代的流行菜肴素甲鱼汤,其实不是甲鱼而是牛肉,如同中国的豆制品素鸡,名为素鸡,其实与鸡无关。

就像它在仙境中的说法一样,BeautifulSoup 尝试化平淡为神奇。它通过定位 HTML 标签来格式化和组织复杂的网络信息,用简单易用的 Python 对象为我们展现 XML 结构信息。

1.2.1 安装BeautifulSoup

由于 BeautifulSoup 库不是 Python 标准库,因此需要单独安装。在本书中,我们将使用最新的 BeautifulSoup 4 版本(也叫 BS4)。BeautifulSoup 4 的所有安装方法都在http://www.crummy.com/software/BeautifulSoup/bs4/doc/ 里面。Linux 系统上的基本安装方法是:

$sudo apt-get install python-bs4

对于 Mac 系统,首先用

$sudo easy_install pip

安装 Python 的包管理器 pip,然后运行

$pip install beautifulsoup4

来安装库文件。

另外,注意如果你的设备同时安装了 Python 2.x 和 Python 3.x,你需要用 python3 运行 Python 3.x:

$python3 myScript.py

当你安装包的时候,如果有可能安装到了 Python 2.x 而不是 Python 3.x 里,就需要使用:

$sudo python3 setup.py install

如果用 pip 安装,你还可以用 pip3 安装 Python 3.x 版本的包:

$pip3 install beautifulsoup4

在 Windows 系统上安装与在 Mac 和 Linux 上安装差不多。从上面的下载链接下载最新的 BeautifulSoup 4 源代码,解压后进入文件,然后执行:

>python setup.py install

这样就可以了! BeautifulSoup 将被当作设备上的一个 Python 库。你可以在 Python 终端里导入它测试一下:

$python
> from bs4 import BeautifulSoup

如果没有错误,说明导入成功了。

另外,还有一个 Windows 版 pip(https://pypi.python.org/pypi/setuptools)的 .exe 格式安装器,装了之后你就可以轻松安装和管理包了:

>pip install beautifulsoup4

用虚拟环境保存库文件

如果你同时负责多个 Python 项目,或者想要轻松打包某个项目及其关联的库文件,再或者你担心已安装的库之间可能有冲突,那么你可以安装一个 Python 虚拟环境来分而治之。

当一个 Python 库不用虚拟环境安装的时候,你实际上是全局安装它。这通常需要有管理员权限,或者以 root 身份安装,这个库文件对设备上的每个用户和每个项目都是存在的。好在创建虚拟环境非常简单:

$ virtualenv scrapingEnv

这样就创建了一个叫作 scrapingEnv 的新环境,你需要先激活它再使用:

$ cd scrapingEnv/
$ source bin/activate

激活环境之后,你会发现环境名称出现在命令行提示符前面,提醒你当前处于虚拟环境中。后面你安装的任何库和执行的任何程序都是在这个环境下运行。

在新建的 scrapingEnv 环境里,可以安装并使用 BeautifulSoup:

(scrapingEnv)ryan$ pip install beautifulsoup4
(scrapingEnv)ryan$ python
> from bs4 import BeautifulSoup
>

当不再使用虚拟环境中的库时,可以通过释放命令来退出环境:

(scrapingEnv)ryan$ deactivate
ryan$ python
> from bs4 import BeautifulSoup
Traceback (most recent call last):
    File "<stdin>", line 1, in <module>
ImportError: No module named 'bs4'

将项目关联的所有库单独放在一个虚拟环境里,还可以轻松打包整个环境发送给其他人。只要他们的 Python 版本和你的相同,你打包的代码就可以直接通过虚拟环境运行,不需要再安装任何库。

尽管本书的例子都不要求你使用虚拟环境,但是请记住,你可以在任何时候激活并使用它。

1.2.2 运行BeautifulSoup

BeautifulSoup 库最常用的对象恰好就是 BeautifulSoup 对象。让我们把本章开头的例子调整一下运行看看:

from urllib.request import urlopen
from bs4 import BeautifulSoup
html = urlopen("http://www.pythonscraping.com/pages/page1.html")
bsObj = BeautifulSoup(html.read())
print(bsObj.h1)

输出结果是:

<h1>An Interesting Title</h1>

和前面例子一样,我们导入 urlopen,然后调用 html.read() 获取网页的 HTML 内容。这样就可以把 HTML 内容传到 BeautifulSoup 对象,转换成下面的结构:

• html → <html><head>...</head><body>...</body></html>
 — head → <head><title>A Useful Page<title></head>
   — title → <title>A Useful Page</title>
 — body → <body><h1>An Int...</h1><div>Lorem ip...</div></body>
   — h1 → <h1>An Interesting Title</h1>
   — div → <div>Lorem Ipsum dolor...</div>

可以看出,我们从网页中提取的 <h1> 标签被嵌在 BeautifulSoup 对象 bsObj 结构的第二层(html → body → h1)。但是,当我们从对象里提取 h1 标签的时候,可以直接调用它:

bsObj.h1

其实,下面的所有函数调用都可以产生同样的结果:

bsObj.html.body.h1
bsObj.body.h1
bsObj.html.h1

希望这个例子可以向你展示 BeautifulSoup 库的强大与简单。其实,任何 HTML(或 XML)文件的任意节点信息都可以被提取出来,只要目标信息的旁边或附近有标记就行。在第 3 章,我们将进一步探讨一些更复杂的 BeautifulSoup 函数,还会介绍正则表达式,以及如何把正则表达式用于 BeautifulSoup 以对网站信息进行提取。

1.2.3 可靠的网络连接

网络是十分复杂的。网页数据格式不友好,网站服务器宕机,目标数据的标签找不到,都是很麻烦的事情。网络数据采集最痛苦的遭遇之一,就是爬虫运行的时候你洗洗睡了,梦想着明天一早数据就都会采集好放在数据库里,结果第二天醒来,你看到的却是一个因某种数据格式异常导致运行错误的爬虫,在前一天当你不再盯着屏幕去睡觉之后,没过一会儿爬虫就不再运行了。那个时候,你可能想骂发明互联网(以及那些奇葩的网络数据格式)的人,但是你真正应该斥责的人是你自己,为什么一开始不估计可能会出现的异常!

让我们看看爬虫 import 语句后面的第一行代码,如何处理那里可能出现的异常:

html = urlopen("http://www.pythonscraping.com/pages/page1.html")

这行代码主要可能会发生两种异常:

  • 网页在服务器上不存在(或者获取页面的时候出现错误)

  • 服务器不存在

第一种异常发生时,程序会返回 HTTP 错误。HTTP 错误可能是“404 Page Not Found”“500 Internal Server Error”等。所有类似情形,urlopen 函数都会抛出“HTTPError”异常。我们可以用下面的方式处理这种异常:

try:
    html = urlopen("http://www.pythonscraping.com/pages/page1.html")
except HTTPError as e:
    print(e)
    # 返回空值,中断程序,或者执行另一个方案
else:
    # 程序继续。注意:如果你已经在上面异常捕捉那一段代码里返回或中断(break),
    # 那么就不需要使用else语句了,这段代码也不会执行

如果程序返回 HTTP 错误代码,程序就会显示错误内容,不再执行 else 语句后面的代码。

如果服务器不存在(就是说链接 http://www.pythonscraping.com/ 打不开,或者是 URL 链接写错了),urlopen 会返回一个 None 对象。这个对象与其他编程语言中的 null 类似。我们可以增加一个判断语句检测返回的 html 是不是 None

if html is None:
    print("URL is not found")
else:
    # 程序继续

当然,即使网页已经从服务器成功获取,如果网页上的内容并非完全是我们期望的那样,仍然可能会出现异常。每当你调用 BeautifulSoup 对象里的一个标签时,增加一个检查条件保证标签确实存在是很聪明的做法。如果你想要调用的标签不存在,BeautifulSoup 就会返回 None 对象。不过,如果再调用这个 None 对象下面的子标签,就会发生 AttributeError 错误。

下面这行代码(nonExistentTag 是虚拟的标签,BeautifulSoup 对象里实际没有)

print(bsObj.nonExistentTag)

会返回一个 None 对象。处理和检查这个对象是十分必要的。如果你不检查,直接调用这个None 对象的子标签,麻烦就来了。如下所示。

print(bsObj.nonExistentTag.someTag)

这时就会返回一个异常:

AttributeError: 'NoneType' object has no attribute 'someTag'

那么我们怎么才能避免这两种情形的异常呢?最简单的方式就是对两种情形进行检查:

try:
    badContent = bsObj.nonExistingTag.anotherTag
except AttributeError as e:
    print("Tag was not found")
else:
    if badContent == None:
        print ("Tag was not found")
    else:
        print(badContent)

初看这些检查与错误处理的代码会觉得有点儿累赘,但是,我们可以重新简单组织一下代码,让它变得不那么难写(更重要的是,不那么难读)。例如,下面的代码是上面爬虫的另一种写法:

from urllib.request import urlopen
from urllib.error import HTTPError
from bs4 import BeautifulSoup
def getTitle(url):
    try:
        html = urlopen(url)
    except HTTPError as e:
        return None
    try:
        bsObj = BeautifulSoup(html.read())
        title = bsObj.body.h1
    except AttributeError as e:
        return None
    return title
title = getTitle("http://www.pythonscraping.com/pages/page1.html")
if title == None:
    print("Title could not be found")
else:
    print(title)

在这个例子中,我们创建了一个 getTitle 函数,可以返回网页的标题,如果获取网页的时候遇到问题就返回一个 None 对象。在 getTitle 函数里面,我们像前面那样检查了 HTTPError,然后把两行 BeautifulSoup 代码封装在一个 try 语句里面。这两行中的任何一行有问题,AttributeError 都可能被抛出(如果服务器不存在,html 就是一个 None 对象,html.read() 就会抛出 AttributeError)。其实,我们可以在 try 语句里面放任意多行代码,或者放一个在任意位置都可以抛出 AttributeError 的函数。

在写爬虫的时候,思考代码的总体格局,让代码既可以捕捉异常又容易阅读,这是很重要的。如果你还希望能够很大程度地重用代码,那么拥有像 getSiteHTML 和 getTitle 这样的通用函数(具有周密的异常处理功能)会让快速稳定地网络数据采集变得简单易行。


  • 4
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值