乐趣区

网络爬虫的原理

互联网上,公开数据(各种网页)都是以 http(或加密的 http 即 https)协议传输的。所以,我们这里介绍的爬虫技术都是基于 http(https)协议的爬虫。

在 Python 的模块海洋里,支持 http 协议的模块是相当丰富的,既有官方的 urllib,也有大名鼎鼎的社区(第三方)模块 requests。它们都很好的封装了 http 协议请求的各种方法,因此,我们只需要熟悉这些模块的用法,不再进一步讨论 http 协议本身。

认识浏览器和服务器

大家对浏览器应该一点都不陌生,可以说,只要上过网的人都知道浏览器。可是,明白浏览器各种原理的人可不一定多。

作为要开发爬虫的小伙伴,是一定一定要明白浏览器的工作原理的。这是你写爬虫的必备工具,别无他。

大家在面试的时候,有没有遇到这么一个非常宏观而又处处细节的解答题:

  • 请说说从你在浏览器地址栏输入网站到你看到网页中间都发生了什么?

这真是一个考验知识面的题啊,经验老道的老猿既可以滔滔不绝的讲上三天三夜,也可以提炼出几分钟的精华讲个大概。大家恐怕对整个过程就一知半解了。

巧的是,对这个问题理解的越透彻,越对写爬虫有帮助。换句话说,爬虫是一个考验综合技能的领域。那么,大家准备好迎接这个综合技能挑战了吗?

废话不多说,我们就从解答这个题目开始,认识浏览器和服务器,看看这中间有哪些知识是爬虫要用到的。

前面也说过,这个问题可以讲上三天三夜,但我们没那么多时间,其中一些细节就略过,把大致流程结合爬虫讲一讲,分成三部分:

  1. 浏览器发出请求
  2. 服务器做出响应
  3. 浏览器接收响应

1. 浏览器发出请求

在浏览器地址栏输入网址后回车,浏览器请服务器提出网页请求,也就是告诉服务器,我要看你的某个网页。
上面短短一句话,蕴藏了无数玄机啊,让我不得不费点口舌一一道来。主要讲述:

  • 网址是不是有效的?
  • 服务器在哪里?
  • 浏览器向服务器发送了些什么?
  • 服务器返回了些什么?

1)网址是不是有效的?

首先,浏览器要判断你输入的网址(URL)是否合法有效。对应 URL,小猿们并不陌生吧,以 http(s)开头的那一长串的字符,但是你知道它还可以以 ftp, mailto, file, data, irc 开头吗?下面是它最完整的语法格式:

URI = scheme:[//authority]path[?query][#fragment]
# 其中,authority 又是这样的:authority = [userinfo@]host[:port]
# userinfo 可以同时包含 user name 和 password,以:分割
userinfo = [user_name:password]

用图更形象的表现处理就是这样的:

经验之谈:要判断 URL 的合法性

Python 里面可以用 urllib.parse 来进行 URL 的各种操作

In [1]: import urllib.parse 

In [2]: url = 'http://dachong:the_password@www.yuanrenxue.com/user/info?page=2'

In [3]: zz = urllib.parse.urlparse(url)
Out[4]: ParseResult(scheme='http', netloc='dachong:the_password@www.yuanrenxue.com', path='/user/info', params='', query='page=2', fragment='')

我们看到,urlparse 函数把 URL 分析成了 6 部分:
scheme://netloc/path;params?query#fragment
需要主要的是 netloc 并不等同于 URL 语法定义中的 host

2) 服务器在哪里?

上面 URL 定义中的 host,就是互联网上的一台服务器,它可以是一个 IP 地址,但通常是我们所说的域名。域名通过 DNS 绑定到一个(或多个)IP 地址上。浏览器要访问某个域名的网站就要先通过 DNS 服务器解析域名,得到真实的 IP 地址。
这里的域名解析一般是由操作系统完成的,爬虫不需要关心。然而,当你写一个大型爬虫,像 Google、百度搜索引擎那样的爬虫的时候,效率变得很主要,爬虫就要维护自己的 DNS 缓存。
老猿经验:大型爬虫要维护自己的 DNS 缓存

3) 浏览器向服务器发送些什么?

浏览器获得了网站服务器的 IP 地址,就可以向服务器发送请求了。这个请求就是遵循 http 协议的。写爬虫需要关心的就是 http 协议的 headers,下面是访问 en.wikipedia.org/wiki/URL 时浏览器发送的请求 headers:

可能已经从图中看出来些端倪,发送的 http 请求头是类似一个字典的结构:

  • authority: 就是访问的目标机器;
  • method: http 请求的方法有很多:

    • GET
    • HEAD
    • POST
    • PUT
    • DELETE
    • CONNECT
    • OPTIONS
    • TRACE
    • PATCH
      一般,爬虫使用最多的是 GETPOST
  • path: 访问的网站的路径
  • scheme: 请求的协议类型,这里是 https
  • accept: 能够接受的回应内容类型(Content-Types)
  • accept-encoding: 能够接受的编码方式列表
  • accept-language: 能够接受的回应内容的自然语言列表
  • cache-control: 指定在这次的请求 / 响应链中的所有缓存机制 都必须 遵守的指令
  • cookie: 之前由服务器通过 Set- Cookie 发送的一个 超文本传输协议 Cookie
    这是爬虫很关心的一个东东,登录信息都在这里。
  • upgrade-insecuree-requests: 非标准请求字段,可忽略之。
  • user-agent: 浏览器身份标识

这也是爬虫很关心的部分。比如,你需要得到手机版页面,就要设置浏览器身份标识为手机浏览器的 user-agent。

经验之谈: 通过设置 headers 跟服务器沟通

4) 服务器返回了些什么?

如果我们在浏览器地址栏输入一个网页网址(不是文件下载地址),回车后,很快就看到了一个网页,里面包含排版文字、图片、视频等数据,是一个丰富内容格式的页面。然而,我通过浏览器查看源代码,看到的却是一对文本格式的 html 代码。

没错,就是一堆的代码,却让浏览器给渲染成了漂亮的网页。这对代码里面有:

  • css:浏览器根据它来排版,安排文字、图片等的位置;
  • JavaScript:浏览器运行它可以让用户和网页交互;
  • 图片等链接:浏览器再去下载这些链接,最终渲染成网页。

而我们想要爬取的信息就藏在 html 代码中,我们可以通过解析方法提取其中我们想要的内容。如果 html 代码里面没有我们想要的数据,但是在网页里面却看到了,那就是浏览器通过 ajax 请求异步加载(偷偷下载)了那部分数据。

这个时候,我们就要通过观察浏览器的加载过程来发现具体是哪个 ajax 请求加载了我们需要的数据。

退出移动版