作为冷数据启动和丰富数据的重要工具,爬虫在业务发展中承担着重要的作用,我们业务在发展过程中积累了不少爬虫使用的经验,在此分享给大家,希望能对之后的业务发展提供一些技术选型方向上的思路,以更好地促进业务发展
我们将会从以下几点来分享我们的经验
在生产上,爬虫主要应用在以下几种场景
接下来我们就由浅入深地为大家介绍爬虫常用的几种技术方案
说起爬虫,大家可能会觉得技术比较高深,会立刻联想到使用像 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_ + 每篇文章独一无二的签名
利用搜索引擎技巧我们可以轻松搞定运营的这个需求
对照图片,步骤如下:
通过这种方式我们也巧妙地实现了运营的需求,这种爬虫获取的数据是个 html 文件,不是 JSON 这些结构化数据,我们需要从 html 中提取出相应的 url 信息(存在 <a> 标签里),可以用正则,也可以用 xpath 来提取。
比如 html 中有如下 div 元素
<div id="test1">大家好!</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 很多,要下载,解析,入库的工作就很大(比如我们有个类似大众点评的业务,需要爬取大众点评的数据,由于涉及到几百万量级的商户,评论等爬取,数据量巨大!),就会涉及到多线程,分布式爬取,用 PHP 这种单线程模型的语言来实现就不合适了,Python 由于其本身支持多线程,协程等特性,来实现这些比较复杂的爬虫设计就绰绰有余了,同时由于 Python 简洁的语法特性,吸引了一大波人写了很多成熟的库,各种库拿来即用,很是方便,大名鼎鼎的 Scrapy 框架就是由于其丰富的插件,易用性俘获了大批粉丝,我们的大部分爬虫业务都是用的scrapy来实现的,所以接下来我们就简要介绍一下 Scrapy,同时也来看看一个成熟的爬虫框架是如何设计的。
我们首先要考虑一下爬虫在爬取数据过程中会可能会碰到的一些问题,这样才能明白框架的必要性以后我们自己设计框架时该考虑哪些点
从以上的几个点我们可以看出写一个爬虫框架还是要费不少功夫的,幸运的是,scrapy 帮我们几乎完美地解决了以上问题,让我们只要专注于写具体的解析入库逻辑即可, 来看下它是如何实现以上的功能点的
CONCURRENT_REQUESTS = 3
,scrapy就可以为我们自己管理多线程操作,无需关心任何的线程创建毁灭生命周期等复杂的逻辑random-useragent
插件为每一次请求随机设置一个UA,使用蚂蚁(mayidaili.com)等代理为每一个请求头都加上proxy
这样我们的 UA 和 IP 每次就基本都不一样了,避免了被封的窘境Selenium + PhantomJs
来抓取抓动态数据可以看到 Scrapy 解决了以上提到的主要问题,在爬取大量数据时能让我们专注于写爬虫的业务逻辑,无须关注 Cookie 管理,多线程管理等细节,极大地减轻了我们的负担,很容易地做到事半功倍!
(注意
! Scrapy 虽然可以使用 Selenium + PhantomJs
来抓取动态数据,但随着 Google 推出的 puppeter 的横空出世,PhantomJs 已经停止更新了,因为 Puppeter 比 PhantomJS 强大太多,所以如果需要大量地抓取动态数据,需要考虑性能方面的影响,Puppeter 这个 Node 库绝对值得一试,Google 官方出品,强烈推荐)
理解了 Scrapy 的主要设计思路与功能,我们再来看下如何用 Scrapy 来开发我们某个音视频业务的爬虫项目,来看一下做一个音视频爬虫会遇到哪些问题
说到爬虫,大家应该会很自然与 python 划上等号,所以我们的技术框架就从 python 中比较脱颖而出的三方库选。scrapy 就是非常不错的一款。相信很多其他做爬虫的小伙伴也都体验过这个框架。
那么说说这个框架用了这么久感受最深的几个优点:
爬虫池 db 对于整个爬取链路来说是非常重要的关键存储节点,所以在早教这边也是经历了很多次的字段更迭。
最初我们的爬虫池 db 表只是正式表的一份拷贝,存储内容完全相同,在爬取完成后,copy 至正式表,然后就失去相应的关联。这时候的爬虫池完全就是一张草稿表,里面有很多无用的数据。
后来发现运营需要看爬虫的具体来源,这时候爬虫池里面即没有网站源链接,也无法根据正式表的专辑 id 对应到爬虫池的数据内容。所以,爬虫池 db 做出了最重要的一次改动。首先是建立爬虫池数据与爬取源站的关联,即source_link 与 source_from 字段,分别代表内容对应的网站原链接以及来源声明定义。第二步则是建立爬虫池内容与正式库内容的关联,为了不影响正式库数据,我们添加 target_id 对应到正式库的内容 id 上。此时,就可以满足告知运营爬取内容具体来源的需求了。
后续运营则发现,在大量的爬虫数据中筛选精品内容需要一些源站数据的参考值,例如:源站播放量等,此时爬虫池db 和正式库 db 存储内容正式分化,爬虫池不再只是正式库的一份拷贝,而是代表源站的一些参考数据以及正式库的一些基础数据。
而后来的同步更新源站内容功能,也是依赖这套关系可以很容易的实现。
整个过程中,最重要的是将本来毫无关联的 「爬取源站内容」 、 「爬虫池内容」 、 「正式库内容」 三个区块关联起来。
本来的话,资源的下载以及一些处理应该是在爬取阶段就可以一并完成的,那么为什么会单独产生资源处理这一流程。
首先,第一版的早教爬虫体系里面确实没有这一单独的步骤,是在scrapy爬取过程中串行执行的。但是后面发现的缺点是:
针对以上的问题,我们增加了爬虫表中的中间态,即资源下载失败的状态,但保留已爬取的信息。然后,增加独立的资源处理任务,采用 python 的多线程进行资源处理。针对这些失败的内容,会定时跑资源处理任务,直到成功为止。(当然一直失败的,就需要开发根据日志排查问题了)
首先需要了解我们去水印的原理是用 ffmpeg 的 delogo 功能,该功能不像转换视频格式那样只是更改封装。它需要对整个视频进行重新编码,所以耗时非常久,而且对应于 cpu 的占用也很大。
基于以上,如果放在资源处理阶段,会大大较低资源转移至 upyun 的效率,而且光优酷而言就有不止 3 种水印类型,对于整理规则而言就是非常耗时的工作了,这个时间消耗同样会降低爬取工作的进行。而首先保证资源入库,后续进行水印处理,一方面,运营可以灵活控制上下架,另一方面,也是给了开发人员足够的时间去整理规则,还有就是,水印处理出错时,还存在源视频可以恢复。
不少爬虫抓取的图片是有水印的,目前没发现完美的去水印方法,可使用的方法:
对于我们视频的音视频爬虫代码体系,不一定能通用于所有的业务线,但是同类问题的思考与解决方案确是可以借鉴与应用于各个业务线的,相信项目主对大家会有不少启发
当爬虫任务变得很多时,ssh+crontab 的方式会变得很麻烦, 需要一个能随时查看和管理爬虫运行状况的平台,
SpiderKeeper+Scrapyd 目前是一个现成的管理方案,提供了不错的UI界面。功能包括:
1.爬虫的作业管理:定时启动爬虫进行数据抓取,随时启动和关闭爬虫任务
2.爬虫的日志记录:爬虫运行过程中的日志记录,可以用来查询爬虫的问题
3.爬虫运行状态查看:运行中的爬虫和爬虫运行时长查看
从以上的阐述中,我们可以简单地总结一下爬虫的技术选型
根据业务场景的复杂度选择相应的技术可以达到事半功倍的效果。我们在技术选型时一定要考虑实际的业务场景。