一份爬虫技巧,送给大家

苦逼的码农

共 8799字,需浏览 18分钟

 · 2020-08-21

前 言

作为冷数据启动和丰富数据的重要工具,爬虫在业务发展中承担着重要的作用,我们业务在发展过程中积累了不少爬虫使用的经验,在此分享给大家,希望能对之后的业务发展提供一些技术选型方向上的思路,以更好地促进业务发展

我们将会从以下几点来分享我们的经验

  • 爬虫的应用场景
  • 爬虫的技术选型
  • 实战详解:复杂场景下的爬虫解决方案
  • 爬虫管理平台

爬虫的应用场景

在生产上,爬虫主要应用在以下几种场景

1、搜索引擎,Google,百度这种搜索引擎公司每天启动着无数的爬虫去抓取网页信息,才有了我们使用搜索引擎查询资料的便捷,全面,高效
2、冷数据启动是丰富数据的主要工具,新业务开始时,由于刚起步,所以没有多少数据,此时就需要抓取数据
3、数据服务或聚合的公司,比如天眼查,企查查,西瓜数据等等
4、提供横向数据比较,聚合服务,比如说电商中经常需要有一种比价系统,从各大电商平台抓取同一个商品的价格信息,以给用户提供最实惠的商品价格。

爬虫的技术选型

接下来就由浅入深地为大家介绍爬虫常用的几种技术方案

简单的爬虫

说起爬虫,大家可能会觉得技术比较高深,会立刻联想到使用像 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: xxxxx.xxxxx.com' 
    -H 'clientversion: 7.14.1' 
        ...
    -H 'birthday: 2018-08-07 00:00:00'  
    --data "body=month%22%3A$month"  ## month作为变量构建 curl 请求
    --compressed 'http://xxxx.xxxxxx.com/xxx-xxx-gateway/api/json/tools/getBabyChange' > $var.log ## 将 curl 请求结果输出到文件中以便后续分析
done
看了这个例子,是否觉得爬虫不过如此,没错,业务中很多这种简单的爬虫实现可以应付绝大多数场景的需求!

脑洞大开的爬虫解决思路

按以上介绍的爬虫思路可以解决日常多数的爬虫需求,但有时候我们需要一些脑洞大开的思路,简单列举两个

1、 去年运营同学给了一个有关奶粉的 url 的链接

https://m.tmall.com/mblist/de_9n40_AVYPod5SU93irPS-Q.html,他们希望能提取此文章的信息,同时找到所有提到奶粉关键字的文章并提取其内容, 这就需要用到一些搜索引擎的高级技巧了, 我们注意到,url 是以以下形式构成的

https://m.tmall.com/mblist/de_ + 每篇文章独一无二的签名

利用搜索引擎技巧我们可以轻松搞定运营的这个需求

对照图片,步骤如下:

  1. 首先我们用在百度框输入高级查询语句「奶粉 site:m.tmall.com inurl:mblist/de_」,点击搜索,就会显示出此页中所有包含奶粉的文章 title
  2. 注意地址栏中浏览器已经生成了搜索的完整 url,拿到这个 url 后,我们就可以去请求此 url,此时会得到上图中包含有 3, 4 这两块的 html 文件
  3. 拿到步骤 2 中获取的 html 文件后,在区域 3 每一个标题其实对应着一个 url(以 ..... )的形式存在,根据正则表达式就可以获取每个标题对应的 url,再请求这些 url 即可获取对应的文章信息。
  4. 同理,拿到步骤 2 中获取的 html 文件后,我们可以获取区域 4 每一页对应的 url,再依次请求这些 url,然后重复步骤 2,即可获取每一页包含有奶粉的文章

通过这种方式我们也巧妙地实现了运营的需求,这种爬虫获取的数据是个 html 文件,不是 JSON 这些结构化数据,我们需要从 html 中提取出相应的 url 信息(存在 标签里),可以用正则,也可以用 xpath 来提取。

比如 html 中有如下 div 元素

<div id="test1">大家好!div>

可以用以下的 xpath 来提取

data = selector.xpath('//div[@id="test1"]/text()').extract()[0]

就可以把「大家好!」提取出来,需要注意的是在这种场景中,「依然不需要使用 Scrapy 这种复杂的框架」,在这种场景下,由于数据量不大,使用单线程即可满足需求。

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 帮我们几乎完美地解决了以上问题,让我们只要专注于写具体的解析入库逻辑即可, 来看下它是如何实现以上的功能点的

可以看到 Scrapy 解决了以上提到的主要问题,在爬取大量数据时能让我们专注于写爬虫的业务逻辑,无须关注 Cookie 管理,多线程管理等细节,极大地减轻了我们的负担,很容易地做到事半功倍!

(注意! Scrapy 虽然可以使用 Selenium + PhantomJs 来抓取动态数据,但随着 Google 推出的 puppeter 的横空出世,PhantomJs 已经停止更新了,因为 Puppeter 比 PhantomJS 强大太多,所以如果需要大量地抓取动态数据,需要考虑性能方面的影响,Puppeter 这个 Node 库绝对值得一试,Google 官方出品,强烈推荐)

理解了 Scrapy 的主要设计思路与功能,我们再来看下如何用 Scrapy 来开发我们某个音视频业务的爬虫项目,来看一下做一个音视频爬虫会遇到哪些问题

音视频爬虫实战

一、先从几个方面来简单介绍我们音视频爬虫项目的体系

1、四个主流程

2、目前支持的功能点

3、体系流程分布图

二、分步来讲下细节

1. 爬虫框架的技术选型

说到爬虫,大家应该会很自然与 python 划上等号,所以我们的技术框架就从 python 中比较脱颖而出的三方库选。scrapy 就是非常不错的一款。相信很多其他做爬虫的小伙伴也都体验过这个框架。

