VB.net 2010 视频教程 VB.net 2010 视频教程 python基础视频教程
SQL Server 2008 视频教程 c#入门经典教程 Visual Basic从门到精通视频教程
当前位置:
首页 > Python基础教程 >
  • python高级—— 从趟过的坑中聊聊爬虫、反爬、反反爬,附送一套高级爬虫试题

前言:

时隔数月,我终于又更新博客了,然而,在这期间的粉丝数也就跟着我停更博客而涨停了,唉

 

是的,我改了博客名,不知道为什么要改,就感觉现在这个名字看起来要洋气一点。

 

那么最近到底咋不更新博客了呢?说起原因那就多了,最主要的还是没时间了,是真的没时间,前面的那些系列博客都还没填坑完毕的(后续都会填上的)

 

最近有点空余就一直在开发我的项目,最近做了两个项目:

 

IPproxy,看名字就知道啦,就是一个ip代理池,爬取了各大免费的代理网站,然后检测可用性,github地址   相关的介绍github上已经说明了

 

get_jobs,爬取了几十个招聘类网站的数据,github地址   同样的,相关的介绍github上已经说明了

 

根据以上爬取的大概也许可能接近上百个网站吧,加上我初学爬虫的时候爬的网站,现在也算是爬了有接近上千个网站了,对爬虫也算是小有心得了,下面就开始说说吧

 

以下是总结式的解析,个人觉并不太适合零基础的朋友,也不会有过多的图文展示,当然我也会尽量的把问题说清楚点,而且我也不是爬虫大佬,只是根据最近的爬虫经历总结出的经验,我确实不太建议零基础的朋友往下看,你可以先看看我之前的博客文章或者看其他大神的文章之后再来看我这篇,因为爬虫涉及了前端和后端还有前后端之间的交互等的技术,一些底层的原理之类的,不是说不给零基础的朋友看,是如果没这些知识做基础可能看不懂(不是瞧不起小白的意思,我也是小白过来的)。当然爬虫界的大佬们如果偶然点开,那还请多多包涵,我目前技术确实还有待提升

 

爬虫前提:

1.法律问题

最近时不时总是冒出一两个因为爬虫入狱的新闻

 

 

 

 

 

 

 

 

 

 

 

 

 

不一一截图了,自己网上搜吧,其实现在越来越多了

 

有朋友要说,“为什么我学个爬虫都被抓吗?我犯法了吗?”  这个目前还真的不好说,主要是什么,目前爬虫相关的就只有一个robots协议,而我们都知道robots协议是针对于通用爬虫而言的,而聚焦爬虫(就是我们平常写的爬虫程序)则没有一个严格法律说禁止什么的,但也没有说允许,所以目前的爬虫就处在了一个灰色地带,而很多情况下是真的不好判定你到底是违法还是不违法的。好消息是,据说有关部门正在起草爬虫法,不久便会颁布,后续就可以按照这个标准来进行了。

 

2.爬虫分类:

 

上面说了,有通用爬虫和聚焦爬虫

 

通用爬虫:

 

针对于百度,搜狗,谷歌这类搜索引擎类的爬虫程序

 

聚焦爬虫:

 

又名定向爬虫,就是我们平时写的针对某个需求或者某个问题而写的程序

 

3.爬虫能爬什么数据:

 

一句话,所见即所得,什么意思,你用浏览器能访问,能查看的网页数据,用爬虫就可以爬到,反过来,你浏览器都无法访问的,爬虫是无法爬到的,除非你用一些违法的手段获取到

举个例子:

  • 你用浏览器打开百度,打开淘宝,可以看到的一切数据,爬虫都可以获取到
  • 你有优酷的vip账号,你可以用浏览器看vip视频,那么爬虫也可以获取到
  • 你没有优酷的vip账号,你无法查看vip视频,爬虫也就无法获取到

 

我知道有一部分圈子里的朋友之间流传着有什么破解vip之类的,那种其实也是符合所见所得的,你没有vip,别人有啊,假如一个已有vip账号的人把视频下载下来上传到云服务器A,然后你访问某个网站,网站后台请求的是云服务器A,最后呈现视频数据给你,不说了,扯远了

 

爬虫工具:

 

一.工具分类:

分类看以什么分了,有自动的,有手动的,有在线的,有离线的,有傻瓜式的,有非傻瓜式的,有分析辅助为主的,有自开发式的,看每个人怎么定义了

 

傻瓜式的:

比如什么八爪鱼,爬山虎,啥啥的一大堆,在线的傻瓜式的工具,根据提示点点鼠标,输入几个字就完了,但是这种工具普遍得到的结果不是很理想

 

