前前后后写过几次爬虫,主要用来追美剧,爬数据,查字典啥的。准备在以后练手的时候顺便写点笔记记录一下。

爬虫是什么

网络蜘蛛(Web spider)也叫网络爬虫(Web crawler),蚂蚁(ant),自动检索工具(automatic indexer),或者(在 FOAF 软件概念中)网络疾走(WEB scutter),是一种“自动化浏览网络”的程序,或者说是一种网络机器人。它们被广泛用于互联网搜索引擎或其他类似网站,以获取或更新这些网站的内容和检索方式。它们可以自动采集所有其能够访问到的页面内容,以供搜索引擎做进一步处理(分检整理下载的页面),而使得用户能更快的检索到他们需要的信息。

这段解释来自 Wikipedia, 直白点来讲,就是通过用代码编写脚本,模拟浏览器的行为,达到高效访问或者获取网络资源的目的。所以,按照饼厂的价值观,写爬虫脚本是要被开除的(开个玩笑 …

预备知识

在编写爬虫之前,我们需要了解和掌握一些基本技能。

环境配置

工欲善其事,必先利其器。强烈推荐 Sublime Text 3PyCharm, 覆盖各个平台,算是为数不多的让我有付费购买的冲动的软件产品。当然,前者可以免费试用,后者可以使用教育版,到期可以续,简直业界良心。具体如何折腾和使用这些工具,这里不作介绍,网上有相应的教程。

Python 入门

提到 Python, 可能很多人的第一反应就是爬虫脚本。事实上,你可以用任何语言写爬虫,完全看你个人喜好;Python 也可以用于其他场景,例如 Web 开发。相比于其他语言,Python 上手快,强大、成熟的第三方库和框架支持,代码简洁优雅 … 使得 Python 适合快速实现,快速验证。

这里不会讲解 Python 相关的内容。要入门 Python,其实现在网络上已经有不少的教程,Google 一下。这里安利一下廖雪峰的 Python 2.7 教程,当然你想学 Python 3.x 也没问题,Python 教程,建议跳过最后安排的实战部分,不适合入门训练。看书的话,两本免费的 A Byte of PythonDive Into Python, 都有对应的 2.x 和 3.x 版本,看完之后,上手 Python 基本没啥问题了,遇到问题,最好的办法是先思考,然后 Google.

爬虫中涉及到的 Python 主要知识:

  • 编码 / 解码,编码 / 解码问题是 Python 2.x 的一个坑,这个问题可以另开一篇博客讨论;
  • 正则表达式,对应 re 模块,顺便安利一个学习和编写正则表达式的工具,RegexBuddy
  • 裸写爬虫阶段,主要会接触到到 urllib, urllib2, requests, threading, Queue, BeautifulSoup, lxml 等第三方库;
  • 高级爬虫阶段,爬虫会涉及专门的爬虫框架,例如 Scrapy, pyspider 等,可以基于这些框架来构建自己的爬虫。

网络相关

这里也不会讲解计算机网络相关知识,但是有些概念还是要明白,不然在调用方法或者接口时,不知道要传递什么参数。

  1. URI vs. URL vs. URN

    • URI 是以一种抽象的,高层次概念定义统一资源标识,而 URL 和 URN 则是具体的资源标识的方式;
    • URI 是个纯粹的语法结构,包含用来指定 Web 资源的字符串的各种组成部分。URL 和 URN 都是 URI 的特例,它包含了用于定位 Web 资源的足够信息;
    • URI 类不包含任何用于访问资源的方法,它的唯一作用就是解析,URL 可以打开一个到达资源的流。

      关于这三者的定义,RFC 3986 有提到,有兴趣可以去读一下,也可以看看 Stackoverflow 上的讨论:What is the difference between a URI, a URL and a URN?

      举例如下:

      URI: scheme:[//[user:[email protected]]host[:port]][/]path[?query][#fragment]

      URL: http://www.baidu.com

      URN: mailto:wuxiaoqiang*@hotmail.com

      爬虫主要处理的是 URL.

  2. 请求和响应

    这其实是 HTTP 协议里面的内容了。我们从浏览器的角度来看请求和响应,因为爬虫就是尽可能模拟浏览器的行为。

    这个过程简单来看,其实就是用户在地址栏输入网址之后,经过 DNS 服务器解析,找到服务器主机地址,然后向服务器发出一个请求,服务器对请求解析之后进行响应,并把响应结果发送给用户的浏览器,浏览器对返回的文件进行渲染;而对于爬虫,得到响应结果之后,重点不是对其进行渲染和展示,而是对返回的内容(例如 HTML 文档)进行分析和过滤,定位到我们需要的信息或者资源,便于下一步处理,这个过程,我们并不在意网页内容的展现。

    这里只是在概念上建立一个认知雏形,具体内容在后面遇到的时候会再讨论。

High 一下

扯了这么一堆,还是没见到爬虫的影子,那就上一条爬虫吧。

1
2
3
4
import urllib2
resopnse = urllib2.urlopen("http://www.baidu.com")
print content.read()

三行就写好了一条最简单的爬虫,姑且称之为“雏形爬虫”,是不是很简单 … 保存以上代码,或者直接在命令行输入以上代码,你会得到类似如下结果:

雏形爬虫执行结果

这就是爬虫眼里“看到”的东西了,通常下一步要做的事就是从这个结果中分析、过滤出我们想要的信息。

雏形爬虫分析

核心方法

雏形爬虫核心代码涉及两个方法,库 urllib2 里面的 urlopen()read(). 首先看看 urlopen() 方法:

1
2
3
4
5
6
7
urlopen(url,
data=None,
timeout=socket._GLOBAL_DEFAULT_TIMEOUT,
cafile=None,
capath=None,
cadefault=False,
context=None)

这个方法有很多参数,但是我们通常比较关心的是前三个。在雏形爬虫中,我们向 urlopen() 方法传递了一个参数 url,这个地址指向百度首页,其他参数默认。调用这个方法后,返回的信息保存在 response 中,再调用 read() 方法便可以获得爬取的内容。

一点延伸

雏形爬虫虽然简单,但是理解起来可能不是很直观,你可能会问:我们看到 response, 但似乎没有 request 啊,我要更改请求的头部信息怎么办 …

1
2
3
4
5
import urllib2
request = urllib2.Request("http://www.baidu.com")
response = urllib2.urlopen(request)
print response.read()

这是爬虫比较推荐的写法,看上去结构清晰,更重要的是发送请求的时候,往往还需要带上一些参数,这时候就可以通过构造 Request 对象来填充数据,然后把 request 传递给 urlopen() 方法,返回 response. 看看 Request 的构造函数:

1
2
3
4
5
urllib2.Request(url,
data=None,
headers={},
origin_req_host=None,
unverifiable=False)

GET vs. POST

默认情况下,我们调用 urlopen() 方法或者构造 Request 对象的时候,没有提供 data 参数,这时候发出的请求是 GET,当我们提供了 data 参数时,发出的请求就是 POST 了。从爬虫的角度来看,GET 和 POST 的区别在于,GET 是单纯的获取数据,而 POST 是在发送请求的时候还要顺带发出数据,典型的场景是模拟登录。当然,GET 和 POST 的区别不仅限于此:

  • GET 在浏览器回退时是无害的,而 POST 会再次提交请求;
  • GET 产生的 URL 地址可以被 Bookmark,而 POST 不可以;
  • GET 请求会被浏览器主动 cache,而 POST 不会,除非手动设置;
  • GET 请求只能进行 url 编码,而 POST 支持多种编码方式;
  • GET 请求参数会被完整保留在浏览器历史记录里,而 POST 中的参数不会被保留;
  • GET 请求在 URL 中传送的参数是有长度限制的,而 POST 没有;
  • 对参数的数据类型,GET 只接受 ASCII 字符,而 POST 没有限制;
  • GET 比 POST 更不安全,因为参数直接暴露在 URL 上,所以不能用来传递敏感信息;
  • GET 参数通过 URL 传递,POST 放在 Request body 中。

以上信息来自 W3School.

基于以上的分析,我们可以分别通过填充 data 来构造 GET 和 POST 请求:

GET 请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
import urllib
import urllib2
values = {}
values['username'] = "username"
values['password'] = "password"
data = urllib.urlencode(values)
url = "http://passport.csdn.net/account/login"
geturl = url + "?" + data
request = urllib2.Request(geturl)
response = urllib2.urlopen(request)
print response.read()

POST 请求:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
import urllib
import urllib2
values = {}
values['username'] = "username"
values['password'] = "password"
data = urllib.urlencode(values)
url = "http://passport.csdn.net/account/login?from=http://my.csdn.net/my/mycsdn"
request = urllib2.Request(url, data)
response = urllib2.urlopen(request)
print response.read()

上述代码只是为了举例说明 GET 请求和 POST 请求在构造的时候的区别,在真实的模拟登录过程中,还需要设置其他参数,例如:设置 cookie 和 UserAgent 等,这个过程可以在实际登录的时候看到。

参考资料

网络蜘蛛

RFC 3986

HTTP 方法:GET 对比 POST

Python 爬虫入门三之 Urllib 库的基本使用