那么说说这个框架用了这么久感受最深的几个优点:

2. 爬虫池 db 的设计

爬虫池 db 对于整个爬取链路来说是非常重要的关键存储节点,所以这边也是经历了很多次的字段更迭。

最初我们的爬虫池 db 表只是正式表的一份拷贝,存储内容完全相同,在爬取完成后,copy 至正式表,然后就失去相应的关联。这时候的爬虫池完全就是一张草稿表,里面有很多无用的数据。

后来发现运营需要看爬虫的具体来源,这时候爬虫池里面即没有网站源链接,也无法根据正式表的专辑 id 对应到爬虫池的数据内容。所以,爬虫池 db 做出了最重要的一次改动。首先是建立爬虫池数据与爬取源站的关联,即source_link 与 source_from 字段,分别代表内容对应的网站原链接以及来源声明定义。第二步则是建立爬虫池内容与正式库内容的关联,为了不影响正式库数据,我们添加 target_id 对应到正式库的内容 id 上。此时,就可以满足告知运营爬取内容具体来源的需求了。

后续运营则发现,在大量的爬虫数据中筛选精品内容需要一些源站数据的参考值,例如:源站播放量等,此时爬虫池db 和正式库 db 存储内容正式分化,爬虫池不再只是正式库的一份拷贝,而是代表源站的一些参考数据以及正式库的一些基础数据。

而后来的同步更新源站内容功能,也是依赖这套关系可以很容易的实现。

整个过程中,最重要的是将本来毫无关联的  「爬取源站内容」 、  「爬虫池内容」 、 「正式库内容」 三个区块关联起来。

3. 为什么会产生资源处理任务

本来的话,资源的下载以及一些处理应该是在爬取阶段就可以一并完成的,那么为什么会单独产生资源处理这一流程。

首先,第一版的爬虫体系里面确实没有这一单独的步骤,是在scrapy爬取过程中串行执行的。但是后面发现的缺点是:

针对以上的问题,我们增加了爬虫表中的中间态,即资源下载失败的状态,但保留已爬取的信息。然后,增加独立的资源处理任务,采用 python 的多线程进行资源处理。针对这些失败的内容,会定时跑资源处理任务,直到成功为止。(当然一直失败的,就需要开发根据日志排查问题了)

4. 说说为什么水印处理不放在资源处理阶段,而在后处理阶段(即正式入库后)

首先需要了解我们去水印的原理是用 ffmpeg 的 delogo 功能,该功能不像转换视频格式那样只是更改封装。它需要对整个视频进行重新编码,所以耗时非常久,而且对应于 cpu 的占用也很大。

基于以上,如果放在资源处理阶段,会大大较低资源转移至 upyun 的效率,而且有些不止 3 种水印类型,对于整理规则而言就是非常耗时的工作了,这个时间消耗同样会降低爬取工作的进行。而首先保证资源入库,后续进行水印处理,一方面,运营可以灵活控制上下架,另一方面,也是给了开发人员足够的时间去整理规则,还有就是,水印处理出错时,还存在源视频可以恢复。

5. 如何去除图片水印

不少爬虫抓取的图片是有水印的,目前没发现完美的去水印方法,可使用的方法:

  1. 原始图片查找,一般网站都会保存原始图和加水印图,如果找不到原始链接就没办法
  2. 裁剪法,由于水印一般是在图片边角,如果对于被裁减的图片是可接受的,可以将包含水印部分直接按比例裁掉
  3. 使用 opencv 库处理,调用 opencv 这种图形库进行图片类似PS的图片修复,产生的效果也差不多,遇到复杂图形修复效果不好。

三、遇到的问题和解决方案

四、最后做下总结

对于我们视频的音视频爬虫代码体系,不一定能通用于所有的业务线,但是同类问题的思考与解决方案确是可以借鉴与应用于各个业务线的,相信对大家也会有不少启发

爬虫管理平台

当爬虫任务变得很多时,ssh+crontab 的方式会变得很麻烦, 需要一个能随时查看和管理爬虫运行状况的平台,

SpiderKeeper+Scrapyd 目前是一个现成的管理方案,提供了不错的UI界面。功能包括:

1.爬虫的作业管理:定时启动爬虫进行数据抓取,随时启动和关闭爬虫任务

2.爬虫的日志记录:爬虫运行过程中的日志记录,可以用来查询爬虫的问题

3.爬虫运行状态查看:运行中的爬虫和爬虫运行时长查看

总 结

从以上的阐述中,我们可以简单地总结一下爬虫的技术选型

  1. 如果是结构化数据(JSON 等),我们可以使用 curl,PHP 这些单线程模块的语言来处理即可
  2. 如果是非结构化数据(html 等),此时 bash 由于无法处理这类数据,需要用正则, xpath 来处理,可以用BeautifulSoup 等来处理,当然这种情况仅限于待爬取的 url 较少的情况
  3. 如果待爬取的 url 很多,单线程无法应付,就需要多线程来处理了,又或者需要 Cookie 管理,动态 ip 代理等,这种情况下我们就得考虑 scrapy 这类高性能爬虫框架了

根据业务场景的复杂度选择相应的技术可以达到事半功倍的效果。我们在技术选型时一定要考虑实际的业务场景。


帅地搞了个原创小号,我会在这个公众号分享读者问过我的问题,并且给予最真实的回答,同时也会分享自己学习方法、挣钱经历、工作经历、个人经历、沙雕日常,我相信,我的经历与想法,一定可以给你带来一些帮助!扫一扫进入帅地的私密沙雕小号

浏览 20
点赞
评论
收藏
分享

手机扫一扫分享

举报
评论
图片
表情
推荐
点赞
评论
收藏
分享

手机扫一扫分享

举报