分析类的:

以下的都是作为辅助分析整个请求过程的工具

  • fiddler :常用的抓包分析工具
  • charles:和fiddler类似,优点是可以自定义上下行网速、代理、反向代理,且配置简单、可解析AMF协议数据
  • wireshark:网络封包分析软件,功能非常强大,但是在爬虫层面来说,辅助的意义不算大
  • burpsuite:类似fiddler,但是功能更多更具体,且这个包在网络安全行业来说是个必不可少的大杀器
  • .......

 

而需要点技术的就是自开发式的,比如自己借助某个开发语言写爬虫程序(或者说脚本)就是了

 

二.自开发式的哪些开发语言可以作为爬虫

其实还挺多的

  • php
  • javascript —— node.js
  • java
  • python
  • golang

 

php写爬虫是可以的,但是多线程多进程不太支持,所以针对大型一点的项目不够理想

nodejs的curl工具也是可以写爬虫的,有什么优缺点我暂时不知道,我没用过

java的话,听大佬说的,写出来的爬虫程序很臃肿,不是说不好哈,我暂时只是有耳闻,没用过

golang天生支持高并发,性能提升很多,也可以做爬虫,这个我暂时只是有耳闻,没用过

python的话写爬虫真的是一种神器般的存在,目前网上的爬虫程序,可能百分之八九十都是用Python写的

其他语言只要支持网络服务应该都可以写爬虫,这里就不多说了

 

三.python的哪些相关的工具可以写爬虫

 

自带的库:

python2下:

  • urllib
  • urllib2
  • urllib3
  • httplib
  • htttplib2
  • re
  • ......

python3下:

  • urllib

第三方的库:

  • requests
  • crawley
  • protia
  • newspaper
  • pyspider
  • python-goose
  • scrapy
  • scrapy-redis

其他相关的库:

  • lxml
  • json
  • beautifulsoup
  • re
  • pyquery
  • execjs
  • js2py

目前就想到这么多

 

平常的话,做网络交互我就用requests库,但是有些小问题,用多了的人应该都知道,requests库里的异常类不够全,有时候报错了无法捕获

做数据解析的话我用json,re , lxml, beautifulsoup,pyquery,execjs,js2py都有在用,只要有场景需要就会用

 

大型项目就上scrapy,需要分布式就上scrapy-redis

 

四.爬虫中常见的问题,常见的反爬机制

 

其实我之前转载过一篇爬虫相关的文章 打造一个健壮高效的网络爬虫  ,转载的爬虫大佬崔庆才的文章,他的文章总结性就非常强了,各种层面都涉及了,我还要自己再写一篇的原因就是,下面的问题真的是我亲身经历的问题,以下的有些问题很可能对有些老哥还说不算问题,各有各的开发思路,按照我那样的思路也许就会遇到这些问题,一来是做个记录,二来是帮助那些遇到跟我同样问题的朋友。

 

 

重头戏终于来了,哈哈,我也有点按奈不住了

 

1.请求头之User-agent

这个稍微接触过一点点爬虫的应该都不陌生,不是说接触Python爬虫,不管你用什么开发语言来写爬虫,应该都会用到这个。大概解释一下,就是一个身份的象征,这个可以用浏览器自带的调试工具查看,访问一个网站的时候,按f12键或者鼠标右键打开调试(有的浏览器叫检查,或者查看元素),然后切换到network(网络),重新刷新一次网站,就会出现所有的请求,随机点击一个,右边出现的就是请求头信息了,如下,这是我访问某某网站的,我使用的浏览器是火狐,然后图上标注的就是user-agent

 

 

具体怎么用呢?

比如:

 

 

这样就可以带上UA了

如果不带的话,你的目标网站服务端是可以检测到是浏览器还是爬虫工具在访问数据的,就看你的目标网站的友好度了,如果反爬机制做的很高效,到这里你就被ban了

 

 

2.调试工具之痛

 

很多时候我们为了查看网页的DOM结构可能就直接用浏览器自带的调试工具(就是上面说的按f12键)来查看,这个的话,大部分网页是可以应对的,但是,少部分网站用调试工具查看的DOM结构和整个网页的源码是不一致的,说个最近的事,我爬某视频网站,调试工具打开他在每个重要信息都加了一个css样式,这个css样式是通过定位某个html标签(假设为<span></span>标签)设置上的,我解析网页的时候就很痛苦,调了很久,就是得不到结果,最后发现这个span标签是用js拼接上的,换句话说,服务端回应的是不带有这个span标签的,所以就要没有这个span标签来处理。说这么多不知道看官您能不能理解,遇到过这个问题的朋友应该明白我在说什么

 

 

 

 

 

