300字范文,内容丰富有趣,生活中的好帮手!
300字范文 > 《Python 3网络爬虫开发实战 》崔庆才著 第二章笔记

《Python 3网络爬虫开发实战 》崔庆才著 第二章笔记

时间:2019-09-02 06:36:46

相关推荐

《Python 3网络爬虫开发实战 》崔庆才著 第二章笔记

使用urllib

它是 Python内置的HTTP请求库,也就是说不需要额外安装即可使用。

包含以下4个模块:

重点讲解下前3个模块。

发送请求

使用urllib 的request模块,我们可以方便地实现请求的发送并得到响应。

1.urlopen()

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

import urllib.requestresponse=urllib.request.urlopen('')print(response.read().decode('utf-8'))

查看返回的类型

import urllib.requestresponse=urllib.request.urlopen('')print(type(response))

发现是HTTPResponse类型

该对象包含了以下方法:

read():返回网页的内容。readinto()getheader(name)getheaders()fileno()

以及以下属性:

msgversionstatus:请求页面的状态码。reasondebuglevelclosed

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

urlopen API:

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

data参数

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

import urllib.parseimport urllib.requestdata=bytes(urllib.parse.urlencode({'word':'hello'}),encoding='utf-8')response = urllib.request.urlopen('/post', data=data)print(response.read())

这里我们传递了一个参数 word,值是 hello。它需要被转码成 bytes(字节流)类型。其中转字节流采用了bytes()方法,该方法的第一个参数需要是str(字符串)类型,需要用urllib.parse模块里的urlencode()方法来将参数字典转化为字符串;第二个参数指定编码格式,这里指定为utf8。

timeout参数

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

import urllib.requestresponse = urllib.request.urlopen('/get', timeout=1)print(response.read())

这里我们设置超时时间是1秒。程序1秒过后,服务器依然没有响应,于是抛出了URLError 异常该异常属于urllib.error模块,错误原因是超时。

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

import socketimport urllib.requestimport urllib.errortry:response = urllib.request.urlopen( '/get', timeout=0.1)except urllib.error.URLError as e:if isinstance(e.reason,socket.timeout):print( 'TIME OUT')

这里我们请求了http://get测试链接,设置超时时间是0.1秒,然后捕获了URLError异常,接着判断异常是socket.timeout类型(意思就是超时异常),从而得出它确实是因为超时而报错,打印输出了 TIME OUT。

其他参数

除了data参数和timeout参数外,还有context参数,它必须是ssl.SsLContext类型,用来指定SSL设置。

此外,cafile和 capath这两个参数分别指定CA证书和它的路径,这个在请求HTTPS链接时会有用。

cadefault参数现在已经弃用了,其默认值为False。

2.Request

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

import urllib.requestrequest = urllib.request.Request( '')response = urllib.request.urlopen(request)print(response.read().decode( 'utf-8 '))

下面我们看一下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是一个字典,它就是请求头,我们可以在构造请求时通过headers参数直接构造,也可以通过调用请求实例的add_header()方法添加。添加请求头最常用的用法就是通过修改 User-Agent来伪装浏览器,默认的User-Agent是Python-urllib,我们可以通过修改它来伪装浏览器。比如要伪装火狐浏览器,你可以把它设置为:

Mozilla/5.0 (X11; U;Linux i686)Gecko/1127 Firefox/2.0.0.11

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

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

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

下面我们传入多个参数构建请求来看一下:

from urllib import request, parseurl = '/post'headers = {'User-Agent':'Mozilla/4.o (compatible;MSIE 5.5; windows NT)' ,'Host':''}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'))

headers也可以用add_header()方法来添加:

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

3.高级用法

在上面的过程中,我们虽然可以构造请求,但是对于一些更高级的操作(比如 Cookies处理、代理设置等),就需要更强大的工具 Handler 登场了。简而言之,我们可以把它理解为各种处理器,有专门处理登录验证的,有处理Cookies的,有处理代理设置的。利用它们,我们几乎可以做到 HTTP请求中所有的事情。

首先,介绍一下urllib.request模块里的BaseHandler类,它是所有其他Handler的父类,它提供了最基本的方法,例如 default_open()、 protocol_request()等。有各种 Handler子类继承这个BaseHandler类,举例如下。

另一个比较重要的类就是OpenerDirector,我们可以称为Opener。Opener可以使用open()方法,返回的类型和urlopen()如出一辙。它就是利用Handler来构建Opener。下面用几个实例来看看它们的用法。

验证

有些网站在打开时就会弹出提示框,直接提示你输入用户名和密码,验证成功后才能查看页面。

借助HTTPBasicAuthHandler就可以完成

from urllib.request import HTTPPasswordMgrWithDefaultRealm,HTTPBasicAuthHandler,build_openerfrom urllib.error import URLErrorusername = 'username'password = 'password'url = 'http://localhost:5000/'p= HTTPPasswordMgrWithDefaultRealm()p.add_password(None,url,username,password)auth_handler = HTTPBasicAuthHandler(p)opener = build_opener(auth_handler)try:result = opener.open(url)html = result.read().decode('utf-8')print(html)except URLError as e:print(e.reason)

这里首先实例化HTTPBasicAuthHandler对象,其参数是HTTPPasswordMgrWithDefaultRealm对象,它利用add_password()添加进去用户名和密码,这样就建立了一个处理验证的 Handler。

