这是我参与「第四届青训营 」笔记创作活动的第4天。
我们确定了目标为开发一个基于流式计算框架、遵循robots协议的分布式爬虫,并能够对爬取到的数据进行解析并存储。本文章将就flink程序设计进行讲解。
下图为一个flink流式计算框架的执行流程。
从图中可以看到,一个flink计算过程需要稳定的数据源,转换操作以及输出。经过多方学习,在本项目中,我们设置:
- 数据源:redis数据库
- 转换操作:爬虫系统
- 输出:redis数据库
基于此,系统的执行流程为:
到这里,系统的关键问题在于:
- 如何从redis中读取数据
- 如何创建新进程
- 如何将数据写入redis中
接下来我们将结合代码分别介绍这三个问题的实现。
- 如何从redis中读取数据
this.jedis = new Jedis("127.0.0.1", 6379); // 连接redis
while(isRunning){
String url = null;
while (url ==null){
String randomDomain = jedis.srandmember(SpiderConstants.SPIDER_WEBSITE_DOMAINS_KEY); // jd.com
String key = randomDomain + SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX; // jd.com.higher
url = jedis.lpop(key); // 从高优先级队列中取出一个url
if(url == null) { // 如果为null,则从低优先级中获取
key = randomDomain + SpiderConstants.SPIDER_DOMAIN_LOWER_SUFFIX; // jd.com.lower
url = jedis.lpop(key);
}
SpiderUtil.sleep(5000); // 等待一段时间,防止被反爬
}
sourceContext.collect(url);
- 如何创建新线程
public class SpiderFlatMapFunction implements FlatMapFunction<String, UrlList> {
private ISpider iSpider;
public SpiderFlatMapFunction(ISpider iSpider){
this.iSpider = iSpider;
}
@Override
public void flatMap(String s, Collector<UrlList> collector) throws Exception {
UrlList urlList = iSpider.startSingle(s); // 启动爬虫系统,返回获得的新url
collector.collect(urlList); // 将获得的url存入数据库
}
}
- 如何将数据写入redis中
// 根据优先级的高低将数据分别存入
public void invoke(UrlList value, Context context) {
List<String> high = value.getHighList();
List<String> low = value.getLowList();
if(!high.isEmpty()){
for(String url:high){
String domain = SpiderUtil.getTopDomain(url); // 获取url对应的顶级域名,如jd.com
String key = domain + SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX; // 拼接url队列的key,如jd.com.higher
jedis.lpush(key, url);
}
}
if(!low.isEmpty()){
for(String url:low){
String domain = SpiderUtil.getTopDomain(url); // 获取url对应的顶级域名,如jd.com
String key = domain + SpiderConstants.SPIDER_DOMAIN_LOWER_SUFFIX; // 拼接url队列的key,如jd.com.higher
jedis.lpush(key, url);
}
}
}
大功告成!接下来看看flink主函数是怎么设置的
public class FlinkSpider {
public static void main(String[] args) throws Exception {
ISpider iSpider = ISpider.getInstance();
// 创建流式执行环境
StreamExecutionEnvironment env =
StreamExecutionEnvironment.getExecutionEnvironment();
env.setParallelism(4);
// 添加数据源
DataStreamSource<String> stream = env.addSource(new MyRedisSource());
// 处理数据
SingleOutputStreamOperator<UrlList> urlListSingleOutputStreamOperator = stream.flatMap(new SpiderFlatMapFunction(iSpider));
urlListSingleOutputStreamOperator.addSink(new MyRedisSinkFunction());
env.execute();
}
}
ps. 主函数运行时务必保持redis-server在后台运行。