终于要开始写爬虫代码了

我们首先了解一下 Urllib 库,它是 Python 内置的 HTTP 请求库,也就是说我们不需要额外安装即可使用,它包含四个模块:

第一个模块 request,它是最基本的 HTTP 请求模块,我们可以用它来模拟发送一请求,就像在浏览器里输入网址然后敲击回车一样,只需要给库方法传入 URL 还有额外的参数,就可以模拟实现这个过程了。

第二个 error 模块即异常处理模块,如果出现请求错误,我们可以捕获这些异常,然后进行重试或其他操作保证程序不会意外终止。

第三个 parse 模块是一个工具模块,提供了许多 URL 处理方法,比如拆分、解析、合并等等的方法。

第四个模块是 robotparser,主要是用来识别网站的 robots.txt 文件,然后判断哪些网站可以爬,哪些网站不可以爬的,其实用的比较少。


在这里重点对前三个模块进行下讲解。

一、发送请求

使用 Urllib 的 request 模块我们可以方便地实现 Request 的发送并得到 Response

 
1、urlopen()

urllib.request 模块提供了最基本的构造 HTTP 请求的方法,利用它可以模拟浏览器的一个请求发起过程,同时它还带有处理authenticaton(授权验证),redirections(重定向),cookies(浏览器Cookies)以及其它内容。



我们来感受一下它的强大之处,以 Python 官网为例,我们来把这个网页抓下来:

import urllib.request


response = urllib.request.urlopen('https://www.python.org')
print(response.read().decode('utf-8'))

运行结果如下:

06-第一个python爬虫库urllib_编程

 

接下来我们看下它返回的到底是什么,利用 type() 方法输出 Response 的类型。

import urllib.request


response = urllib.request.urlopen('https://www.python.org')
print(type(response))

输出结果如下:

<class 'http.client.HTTPResponse'>

通过输出结果可以发现它是一个 HTTPResposne 类型的对象,它主要包含的方法有 read()、readinto()、getheader(name)、getheaders()、fileno() 等方法和 msg、version、status、reason、debuglevel、closed 等属性。



得到这个对象之后,我们把它赋值为 response 变量,然后就可以调用这些方法和属性,得到返回结果的一系列信息了。

例如调用 read() 方法可以得到返回的网页内容,调用 status 属性就可以得到返回结果的状态码,如 200 代表请求成功,404 代表网页未找到等。

下面再来一个实例感受一下:

import urllib.request


response = urllib.request.urlopen('https://www.python.org')
print(response.status)
print(response.getheaders())
print(response.getheader('Server'))

运行结果如下:

200
[('Server', 'nginx'), ('Content-Type', 'text/html; charset=utf-8'), ('X-Frame-Options', 'SAMEORIGIN'), ('X-Clacks-Overhead', 'GNU Terry Pratchett'), ('Content-Length', '47397'), ('Accept-Ranges', 'bytes'), ('Date', 'Mon, 01 Aug 2016 09:57:31 GMT'), ('Via', '1.1 varnish'), ('Age', '2473'), ('Connection', 'close'), ('X-Served-By', 'cache-lcy1125-LCY'), ('X-Cache', 'HIT'), ('X-Cache-Hits', '23'), ('Vary', 'Cookie'), ('Strict-Transport-Security', 'max-age=63072000; includeSubDomains')]
nginx

可见,三个输出分别输出了响应的状态码,响应的头信息,以及通过调用 getheader() 方法并传递一个参数 Server 获取了 headers 中的 Server 值,结果是 nginx,意思就是服务器是 nginx 搭建的


利用以上最基本的 urlopen() 方法,我们可以完成最基本的简单网页的 GET 请求抓取。


如果我们想给链接传递一些参数该怎么实现呢?我们首先看一下 urlopen() 函数的API:

urllib.request.urlopen(url, data=None, [timeout, ]*, cafile=None, capath=None, cadefault=False, context=None)

可以发现除了第一个参数可以传递 URL 之外,我们还可以传递其它的内容,比如 data(附加数据)、timeout(超时时间)等等。


下面我们详细说明下这几个参数的用法:

 
data参数