接下来,利用这个Handler并使用build_opener()方法构建一个Opener,这个Opener在发送请求时就相当于已经验证成功了。

接下来,利用Opener的open()方法打开链接,就可以完成验证了。这里获取到的结果就是验证后的页面源码内容。

代理

在做爬虫的时候,免不了要使用代理,如果要添加代理,可以这样做:

from urllib.error import URLErrorfrom urllib.request import ProxyHandler,build_openerproxy_handler = ProxyHandler({'http': 'http://127.0.0.1:9743','https ': 'https://127.0.0.1:9743'})opener = build_opener(proxy_handler)try:response = opener.open( '')print(response.read().decode( 'utf-8'))except URLError as e:print(e.reason)

这里我们在本地搭建了一个代理,它运行在9743端口上。

这里使用了ProxyHandler,其参数是一个字典,键名是协议类型(比如HTTP或者HTTPS等).键值是代理链接,可以添加多个代理。

然后,利用这个Handler 及 build opener()方法构造一个Opener,之后发送请求即可。

Cookies

Cookies的处理就需要相关的Handler 了。我们先用实例来看看怎样将网站的Cookies获取下来,相关代码如下:

import http.cookiejar,urllib.requestcookie = http.cookiejar.CookieJar()handler = urllib.request.HTTPCookieProcessor(cookie)opener = urllib.request.build_opener(handler)response = opener.open( ' ')for item in cookie:print(item.name+"="+item.value)

首先,我们必须声明一个CookieJar对象。接下来,就需要利用HTTPCookieProcessor来构建一个Handler,最后利用build_opener()方法构建出Opener,执行open()函数即可。运行结果如下:

我们还可以输出为文件格式:

import http.cookiejar,urllib.requestfilename = "cookies.txt"cookie = http.cookiejar.MozillaCookieJar(filename)handler = urllib.request.HTTPCookieProcessor(cookie)opener = urllib.request.build_opener(handler)response = opener. open('' )cookie.save(ignore_discard=True,ignore_expires=True)

这时CookieJar就需要换成MozillaCookieJar,它在生成文件时会用到,是CookieJar的子类,可以用来处理Cookies 和文件相关的事件,比如读取和保存Cookies,可以将Cookies保存成Mozilla型浏览器的 Cookies格式。运行之后,可以发现生成了一个cookies.txt文件.其内容如下:

另外,LWPCookieJar同样可以读取和保存Cookies,但是保存的格式和MozillaCookieJar不一样,它会保存成libwww-perl(LWP)格式的Cookies文件。要保存成LWP格式的Cookies文件,可以在声明时就改为:

cookie = http.cookiejar.LWPCookieJar(filename)

可以发现他们之间的差别有点大。

下面我们以LWPCookieJar格式来看看如何使用cookie文件

import http.cookiejar,urllib.requestcookie = http.cookiejar.LWPCookieJar()cookie.load('cookies.txt',ignore_discard=True,ignore_expires=True)handler = urllib.request.HTTPCookieProcessor(cookie)opener = urllib.request.build_opener(handler)response = opener.open('')print(response.read().decode('utf-8'))

可以看到,这里调用load()方法来读取本地的Cookies文件,获取到了Cookies的内容。不过前提是我们首先生成了LWPCookieJar格式的Cookies,并保存成文件,然后读取 Cookies之后使用同样的方法构建Handler和 Opener即可完成操作。

运行结果正常的话,会输出百度网页的源代码。

通过上面的方法,我们可以实现绝大多数请求功能的设置了。

处理异常

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

1.URLError

URLError类来自urllib库的error模块,它继承自OSError类,是error异常模块的基类,由request模块生的异常都可以通过捕获这个类来处理。

它具有一个属性reason,即返回错误的原因。

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

程序没有直接报错,而是输出了如上内容,这样通过如上操作,我们就可以避免程序异常终止,同时异常得到了有效处理。

2.HTTPError

它是URLError的子类,专门用来处理IITTP请求错误,比如认证请求失败等。它有如下3个属性。

from urllib import request,errortry:response=request.urlopen('/index.htm')except error.HTTPError as e:print(e.reason,e.code,e.headers,sep='\n')

依然是同样的网址,这里捕获了HTTPError异常,输出了reason、code和 headers属性。

因为URLError是 HTTPError的父类,所以可以先选择捕获子类的错误,再去捕获父类的错误

有时候,reason属性返回的不一定是字符串,也可能是一个对象。

import socketimport urllib.requestimport urllib.errortry:response=urllib.request.urlopen('',timeout=0.01)except urllib.error.URLError as e:print(type(e.reason))if isinstance(e.reason,socket.timeout):print('TIME OUT')

可以发现,reason属性的结果是socket.timeout类。所以,这里我们可以用isinstance()方法来判断它的类型,作出更详细的异常判断。

解析链接

urllib 库里还提供了 parse 模块,它定义了处理 URL 的标准接口,例如实现 URL 各部分的抽取、合并以及链接转换。它支持如下协议的 URL 处理:file、ftp、gopher、hdl、http、https、imap、mailto、mms、news、nntp、prospero、rsync、rtsp、rtspu、sftp、sip、sips、snews、svn、svn+ssh、telnet 和 wais。

1.urlparse()

该方法可以实现 URL 的识别和分段