3.异步请求

上面说的DOM结构不一致还有一种可能,就是前后端用的Ajax异步请求的,所以你打开浏览器的调试工具查看DOM结构和你用Python获取的结果也是不一致的,这个相信会玩爬虫的老哥们都不陌生

 

4.请求头之Cookies

有一部分网站,当你访问首页时,会自动设置一个cookie,然后访问同站下的其他页面就会验证这个字段,如果不匹配就会禁止访问

如下,访问百度都会自动设置一些cookie:

 

 

 

 

 

5.请求头之特殊字段

 

特殊字段是什么呢,就是某网站特有的一些字段,比如以下的boss直聘网:

 

 

 

会带有这些特殊的字段。当然这里只是举个例子,,经过我的测试,我圈出来的那几个[:method]等的字段其实请求的时候是不用带上的

 

6.请求头之Referer

这个referer是干嘛的呢?首先该字段的值都是上一级网站的url,根据我的理解,它有以下作用:

  1.做前端的朋友知道,可以借用这个字段直接返回到上个页面

  2.还可以通过这个追踪流量来源,比如某某公司在百度上做了SEO(打了个推广广告),当用户通过百度点进来的话,就可以通过referer追踪来源,对用户做进一步的行为分析

  3.检测来源的合法性,因为都可以知道通过某某url路径过来的,那么就可以判断来源是否合法,如果异常的话就可以做拦截请求等等的

 

 

 

有的网站就是因为有这个验证,所以返回的数据不正常,带上就OK了。还有的网站更奇怪,你不带上也不会报错,返回的数据也是希望的数据,但是无法和页码匹配,比如你请求的是第一页的数据,它有可能返回的是第5页的数据。遇到过这个问题的老哥应该知道我在说什么

 

 

 

7.请求头之accept:

不知道老哥您遇到过这个问题没有,在请求头里,如果服务端返回的结果是普通的html页面的话,值就应该是如下的:

'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3',

如果返回的是json字符串(返回json字符串的话,往往是属于异步请求)的话,值就应该是这个:

'Accept': 'application/json, text/javascript, */*; q=0.01',

 

这个不知道你们有没有体会,反正我记得我爬某网站的时候,因为都通用的同一个请求头,有的网站就是返回json数据,我怎么改代码都无法得到正确的值,就是因为本来是json字符串的我的accept用的上面的html页面用的,导致返回数据不符合事实。


8.请求头之Connection

 

 

 

 

这个字段字面意思就是http连接嘛,http链接最根本的就是tcp/ip连接了,什么三次握手,四次握手之类的,这些就不展开了,要说就占篇幅了。我们都知道,http请求属于短连接,访问就有,关闭浏览器就会自动断开的,这种就是短连接,对应的长连接就是websocket,这个就不展开了,自行百度了。这个Connection字段有两个值,一个是keep-alive,一个是close,keep-alive的话往往就跟前面的带有cookie相关,他会保存session会话(如果关闭浏览器的话就没了,有的网站是保存一个字段,默认有几天的有效期),作为同一个连接来请求另一个页面,如果是close的话,就是每次访问都是重新和服务端建立一个连接,不会保存session

 

这个问题的话,在一般情况下还是不会遇到,主要就是在高并发请求的时候,有可能同一个时刻请求多次来自同一个站点的数据,触发该网站的反爬机制的频率限制,就会出现什么scoket.timeout,urllib3.connection.HTTPConnection之类的错误。所以从那次之后我的爬虫程序如果用了高并发的话,我都会把这个connection设置为close

 

9.返回数据gzip

 

 

 

gzip的意思就是这个网站的数据是做呀gzip压缩的,在浏览器(客户端)访问之后,会自动处理这类格式的数据。但是使用Python的标准款urllib的话,是不会处理这类格式的, 解决方法就是用requests库,会自动处理gzip格式,反正这个库真的太强大了

 

10.reqeusts库的弊端

 

嘿嘿,刚说完requests很强大,马上说它的弊端,是的,就是这么骚气。它有什么弊端呢,如果经常使用requests库作为主要的网络请求的话,使用时间越长的老哥,就越会发现它有个弊端,就是它封装的异常类不够全,在报错各种各样的异常的时候,它有时候捕获不到,无法进行针对性的处理,这就是它的弊端,这个怎么解决呢,目前我的话,没有一个很好的解决方法,只能用Exception或者BaseException类来捕获。