data 参数是可选的,如果要添加 data,它要是字节流编码格式的内容,即 bytes 类型,通过 bytes() 方法可以进行转化,另外如果传递了这个 data 参数,它的请求方式就不再是 GET 方式请求,而是 POST。

下面用一个实例来感受一下:

import urllib.parse
import urllib.request


data = bytes(urllib.parse.urlencode({'word': 'hello'}), encoding='utf8')
response = urllib.request.urlopen('http://httpbin.org/post', data=data)
print(response.read())

在这里我们传递了一个参数 word,值是 hello。它需要被转码成bytes(字节流)类型。其中转字节流采用了 bytes() 方法

第一个参数需要是 str(字符串)类型,需要用 urllib.parse 模块里的 urlencode() 方法来将参数字典转化为字符串。

第二个参数指定编码格式,在这里指定为 utf8。

timeout参数

timeout 参数可以设置超时时间,单位为秒,意思就是如果请求超出了设置的这个时间还没有得到响应,就会抛出异常,如果不指定,就会使用全局默认时间。它支持 HTTP、HTTPS、FTP 请求。


因此我们可以通过设置这个超时时间来控制一个网页如果长时间未响应就跳过它的抓取,利用 try except 语句就可以实现这样的操作,代码如下:

import socket
import urllib.request
import urllib.error


try:
    response = urllib.request.urlopen('http://httpbin.org/get', timeout=0.1)
except urllib.error.URLError as e:
    if isinstance(e.reason, socket.timeout):
        print('TIME OUT')
 
其他参数

还有 context 参数,它必须是 ssl.SSLContext 类型,用来指定 SSL 设置。
cafile 和 capath 两个参数是指定 CA 证书和它的路径,这个在请求 HTTPS 链接时会有用。


cadefault 参数现在已经弃用了,默认为 False。
以上讲解了 urlopen() 方法的用法,通过这个最基本的函数可以完成简单的请求和网页抓取,如需更加详细了解,可以参见官方文档:

https://docs.python.org/3/library/urllib.request.html。

2. Request

由上我们知道利用 urlopen() 方法可以实现最基本请求的发起,但这几个简单的参数并不足以构建一个完整的请求,如果请求中需要加入 Headers 等信息,我们就可以利用更强大的 Request 类来构建一个请求。
首先我们用一个实例来感受一下 Request 的用法:

import urllib.request
request = urllib.request.Request('https://python.org')
response = urllib.request.urlopen(request)
print(response.read().decode('utf-8'))

可以发现,我们依然是用 urlopen() 方法来发送这个请求,只不过这次 urlopen() 方法的参数不再是一个 URL,而是一个 Request 类型的对象,通过构造这个这个数据结构,一方面我们可以将请求独立成一个对象,另一方面可配置参数更加丰富和灵活。


下面我们看一下 Request 都可以通过怎样的参数来构造,它的构造方法如下:

class urllib.request.Request(url, data=None, headers={}, origin_req_host=None, unverifiable=False, method=None)

第一个 url 参数是请求 URL,这个是必传参数,其他的都是可选参数。


第二个 data 参数如果要传必须传 bytes(字节流)类型的,如果是一个字典,可以先用 urllib.parse 模块里的 urlencode() 编码。


第三个 headers 参数是一个字典,这个就是 Request Headers 了,你可以在构造 Request 时通过 headers 参数直接构造,

也可以通过调用 Request 实例的 add_header() 方法来添加, Request Headers 最常用的用法就是通过修改 User-Agent 来伪装浏览器,默认的 User-Agent 是 Python-urllib,我们可以通过修改它来伪装浏览器。


第四个 origin_req_host 参数指的是请求方的 host 名称或者 IP 地址。



第五个 unverifiable 参数指的是这个请求是否是无法验证的,默认是False。意思就是说用户没有足够权限来选择接收这个请求的结果。例如我们请求一个 HTML 文档中的图片,但是我们没有自动抓取图像的权限,这时 unverifiable 的值就是 True。



第六个 method 参数是一个字符串,它用来指示请求使用的方法,比如GET,POST,PUT等等。