from urllib.parse import urlparseresult = urlparse('/index.html;user?id=5#comment')print(type(result))print(result)

这里我们利用urlparse()方法进行了一个URL 的解析。首先,输出了解析结果的类型,然后将结果也输出出来。

可以看到,返回结果是一个ParseResult类型的对象,它包含6个部分,分别是scheme,netloc,path,params,query和 fragment。一般的URL标准格式如下:

scheme://netloc/path;params?query#fragment

urlparse参数如下:

urlparse(url=,scheme=,allow_fragments=)

url:这是必填项,即待解析的URL。schema:这是默认的协议(HTTP、HTTPS等),默认为HTTPS。allow_fragments:是否忽略fragment,默认为True,即不忽视fragment。当URL中不包含params和 query时,fragment便会被解析为path的一部分。

urlparse的返回结果是一个元组,我们可以用索引顺序来获取,也可以用属性名获取。

from urllib.parse import urlparseresult = urlparse('/index.html;user?id=5#comment')print(result[0])print(result.scheme)

2.urlunparse()

有了urlparse(),相应地就有了它的对立方法urlunparse()。它接受的参数是一个可迭代对象,但是它的长度必须是6,否则会抛出参数数量不足或者过多的问题。先用一个实例看一下:

from urllib.parse import urlunparsedata=['http','','index.html','user','a=6','comment']print(urlunparse(data))

这里参数data用了列表类型。当然,你也可以用其他类型,比如元组或者特定的数据结构。

观察结果可以发现,我们成功构造了url。

3.urlsplit()

这个方法和 urlparse()方法非常相似,只不过它不再单独解析params这一部分,只返回5个结果。上面例子中的params会合并到path 中。示例如下:

from urllib.parse import urlsplitresult=urlsplit('/index.html;user?id=5#comment')print(result)

可以发现,返回结果是SplitResult,它其实也是一个元组类型,既可以用属性获取值,也可以用索引来获取。

4.urlunsplit()

与urlunparse()类似,它也是将链接各个部分组合成完整链接的方法,传入的参数也是一个可迭代对象,例如列表、元组等,唯一的区别是长度必须为5。示例如下:

from urllib.parse import urlunparsedata=['http','','index.html','user','a=6','comment']print(urlunparse(data))

5.urljoin()

生成链接还有另一个方法,那就是urljoin()方法。我们可以提供一个base_url(基础链接)作为第一个参数,将新的链接作为第二个参数,该方法会分析base_url的 scheme、netloc和path这3个内容并对新链接缺失的部分进行补充,最后返回结果。

from urllib.parse import urljoinprint(urljoin('', 'FAQ.html'))print(urljoin('', '/FAQ.html'))print(urljoin('/about.html', '/FAQ.html'))print(urljoin('/about.html', '/FAQ.html?question=2'))print(urljoin('?wd=abc', '/index.php'))print(urljoin('', '?category=2#comment'))print(urljoin('', '?category=2#comment'))print(urljoin('#comment', '?category=2'))

可以发现,base_url 提供了三项内容 scheme、netloc 和 path。如果这 3 项在新的链接里不存在,就予以补充;如果新的链接存在,就使用新的链接的部分。而 base_url 中的 params、query 和 fragment 是不起作用的。

通过 urljoin 方法,我们可以轻松实现链接的解析、拼合与生成。

6.urlencode()

这个方法非常常用。有时为了更加方便地构造参数,我们会事先用字典来表示。要转化为URL的参数时,只需要调用该方法即可。

from urllib.parse import urlencodeparams={'name': 'germey','age': 22}base_url='?'url=base_url+urlencode(params)print(url)

这里首先声明了一个字典来将参数表示出来,然后调用urlencode()方法将其序列化为GET请求参数。

可以看到,参数就成功地由字典类型转化为GET请求参数了。

7.parse_qs()

有了序列化,必然就有反序列化。如果我们有一串 GET 请求参数,利用 parse_qs()方法,就可以将它转回字典,示例如下:

from urllib.parse import parse_qsquery = 'name=germey&age=25'print(parse_qs(query))

可以看到,这样就成功转回为字典类型了。

8.parse_sql()

还有一个 parse_qsl 方法,它用于将参数转化为元组组成的列表,示例如下:

from urllib.parse import parse_qslquery = 'name=germey&age=25'print(parse_qsl(query))

可以看到,运行结果是一个列表,而列表中的每一个元素都是一个元组,元组的第一个内容是参数名,第二个内容是参数值。

9.quote()

该方法可以将内容转化为 URL 编码的格式。URL 中带有中文参数时,有时可能会导致乱码的问题,此时可以用这个方法可以将中文字符转化为 URL 编码,示例如下:

from urllib.parse import quotekeyword = '壁纸'url = '/s?wd=' + quote(keyword)print(url)

10.unquote()

它可以进行 URL 解码,示例如下:

from urllib.parse import unquoteurl = '/s?wd=%E5%A3%81%E7%BA%B8'print(unquote(url))

本节中,我们介绍了 parse 模块的一些常用 URL 处理方法。有了这些方法,我们可以方便地实现 URL 的解析和构造,建议熟练掌握。

分析Robots协议

利用 urllib 的 robotparser 模块,我们可以实现网站 Robots 协议的分析。本节中,我们来简单了解一下该模块的用法。

1.Robots协议

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

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

下面我们看一个 robots.txt 的样例:

