周末两天在家闲着没事,于是整理了一下之前的的文档和一些琐碎的测试代码,居然发现了几个月前写的一个新闻类主题型网页正文文本自动抽取模块。当时写的比较简单和粗糙,虽然抽取结果差强人意,但是也还勉强说得过去。于是清理一下代码上的灰尘,做了一个小Demo,分享一下。
作者写这篇文章的主要目的在于抛砖引玉,同时希望能够以此为契机,与诸位大牛讨论一下机器网页内容智能识别方面的。作者自知才疏学浅,文中如有描述不当之处,请不吝指正,感激不尽!
应该说,在WEB分块领域,已经有大量的研究工作。由于HTML语法的灵活性,目前大部分的网页都没有完全遵循W3C规范,这样可能会导致DOM树结果的错误。更重要的是,DOM树最早引入是为了在浏览器中进行布局显示,而不是进行WEB页面的语义结构描述。某些文献中提到,根据标签把网页分成若干内容块,这些分块方法流程简单,但面对日益复杂的网页和不规范的网页结果,其分块效果往往不能令人满意。
另一类方法从视觉特征对页面结构进行挖掘。典型的代表就是微软亚洲研究院提出的VIPS(Vision-based Page Segmentation)。它利用WEB页面的视觉提示,如背景颜色、字体颜色和大小、边框、逻辑块和逻辑块之间的间距等,结合DOM树进行语义分块,并把它应用在了TREC2003的评测中,取得较好的效果。但是由于视觉特征的复杂性,如何保证规则集的一致性是一大难点,另外VIPS算法需要计算和保存DOM树中所有节点的视觉信息,导致该算法在时间和内存上的消耗比较大,使得在处理含有大量DOM节点的网页时性能不高。
利用网页的视觉特征和DOM树的结构特性对网页进行分块,并采用逐层分块逐层删减的方法将与正文无关的噪音块删除,从而得到正文块。对得到的正文块运用VIPS算法得到完整的语义块,最后在语义块的基础上提取正文内容。试验表明,这种方法是切实可行的。
文本网页可分为两种类型:主题型网页、目录型网页。主题型网页通常通过成段的文字描述一个活多个主题,虽然主体性网页也会出现图片和超链接,但是这些图片和超链接并不是网页的主体。目录型网页通常提供一组相关或者不相关的连接,本文所研究的正文信息提取指的是针对新闻类主题型网页中核心文本的提取,理论上来讲,应用本文所述研究成果,将算法稍加改造,将同样适用于论坛类主题型网页的正文提取。
分块统计算法
该算法中首先将DOM树中的节点类型分为四类,分别是容器类、文本类、移除类和图片类。
/** * 检查指定DOM节点是否为容器类节点 * @param node * @return */ protected boolean checkContainerType(DomNode node) { return (node instanceof HtmlUnknownElement || node instanceof HtmlFont || node instanceof HtmlListItem || node instanceof HtmlUnorderedList || node instanceof HtmlDivision || node instanceof HtmlCenter || node instanceof HtmlTable || node instanceof HtmlTableBody || node instanceof HtmlTableRow || node instanceof HtmlTableDataCell || node instanceof HtmlForm); } /** * 检查指定DOM节点是否为文本类节点 * @param node * @return */ protected boolean checkTextType(DomNode node) { return (node instanceof HtmlSpan || node instanceof DomText || node instanceof HtmlParagraph); } /** * 检查指定DOM节点是否为移除类节点 * @param node * @return */ protected boolean checkRemoveType(DomNode node) { return (node instanceof HtmlNoScript || node instanceof HtmlScript || node instanceof HtmlInlineFrame || node instanceof HtmlObject || node instanceof HtmlStyle || node instanceof DomComment); } /** * 检查指定DOM节点是否为图片类节点 * @param node * @return */ protected boolean checkImageType(DomNode node) { return (node instanceof HtmlImage); }
算法将文本类节点作为探测的直接对象,在探测过程中遇到移除类节点时直接移除,遇到图片类节点时不做统计,直接跳过,但是不会移除该类节点,遇到容器类节点时,直接探测其子节点。
/** * 检查指定节点是否含有容器类节点,如果有则继续递归跟踪,否则探测该节点文本数据 * @param nodeList * @return 是否含有容器类节点 */ protected boolean checkContentNode(List<DomNode> nodeList) { boolean hasContainerNode = false; if (nodeList != null) { for (DomNode node : nodeList) { if (checkRemoveType(node)) { node.remove(); } else if (checkContainerType(node)) { List<DomNode> list = node.getChildNodes(); if (list != null) { hasContainerNode = true; if (!this.checkContentNode(list)) { if (cleanUpDomNode(node)) { checkNode(node); } } } } else { if (node.isDisplayed() && checkTextType(node)) { checkNode(node); } else { cleanUpDomNode(node); } } } } return hasContainerNode; } /** * 清理指定节点内的无效节点 * @param element * @return 该节点是否有效 */ protected boolean cleanUpDomNode(DomNode element) { if (element == null) { return false; } List<DomNode> list = element.getChildNodes(); int linkTextLength = 0; boolean flag = false; if (list != null) { for (DomNode node : list) { if (checkTextType(node)) { continue; } else if (checkRemoveType(node)) { node.remove(); flag = true; } else if (checkImageType(node)) { //图片类型节点暂时不作处理 } else if (node instanceof HtmlAnchor) { String temp = node.asText(); temp = encoder.encodeHtml(temp); int length = Chinese.chineseLength(temp.trim()); if (length > 0) { linkTextLength += length; } } else if (checkContainerType(node)) { if (!cleanUpDomNode(node)) { node.remove(); } flag = true; } } } String content = element.asText(); content = encoder.encodeHtml(content); return (flag || Chinese.chineseLength(content.trim()) - linkTextLength > 50 || (content.trim().length() - Chinese.chineseLength(content) > 5 && !flag)); }
当算法首次探测到有效文本块时,记录该文本块的父节点块,并推测其为该网页正文文本所在区域,稍后发现可能性更高的块时,直接替换前一个推测为网页正文区域的块。
/** * 推测正文文本区域信息 * @param node */ protected void checkNode(DomNode node) { String temp = node.getTextContent(); temp = encoder.encodeHtml(temp); int length = Chinese.chineseLength(temp.trim()); temp = null; if (contentNode != null && contentNode.equals(node.getParentNode())) { maxLength += length; } else if (length > maxLength) { maxLength = length; contentNode = node.getParentNode(); } }
这样的逻辑适合于新闻、博客等整个网页中只有一个文本区域的主题型网页,而不适合与论坛、贴吧等网页中有多个兄弟文本区域的主题型网页。这里可以稍加改造,对探测到的所有文本块,及其所在区域做一个联合统计和推测,即可适用于论坛主题的网页。
该步骤采用递归探测,探测结果为0个或1个DOM节点(如果是论坛类主题页面,识别结果就会是N个DOM节点)。
/** * 在N个DOM节点中搜索正文文本所在的节点 * @param nodeList * @return */ public DomNode searchContentNode(List<DomNode> nodeList) { checkContentNode(nodeList); if (cleanUpDomNode(contentNode)) { return contentNode; } else { return null; } }
机器学习
当智能识别模块识别某个网页正文文本成功时,其将自动保存该网页的相关信息和探测结果数据(目前仅仅记录探测到的正文区域的XPath,稍后可能会保存一个支持序列化和反序列化的对象来存储更多探测结果),下次再探测该网页或探测来自该网页所在domain的其他网页时,将首选从知识库中取出之前的经验,来抽取正文,如果抽取失败,则继续尝试智能识别。目前每一个domain只支持保存一条经验,但是稍后可以扩展成一个列表,并按照其成功应用的次数来排序,甚至于在该模块内添加一个过滤链,在智能识别前首先将任务通过过滤链,如果在过滤链内根据以往经验抽取成功,则直接返回,否则再由智能识别模块探测正文文本。将来也有可能会将机器学习和人工指导结合起来,既可以自主积累知识,也可以从数据源中加载认为添加的知识。
以下代码并不能构成真正的机器学习,实际上,这里仅仅只做了一个演示。
/** * 在网页中搜索正文文本 * @param html * @return 识别成功后,将封装一个PageData对象返回<br/> * 之所以返回一个对象,是考虑到将来可能加强该算法后,能够从页面中抽取更多数据,PageData对象能够更好的封装这些结果 */ public static PageData spotPage(HtmlPage html) { if (html == null) { return null; } String domain = URLHelper.getDomainByUrl(html.getUrl()); String contentXPath = contentXPathMap.get(domain); PageData pageData = null; HtmlEncoder encoder = HtmlEncoderFactory.createHtmlEncoder(html.getPageEncoding()); AutoSpot2_2 spoter = new AutoSpot2_2(encoder); List<DomNode> nodeList = null; if (contentXPath != null) { log.debug("在经验库中找到站点[" + domain + "]的相关数据,正在尝试应用. . ."); nodeList = (List<DomNode>) html.getByXPath(contentXPath); } else { log.debug("第一次遇到站点[" + domain + "],正在尝试智能识别. . ."); } if (nodeList == null || nodeList.isEmpty()) { nodeList = html.getBody().getChildNodes(); log.debug("经验库中关于站点[" + domain + "]的相关数据无法应用于当前网页,正在尝试重新识别. . ."); } if (nodeList != null) { DomNode node = spoter.searchContentNode(nodeList); if (node != null) { pageData = new PageData(); pageData.setContent(encoder.encodeHtml(node.asXml())); pageData.setTitle(html.getTitleText()); contentXPathMap.put(domain, node.getCanonicalXPath()); } } return pageData; }
Demo运行截图
第一次识别网页用时50毫秒
第二次识别,使用之前的探测结果,直接在页面中抽取数据,用时更短
抽取结果--网页正文的HTML代码,当然,结果也可以是纯文本。
就目前而言,该算法的探测结果还是勉强能够让人接受的,通过对来自百度新闻的新闻页面进行跟踪和识别,识别成功率超过90%,准确率则低很多,不到80%。
原创文章,转载请注明出处:http://www.yshjava.cn/post/331.html
相关推荐
基于分块技术的改进LPB人脸识别算法的研究 人脸识别是计算机视觉和机器学习领域中的一种关键技术,旨在通过...基于分块技术的改进LPB人脸识别算法的研究旨在提高人脸识别的精度和效率,推动人脸识别技术的发展和应用。
人脸识别算法可以分为两大类:基于传统方法的人脸识别算法和基于深度学习方法的人脸识别算法。传统方法的人脸识别算法主要依靠手工设计的特征提取方法,而深度学习方法的人脸识别算法则依靠卷积神经网络...
"基于分块LBP融合特征和SVM的人脸识别算法" 人脸识别是一种生物识别技术,通过提取人脸图像的特征来识别个体身份。近年来,人脸识别技术得到了快速发展,应用于各种领域,如身份验证、人机交互、监控系统等。然而,...
提出一种基于相似性竞争选择的多彩色图像自适应颜色混合迁移算法。首先将彩色图像的颜色空间转换为l?琢?茁空间,然后利用亮度图像信息熵检索选择与灰度图像内容最匹配的多幅彩色图像作为彩色参照图像;然后利用相似...
"基于分块加权LBP技术的人脸识别算法" 人脸识别是生物识别技术的一种,通过人的脸部特征信息进行身份识别。LBP(Local Binary Patterns)算法是一种灰度范围内的纹理描述方式,但传统LBP算子提取的特征信息只能体现...
对于基于统计特征的人脸识别方法,这种基于分块小波的算法为提高识别准确性和效率提供了一个新的思路。 关键词:分块小波变换、PCA+LDA、KNN、SVM、分类器组合 总结来说,本文提出的人脸识别算法通过分块小波变换...
【基于分块加权的LBP算法在人脸识别中的应用】 人脸识别技术是计算机视觉领域的一个重要研究方向,广泛应用于安全监控、身份验证、智能家居等多个场景。然而,传统的基于PC平台的人脸识别系统通常需要高性能硬件...
一种基于LBP和CNN的人脸识别算法 本文主要研究一种基于LBP(Local Binary Pattern,局部二值模式)和CNN(Convolutional Neural Network,卷积神经网络)的人脸识别算法,以解决直接将人脸图像作为卷积神经网络的...
《基于分块相位一致性的人脸识别算法》 人脸识别技术是生物识别领域的重要研究方向,其在安全、监控、身份认证等应用场景中有着广泛的应用。然而,传统的基于可见光的人脸识别方法容易受到光照条件、面部表情变化等...
总结来说,"测试基于遥感影像的机器学习算法"旨在利用SVM、随机森林和LightGBM等工具,对遥感影像进行分类和目标识别。这些算法各有优势,可以根据实际问题的特性选择合适的方法。"data"文件可能提供了实验所需的...
"基于MBP算法和深度学习的人脸识别" 这篇论文提出了一个基于MBP算法和深度学习的人脸识别方法,以解决深度学习中的人脸图像特征提取时忽略局部结构特征和缺乏旋转不变性学习的问题。该方法首先使用多尺度单演滤波器...
近几年,人脸识别技术在不同场景下应用越来越广,人们基于人脸分块近似对称性预处理的人脸识别算法来解决非限制场景下局部遮挡带来的准确率不高、稳定性差等问题。 人脸识别是一个典型的图像模式分析、理解与分类...
数字图像中通过fisher变换图像分块能有效的识别人脸,同时有很好的鲁棒性。
总的来说,基于图像分块的改进Fisher人脸识别算法是针对原始Fisherfaces算法的一种优化,通过分块和2D LDA的结合,有效地解决了小样本问题,提高了分类速度,并在实际数据库上展示了其优良的识别性能。这一方法对于...
"基于稀疏表示的分块人脸识别算法" 人脸识别是一种复杂的模式识别问题,受到多种因素的影响,如光照、表情、遮挡和对齐等。因此,人脸识别算法需要考虑到这些影响因素,以提高识别率。 稀疏表示是一种有效的特征...