在昨天的demo中的代码还有一个问题就是,假如某个地方出错了怎么办,可能是网络掉线了,可能是某一次请求被拦截了,那么会造成程序直接停掉了。数据量小的爬虫还可以找到错误,重新启动,如果是数据量大的,重跑会造成时间、空间等资源的浪费。所以我们还需要对这个爬虫进行一些改造,增加一些异常处理,使其更加强大。
其中有几处代码中添加了time.sleep(2)方法,这句话作用是当前线程暂停两秒,目的是为了降低当前IP的请求频率,从而绕过反爬。其实很多网站为了防止流量攻击,如果检测到某个IP短时间内发送了大量请求,那么此IP后续一段时间内的请求将不能得到正确的响应。
在这个例子中,仅仅是抓取250条信息,时间还好。如果需要抓取25000条,那么这样的方式在时间上要100倍,肯定效率太慢了,那还有什么方法呢?
接下来我们就了解一下爬虫的异常处理以及常见的反扒措施。
异常处理
规划异常处理也是爬虫中比较重要的一环,好的异常处理会给一个好的数据结果打好基础。这就关系到异常处理的刻度的问题,可以分为以下几种常见场景的处理方式。
1.只在main方法中添加异常处理
优点:方法简单,添加容易
缺点:颗粒度太粗,只能保证程序运行,数据质量可能会不高
2.在requests请求上添加异常处理
优点:可以捕获请求的异常
缺点:颗粒度适中,无法排除因为原始数据质量差而造成的异常
3.在requests请求和数据操作上都添加异常处理
优点:颗粒度细,最大限度保证数据质量
缺点:需要添加的异常处理内容太多
最后在添加异常处理的位置上,还是需要看具体的需求来定,对每一条数据元素的重要性进行排序,可以在重要数据中添加异常处理,如果发生异常选择跳过或者使用默认值,以此可以最大限度保证数据的完整性。
代理IP
代理IP其实是就是代理服务器,它的作用就是相当于跑腿的,帮你去取得网络信息。它是网络信息的中转站。
一个形象的比喻,某一家店卖的东西特别好吃,但是有一个要求是一个用户每年只能买三个,这时候你吃了三个还想吃,怎么办,那就让外卖小哥来帮你去买,这个时候店家一看,来的不是同一个人,那就卖给外卖小哥,外卖小哥最后送给了谁,店家并不知道。这时候外卖小哥的作用就是一个代理服务器的作用,只是作为来一个请求的转发。
对于爬虫程序来说,因为是机器处理,所以访问速度会非常快,很多应用服务器都会对IP进行限制,如果一定时间内超过了允许的阈值,那么将不会对此IP发送来的请求进行响应。代理IP就通过转发请求来进行访问,这样应用服务器并不能知道真实的访问IP是多少,不会触发反爬机制。
现在很多的市面上很多的代理服务商,都会有自己的代理服务器,帮助用户来转发请求,当然这些代理服务器也是付费使用的,而且使用代理IP上也有一定的规则和存活时间。
代理IP类型中也分为透明、匿名、高匿三种,透明的代理在对方服务器中可以获取到真实的请求IP,好比是商家虽然知道来取货的是外卖小哥,但是购买方并不是外卖小哥一样,高匿的代理IP的会隐藏自己的真实IP,服务器很难找到真实的请求IP。
目前市面上很多的代理服务商也推出了一些隧道代理IP的服务,即是代理服务商自己对请求进行了封装和转发,并且对用户提供一个Api接口,用户只需要将自己的请求的地址、参数、请求方式以及代理商的认证等信息发到Api接口中,直接获取返回的内容即可。对于用户来说,并不需要知道当前的请求是发送到哪个IP的,只需要关心请求是否可以返回成成功即可。
因为每一家代理服务商都有自定义的代理IP使用方式,并且在购买了相关的服务后,会提供相关的使用说明,这里就不一一解释说明。如果碰到需要使用代理IP的场景,可以自行搜索一些代理服务商购买一些服务。
正则表达式
正则表达式(regular expression)是一种字符串匹配的模式,有自身特定的语法,很多时候在爬虫中是使用正则来查找符合要求的字符串。下面是爬虫中常用的正则字符和其含义。
类型 | 字符 | 含义 |
---|---|---|
元字符 | ^ | 匹配行或者字符串的开头 |
$ | 匹配行或字符串的结尾 | |
d | 匹配数字 | |
w | 匹配字母,数字,下划线 | |
. | 匹配除了换行符以外的任何字符 | |
s | 匹配空格 | |
[abc] | 匹配包含括号内元素的字符 | |
量词 | * | 重复零次或多次 |
| 重复一次或多次 | |
? | 重复零次或一次 | |
{n} | 重复n次 | |
{n,m} | 重复n-m次 |
写好正则并不是一个容易的事情,因为匹配同一个字符串可以有多种写法,但是一条正则表达式,可能匹配了多个结果,可能会掺杂一些我们并不想要的结果。在刚开始接触的正则的时候需要慢慢的摸索,拿出其中一个页面来做测试,直到最后数据效果满意后再应用于其他页面。
在Python中,re 模块包含了全部正则语法的功能,下面介绍爬虫中常用的re模块的方法。
re.match()
函数参数:re.match(pattern, string, flags=0),pattern是正则表达式,string为字符串,flags是模式标志位,比如设置是否大小写敏感等等。
此方法用来从开头位置查找是否满足匹配条件的字段串,如果没有配置成功返回None,如果有,则会返回一个包含对应结果值的对象,可以用group()方法进行查看。
代码语言:javascript复制import re
#待匹配的字符串---以数字开头
text = "123abc"
#匹配是否数字开头
res = re.match("d", text)
print(res)
print(res.group())
代码结果:
代码语言:javascript复制<re.Match object; span=(0, 1), match='1'>
1
re.search()
参数跟match()一样,不同的是search()会搜索整个字符串,直到找到第一个匹配的字符串。而match()仅仅只是最开头做匹配,返回值也是一个对象,可以用group(num)或groups()方法进行查看。
代码语言:javascript复制import re
#待匹配的字符串---以字母开头
text = """abc123"""
res = re.search("d", text)
print(res)
print(res.group())
代码结果:
代码语言:javascript复制<re.Match object; span=(3, 4), match='1'>
1
re.findall()
参数跟上面两个方法一样,findall()查找所有正则表达式的所有结果,并且直接返回的结果列表,对象是list。
代码语言:javascript复制import re
#待匹配的字符串---以字母开头
text = """abc123"""
res = re.findall("d", text)
print(res)
代码结果:
代码语言:javascript复制['1', '2', '3']
re. compile ()
函数参数:compile(pattern,flag=0),compile()函数的作用是编译正则表达式,返回一个正则表达式对象。
代码语言:javascript复制import re
# 编译Pattern对象
res = re.compile("d")
print(type(res))
代码结果:
代码语言:javascript复制<class 're.Pattern'>
Pattern对象中也有上述三种常用的方法,其实上述三个方法已经在方法内执行了compile()的过程,以search()为例子,源码中是这样写的:
代码语言:javascript复制def search(pattern, string, flags=0):
return _compile(pattern, flags).search(string)
本节内容中,我们主要是数据来源,重点了解网络数据的爬取,通过Python中丰富的库可以快速的帮助搭建起爬虫,来获取网上公开的数据。当然在爬虫方面还有很多内容,比如说验证码识别、登录状态的维护等等,但是最终还是以HTML文本或者JSON字符串的形式获取到数据,用于后续的内容。
我们也能发现,不管是BeautifulSoup还是lxml都是只能针对于特定的页面有作用,如果数据源改变了,那么后续的内容都无法生效。所以在后面的数据获取时候还是需要多多探索,并不没有一招吃遍天的招式。