package cn.xpleaf.spider.core.repository.impl; import cn.xpleaf.spider.constants.SpiderConstants; import cn.xpleaf.spider.core.repository.IRepository; import cn.xpleaf.spider.utils.JedisUtil; import cn.xpleaf.spider.utils.SpiderUtil; import redis.clients.jedis.Jedis; import java.util.Set; /** * 基于Redis的全网爬虫,随机获取爬虫url: * * Redis中用来保存url的数据结构如下: * 1.需要爬取的域名集合(存储数据类型为set,这个需要先在Redis中添加) * key * spider.website.domains * value(set) * jd.com suning.com gome.com * key由常量对象SpiderConstants.SPIDER_WEBSITE_DOMAINS_KEY 获得 * 2.各个域名所对应的高低优先url队列(存储数据类型为list,这个由爬虫程序解析种子url后动态添加) * key * jd.com.higher * jd.com.lower * suning.com.higher * suning.com.lower * gome.com.higher * gome.come.lower * value(list) * 相对应需要解析的url列表 * key由随机的域名 + 常量 SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX或者SpiderConstants.SPIDER_DOMAIN_LOWER_SUFFIX获得 * 3.种子url列表 * key * spider.seed.urls * value(list) * 需要爬取的数据的种子url * key由常量SpiderConstants.SPIDER_SEED_URLS_KEY获得 * * 种子url列表中的url会由url调度器定时向高低优先url队列中 */ public class RandomRedisRepositoryImpl implements IRepository { /** * 构造方法 */ public RandomRedisRepositoryImpl() { init(); } /** * 初始化方法,初始化时,先将redis中存在的高低优先级url队列全部删除 * 否则上一次url队列中的url没有消耗完时,再停止启动跑下一次,就会导致url仓库中有重复的url */ public void init() { Jedis jedis = JedisUtil.getJedis(); Set<String> domains = jedis.smembers(SpiderConstants.SPIDER_WEBSITE_DOMAINS_KEY); String higherUrlKey; String lowerUrlKey; for(String domain : domains) { higherUrlKey = domain + SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX; lowerUrlKey = domain + SpiderConstants.SPIDER_DOMAIN_LOWER_SUFFIX; jedis.del(higherUrlKey, lowerUrlKey); } JedisUtil.returnJedis(jedis); } /** * 从队列中获取url,目前的策略是: * 1.先从高优先级url队列中获取 * 2.再从低优先级url队列中获取 * 对应我们的实际场景,应该是先解析完列表url再解析商品url * 但是需要注意的是,在分布式多线程的环境下,肯定是不能完全保证的,因为在某个时刻高优先级url队列中 * 的url消耗完了,但实际上程序还在解析下一个高优先级url,此时,其它线程去获取高优先级队列url肯定获取不到 * 这时就会去获取低优先级队列中的url,在实际考虑分析时,这点尤其需要注意 * @return */ @Override public String poll() { // 从set中随机获取一个顶级域名 Jedis jedis = JedisUtil.getJedis(); String randomDomain = jedis.srandmember(SpiderConstants.SPIDER_WEBSITE_DOMAINS_KEY); // jd.com String key = randomDomain + SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX; // jd.com.higher String url = jedis.lpop(key); if(url == null) { // 如果为null,则从低优先级中获取 key = randomDomain + SpiderConstants.SPIDER_DOMAIN_LOWER_SUFFIX; // jd.com.lower url = jedis.lpop(key); } JedisUtil.returnJedis(jedis); return url; } /** * 向高优先级url队列中添加url * @param highUrl */ @Override public void offerHigher(String highUrl) { offerUrl(highUrl, SpiderConstants.SPIDER_DOMAIN_HIGHER_SUFFIX); } /** * 向低优先url队列中添加url * @param lowUrl */ @Override public void offerLower(String lowUrl) { offerUrl(lowUrl, SpiderConstants.SPIDER_DOMAIN_LOWER_SUFFIX); } /** * 添加url的通用方法,通过offerHigher和offerLower抽象而来 * @param url 需要添加的url * @param urlTypeSuffix url类型后缀.higher或.lower */ public void offerUrl(String url, String urlTypeSuffix) { Jedis jedis = JedisUtil.getJedis(); String domain = SpiderUtil.getTopDomain(url); // 获取url对应的顶级域名,如jd.com String key = domain + urlTypeSuffix; // 拼接url队列的key,如jd.com.higher jedis.lpush(key, url); // 向url队列中添加url JedisUtil.returnJedis(jedis); } }