当然这个问题也不是一定会遇到,有时候只是某个其他的异常爆出,导致后续的代码跟着出错,然后跟着抛出异常,所以也不能盲目的用异常基类来捕获

 

11.登录验证

有的网站为了解决网络请求的拥塞,就做了账号登录验证,你必须登录账号之后才能正常访问页面,一般的网站登录之后返回的就是上面说的cookie字段,有的也会返回一个特殊的字段作为验证,我记得百度登录之后用的字段是BAIDUID。这个不需要多说什么,找到登录接口,提交账号密码参数访问登录接口就行了

 

12.请求头之token

 

 

这个token在语义上就是个令牌的意思,用的比较多的地方就是在手机端上用token验证,此手机端的验证对应的电脑端用的session验证,而电脑端用token的情况就是,特殊字段的变异,有的网站就放在请求头的authorized键里,值就是token,用uuid生成的,有的就是自定义的键,比如boss直聘的就是__zp_stoken__,必须要带上这个字段才能爬取(因为boss直聘的反爬机制升级了,以前直接可以爬,现在必须带上这个字段才行,说句题外话,目前我还没找到boss直聘这个字段是怎么生成的,用fiddler抓包发现请求了阿里云的服务端,但是返回的数据是空,反正没找到它是怎么生产的)

 

 

 

 

 

 

13.登录验证+token

有的网站在用户登录之后感觉还不够安全,就会再对token验证,如果登录验证和token都通过了说明是正常登录操作,才放行。当然也有的网站不需要登录也会分配一个token,比如上面的boss直聘,这个就不多说了,就是以上的结合

 

14.设备验证(手机,电脑)

 

一般情况下,电脑能访问的页面,手机一定能访问,只是如果该站没有作响应式的话,用手机访问的页面排版很乱,因为手机和电脑的尺寸不一样嘛。

 

但是手机能访问的,电脑不一定能访问,比如某app,或者微信小程序,它会做设备验证,发现不是手机页面的话就会拦截或者提示让你用手机下载该app查看。有朋友要问了,是用的什么来检测你是手机还是电脑的呢?就是前面最开始说的User-agent,说到这,怎么解决这个问题不用多说了吧,同样的伪造成手机的UA就行了,当然如果是针对app的爬取的话,一般就在电脑上下载模拟器(夜神模拟器啥啥的),然后fiddler抓包分析,然后再爬取

 

15.重定向

这个跟http的状态码有关了,不多说,返回的状态码是3**的就是重定向范畴里的,比如下面的百度:

 

 

 

 

 

这种网站怎么爬取呢,requests会自动处理重定向的问题,没错,requests就是这么抗打

 

16.触发跳转到新的标签页

什么意思呢,就是比如我在某一个网站点一下按钮,触发机制,用新的网页标签打开的,前端的朋友应该知道,就是a标签属性里加了个target='_blank',这种的话,如果用浏览器的调试工具来分析的话就无法即时的查看请求了。因为习惯上我喜欢用浏览器自带的调试工具分析,比较方便快捷,实在不行的话就上fiddler啥的抓包工具。没错,解决方法我已经说了,像这种点击一个会以新的浏览器窗口打开的就用抓包工具来分析

 

17.调试工具触发js的debug监听器

这个我不知道你们遇到过没,我有一次抓某个网站,它就是为了防止我这类瞎jb搞的人分析它的代码,分析它的请求过程,所以当我一打开调试工具,就触发它的监听器,然后我就发现我电脑的CPU立马跑满了,一直在消耗我的电脑资源直到电脑卡死,所以我只能感觉关闭调试工具或者关闭浏览器,实在不行只能重启了,它就是监听的debuger,我不知道这个是一个函数还是一个类,以下是我保存的代码,看有缘人能不能看懂,看懂的话我就以身相....,不对,走错片场了,卧槽。。。

 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
var check = (function () {
    var callbacks = [], timeLimit = 50, open = false;
    setInterval(loop, 1);
    return {
        addListener: function (fn) {
            callbacks.push(fn);
        },
        cancleListenr: function (fn) {
            callbacks = callbacks.filter(function (v) {
                return v !== fn;
            });
        }
    }
    function loop() {
        var startTime = new Date();
        debugger;
        if (new Date() - startTime > timeLimit) {
            if (!open) {
                callbacks.forEach(function (fn) {
                    fn.call(null);
                });
            }
            open = true;
            window.stop();
        else {
            open = false;
        }
    }
})();