写个例子:

from urllib import request, parse


url = 'http://httpbin.org/post'
headers = {
    'User-Agent': 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)',
    'Host': 'httpbin.org'
}
dict = {
    'name': 'Germey'
}
data = bytes(parse.urlencode(dict), encoding='utf8')
req = request.Request(url=url, data=data, headers=headers, method='POST')
response = request.urlopen(req)
print(response.read().decode('utf-8'))

在这里我们通过四个参数构造了一个 Request,url 即请求 URL,在headers 中指定了 User-Agent 和 Host,传递的参数 data 用了 urlencode() 和 bytes() 方法来转成字节流,另外指定了请求方式为 POST。


通过观察结果可以发现,我们成功设置了 data,headers 以及 method。
另外 headers 也可以用 add_header() 方法来添加。

req = request.Request(url=url, data=data, method='POST')
req.add_header('User-Agent', 'Mozilla/4.0 (compatible; MSIE 5.5; Windows NT)')

如此一来,我们就可以更加方便地构造一个 Request,实现请求的发送啦。

 

二、 处理异常

我们了解了 Request 的发送过程,但是在网络情况不好的情况下,出现了异常怎么办呢?这时如果我们不处理这些异常,程序很可能报错而终止运行,所以异常处理还是十分有必要的。

Urllib 的 error 模块定义了由 request 模块产生的异常。如果出现了问题,request 模块便会抛出 error 模块中定义的异常。

主要有这两个处理异常类,URLError,HTTPError


下面写个例子:

from urllib import request, error
try:
    response = request.urlopen('http://cuiqingcai.com/index.htm')
except error.URLError as e:
    print(e.reason)

我们打开一个不存在的页面,照理来说应该会报错,但是这时我们捕获了 URLError 这个异常,运行结果如下:

Not Found
from urllib import request,error
try:
    response = request.urlopen('http://cuiqingcai.com/index.htm')
except error.HTTPError as e:
    print(e.reason, e.code, e.headers, seq='\n')

运行结果:

Not Found
404
Server: nginx/1.4.6 (Ubuntu)
Date: Wed, 03 Aug 2016 08:54:22 GMT
Content-Type: text/html; charset=UTF-8
Transfer-Encoding: chunked
Connection: close
X-Powered-By: PHP/5.5.9-1ubuntu4.14
Vary: Cookie
Expires: Wed, 11 Jan 1984 05:00:00 GMT
Cache-Control: no-cache, must-revalidate, max-age=0
Pragma: no-cache
Link: <http://cuiqingcai.com/wp-json/>; rel="https://api.w.org/"

HTTPError,它有三个属性。

  • code,返回 HTTP Status Code,即状态码,比如 404 网页不存在,500 服务器内部错误等等。

  • reason,同父类一样,返回错误的原因。

  • headers,返回 Request Headers。
    因为 URLError 是 HTTPError 的父类,所以我们可以先选择捕获子类的错误,再去捕获父类的错误,所以上述代码更好的写法如下:

from urllib import request, error


try:
    response = request.urlopen('http://cuiqingcai.com/index.htm')
except error.HTTPError as e:
    print(e.reason, e.code, e.headers, sep='\n')
except error.URLError as e:
    print(e.reason)
else:
    print('Request Successfully')

这样我们就可以做到先捕获 HTTPError,获取它的错误状态码、原因、Headers 等详细信息。如果非 HTTPError,再捕获 URLError 异常,输出错误原因。最后用 else 来处理正常的逻辑,这是一个较好的异常处理写法。

三、 实例抓取豆瓣电影排行榜

import urllib.parse
import urllib.request
url='https://movie.douban.com/'
herders={
    'User-Agent':'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/85.0.4183.102 Safari/537.36',
    'Referer':'https://movie.douban.com/',
    'Connection':'keep-alive'}


req=urllib.request.Request(url,headers=herders)
response=urllib.request.urlopen(req)
html=response.read().decode('utf8')
print(html)

 结果

