背景

我们的数据推荐算法需要大量的数据,所以结合推荐算法,我们需要爬取大量的针对物品的评分、用户ID、商品ID等信息,所以我选择在豆瓣上爬取大量的评分数据以支持算法的数据部分。为此实现了一个针对较大量数据的分布式爬虫组件。

网页分析

根据对于豆瓣网站页面以及相关API的分析发现,我们可以通过如下几个URL获取电影的条目信息。

# 豆瓣公开提供的API接口,只能提供部分局限的电影条目
https://api.douban.com/v2/movie/top250
https://api.douban.com/v2/movie/in_theaters
https://api.douban.com/v2/movie/us_box
# 豆瓣返回搜索结果的Web接口,提供的电影条目较少,有局限
https://movie.douban.com/j/search_subjects
# 豆瓣根据Tag返回查询结果的Web接口,支持搜索功能,提供的电影条目较多
https://movie.douban.com/j/new_search_subjects

这些API均返回JSON格式的Web信息,从中我们可以提取出大量豆瓣上的电影条目,从而进行下一步的处理。

我们经过爬取,大致可以得出所有电影的SubjectID,以及相对应的主页的URL,通过SubjectID我们可以得到用户进行评分的页面,URL如下。

https://movie.douban.com/subject/<subject_id>/comments

从而获得所有用户的评分。

流程分析

整个爬取过程的过程应该是这样的。

g_start=>start: 开始爬虫 
g_end=>end: 爬取成功
g_is_api=>condition: 是否是API的URL
g_subject_api=>operation: 公开API数据
g_start->g_is_api
g_is_api(yes)->g_subject_api
g_is_search=>condition: 是否是搜索接口
g_is_api(no)->g_is_search
g_subject_search=>operation: Web旧版搜索页接口
g_is_search(yes)->g_subject_search
g_is_new_search=>condition: 是否是新版搜索接口
g_is_search(no)->g_is_new_search
g_subject_new_search=>operation: Web新版搜索接口
g_is_new_search(yes)->g_subject_new_search
g_index=>operation: 采集SubjectID
g_subject_api->g_index
g_subject_search->g_index
g_subject_new_search->g_index
g_is_finished=>condition: 是否获取所有SubjectID
g_index->g_is_finished
g_is_finished(no)->g_is_api
g_input_subject=>operation: 输入下一个SubjectID
g_is_finished(yes)->g_input_subject
g_parse_comment=>operation: 解析页面
g_input_subject->g_parse_comment
g_is_more=>condition: 是否有剩余评分
g_parse_comment->g_is_more
g_is_more(yes)->g_parse_comment
g_has_next=>condition: 是否获取所有项目评分
g_is_more(no, down)->g_has_next
g_has_next(no)->g_input_subject
g_reduce_all=>operation: 规约所有评分
g_has_next(yes)->g_reduce_all
g_reduce_all->g_end

结构设计

为此,我构建了如下的项目结构以构建我们的分布式爬虫项目。

Spider作为整个项目的Driver,驱动所有的MapReduce的Job依次执行。

我将所有的Web Client的配置封装在了Utillity类中,利用单例模式确保了整个爬虫可以共享所有的配置以及HTTP会话以保持爬虫对网站的访问能够保持登录身份以得到所有的访问权限。同时针对豆瓣会针对IP地址进行反爬虫策略的特性,同时封装了一个付费的代理池,进行了动态代理的保护,以确保项目稳定进行。

运行过程

本项目的思路是将所有的不同渠道,获取的SubjectID统一集成到获取评分操作的的输入中去,不同数据源对应的类如下。

数据源
API MovieRankMapper
SearchSubject SearchSubjectMapper
NewSearchSubject NewSearchSubjectMapper
Comments CommentCollectMapper

通过一系列的Map操作,使所有的数据都能够进入分布式系统中,然后最终进行规约,使用RedundantReducer进行所有评分项目的去重复以及删减异常数据的操作。

graph TB
File-->Input
subgraph Layer1
Input-->|URL|API
Input-->|URL|Search
Input-->|URL|NewSearch
end
subgraph Layer2
API-->|SubjectID|Comments
Search-->|SubjectID|Comments
NewSearch-->|SubjectID|Comments
end
Comments-->|SubjectID, UserID, Score|Output
Output-.->|Formated Data|RecommendModule

根据上图的运行流程我们的分布式爬虫能够输出符合各式的结果,同时根据我们具体的运行结果,可以得出如下的吞吐量

Subject Average Comments per Subject
10000 200+

出于执行时间的考虑,我们最终在第一层的输出中随机截取了500条项目,然后投入Layer2的输入中,获得了23127条用户评分,足以支持用户推荐模块进行效果较好的用户推荐效果。