User-agent: *<!-- 该协议对任何爬取爬虫有效 -->Disallow: /<!-- 不允许抓取所有页面。 -->Allow: /public/<!-- 可以抓取 public 目录 -->

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

2.爬虫名称

一些常见搜索爬虫的名称及其对应的网站。

3.robotparser

我们就可以使用 robotparser 模块来解析 robots.txt 了。该模块提供了一个类 RobotFileParser,它可以根据某网站的 robots.txt 文件来判断一个爬虫是否有权限来爬取这个网页。

该类用起来非常简单,只需要在构造方法里传入 robots.txt 的链接即可。也可以在声明时不传入,默认为空,最后再使用 set_url 方法设置一下即可。

urllib.robotparser.RobotFileParser(url='')

这个类常用的几个方法:

set_url():用来设置 robots.txt 文件的链接。如果在创建RobotFileParser对象时传入了链接,那么就不需要再使用这个方法设置了。read():读取 robots.txt 文件并进行分析。注意,这个方法执行一个读取和分析操作,如果不调用这个方法,接下来的判断都会为False,所以一定记得调用这个方法。这个方法不会返回任何内容,但是执行了读取操作。parse():用来解析 robots.txt 文件,传入的参数是 robots.txt 某些行的内容,它会按照 robots.txt 的语法规则来分析这些内容。can_fetch():该方法用两个参数,第一个是User-Agent,第二个是要抓取的 URL。返回的内容是该搜索引擎是否可以抓取这个 URL,返回结果是TrueFalsemtime():返回的是上次抓取和分析 robots.txt 的时间,这对于长时间分析和抓取的搜索爬虫是很有必要的,你可能需要定期检查来抓取最新的 robots.txt。modified():它同样对长时间分析和抓取的搜索爬虫很有帮助,将当前时间设置为上次抓取和分析 robots.txt 的时间。

接下来我们查看例子:

from urllib.robotparser import RobotFileParserrp = RobotFileParser()# rp = RobotFileParser('/robots.txt')rp.set_url('/robots.txt')rp.read()print(rp.can_fetch('Baiduspider', ''))print(rp.can_fetch('Baiduspider', '/homepage/'))print(rp.can_fetch('Googlebot', '/homepage/'))

使用requests

使用 urllib 处理网页验证和 Cookie 时,需要写 Opener 和 Handler 来处理。另外我们要实现 POST、PUT 等请求时写法也不方便。

为了更加方便地实现这些操作,就有了更为强大的库 requests,有了它,Cookie、登录验证、代理设置等操作都不是事儿。

基本用法

1.示例

urllib 库中的 urlopen 方法实际上是以 GET 方式请求网页,而 requests 中相应的方法就是 get 方法:

import requestsr=requests.get('')print(type(r))print(r.status_code)print(type(r.text))print(r.text)print(r.cookies)

除了get方法外,还有其他的请求方法:

import requestsr = requests.get('/get')r = requests.post('/post')r = requests.put('/put')r = requests.delete('/delete')r = requests.patch('/patch')

2.GET请求

GET请求如何携带参数?

一种方法是,我们可以直接在url中附加即可,如:

r=requests.get('/get?name=germey&age=20')

还可以使用requests的params这个参数,如:

import requestsdata = {'name': 'germey','age': 25}r = requests.get('/get', params=data)print(r.text)

网页的返回类型实际上是 str 类型,但是它很特殊,是 JSON 格式的。所以,如果想直接解析返回结果,得到一个 JSON 格式的数据的话,可以直接调用 json 方法。如:

import requestsr = requests.get('/get')print(type(r.text))print(r.json())print(type(r.json()))

可以发现,调用 json 方法,就可以将返回结果是 JSON 格式的字符串转化为字典。

但需要注意的是,如果返回结果不是 JSON 格式,便会出现解析错误,抛出 json.decoder.JSONDecodeError 异常。

抓取网页

import requestsimport rer = requests.get('https://ssr1.scrape.center/')pattern = pile('<h2.*?>(.*?)</h2>', re.S)titles = re.findall(pattern, r.text)print(titles)

抓取二进制数据

在上面的例子中,我们抓取的是网站的一个页面,实际上它返回的是一个 HTML 文档。

图片、音频、视频这些文件本质上都是由二进制码组成的,由于有特定的保存格式和对应的解析方式,我们才可以看到这些形形色色的多媒体。所以,想要抓取它们,就要拿到它们的二进制数据。

如果直接使用抓取页面的方法抓取二进制文件,会出现乱码的情况。

import requestsr=requests.get("/favicon.ico")with open('favicon.ico','wb')as f:f.write(r.content)

这里用了open()方法,它的第一个参数是文件名称,第二个参数代表以二进制写的形式打开,可以向文件里写入二进制数据。

运行结束之后,可以发现在文件夹中出现了名为favicon.ico 的图标。

同样,其他二进制文件也可以这样子来进行爬取。

添加headers

import requestsheaders = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/52.0.2743.116 Safari/537.36'}r = requests.get('/', headers=headers)print(r.text)

3.POST请求

实例如下:

import requestsdata={'name': 'germey','age': '22'}r=requests.post("/post",data=data)print(r.text)

4.响应

发送请求后,得到的自然就是响应。除了前面使用到的text和content之外,还有状态码、响应头、Cookies等。

