前言
作为冷数据启动和丰富数据的重要工具,爬虫在业务发展中承担着重要的作用,我们业务在发展过程中积累了不少爬虫使用的经验,在此分享给大家,希望能对之后的业务发展提供一些技术选型方向上的思路,以更好地促进业务发展
我们将会从以下几点来分享我们的经验
爬虫的应用场景
爬虫的技术选型
实战详解:复杂场景下的爬虫解决方案
爬虫管理平台
爬虫的应用场景
在生产上,爬虫主要应用在以下几种场景
搜索引擎,Google,百度这种搜索引擎公司每天启动着无数的爬虫去抓取网页信息,才有了我们使用搜索引擎查询资料的便捷,全面,高效(关于搜索引擎工作原理,在这篇文章作了详细的讲解,建议大家看看)
冷数据启动时丰富数据的主要工具,新业务开始时,由于刚起步,所以没有多少数据,此时就需要爬取其他平台的数据来填充我们的业务数据,比如说如果我们想做一个类似大众点评这样的平台,一开始没有商户等信息,就需要去爬取大众,美团等商家的信息来填充数据
数据服务或聚合的公司,比如天眼查,企查查,西瓜数据等等
提供横向数据比较,聚合服务,比如说电商中经常需要有一种比价系统,从各大电商平台,如拼多多,淘宝,京东等抓取同一个商品的价格信息,以给用户提供最实惠的商品价格,这样就需要从各大电商平台爬取信息。
黑产,灰产,风控等,比如我们要向某些资金方申请授信,在资金方这边首先要部署一道风控,来看你的个人信息是否满足授信条件,这些个人信息通常是某些公司利用爬虫技术在各个渠道爬取而来的,当然了这类场景还是要慎用,不然正应了那句话「爬虫用的好,监控进得早」
爬虫的技术选型
接下来我们就由浅入深地为大家介绍爬虫常用的几种技术方案
简单的爬虫
说起爬虫,大家可能会觉得技术比较高深,会立刻联想到使用像 Scrapy 这样的爬虫框架,这类框架确实很强大,那么是不是一写爬虫就要用框架呢?非也!要视情况而定,如果我们要爬取的接口返回的只是很简单,固定的结构化数据(如JSON),用 Scrapy 这类框架的话有时无异于杀鸡用牛刀,不太经济!
举个简单的例子,业务中有这么一个需求:需要抓取育学园中准妈妈从「孕4周以下」~「孕36个月以上」每个阶段的数据
对于这种请求,bash 中的 curl 足堪大任!
首先我们用 charles 等抓包工具抓取此页面接口数据,
通过观察,我们发现请求的数据中只有 month 的值(代表孕几周)不一样,所以我们可以按以下思路来爬取所有的数据:
1、 找出所有「孕4周以下」~「孕36个月以上」对应的 month 的值,构建一个 month 数组 2、 构建一个以 month 值为变量的 curl 请求,在 charles 中 curl 请求我们可以通过如下方式来获取
3、 依次遍历步骤 1 中的 month,每遍历一次,就用步骤 2 中的 curl 和 month 变量构建一个请求并执行,将每次的请求结果保存到一个文件中(对应每个孕期的 month 数据),这样之后就可以对此文件中的数据进行解析分析。
示例代码如下,为了方便演示,中间 curl 代码作了不少简化,大家明白原理就好
!/bin/bash
获取所有孕周对应的 month,这里为方便演示,只取了两个值
month=(21 24)
遍历所有 month,组装成 curl 请求
for month in ${month[@]};
do
curl -H 'Host: yxyapi2.drcuiyutao.com'
-H 'clientversion: 7.14.1'
...
-H 'birthday: 2018-08-07 00:00:00'
--data "body=month%22%3A$month" ## month作为变量构建 curl 请求
--compressed 'http://yxyapi2.drcuiyutao.com/yxy-api-gateway/api/json/tools/getBabyChange' > $var.log ## 将 curl 请求结果输出到文件中以便后续分析
done
前期我们业务用 PHP 的居多,不少爬虫请求都是在 PHP 中处理的,在 PHP 中我们也可以通过调用 libcurl 来模拟 bash 中的 curl 请求,比如业务中有一个需要抓取每个城市的天气状况的需求,就可以用 PHP 调用 curl,一行代码搞定!
看了两个例子,是否觉得爬虫不过如此,没错,业务中很多这种简单的爬虫实现可以应付绝大多数场景的需求!
脑洞大开的爬虫解决思路
按以上介绍的爬虫思路可以解决日常多数的爬虫需求,但有时候我们需要一些脑洞大开的思路,简单列举两个
1、 去年运营同学给了一个天猫精选的有关奶粉的 url 的链接
https://m.tmall.com/mblist/de_9n40_AVYPod5SU93irPS-Q.html,他们希望能提取此文章的信息,同时找到天猫精选中所有提到奶粉关键字的文章并提取其内容, 这就需要用到一些搜索引擎的高级技巧了, 我们注意到,天猫精选的 url 是以以下形式构成的
https://m.tmall.com/mblist/de_ + 每篇文章独一无二的签名
利用搜索引擎技巧我们可以轻松搞定运营的这个需求
对照图片,步骤如下:
首先我们用在百度框输入高级查询语句「奶粉 site:m.tmall.com inurl:mblist/de_」,点击搜索,就会显示出此页中所有天猫精选中包含奶粉的文章 title
注意地址栏中浏览器已经生成了搜索的完整 url,拿到这个 url 后,我们就可以去请求此 url,此时会得到上图中包含有 3, 4 这两块的 html 文件
拿到步骤 2 中获取的 html 文件后,在区域 3 每一个标题其实对应着一个 url(以 ..... )的形式存在,根据正则表达式就可以获取每个标题对应的 url,再请求这些 url 即可获取对应的文章信息。
同理,拿到步骤 2 中获取的 html 文件后,我们可以获取区域 4 每一页对应的 url,再依次请求这些 url,然后重复步骤 2,即可获取每一页天猫精选中包含有奶粉的文章
通过这种方式我们也巧妙地实现了运营的需求,这种爬虫获取的数据是个 html 文件,不是 JSON 这些结构化数据,我们需要从 html 中提取出相应的 url 信息(存在 标签里),可以用正则,也可以用 xpath 来提取。
比如 html 中有如下 div 元素
可以用以下的 xpath 来提取
data = selector.xpath('//div[@id="test1"]/text()').extract()[0]
就可以把「大家好!」提取出来,需要注意的是在这种场景中,「依然不需要使用 Scrapy 这种复杂的框架」,在这种场景下,由于数据量不大,使用单线程即可满足需求,在实际生产上我们用 php 实现即可满足需求
2、 某天运营同学又提了一个需求,想爬取美拍的视频
通过抓包我们发现美拍每个视频的 url 都很简单,输入到浏览器查看也能正常看视频,于是我们想当然地认为直接通过此 url 即可下载视频,但实际我们发现此 url 是分片的(m3u8,为了优化加载速度而设计的一种播放多媒体列表的档案格式),下载的视频不完整,后来我们发现打开http://www.flvcd.com/
网站
输入美拍地址转化一下就能拿到完整的视频下载地址
「如图示:点击「开始GO!」后就会开始解析视频地址并拿到完整的视频下载地址」
进一步分析这个「开始GO!」按钮对应的请求是「http://www.flvcd.com/parse.php?format=&kw= + 视频地址」,所以只要拿到美拍的视频地址,再调用 flvcd 的视频转换请求即可拿到完整的视频下载地址,通过这种方式我们也解决了无法拿到美拍完整地址的问题。
复杂的爬虫设计
上文我们要爬取的数据相对比较简单, 数据属于拿来即用型,实际上我们要爬取的数据大部分是非结构化数据(html 网页等),需要对这些数据做进一步地处理(爬虫中的数据清洗阶段),而且每个我们爬取的数据中也很有可能包含着大量待爬取网页的 url,也就是说需要有 url 队列管理,另外请求有时候还需求登录,每个请求也需要添加 Cookie,也就涉及到 Cookie 的管理,在这种情况下考虑 Scrapy 这样的框架是必要的!不管是我们自己写的,还是类似 Scrapy 这样的爬虫框架,基本上都离不开以下模块的设计
url 管理器
网页(HTML)下载器, 对应 Python 中的urllib2, requests等库
(HTML)解析器,主要有两种方式来解析
下图详细解释了各个模块之间是如何配合使用的
正则表达式
以css, xpath为代表的结构化解析(即将文档以DOM树的形式重新组织,通过查找获取节点进而提取数据的方式), Python中的 html.parser,BeautifulSoup,lxml 皆是此类范畴
首先调度器会询问 url 管理器是否有待爬取的 url
如果有,则获取出其中的 url 传给下载器进行下载
下载器下载完内容后会将其传给解析器做进一步的数据清洗,这一步除了会提取出有价值的数据,还会提取出待爬取的URL以作下一次的爬取
调度器将待爬取的URL放到URL管理器里,将有价值的数据入库作后续的应用
以上过程会一直循环,直到再无待爬取URL
可以看到,像以上的爬虫框架,如果待爬取 URL 很多,要下载,解析,入库的工作就很大(比如我们有个类似大众点评的业务,需要爬取大众点评的数据,由于涉及到几百万量级的商户,评论等爬取,数据量巨大!),就会涉及到多线程,分布式爬取,用 PHP 这种单线程模型的语言来实现就不合适了,Python 由于其本身支持多线程,协程等特性,来实现这些比较复杂的爬虫设计就绰绰有余了,同时由于 Python 简洁的语法特性,吸引了一大波人写了很多成熟的库,各种库拿来即用,很是方便,大名鼎鼎的 Scrapy 框架就是由于其丰富的插件,易用性俘获了大批粉丝,我们的大部分爬虫业务都是用的scrapy来实现的,所以接下来我们就简要介绍一下 Scrapy,同时也来看看一个成熟的爬虫框架是如何设计的。
我们首先要考虑一下爬虫在爬取数据过程中会可能会碰到的一些问题,这样才能明白框架的必要性以后我们自己设计框架时该考虑哪些点
url 队列管理:比如如何防止对同一个 url 重复爬取(去重),如果是在一台机器上可能还好,如果是分布式爬取呢
Cookie 管理:有一些请求是需要帐号密码验证的,验证之后需要用拿到的 Cookie 来访问网站后续的页面请求,如何缓存住 Cookie 以便后续进一步的操作
多线程管理:前面说了如果待爬取URL很多的话,加载解析的工作是很大的,单线程爬取显然不可行,那如果用多线程的话,管理又是一件大麻烦
User-Agent 与动态代理的管理: 目前的反爬机制其实也是比较完善的,如果我们用同样的UA,同样的IP不节制地连续对同一个网站多次请求,很可能立马被封, 此时我们就需要使用 random-ua ,动态代理来避免被封
动态生成数据的爬取:一般通过 GET 请求获取的网页数据是包含着我们需要的数据的,但有些数据是通过 Ajax 请求动态生成,这样的话该如何爬取
DEBUG
爬虫管理平台: 爬虫任务多时,如何查看和管理这些爬虫的状态和数据
从以上的几个点我们可以看出写一个爬虫框架还是要费不少功夫的,幸运的是,scrapy 帮我们几乎完美地解决了以上问题,让我们只要专注于写具体的解析入库逻辑即可, 来看下它是如何实现以上的功能点的
url 队列管理: 使用 scrapy-redis 插件来做 url 的去重处理,利用 redis 的原子性可以轻松处理url重复问题
Cookie管理: 只要做一次登录校验,就会缓存住Cookie,在此后的请求中自动带上此Cookie,省去了我们自己管理的烦恼
多线程管理: 只要在中间件中指定线程次数CONCURRENT_REQUESTS = 3,scrapy就可以为我们自己管理多线程操作,无需关心任何的线程创建毁灭生命周期等复杂的逻辑
User-Agent与动态代理的管理: 使用random-useragent插件为每一次请求随机设置一个UA,使用蚂蚁(mayidaili.com)等代理为每一个请求头都加上proxy这样我们的 UA 和 IP 每次就基本都不一样了,避免了被封的窘境
动态数据(通过 ajax 等生成)爬取: 使用Selenium + PhantomJs来抓取抓动态数据
DEBUG: 如何有效测试爬取数据是否正确非常重要,一个不成熟的框架很可能在我们每次要验证用 xpath,正则等获取数据是否正确时每一次都会重新去下载网页,效率极低,但Scray-Shell 提供了很友好的设计,它会先下载网页到内存里,然后你在 shell 做各种 xpath 的调试,直到测试成功!
使用 SpiderKeeper+Scrapyd 来管理爬虫, GUI 操作,简单易行
可以看到 Scrapy 解决了以上提到的主要问题,在爬取大量数据时能让我们专注于写爬虫的业务逻辑,无须关注 Cookie 管理,多线程管理等细节,极大地减轻了我们的负担,很容易地做到事半功倍!
(注意! Scrapy 虽然可以使用 Selenium + PhantomJs 来抓取动态数据,但随着 Google 推出的 puppeter 的横空出世,PhantomJs 已经停止更新了,因为 Puppeter 比 PhantomJS 强大太多,所以如果需要大量地抓取动态数据,需要考虑性能方面的影响,Puppeter 这个 Node 库绝对值得一试,Google 官方出品,强烈推荐)
理解了 Scrapy 的主要设计思路与功能,我们再来看下如何用 Scrapy 来开发我们某个音视频业务的爬虫项目,来看一下做一个音视频爬虫会遇到哪些问题
音视频爬虫实战
一、先从几个方面来简单介绍我们音视频爬虫项目的体系
1、四个主流程
爬取阶段
资源处理(包括音频,视频,图片下载及处理)
正式入库
后处理阶段(类似去水印)
2、目前支持的功能点
各类视频音频站点的爬取(喜马拉雅,爱奇艺,优酷,腾讯,儿歌点点等)
主流视频音频站点的内容同步更新(喜马拉雅,优酷)
视频去水印(视频 logo)
视频截图(视频内容无封面)
视频转码适配(flv 目前客户端不支持)
3、体系流程分布
二、分步来讲下细节
- 爬虫框架的技术选型
说到爬虫,大家应该会很自然与 python 划上等号,所以我们的技术框架就从 python 中比较脱颖而出的三方库选。scrapy 就是非常不错的一款。相信很多其他做爬虫的小伙伴也都体验过这个框架。
那么说说这个框架用了这么久感受最深的几个优点:
request 触发底层采用的是 python 自带的 yied 协程,可以节省内容的同时,回调式的编程方式也显得优雅舒适
对于 html 内容的高效筛选处理能力,selecter 的 xpath 真的很好用
由于迭代时间已经很长了,具备了很完善的扩展 api,例如:middlewares 就可以全局 hook 很多事件点,动态 ip 代理就可以通过 hook request_start 实现
- 爬虫池 db 的设计
爬虫池 db 对于整个爬取链路来说是非常重要的关键存储节点,所以在早教这边也是经历了很多次的字段更迭。
最初我们的爬虫池 db 表只是正式表的一份拷贝,存储内容完全相同,在爬取完成后,copy 至正式表,然后就失去相应的关联。这时候的爬虫池完全就是一张草稿表,里面有很多无用的数据。
后来发现运营需要看爬虫的具体来源,这时候爬虫池里面即没有网站源链接,也无法根据正式表的专辑 id 对应到爬虫池的数据内容。所以,爬虫池 db 做出了最重要的一次改动。首先是建立爬虫池数据与爬取源站的关联,即source_link 与 source_from 字段,分别代表内容对应的网站原链接以及来源声明定义。第二步则是建立爬虫池内容与正式库内容的关联,为了不影响正式库数据,我们添加 target_id 对应到正式库的内容 id 上。此时,就可以满足告知运营爬取内容具体来源的需求了。
后续运营则发现,在大量的爬虫数据中筛选精品内容需要一些源站数据的参考值,例如:源站播放量等,此时爬虫池db 和正式库 db 存储内容正式分化,爬虫池不再只是正式库的一份拷贝,而是代表源站的一些参考数据以及正式库的一些基础数据。
而后来的同步更新源站内容功能,也是依赖这套关系可以很容易的实现。
整个过程中,最重要的是将本来毫无关联的 「爬取源站内容」 、 「爬虫池内容」 、 「正式库内容」 三个区块关联起来。
- 为什么会产生资源处理任务
本来的话,资源的下载以及一些处理应该是在爬取阶段就可以一并完成的,那么为什么会单独产生资源处理这一流程。
首先,第一版的早教爬虫体系里面确实没有这一单独的步骤,是在scrapy爬取过程中串行执行的。但是后面发现的缺点是:
scrapy 自带的 download pipe 不太好用,而且下载过程中并不能并行下载,效率较低
由于音视频文件较大,合并资源会有各种不稳定因素,有较大概率出现下载失败。失败后会同步丢失掉爬取信息。
串行执行的情况下,会失去很多扩展性,重跑难度大。
针对以上的问题,我们增加了爬虫表中的中间态,即资源下载失败的状态,但保留已爬取的信息。然后,增加独立的资源处理任务,采用 python 的多线程进行资源处理。针对这些失败的内容,会定时跑资源处理任务,直到成功为止。(当然一直失败的,就需要开发根据日志排查问题了)
- 说说为什么水印处理不放在资源处理阶段,而在后处理阶段(即正式入库后)
首先需要了解我们去水印的原理是用 ffmpeg 的 delogo 功能,该功能不像转换视频格式那样只是更改封装。它需要对整个视频进行重新编码,所以耗时非常久,而且对应于 cpu 的占用也很大。
基于以上,如果放在资源处理阶段,会大大较低资源转移至 upyun 的效率,而且光优酷而言就有不止 3 种水印类型,对于整理规则而言就是非常耗时的工作了,这个时间消耗同样会降低爬取工作的进行。而首先保证资源入库,后续进行水印处理,一方面,运营可以灵活控制上下架,另一方面,也是给了开发人员足够的时间去整理规则,还有就是,水印处理出错时,还存在源视频可以恢复。
- 如何去除图片水印
不少爬虫抓取的图片是有水印的,目前没发现完美的去水印方法,可使用的方法:
原始图片查找,一般网站都会保存原始图和加水印图,如果找不到原始链接就没办法
裁剪法,由于水印一般是在图片边角,如果对于被裁减的图片是可接受的,可以将包含水印部分直接按比例裁掉
使用 opencv 库处理,调用 opencv 这种图形库进行图片类似PS的图片修复,产生的效果也差不多,遇到复杂图形修复效果不好。
三、遇到的问题和解决方案
资源下载阶段经常出现中断或失败等问题【方案:将资源下载及相关处理从爬取过程中独立出来,方便任务重跑】
虽然是不同平台,但是重复资源太多,特别是视频网站【方案:资源下载前根据title匹配,完全匹配则过滤,省下了多余的下载时间消耗】
大量爬取过程中,会遇到ip被封的情况【方案:动态 ip 代理】
大型视频网站资源获取规则频繁替换(加密,视频切割,防盗链等),开发维护成本高【方案:you-get 三方库,该库支持大量的主流视频网站的爬取,大大减少开发维护成本】
app相关爬取被加密【方案:反编译】
优酷和腾讯视频会有 logo【方案:ffmpeg delogo 功能】
爬过来的内容没有主播关联像盗版【方案:在内容正式入库时,给内容穿上主播马甲】
爬取源站内容仍在更新中,但是我们的平台内容无法更新【方案:db 存入原站链接,根据差异性进行更新】
类似优酷,爱奇艺等主流视频网站的专辑爬取任务媒介存于服务器文本文件中,并需开发手动命令触发,耗费人力【方案:整合脚本逻辑,以 db 为媒介,以定时任务检测触发】
运营需要添加一些类似原站播放量等的数据到运营后台显示,作为审核,加精,置顶等操作的依据【方案:之前爬虫表在将数据导入正式表后失去关联,现在建立起关联,在爬虫表添加爬虫原站相关数据字段】
由于自己的很多资源是爬过来的,所以资源的安全性和反扒就显得很重要,那么怎么保证自己资源在接口吐出后仍然安全【方案:upyun的防盗链空间,该空间下的资源地址有相应的时效性】
接口中没有媒体文件相关信息,而自己平台需要,例如:时长【方案:ffmpeg 支持的媒体文件解析】
下载后的视频很多在客户端无法播放【方案:在资源上传 upyun 前,进行格式和码率验证,不符合则进行相应的转码】
四、最后做下总结
对于我们视频的音视频爬虫代码体系,不一定能通用于所有的业务线,但是同类问题的思考与解决方案确是可以借鉴与应用于各个业务线的,相信项目主对大家会有不少启发
爬虫管理平台
当爬虫任务变得很多时,ssh+crontab 的方式会变得很麻烦, 需要一个能随时查看和管理爬虫运行状况的平台,
SpiderKeeper+Scrapyd 目前是一个现成的管理方案,提供了不错的UI界面。功能包括:
1.爬虫的作业管理:定时启动爬虫进行数据抓取,随时启动和关闭爬虫任务
2.爬虫的日志记录:爬虫运行过程中的日志记录,可以用来查询爬虫的问题
3.爬虫运行状态查看:运行中的爬虫和爬虫运行时长查看
总结
从以上的阐述中,我们可以简单地总结一下爬虫的技术选型
如果是结构化数据(JSON 等),我们可以使用 curl,PHP 这些单线程模块的语言来处理即可
如果是非结构化数据(html 等),此时 bash 由于无法处理这类数据,需要用正则, xpath 来处理,可以用 php, BeautifulSoup 来处理,当然这种情况仅限于待爬取的 url 较少的情况
如果待爬取的 url 很多,单线程无法应付,就需要多线程来处理了,又或者需要 Cookie 管理,动态 ip 代理等,这种情况下我们就得考虑 scrapy 这类高性能爬虫框架了
根据业务场景的复杂度选择相应的技术可以达到事半功倍的效果。我们在技术选型时一定要考虑实际的业务场景。