爬虫这件小事》 注:本文为“小米安全中心”原创 ,转载请联系“小米安全中心

0x00 前言

这一天终于还是来了。两周前答应部门妹子3月15号之前写篇文章,两周后我只字未动诚惶诚恐,为了避免被当做假同事打假,有了这篇挫文。

至于为什么要写这方面内容,纯粹我临时拍脑袋想的,专业做爬虫的就不用看了,我这属于十分钟了解爬虫系列,而且通篇不会涉及任何代码,毕竟我不是@netxfly,写一篇文章做一个项目,I have my style,而且要『show me the code』100块的稿费可是不够的 o( ̄ヘ ̄o#)。

pachong

0x01 组成

写个爬虫无非几个模块:爬、解、存,爬是爬取页面,解是解析页面,存当然是存储结果。有一些成熟的框架能帮你干这些事,比如scrapy,比如pysider,如果需求比较严肃,可以去了解下,套餐有时候还是会比散工省事。接下来我逐一介绍三个方面,其中少不了我大Python的一骑绝尘。

简单来说就是request/response,当然我肯定是要往复杂了说的。首先是请求,这个大家都会,不过在这个追求速度的年代,多线程都不好意思出来打招呼啊,典型的IO密集操作,直接喀喀喀往上怼异步协程吧,requests/gevent、aiohttp不用我再说了吧,当然这是单机追求速度的方式。对于分布式爬虫,你一发一发来也没问题,celery、huey、mrq这些分布式任务队列选一个就是了(rq就算了),不过同时结合二者不要更好。

请求、并发请求、分布式并发请求这些都so easy,接下来是筛选,爬取之前要明白目标是什么,准备爬多深,怎么爬,爬叉了可能爬到隔壁老王家,爬深了爬虫可能找不到来时的路。

所以你要按自己的需求做好限定,比如只爬同域名的或者只爬图片,深度只到3层还是5层(把各个页面中的URL组成为URL树,起始URL当做根节点,当前URL页面中解析出的URL当做第1层,以此类推,树的第3/5层当做叶子节点),爬取方式由你定,深度遍历广度遍历都成。除此之外,这么多URL,难道见URL就爬吗?当然不是,要去重。(《爬虫这件小事》 注:本文为“小米安全中心”原创 ,转载请联系“小米安全中心”)

谈到去重,我这里只说两个维度:URL本身的去重以及html页面的去重。

1.对于URL本身的去重

请原谅我见识浅薄,我没想到什么特别好的方法,直接采用文本相似的算法(具体可以自己查找下,不列举)感觉不太靠谱,首先是因为URL本身的长度比较短,所以每个字符对整体的影响会相对较大,更重要的是,URL是有固定模式的,单纯的文本相似会把模式结构的影响隐藏掉,比如http://www.mi.com/bbq/test/20170315.html 与 http://www.mi.com/test/bbq/20170315.html 其实是不同的URL,而 http://www.mi.com/bbq/test/20170315.html 与 http://www.mi.com/bbq/test/20160214.html 则可以看做是相似的。

基于此想法,我倾向于把URL基于域名归一化后做处理。如何归一化呢?

我之前做过一个简单的尝试,例如:我们可以把 http://www.mi.com/bbq/test/20170315.html 归一化为 www.mi.com/bbq/test/19700101.html ,把http://www.mi.com/bbq/test/100/c.html 和 http://www.mi.com/bbq/test/999/d.html 都归一化为 http://www.mi.com/bbq/test/0/a.html 等等,其中可归一的模式需要自己慢慢摸索总结。

更进一步可以把常见项做编号,比如 com标号为0,org标号为1,www 标号为0, blog编号为1,http:// 标号为1,https:// 标号为0,这样http://www.mi.com 可以编号为 10mi0,然后把日期编号为0,字母表编号为1,html编号为+,那么20170315.html归一化后就是0+。

好了,接下来就是你们举一反三了。

如果域名确定的话,甚至可以把这些编号按URL路径逐层分解,再重构成一个巨大的树结构,这样不仅避免了逐条规则的过滤匹配,而且可以大大缩小需要的存储,过滤也相对准确。当然,这个工作其实是比较繁琐的,通用性做好也会比较困难,不过对于一些网站固定、需求简单的爬虫而言,去重效果以及工作量还是可以接受的,仅供参考。

2.页面去重

这个就可以采用一些常见的做法了,比如常见的simhash等,或者直接抽取dom树做比较,或者二者结合,择你所爱。

页面去重说来不难,不过获取页面可不那么简单,静态HTML页面可以直接请求获取,但当前很多网站并不是直接返回静态html页面,而是采用ajax或者各种奇葩方式,一来是交互复杂性催生的要求,二来是防爬虫。可以去看看淘宝/京东/亚马逊图书排名的页面,对于抓包可以获取的ajax接口(这类接口一般都没验证),我们可以直接模拟请求获取数据,亚马逊和京东采用过这种方式,而淘宝我记得好像是采用了jsonp方式,把数据保存在html页面隐藏块中,然后再用js处理这些数据渲染成页面,所以你可以直接解析得到保存在隐藏部分的数据。当然以上情形都是两年前的情况了,现在什么世道我也不是很清楚,各位自行考察下吧,总之是需要费点心思。

当然了,也有一劳永逸的方式,缺点就是慢,是真的慢,因为要模拟整个浏览器的行为,想想chrome打开一个网页的速度。这种方式一般是利用headless浏览器,顾名思义,就是没有GUI界面但内部处理方式与浏览器完全一样的工具,包括请求、解析js、运行js、渲染html页面等等。你一定听说过phantomjs,其内核就是webkit,可以看做chrome版的headless浏览器,当然也有基于firefox的slimerjs,不过这俩货的API那叫一个难用,谁用谁想死。于是有了casperjs,也就是把上述二者包装了一下,统一并简化下API,好用不少,也不那么想死了。对于更有生活追求的人,我推荐使用nightmare,可以去官方网站看下phantomjs与nightmare的对比,感觉就是村头裹棉袄的二大娘和巴黎T台上时装傍身的维秘。

上述几个工具更贴近js生态圈,我大Python当然不能坐视不理,于是有了Ghost.py,世界瞬间就清新多了。当然还有语言无关的splash,甚至在前端挂个代理可以搭建成专门解析js动态页面的服务集群,你不是慢吗,我可以堆机器啊。上述几个工具中phontomjs/ghost/splash都是基于qt webkit,而nightmare则是基于electron。选哪个可以自行琢磨下,至于如何使用,去看文档吧。

不管采用哪种方式,最终的HTML页面总算爬回来了,那么接下来就是解析页面,说白了就是获取HTML某些节点的数据,是不是很熟悉,jQuery不就是干这个的嘛。对于之前提到的几个工具,可以直接引入jQuery或者编写js来处理,而对于已经获取得到完整HTML页面的其他情况,则需要自己解析。

解析的方式有很多种,最不济的正则也可以拿来顶两下,当然对于复杂需求用正则那感觉是真酸爽。其实稍微进化一点可以采用自带的xml或者第三方的lxml,三年前我用lxml的时候,对于wap页面,如果页面中设置了encoding(好像是),lxml是有BUG的,现在不知如何了,如果只需要解析html5页面,html5lib也是不错的选择。当然这些xml库操作起来还是比较麻烦,所以有了PyQuery和BeautifulSoup,pyquery看名字就知道了,基本就是jQuery的Python版本,相反,BeautifulSoup看名字就只能流口水了,二者各有优缺点,总体而言,PyQuery性能更好一些,bs更Pythonic一些,喜欢哪个用哪个,解析出数据才是根本。

数据解析出来,接下来就是保存,这部分更多是设计的问题,和爬虫本身关系不是很大,不过也要选择好落地方案。爬虫的数据可能会经常变动,所以我倾向于用NoSQL类数据库,当然存MySQL完全可以,因数据而异,如果我爬的是微博关注列表,我可能会采用图数据库,如果爬的是图片,可能直接使用普通文件系统或者对象存储,如果爬取的是人物固定信息,那可能会使用MySQL,如果是爬的是一个人的课程列表或者兴趣爱好,那我可能采用文档数据库,比如MongoDB,如果后期需要频繁搜索统计,Elasticsearch可能会更好,万一哪天我准备再造个百度,可能就会采用Hadoop那一套了,没有固定模式,满足需求即可。

0x02 反反爬

说到爬虫,就不能不说反爬,毕竟数据是别人辛辛苦苦积累来的,甚至是公司的核心资产,被人平白无故就拿去总归是不爽的。爬虫是一门技术,反爬则是一门艺术,各个大公司基本都有专门负责反爬的人员,与那些爬虫斗智斗勇,真要详细讲的话,一天一夜可能都不一定够,当然我是没这本事的,所以我只列举下简单的如何反反爬技术,这些都是规避反爬的基本常识。

1,UA

这个不用说了,勤换UA。如果一看UA就是什么requests,curl之类的,想都不用想了,肯定是爬虫了,所以要设置为正常浏览器常见的UA,能骗一次算一次。

2,限速

正常人不会一秒几十次的获取请求,爬取速度太快肯定被当做是爬虫咯。这就涉及到平衡取舍问题,速度不能太慢以免影响爬取效率,但也不能太快被当爬虫封掉,即使控制频率也尽量随机一些,你每隔三秒爬一次,速度是慢下来了,但也太有规律了吧,正常人谁会这么无聊,不封你封谁。

3,多IP

固定IP很容易被识别,多个IP可以更好的隐藏自己,分布式爬虫可以一定程度改善这种情况,说白了就是买机器。另外一种就是单机器多IP,也就是买IP,总之,多花钱。

4,验证码识别

这个比较常见,例如常见数字字母识别以及丧心病狂的12306图片识别,对于数字字母识别,很多库可以做,因为就那几个数字字母;对于类似12306的图片识别,可以先爬取那些验证图片,毕竟图片数量肯定是有限的,而且没有限制你不能爬那些图片,积累足够多样本后做个标识就可以了,当然更好的方式是使用机器学习中的图像识别技术,这样更加自动化智能化,任何图片集都可以作为学习样本。

5,滑动条识别

对,说的就是淘宝登录那个玩意。这个滑动条识别是基于行为模式的识别方式,比如正常人为登录时,鼠标移动的轨迹肯定是很杂乱的,而机器登录则可能很简单直接,当然这仅仅是一方面,还有更多的行为要作为考量,比如结合UA、设备ID、访问IP、Cookie等等,维度越多控制也越精细,如果想突破这种方式,那就需要尽可能的模拟自然人登录的行为,甚至人为的在爬虫执行中加入一些噪音,比如利用headless浏览器来模拟,即使这样难度也会不小,只能一定程度降低被识别概率。

6,遵守robots.txt

如果你要爬别人不允许爬的东西,就当这点没说。如果你要爬取的网站允许,那就尽量遵守robots.txt,哪些子站可以爬哪些不可以爬一目了然,只要不过分,人也懒得搭理你。

基本就这样了,时间有限,就不多说了。

0x03 终

人的烦恼就是记性太好,如果可以把所有事都忘掉,以后每一日都是个新开始,你说多好。这样,就不用完成答应别人的事了。

0x04 参考

【注:本文为“小米安全中心”原创     作者:那我就随便唱两句 小米安全中心入驻安全脉搏账号发布  转载请注明来源安全脉搏】

源链接

Hacking more

...