import requestsr = requests.get('https://ssr1.scrape.center/')print(type(r.status_code), r.status_code)print(type(r.headers), r.headers)print(type(r.cookies), r.cookies)print(type(r.url), r.url)print(type(r.history), r.history)

这里分别打印输出 status_code 属性得到状态码,输出 headers 属性得到响应头,输出 cookies 属性得到 Cookie,输出 url 属性得到 URL,输出 history 属性得到请求历史。

高级用法

1.文件上传

import requestsfiles={'file': open('favicon.ico','rb')}r=requests.post("/post",files=files)print(r.text)

2.Cookies

import requestsr=requests.get("")print(r.cookies)for key,value in r.cookies.items():print(key+'='+value)

这里我们首先调用 cookies 属性即可成功得到 Cookie,可以发现它是 RequestCookieJar 类型。然后用 items 方法将其转化为元组组成的列表,遍历输出每一个 Cookie 条目的名称和值,实现 Cookie 的遍历解析。

我们还可以往headers里面添加Cookie,实现登录。

import requestsheaders = {'Cookie': '自己的Cookie值','User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36',}r = requests.get('/', headers=headers)print(r.text)

3.会话维持

在 requests 中,如果直接利用 get 或 post 等方法的确可以做到模拟网页的请求,但是这实际上是相当于不同的 Session,也就是说相当于你用了两个浏览器打开了不同的页面。

其实解决这个问题的主要方法就是维持同一个 Session,也就是相当于打开一个新的浏览器选项卡而不是新开一个浏览器。但是我又不想每次设置 Cookies,那该怎么办呢?这时候就有了新的利器 Session 对象。Session 在平常用得非常广泛,可以用于模拟在一个浏览器中打开同一站点的不同页面。

import requestss = requests.Session()s.get('/cookies/set/number/123456789')r = s.get('/cookies')print(r.text)

利用 Session,可以做到模拟同一个会话而不用担心 Cookie 的问题。它通常用于模拟登录成功之后再进行下一步的操作。

4.SSL证书验证

现在很多网站都要求使用 HTTPS 协议,但是有些网站可能并没有设置好 HTTPS 证书,或者网站的 HTTPS 证书可能并不被 CA 机构认可,这时候,这些网站可能就会出现 SSL 证书错误的提示。

如果我们一定要爬取这个网站怎么办呢?我们可以使用 verify 参数控制是否验证证书,如果将其设置为 False,在请求时就不会再验证证书是否有效。如果不加 verify 参数的话,默认值是 True,会自动验证。因此我们将verify参数设为False即可。

import requestsresponse = requests.get('https://ssr2.scrape.center/', verify=False)print(response.status_code)

但是报了一个警告,它建议我们给它指定证书。我们可以对警告进行忽略。

import requestsfrom requests.packages import urllib3urllib3.disable_warnings()response = requests.get('https://ssr2.scrape.center/', verify=False)print(response.status_code)

我们还可以导入证书进行访问,注意,私有证书的key必须是解密状态,加密状态的key是不支持的。

import requestsresponse=requests.get('',cert=('/path/server.crt','/path/key'))print(response.status_code)

5.代理设置

一旦开始大规模爬取,对于大规模且频繁的请求,网站可能会弹出验证码,或者跳转到登录认证页面,更甚者可能会直接封禁客户端的 IP,导致一定时间段内无法访问。为了防止这种情况,我们需要设置代理来解决这个问题,这就需要用到 proxies 参数。

import requestsproxies={"http": "http://10.10.1.10.:3128","https": "https://10.10.1.10:1080",}requests.get("",proxies=proxies)

除了基本的HTTP代理意外,还有SOCKS协议代理。

import requestsproxies = {'http': 'socks5://user:password@host:port','https': 'socks5://user:password@host:port'}requests.get('/get', proxies=proxies)

6.超时设置

为了防止服务器不能及时响应,应该设置一个超时时间,即超过了这个时间还没有得到响应,那就报错。这需要用到 timeout 参数。这个时间的计算是发出请求到服务器返回响应的时间。

import requestsr = requests.get('/get', timeout=1)print(r.status_code)

7.身份认证

这个网站就是启用了基本身份认证,在上一节中我们可以利用 urllib 来实现身份的校验,但实现起来相对繁琐。

我们可以使用 requests 自带的身份认证功能,通过 auth 参数即可设置。这个示例网站的用户名和密码都是 admin,在这里我们可以直接设置。

import requestsfrom requests.auth import HTTPBasicAuthr = requests.get('https://ssr3.scrape.center/', auth=HTTPBasicAuth('admin', 'admin'))print(r.status_code)

如果参数都传一个 HTTPBasicAuth 类,就显得有点烦琐了,所以 requests 提供了一个更简单的写法,可以直接传一个元组,它会默认使用 HTTPBasicAuth 这个类来认证。

import requestsr = requests.get('https://ssr3.scrape.center/', auth=('admin', 'admin'))print(r.status_code)

8.Prepared Request

实际上,requests 在发送请求的时候,是在内部构造了一个 Request 对象,并给这个对象赋予了各种参数,包括 url、headers、data 等等,然后直接把这个 Request 对象发送出去,请求成功后会再得到一个 Response 对象,再解析即可。Response 对象实际上就是 Prepared Request。

