zoukankan      html  css  js  c++  java
  • 问题--feed列表有新闻重复的问题

      1. 经常有运营反应,客户端展示的feed列表有重复的问题。

      重复问题分为两种,一种是两条新闻标题类似,另一种是两条新闻标题是完全相同。

      (1)标题类似

      原来过滤的逻辑,是两个标题完全相等,才认为两条新闻内容一样,后来改进了一下,比较两个标题的相度,如果相似度超过70%,那么就认为这是同一条新闻,如(北京今日降大雨,公交停运   and 北京降大雨,公交停运)这两条新闻

    相似度超过了70%,那么就把其中的一条过滤掉,过滤的算法

    public static double getSimilarity(String doc1, String doc2) {
            if (doc1 != null && doc1.trim().length() > 0 && doc2 != null
                    && doc2.trim().length() > 0) {
                 
                Map<Integer, int[]> AlgorithmMap = new HashMap<Integer, int[]>();
                 
                //将两个字符串中的中文字符以及出现的总数封装到,AlgorithmMap中
                for (int i = 0; i < doc1.length(); i++) {
                    char d1 = doc1.charAt(i);
                    if(isHanZi(d1)){
                        int charIndex = getGB2312Id(d1);
                        if(charIndex != -1){
                            int[] fq = AlgorithmMap.get(charIndex);
                            if(fq != null && fq.length == 2){
                                fq[0]++;
                            }else {
                                fq = new int[2];
                                fq[0] = 1;
                                fq[1] = 0;
                                AlgorithmMap.put(charIndex, fq);
                            }
                        }
                    }
                }
     
                for (int i = 0; i < doc2.length(); i++) {
                    char d2 = doc2.charAt(i);
                    if(isHanZi(d2)){
                        int charIndex = getGB2312Id(d2);
                        if(charIndex != -1){
                            int[] fq = AlgorithmMap.get(charIndex);
                            if(fq != null && fq.length == 2){
                                fq[1]++;
                            }else {
                                fq = new int[2];
                                fq[0] = 0;
                                fq[1] = 1;
                                AlgorithmMap.put(charIndex, fq);
                            }
                        }
                    }
                }
                 
                Iterator<Integer> iterator = AlgorithmMap.keySet().iterator();
                double sqdoc1 = 0;
                double sqdoc2 = 0;
                double denominator = 0; 
                while(iterator.hasNext()){
                    int[] c = AlgorithmMap.get(iterator.next());
                    denominator += c[0]*c[1];
                    sqdoc1 += c[0]*c[0];
                    sqdoc2 += c[1]*c[1];
                }
                 
                return denominator / Math.sqrt(sqdoc1*sqdoc2);
            } else {
                throw new NullPointerException(
                        "比较过程发生异常");
            }
        }
     
        public static boolean isHanZi(char ch) {
            // 判断是否汉字
            return (ch >= 0x4E00 && ch <= 0x9FA5);
     
        }
     
        /**
         * 根据输入的Unicode字符,获取它的GB2312编码或者ascii编码,
         * 
         * @param ch
         *            输入的GB2312中文字符或者ASCII字符(128个)
         * @return ch在GB2312中的位置,-1表示该字符不认识
         */
        public static short getGB2312Id(char ch) {
            try {
                byte[] buffer = Character.toString(ch).getBytes("GB2312");
                if (buffer.length != 2) {
                    // 正常情况下buffer应该是两个字节,否则说明ch不属于GB2312编码,故返回'?',此时说明不认识该字符
                    return -1;
                }
                int b0 = (int) (buffer[0] & 0x0FF) - 161; // 编码从A1开始,因此减去0xA1=161
                int b1 = (int) (buffer[1] & 0x0FF) - 161; // 第一个字符和最后一个字符没有汉字,因此每个区只收16*6-2=94个汉字
                return (short) (b0 * 94 + b1);
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
            return -1;
        }
    View Code

    这样就不会再出现标题类似的情况

      (2)标题重复

      为什么会标题重复?

        数据库里抓取了两条相同的内容,查看了一下数据库,发现确实有标题相同的新闻。

      为什么数据库会有标题相同的新闻?

        查记录发现,两条相同的同容,相隔时间很短,这很可能是高并发造成的。多线程操作数据库引起的。

      解决方案:

      采用 生产者消费者 模式,多个生产者线程抓取数据,一个消费者线程消费数据,测试了一下,每个对象大小约为0.1075k 10000个大约是1M, ,所以我就给队列设置大小为10000,即就算队列里存了10000个对象所占内存也才1M,这完全可以接受。

    消费者代码

        public static Logger logger = LoggerFactory.getLogger(SingleThreadTakeQueueTask.class);
        
        /*
         *  每个对象大小大约是0.1075k 10000个大约是1M,
         *  在测试环境中,120个接口,全部开始跑,feedQueue队列中,剩余未被消费的对象,最多的时候剩余量是24个,所以队列长度设置为10000
         *  是够用的
         */
        private static final Integer QUEUE_SIZE = 10000;
        public static BlockingQueue<FeedQueueBean> feedQueue = new  LinkedBlockingQueue<FeedQueueBean>(QUEUE_SIZE); 
        
        
        private FeedService feedService;
        private FeedBannerService feedBannerService;
        private FeedIconService feedIconService;
        
        public SingleThreadTakeQueueTask(){}
        
        
        public SingleThreadTakeQueueTask(FeedService feedService , FeedBannerService feedBannerService,
                FeedIconService feedIconService){
            this.feedService = feedService;
            this.feedBannerService = feedBannerService;
            this.feedIconService = feedIconService;
        }
        @Override
        public void run() {
            
             while(true){
                 
                     
                    logger.info("阻塞,等待读取队列==" , feedQueue.size());
                    FeedQueueBean feedQueueBean = null;
                    try {
                        feedQueueBean = feedQueue.take();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                        logger.error("feedQueueError:{} " , e.getLocalizedMessage());
                        continue;
                    }
                    
                    logger.info("从队列取出 {}" , feedQueue.size());
                    ArticleJsonBean bean = feedQueueBean.getArticleJsonBean();
                    
                    
                    if(isFilterOut(bean)){
                        continue;
                    }
                    
                    // 主表
                    Long feedId = saveFeed(bean, bean.getPartner(), bean.getFeedCategory());
                    if(feedId == null){
                        logger.info("保存feed发生异常");
                        continue;
                    }
                    logger.info("保存feed完成");
                    
                    
                    // 保存banner
                    FeedBanner feedBanner = feedQueueBean.getFeedBanner();
                    saveBanner(feedId, feedBanner);
                    logger.info("保存banner完成");
                    
                    // 保存icon
                    List<FeedIcon> feedIcons = feedQueueBean.getFeedIcons();
                    saveIconList(feedIcons,feedId);
                    logger.info("保存icon完成");
                    
                    // 图片集合
                    if(FeedTaskJob.SHOW_TYPE_PIC_SET == bean.getShow_type() 
                            && feedQueueBean.getFeedPicSetBanner() != null){
                        FeedBanner feedPicSetBanner = feedQueueBean.getFeedPicSetBanner();
                        saveBanner(feedId,feedPicSetBanner);
                    }
                    
                    logger.info("保存set完成");
                    
                    // 白名单
                    if (null != bean.getWhiteList() && bean.getWhiteList().intValue() == 1) {
                        try{
                            feedService.updateFeedStatus(feedId);
                            feedService.auditArticle(feedId, bean.getFeedCategory());
                        }catch(Exception e){
                            e.printStackTrace();
                            logger.error("updateError : {} " , e.getLocalizedMessage());
                        }
                        
                    }
                    logger.info("本次从队列取对象完成");
                    //feedQueue.remove(feedQueueBean);
                 
             }
    
        }
        
        private Long saveFeed(ArticleJsonBean bean, String partner, Long feedCategory) {
            Feed feed = new Feed();
            ExpandParam param = new ExpandParam();
            feed.setCreateTime(System.currentTimeMillis());
            param.setIsCaputer(1);
            param.setThirdUrl(bean.getUrl());
            param.setSource(bean.getAuthor_name()+"("+partner+")");
            bean.setShow_type(bean.getShow_type() == null ? 3 : bean.getShow_type());
            if(bean.getShow_type().intValue() == FeedTaskJob.SHOW_TYPE_PIC_SET){ //showtype是图片集合时,image_number必传
                param.setImageNumber(bean.getImage_number());
            }
            if(bean.getShow_type().intValue() == FeedTaskJob.SHOW_TYPE_VIDEO){ //showtype是视频时,时间必传
                param.setTime(bean.getTime());
            }
            
            JsonConfig config = new JsonConfig(); 
            config.setJsonPropertyFilter(new PropertyFilter() {
                @Override
                public boolean apply(Object source, String name, Object value) {
                    if (value == null || "".equals(value)) {
                        return true;
                    }
                    return false;
                }
            });
            
            feed.setExpandParam(JSONObject.fromObject(param,config).toString());
            feed.setFeedCategory(feedCategory);
            if(StringUtils.isBlank(bean.getDesc())) {
                feed.setFeedDesc(bean.getTitle().trim().replace("
    ", ""));
            } else {
                feed.setFeedDesc(bean.getDesc().trim());
            }
            feed.setFeedType(bean.getShow_type() == null ? 3 : bean.getShow_type());
            feed.setFeedUrl(getHtml5Url(bean.getUrl()));
            feed.setFeedTitle(bean.getTitle().trim().replace("
    ", ""));
            feed.setStatus(0);
            feed.setPublishTime(System.currentTimeMillis());
            
            try{
                
                logger.info("组装feed对象over");
                int result = filterFeedColumnLength(feed);
                
                if(result == 1){
                    feedService.saveFeed(feed);
                    logger.info("保存feed对象over");
                }
                
                
                
            }catch(Exception e){
                e.printStackTrace();
                logger.error("error:{}" , e.getLocalizedMessage());
            }
            return feed.getId();
        }
        
        private int filterFeedColumnLength(Feed feed){
             
            if(feed == null ){
                return 0;
            }
            
            if(feed.getFeedTitle() != null){
                if(feed.getFeedTitle().length() >= 255){
                    logger.info("title需要截取,url {} " , feed.getFeedTitle());
                    feed.setFeedTitle(feed.getFeedTitle().substring(0,254));
                    return 1;
                }
            }
            
            if(feed.getFeedDesc() != null){
                if(feed.getFeedDesc().length() >= 500){
                    logger.info("desc需要截取,url {} " , feed.getFeedDesc());
                    feed.setFeedDesc(feed.getFeedDesc().substring(0,499));
                    return 1;
                }
            }
            
            if(feed.getExpandParam() != null){
                if(feed.getExpandParam().length() >= 1024){
                    logger.info("expandParam需要截取,url {} " , feed.getExpandParam());
                    feed.setExpandParam(feed.getExpandParam().substring(0,1023));
                    return 0;
                }
            }
            
            if(feed.getFeedUrl() != null){
                if(feed.getFeedUrl().length() >= 100){
                    logger.info("url需要截取,url {} " , feed.getFeedUrl());
                    feed.setFeedUrl(feed.getFeedUrl().substring(0,99));
                    return 0;
                }
            }
            return 1;
        }
        
        private String getHtml5Url(String urlString) {
            logger.info("文章地址:{}",urlString);
            String url = null;
            String fileFileName = System.currentTimeMillis() + "_feedStream.html";
            SimpleDateFormat sdf = new SimpleDateFormat("yyyy/MM/dd/");
            String path = sdf.format(new Date());
            String realpath = ConstantApp.getImageRealPathFeedStream() + path;
            File savefile = new File(new File(realpath), fileFileName); 
            String content = "<html>"+
                                "<body>"+
                                "</body>"+
                                "<script type='text/javascript'>"+
                                "    window.location='"+urlString+"';"+
                                "</script>"+
                             "</html>";
            
            try {
                FileUtils.writeStringToFile(savefile, content);
            } catch (IOException e) {
                logger.error("string转换为html出错:" ,e);
            }
            
            url = path + fileFileName;
            return url;
        }
        
        private void saveBanner(Long feedId , FeedBanner feedBanner){
            if(feedId == null || feedBanner == null){
                return;
            }
            feedBanner.setFeedId(feedId);
            logger.info("组装feedBanner over");
            try{
                feedBannerService.saveFeedBanner(feedBanner);
            }catch(Exception e){
                e.printStackTrace();
                logger.error("bannerError : {}" , e.getLocalizedMessage());
                return;
            }
            
            logger.info("保存feedBanner over");
        }
        
        
        private void saveIconList(List<FeedIcon> feedIcons , Long feedId){
            List<FeedIcon> feedIconsTmp = new ArrayList<FeedIcon>();
            for(FeedIcon feedIcon : feedIcons){
                if(feedIcon != null){
                    feedIcon.setFeedId(feedId);
                    feedIconsTmp.add(feedIcon);
                }
            }
            logger.info("组装feedIconList over");
            
            try{
                feedIconService.saveFeedIcons(feedIconsTmp);
            }catch(Exception e){
                e.printStackTrace();
                logger.error("iconError:{} " + e.getLocalizedMessage());
                return;
            }
            logger.info("保存feedIconList over");
        }
    
        private boolean isFilterOut(ArticleJsonBean bean) {
            if(null == bean){
                return true;
            }
            
            if(StringUtils.isBlank(bean.getThumbnail_pic_s())){
                return true;
            }
            if(StringUtils.isBlank(bean.getTitle())){
                return true;
            }
            if(StringUtils.isBlank(bean.getThumbnail_pic_small1())
                    && StringUtils.isBlank(bean.getThumbnail_pic_small2())
                    && StringUtils.isBlank(bean.getThumbnail_pic_small3())){
                return true;
            }
            for (String key : FeedTaskJob.KEY_WORLD) {
                if (bean.getTitle().contains(key)) {
                    logger.info("包含特殊关键词:{}", key);
                    return true;
                }
            }
            
            for (String title : FeedTaskJob.concurentTitleSet) {
                double similarStandard = CosineSimilarAlgorithm.getSimilarity(bean.getTitle(), title);
                if (similarStandard > 0.6d) {
                    logger.info("已经存在该文章标题 ,title:{}", bean.getTitle());
                    return true;
                }
            }
            if (FeedTaskJob.concurrentUrlSet.contains(bean.getUrl())) {
                logger.info("已存在url, title:{} , url:{}", bean.getTitle(),bean.getUrl());
                return true;
            }
            
            FeedTaskJob.concurentTitleSet.add(bean.getTitle());
            FeedTaskJob.concurrentUrlSet.add(bean.getUrl());
            return false;
        }
    View Code
  • 相关阅读:
    如何计算两个日期之间相差天数
    解决并发问题的小技巧
    Linq实现下拉框绑定
    No DataType in DataTemplate in Windows Phone(二)
    使用TOAD操作oracle初步
    使用log4net记录server Log
    尘世一场烟火
    No DataType in DataTemplate in Windows Phone(—)
    MVC设置初始页时发生的无法找到资源的简单错误
    oracle 使用in的灵异事件
  • 原文地址:https://www.cnblogs.com/fubaizhaizhuren/p/5694198.html
Copyright © 2011-2022 走看看