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;
}

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

  (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("\n", ""));
} 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("\n", ""));
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;
}

问题--feed列表有新闻重复的问题的更多相关文章

  1. TListView列表拒绝添加重复信息

      //TListView列表拒绝添加重复信息 procedure TForm1.Button1Click(Sender: TObject);var  i: Integer;begin  if (Tr ...

  2. 兰亭集势笔试题:用最优方法从LinkedList列表中删除重复元素

    用运行速度最优的方法从LinkedList列表里删除重复的元素,例如A->B->BB->B->C,返回A->B->BB->C. 考试的时候没完全想明白,考完又 ...

  3. 微信小程序开发-新闻列表之新闻列表绑定

    微信小程序开发-新闻列表之新闻列表绑定开发教程: 1.效果图预览 2.准备工作 在拿到效果图后不要先急着去写代码,而是要去分析一下页面的整体结构,用什么方式定位和布局.小程序里建议使用flex布局,因 ...

  4. Python统计列表中的重复项出现的次数的方法

    本文实例展示了Python统计列表中的重复项出现的次数的方法,是一个很实用的功能,适合Python初学者学习借鉴.具体方法如下:对一个列表,比如[1,2,2,2,2,3,3,3,4,4,4,4],现在 ...

  5. 用最优方法从LinkedList列表中删除重复元素

    用运行速度最优的方法从LinkedList列表里删除重复的元素,例如A->B->BB->B->C,返回A->B->BB->C. 考试的时候没完全想明白,考完又 ...

  6. 在 Excel 中如何使用宏示例删除列表中的重复项

    概要:在 Microsoft Excel 中,可以创建宏来删除列表中的重复项.也可以创建宏来比较两个列表,并删除第二个列表中那些也出现在第一个(主)列表中的项目.如果您想将两个列表合并在一起,或者如果 ...

  7. python删除列表中得重复得数据

    解决思想:将列表转换为 集合,利用集合删除重复数据得特性删除重复数据,然后将集合转换为列表 #删除列表中得重复元素 def delect_1 (lt): s = set(lt) lt = list(s ...

  8. python极简代码之检测列表是否有重复元素

    极简python代码收集,实战小项目,不断撸码,以防遗忘.持续更新: 1,检测列表是否有重复元素: 1 # !usr/bin/env python3 2 # *-* coding=utf-8 *-* ...

  9. Python3基础 set() 删除一个列表中的重复项

    镇场诗: 诚听如来语,顿舍世间名与利.愿做地藏徒,广演是经阎浮提. 愿尽吾所学,成就一良心博客.愿诸后来人,重现智慧清净体.-------------------------------------- ...

随机推荐

  1. MIT研发的新型匿名网络Riffle,下一个Tor

    现在的隐私问题是一个网络热词,如果你担心你上网的隐私会泄露,最有效的解决办法就是使用Tor.这款免费的匿名通信软件,能够让人们在与其他人通信时隐藏自己真实的信息. 虽然Tor是一个很好的匿名网络系统, ...

  2. log4j.properties配置

    一.日志:除了能记录异常信息,还可以记录程序正常运行时的关键信息. 使用log4j来进行日志文件记录经典步骤: 01.在项目中创建一个lib文件夹,然后将下载好的jar包copy到该文件夹下 02.对 ...

  3. jdbcTemplate之jdbc模板技术

    1:为什么要使用jdbcTemplate? 在实际开发中使用jdbc技术太过复杂,为了减少代码冗余,操作简单 步骤一:创建实体类 package beans; public class Book { ...

  4. [No000087]Linq排序,SortedList排序,二分法排序性能比较

    using System; using System.Collections; using System.Collections.Generic; using System.Diagnostics; ...

  5. UINavigationController的创建和相关设置---学习笔记四

    导航控制器 一.设置字体大小,背景等. 二.自定义返回按钮. 三.设置手势. 一.导航中也有个appearance属性,通过它可以设置所有导航的颜色. 二.自定义返回按钮. 1.首先需要知道的是,要把 ...

  6. 配置Jenkins使用Gitlab的代码库进行构建

    1. 首先确认Jenkins上安装了Git plugin, 以及Subversion plugin Manage Jenkins -> Plugin Manager -> Availabl ...

  7. C#.NET 大型通用信息化系统集成快速开发平台 4.1 版本 - .NET商业化成品成熟各种数据权限的需求对应例子代码

    还是我上次提出的那个问题问题:假设一个订单表,1.角色A可以看自己的2.角色B可以看工作组的3.角色C可以看金额是1000元以下的(自定义条件是否可行?如果可以,请详细说明)4.角色D可以看整个部门的 ...

  8. mysqli_fetch_assoc php的新的库函数

    注释:mysql_fetch_assoc() 函数 定义和用法mysql_fetch_assoc() 函数从结果集中取得一行作为关联数组. 返回根据从结果集取得的行生成的关联数组,如果没有更多行,则返 ...

  9. 代码覆盖工具(gcov、lcov)的使用

    一.安装 gcov:是随gcc一起发布的,并不需要独立安装:lcov:其他博客说是随ltp发布的,结果下载下ltp之后编译了10多分钟,最后也没见lcov,最后到sourceforge下载了lcov单 ...

  10. 报错: Unable to start activity ComponentInfo:You need to use a Theme.AppCompat theme

    转自 http://www.2cto.com/kf/201605/506596.html