from requests import Request, Sessionurl = '/post'data = {'name': 'germey'}headers = {'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_11_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/53.0.2785.116 Safari/537.36'}s = Session()req = Request('POST', url, data=data, headers=headers)prepped = s.prepare_request(req)r = s.send(prepped)print(r.text)

我们引入了 Request 这个类,然后用 url、data 和 headers 参数构造了一个 Request 对象,这时需要再调用 Session 的 prepare_request 方法将其转换为一个 Prepared Request 对象,然后调用 send 方法发送。

正则表达式

基础

对于 URL 来说,可以用下面的正则表达式匹配:

[a-zA-z]+://[^\s]*

这个正则表达式去匹配一个字符串,如果这个字符串中包含类似 URL 的文本,就会被提取出来。

比如,a-z 代表匹配任意的小写字母,\s 表示匹配任意的空白字符,* 就代表匹配前面的字符任意多个,这一长串的正则表达式就是这么多匹配规则的组合。

常见的匹配规则

match()

match 方法会尝试从字符串的起始位置匹配正则表达式,如果匹配,就返回匹配成功的结果;如果不匹配,就返回None。示例如下:

import recontent = 'Hello 123 4567 World_This is a Regex Demo'print(content)print(len(content))result = re.match('^Hello\s\d\d\d\s\d{4}\s\w{10}', content)print(result)print(result.group())print(result.span())

开头的^是匹配字符串的开头,也就是以Hello开头;然后\s匹配空白字符,用来匹配目标字符串的空格;\d匹配数字,3 个\d匹配123;然后再写 1 个\s匹配空格;后面还有4567,我们其实可以依然用 4 个\d来匹配,但是这么写比较烦琐,所以后面可以跟{4}以代表匹配前面的规则 4 次,也就是匹配 4 个数字;后面再紧接 1 个空白字符,最后的\w{10}匹配 10 个字母及下划线。

match方法中,第一个参数传入了正则表达式,第二个参数传入了要匹配的字符串。

打印输出结果,可以看到结果是SRE_Match对象,这证明成功匹配。该对象有两个方法:

group方法可以输出匹配到的内容,结果是Hello 123 4567 World_This,这恰好是正则表达式规则所匹配的内容。span方法可以输出匹配的范围,结果是(0, 25),这就是匹配到的结果字符串在原字符串中的位置范围。

1.匹配目标

如果想从字符串中提取一部分内容,该怎么办呢?就像最前面的实例一样,从一段文本中提取出邮件或电话号码等内容。

可以使用括号()将想提取的子字符串括起来。()实际上标记了一个子表达式的开始和结束位置,被标记的每个子表达式会依次对应每一个分组,调用group方法传入分组的索引即可获取提取的结果。示例如下:

import recontent = 'Hello 1234567 World_This is a Regex Demo'result = re.match('^Hello\s(\d+)\sWorld', content)print(content)print(result)print(result.group())print(result.group(1))print(result.span())

这里我们想把字符串中的1234567提取出来,此时可以将数字部分的正则表达式用()括起来,然后调用了group(1)获取匹配结果。

我们成功得到了1234567。这里用的是group(1),它与group()有所不同,后者会输出完整的匹配结果,而前者会输出第一个被()包围的匹配结果。假如正则表达式后面还有()包括的内容,那么可以依次用group(2)group(3)等来获取。

2.通用匹配

前面我们写的正则表达式其实比较复杂,出现空白字符我们就写\s匹配,出现数字我们就用\d匹配,这样的工作量非常大。其实完全没必要这么做,因为还有一个万能匹配可以用,那就是.*。其中.可以匹配任意字符(除换行符),*代表匹配前面的字符无限次,所以它们组合在一起就可以匹配任意字符了。有了它,我们就不用挨个字符匹配了。

import recontent = 'Hello 123 4567 World_This is a Regex Demo'result = re.match('^Hello.*Demo$', content)print(content)print(result)print(result.group())print(result.span())

因此,我们可以使用.*简化正则表达式的书写。

3.贪婪与非贪婪

使用上面的通用匹配.*时,可能有时候匹配到的并不是我们想要的结果。看下面的例子:

import recontent = 'Hello 1234567 World_This is a Regex Demo'result = re.match('^He.*(\d+).*Demo$', content)print(content)print(result)print(result.group(1))

我们依然想获取中间的数字,所以中间依然写的是(\d+)。而数字两侧由于内容比较杂乱,所以想省略来写,都写成.*。最后,组成^He.*(\d+).*Demo$,看样子并没有什么问题。我们看下运行结果:

可以发现最终匹配到的结果是一个数字7。这里就涉及贪婪匹配与非贪婪匹配的问题。

在贪婪匹配下,.*会匹配尽可能多的字符。正则表达式中.*后面是\d+,也就是至少一个数字,并没有指定具体多少个数字,因此,.*就尽可能匹配多的字符,这里就把123456匹配了,给\d+留下一个可满足条件的数字7,最后得到的内容就只有数字7了。

这里只需要使用非贪婪匹配就好了。非贪婪匹配的写法是.*?,多了一个?,那么它可以达到怎样的效果?我们再用实例看一下:

import recontent = 'Hello 1234567 World_This is a Regex Demo'result = re.match('^He.*?(\d+).*Demo$', content)print(content)print(result)print(result.group(1))