<!DOCTYPE html>
<html lang="zh-CN" class="ua-windows ua-webkit">
<head>
     ...
    <title>豆瓣电影</title>  


    <meta name="keywords" content="电影、经典电影、热映、电视剧、美剧、影评、电影院、电影票、排行、推荐"/>
    <meta name="description" content="豆瓣电影提供最新的电影介绍及评论包括上映影片的影讯查询及购票服务。你可以记录想看、在看和看过的电影电视剧,顺便打分、写影评。根据你的口味,豆瓣电影会推荐好电影给你。" />
    ....
    以下省略

四、分析Robots协议

 利用urllib的robotparser模块,我们可以实现网站Robots协议的分析

1Robots协议

Robots协议也称为爬虫协议、机器人协议,它的全名叫做网络爬虫排除标准(Robots Exclusion Protocol),用来告诉爬虫和搜索引擎哪些网页可以抓取,哪些不可以抓取,它通常是一个robots.txt的文本文件,一般放在网站的根目录下。

当搜索爬虫访问一个站点时,它首先会检查这个站点根目录下是否存在robots.txt文件,如果存在,搜索爬虫会根据其中定义的爬去范围来爬取,如果没有找到,搜索爬虫会访问所有可直接访问的页面

我们来看下robots.txt的样例:

User-agent: *
Disallow: /
Allow: /public/

它实现了对所有搜索爬虫只允许爬取public目录的功能,将上述内容保存为robots.txt文件放在网站根目录下,和网站的入口文件(index.html)放在一起

User-agent描述了搜索爬虫的名称,将其设置为*则代表协议对任何爬虫有效,如设置为Baiduspider则代表规则对百度爬虫有效,如果有多条则对多个爬虫受到限制,但至少需要指定一条

一些常见的搜索爬虫名称:

BaiduSpider  百度爬虫   www.baidu.com
Googlebot  Google爬虫   www.google.com
360Spider  360爬虫   www.so.com
YodaoBot  有道爬虫   www.youdao.com
ia_archiver  Alexa爬虫   www.alexa.cn
Scooter  altavista爬虫     www.altavista.com

Disallow指定了不允许抓取的目录,如上例中设置的/则代表不允许抓取所有的页面

Allow一般和Disallow一起使用,用来排除单独的某些限制,如上例中设置为/public/则表示所有页面不允许抓取,但可以抓取public目录

设置示例:

#禁止所有爬虫
User-agent: *
Disallow: /


#允许所有爬虫访问任何目录,另外把文件留空也可以
User-agent: *
Disallow:


#禁止所有爬虫访问某那些目录
User-agent: *
Disallow: /home/
Disallow: /tmp/


#只允许某一个爬虫访问
User-agent: BaiduSpider
Disallow:
User-agent: *
Disallow: /

06-第一个python爬虫库urllib_Python_02

2 robotparser

rebotparser模块用来解析robots.txt,该模块提供了一个类RobotFileParser,它可以根据某网站的robots.txt文件来判断一个抓取爬虫时都有权限来抓取这个网页

urllib.robotparser.RobotFileParser(url='')

robotparser类常用的方法:

set_url():用来设置robots.txt文件的连接,如果在创建RobotFileParser对象是传入了连接,就不需要在使用这个方法设置了

read():读取reobts.txt文件并进行分析,它不会返回任何内容,但执行那个了读取和分析操作

parse():用来解析robots.txt文件,传入的参数是robots.txt某些行的内容,并安装语法规则来分析内容

can_fetch():该方法传入两个参数,第一个是User-agent,第二个是要抓取的URL,返回的内容是该搜索引擎是否可以抓取这个url,结果为True或False

mtime():返回上次抓取和分析robots.txt的时间

modified():将当前时间设置为上次抓取和分析robots.txt的时间

#!/usr/bin/env python
#coding:utf8
from urllib.robotparser import RobotFileParser
rp = RobotFileParser()  #创建对象
rp.set_url #设置robots.txt连接,也可以在创建对象时指定
rp.read()  #读取和解析文件
print(rp.can_fetch('*',') #坚持链接是否可以被抓取

希望各位同学以后在写爬虫时候可以遵循 Robot 协议,为了自己的安全着想。

 

06-第一个python爬虫库urllib_java_03