此时就可以成功获取1234567了。贪婪匹配是尽可能匹配多的字符,非贪婪匹配就是尽可能匹配少的字符。当.*?匹配到Hello后面的空白字符时,再往后的字符就是数字了,而\d+恰好可以匹配,那么这里.*?就不再进行匹配,交给\d+去匹配后面的数字。所以这样.*?匹配了尽可能少的字符,\d+的结果就是1234567了。

因此,在做匹配的时候,字符串中间尽量使用非贪婪匹配,也就是用.*?来代替.*,以免出现匹配结果缺失的情况。

import recontent = '/comment/kEraCN'result1 = re.match('http.*?comment/(.*?)', content)result2 = re.match('http.*?comment/(.*)', content)print(content)print('result1', result1.group(1))print('result2', result2.group(1))

可以观察到,.*?没有匹配到任何结果,而.*则尽量匹配多的内容,成功得到了匹配结果。

4.修饰符

.*是用来匹配除换行符之外的任意符号,因此在遇到换行符的时候会报错,因此我们需要添加修饰符re.S进行修正,如:

result = re.match('^He.*?(\d+).*?Demo$', content, re.S)

这个re.S在网页匹配中经常用到。因为 HTML 节点经常会有换行,加上它,就可以匹配节点与节点之间的换行了。

修饰符及其描述:

在网页匹配中,较为常用的有re.Sre.I

5.转义匹配

我们知道正则表达式定义了许多匹配模式,如.匹配除换行符以外的任意字符,但是如果目标字符串里面就包含.,那该怎么办呢?

这里就需要用到转义匹配了,示例如下:

import recontent = '(百度) 'result = re.match('\(百度\) www\.baidu\.com', content)print(result)

当遇到用于正则匹配模式的特殊字符时,在前面加反斜线转义一下即可。例如可以用\.来匹配.,运行结果如下:

可以发现成功匹配到了原字符串。

search()

前面提到过,match方法是从字符串的开头开始匹配的,一旦开头不匹配,那么整个匹配就失败了。我们看下面的例子:

import recontent = 'Extra stings Hello 1234567 World_This is a Regex Demo Extra stings'result = re.match('Hello.*?(\d+).*?Demo', content)print(result)

这里的字符串以Extra开头,但是正则表达式以Hello开头,整个正则表达式是字符串的一部分,但是这样匹配是失败的。运行结果如下:

因为match方法在使用时需要考虑到开头的内容,这在做匹配时并不方便。它更适合用来检测某个字符串是否符合某个正则表达式的规则。

这里就有另外一个方法search,它在匹配时会扫描整个字符串,然后返回第一个成功匹配的结果。也就是说,正则表达式可以是字符串的一部分,在匹配时,search方法会依次扫描字符串,直到找到第一个符合规则的字符串,然后返回匹配内容,如果搜索完了还没有找到,就返回None

import recontent = 'Extra stings Hello 1234567 World_This is a Regex Demo Extra stings'result = re.search('Hello.*?(\d+).*?Demo', content)print(result)

下面再用几个实例来看看search方法的用法。

这里有一段待匹配的 HTML 文本,接下来写几个正则表达式实例来实现相应信息的提取:

<div id="songs-list"><h2 class="title">经典老歌</h2><p class="introduction">经典老歌列表</p><ul id="list" class="list-group"><li data-view="2">一路上有你</li><li data-view="7"><a href="/2.mp3" singer="任贤齐">沧海一声笑</a></li><li data-view="4" class="active"><a href="/3.mp3" singer="齐秦">往事随风</a></li><li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li><li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li><li data-view="5"><a href="/6.mp3" singer="邓丽君">但愿人长久</a></li></ul></div>

首先,我们尝试提取classactiveli节点内部的超链接包含的歌手名和歌名,此时需要提取第三个li节点下a节点的singer属性和文本。

此时正则表达式可以以li开头,然后寻找一个标志符active,中间的部分可以用.*?来匹配。接下来,要提取singer这个属性值,所以还需要写入singer="(.*?)",这里需要提取的部分用小括号括起来,以便用group方法提取出来,它的两侧边界是双引号。然后还需要匹配a节点的文本,其中它的左边界是>,右边界是</a>。然后目标内容依然用(.*?)来匹配,所以最后的正则表达式就变成了:

<li.*?active.*?singer="(.*?)">(.*?)</a>

然后再调用search方法,它会搜索整个 HTML 文本,找到符合正则表达式的第一个内容返回。另外,由于代码有换行,所以这里第三个参数需要传入re.S。整个匹配代码如下:

import rehtml = '''<div id="songs-list"><h2 class="title">经典老歌</h2><p class="introduction">经典老歌列表</p><ul id="list" class="list-group"><li data-view="2">一路上有你</li><li data-view="7"><a href="/2.mp3" singer="任贤齐">沧海一声笑</a></li><li data-view="4" class="active"><a href="/3.mp3" singer="齐秦">往事随风</a></li><li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li><li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li><li data-view="5"><a href="/6.mp3" singer="邓丽君">但愿人长久</a></li></ul></div>'''result = re.search('<li.*?active.*?singer="(.*?)">(.*?)</a>', html, re.S)if result:print(result.group(1), result.group(2))

由于需要获取的歌手和歌名都已经用小括号包围,所以可以用group方法获取。

如果正则表达式不加active(也就是匹配不带classactive的节点内容),那会怎样呢?我们将正则表达式中的active去掉,代码改写如下:

import rehtml = '''<div id="songs-list"><h2 class="title">经典老歌</h2><p class="introduction">经典老歌列表</p><ul id="list" class="list-group"><li data-view="2">一路上有你</li><li data-view="7"><a href="/2.mp3" singer="任贤齐">沧海一声笑</a></li><li data-view="4" class="active"><a href="/3.mp3" singer="齐秦">往事随风</a></li><li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li><li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li><li data-view="5"><a href="/6.mp3" singer="邓丽君">但愿人长久</a></li></ul></div>'''result = re.search('<li.*?singer="(.*?)">(.*?)</a>', html, re.S)if result:print(result.group(1), result.group(2))

由于search方法会返回第一个符合条件的匹配目标,这里结果就变了:

active标签去掉后,从字符串开头开始搜索,此时符合条件的节点就变成了第二个li节点,后面的就不再匹配,所以运行结果就变成第二个li节点中的内容了。

由于绝大部分的 HTML 文本都包含了换行符,所以尽量都需要加上re.S修饰符,以免出现匹配不到的问题。

findall()

前面我们介绍了search方法的用法,它可以返回匹配正则表达式的第一个内容,但是如果想要获取匹配正则表达式的所有内容,这时就要借助findall方法了。该方法会搜索整个字符串,然后返回匹配正则表达式的所有内容。

import rehtml = '''<div id="songs-list"><h2 class="title">经典老歌</h2><p class="introduction">经典老歌列表</p><ul id="list" class="list-group"><li data-view="2">一路上有你</li><li data-view="7"><a href="/2.mp3" singer="任贤齐">沧海一声笑</a></li><li data-view="4" class="active"><a href="/3.mp3" singer="齐秦">往事随风</a></li><li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li><li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li><li data-view="5"><a href="/6.mp3" singer="邓丽君">但愿人长久</a></li></ul></div>'''results = re.findall('<li.*?href="(.*?)".*?singer="(.*?)">(.*?)</a>', html, re.S)print(results)print(type(results))for result in results:print(result)print(result[0], result[1], result[2])

可以看到,返回的列表中的每个元素都是元组类型,我们用对应的索引依次取出即可。如果只是获取第一个内容,可以用search方法。当需要提取多个内容时,可以用findall方法。

sub()

想要把一串文本中的所有数字都去掉,如果只用字符串的replace方法,那就太烦琐了,这时可以借助sub方法。示例如下:

import recontent = '54aK54yr5oiR54ix5L2g'content = re.sub('\d+', '', content)print(content)

这里只需要给第一个参数传入\d+来匹配所有的数字,第二个参数为替换成的字符串(如果去掉该参数的话,可以赋值为空),第三个参数是原字符串。

在上面的 HTML 文本中,如果想获取所有li节点的歌名,直接用正则表达式来提取可能比较烦琐。比如,可以写成这样子:

import rehtml = '''<div id="songs-list"><h2 class="title">经典老歌</h2><p class="introduction">经典老歌列表</p><ul id="list" class="list-group"><li data-view="2">一路上有你</li><li data-view="7"><a href="/2.mp3" singer="任贤齐">沧海一声笑</a></li><li data-view="4" class="active"><a href="/3.mp3" singer="齐秦">往事随风</a></li><li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li><li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li><li data-view="5"><a href="/6.mp3" singer="邓丽君">但愿人长久</a></li></ul></div>'''results = re.findall('<li.*?>\s*?(<a.*?>)?(\w+)(</a>)?\s*?</li>', html, re.S)for result in results:print(result[1])

此时借助sub方法就比较简单了。可以先用sub方法将a节点去掉,只留下文本,然后再利用findall提取就好了:

import rehtml = '''<div id="songs-list"><h2 class="title">经典老歌</h2><p class="introduction">经典老歌列表</p><ul id="list" class="list-group"><li data-view="2">一路上有你</li><li data-view="7"><a href="/2.mp3" singer="任贤齐">沧海一声笑</a></li><li data-view="4" class="active"><a href="/3.mp3" singer="齐秦">往事随风</a></li><li data-view="6"><a href="/4.mp3" singer="beyond">光辉岁月</a></li><li data-view="5"><a href="/5.mp3" singer="陈慧琳">记事本</a></li><li data-view="5"><a href="/6.mp3" singer="邓丽君">但愿人长久</a></li></ul></div>'''html = re.sub('<a.*?>|</a>', '', html)print(html)results = re.findall('<li.*?>(.*?)</li>', html, re.S)for result in results:print(result.strip())

可以看到,a节点经过sub方法处理后就没有了,然后再通过findall方法直接提取即可。可以看到,在适当的时候,借助sub方法可以起到事半功倍的效果。

compile()

这个方法可以将正则字符串编译成正则表达式对象,以便在后面的匹配中复用。示例代码如下:

import recontent1 = '-12-15 12:00'content2 = '-12-17 12:55'content3 = '-12-22 13:21'pattern = pile('\d{2}:\d{2}')result1 = re.sub(pattern, '', content1)result2 = re.sub(pattern, '', content2)result3 = re.sub(pattern, '', content3)print(result1, result2, result3)

另外,compile还可以传入修饰符,例如re.S等修饰符,这样在searchfindall等方法中就不需要额外传了。所以,compile方法可以说是给正则表达式做了一层封装,以便我们更好地复用。

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。