Java中基于模板提取网页信息
Posted
技术标签:
【中文标题】Java中基于模板提取网页信息【英文标题】:Extracting webpage information based on a template in Java 【发布时间】:2013-02-18 13:30:10 【问题描述】:现在我使用Jsoup 从一些第三方网页中提取某些信息(不是所有文本),我会定期这样做。这工作正常,直到某些网页的 html 发生变化,这种变化会导致现有 Java 代码发生变化,这是一项繁琐的工作,因为这些网页变化非常频繁。它还需要程序员来修复 Java 代码。这是我在网页上感兴趣的 HTML 代码示例:
<div>
<p><strong>Score:</strong>2.5/5</p>
<p><strong>Director:</strong> Bryan Singer</p>
</div>
<div>some other info which I dont need</div>
现在这是我想做的,我想在本地保存这个网页(一个 HTML 文件)并从中创建一个模板,例如:
<div>
<p><strong>Score:</strong>MOVIE_RATING</p>
<p><strong>Director:</strong>MOVIE_DIRECTOR</p>
</div>
<div>some other info which I dont need</div>
连同网页的实际 URL,这些 HTML 模板将成为 Java 程序的输入,Java 程序将找出这些预定义关键字的位置(例如 MOVIE_RATING、MOVIE_DIRECTOR ) 并从实际网页中提取值。
这样我就不必在每次网页更改时都修改Java程序,我只需保存网页的HTML并用这些关键字替换数据,其余的将由程序处理。例如,将来实际的 HTML 代码可能如下所示:
<div>
<div><b>Rating:</b>**1/2</div>
<div><i>Director:</i>Singer, Bryan</div>
</div>
相应的模板将如下所示:
<div>
<div><b>Rating:</b>MOVIE_RATING</div>
<div><i>Director:</i>MOVIE_DIRECTOR</div>
</div>
也可以由非程序员、任何可以编辑文件的人来创建此类模板。
现在的问题是,我如何在 Java 中实现这一点,是否有任何现有的更好的方法来解决这个问题?
注意: 在谷歌上搜索时我发现了一些研究论文,但其中大多数都需要一些事先的学习数据,准确性也是一个问题。
【问题讨论】:
【参考方案1】:如何在 Java 中实现这一点,是否有任何现有的更好的方法来解决这个问题?
模板方法是一个很好的方法。您在问题中给出了所有原因。
您的模板将仅包含您要处理的 HTML,没有其他内容。这是基于您的示例的示例。
<div>
<p><strong>Score:</strong>MOVIE_RATING</p>
<p><strong>Director:</strong>MOVIE_DIRECTOR</p>
</div>
基本上,您将使用 Jsoup 来处理您的模板。然后,当您使用 Jsoup 处理网页时,您会检查所有已处理的模板以查看是否有匹配项。
在模板匹配中,您在已处理的模板中找到关键字,然后在已处理的网页中找到相应的值。
是的,这将是很多编码,而且比我的描述更难。您的 Java 程序员将不得不将此描述分解为越来越简单的任务,直到她或他能够编写这些任务。
【讨论】:
我想了想,意识到使用Jsoup解决这个问题可能不是一个好主意,Jsoup高度依赖于DOM元素的位置,Jsoup Java代码必须写一个模板,什么时候网页 HTML 更改,相应的模板也将需要更改,这将导致 Java 代码更改。 另一个问题是模板数量多时,比较次数会增加,例如对于 100 个模板,将进行大约 100^2 次比较,这需要时间。 10,000 次比较并没有那么耗时。我从来没有使用过 Jsoup。但是,任何 HTML 解析器都会保持元素的位置。我认为这就是模板的意义所在。【参考方案2】:如果网页经常更改,那么您可能希望将 MOVIE_RATING 等字段的搜索限制在页面的最小可能部分,而忽略其他所有内容。有两种可能性:您可以为每个字段使用正则表达式,或者您可以使用某种 CSS 选择器。我认为任何一个都可以工作,并且“模板”可以包含一个简单的搜索表达式列表,正则表达式或 css,您可以应用。只需滚动列表并提取您可以提取的内容,如果由于页面更改而未找到某些特定字段,则失败。
例如,正则表达式可能如下所示:
"Score:"(.)*[0-9]\.[0-9]\/[0-9]
(我没有测试过。)
【讨论】:
您提供的方法与吉尔伯特的方法非常相似,除了正则表达式部分。我不想进入丑陋的正则表达式世界,我计划在电影信息之外的许多其他领域使用模板方法,例如价格、产品规格提取等。同样在本例中,审阅者可以给出类似3.5/5, A-,*** or two and half
的评级,我必须创建多个正则表达式才能获得这个值。
正则表达式只是表达提取一段文本的规则的一种方式。您必须以一种或另一种方式表达这些规则。您必须在代码中、作为 css 选择器或在正则表达式中执行此操作。你当然可以简化我建议的正则表达式:“Score:”~“
”。这将捕获所有分数,无论格式如何,代价是依赖尾随“”的存在。
如果 HTML 从 <p><strong>Score:</strong>2.5/5</p>
更改为 <p>Rating: A-</p>
,我将不得不更新正则表达式,这正是我想要避免的。只是为了强调我对正则表达式的看法:***.com/questions/1732348/…
我的观点仍然成立。如果 HTML 发生变化,那么 something 必须在您的爬虫代码或模板或正则表达式中发生变化。没有魔法可以阅读页面并从语义上理解它。谷歌“java screen scraper”来了解其他人是如何解决这个问题的。顺便说一句,bobince 是错误的。正则表达式完全适合定位页面中您不关心 dom 的非常小的部分。
原来的问题里有,当网页的HTML发生变化时,整个模板会发生变化,模板将与原始网页具有相同的HTML代码,但用关键字代替真实数据。您能否提供一个链接,其中有人使用屏幕刮刀解决了类似问题。我对所有语言都开放,而不仅仅是 java。【参考方案3】:
这里不是真正的基于模板的方法,但如果您只是将 Selector 查询外部化到配置文件,jsoup 仍然是一个可行的解决方案。
您的非程序员甚至不必看 HTML,只需更新配置文件中的选择器。像SelectorGadget 这样的东西会让你更容易选择实际使用的选择器。
【讨论】:
【参考方案4】:或者您可以尝试不同的方法,使用我称之为“规则”而不是模板:对于您需要从页面中获取的每条信息,您可以定义提取文本的 jQuery 表达式。通常当页面变化很小时,同样编写良好的 jQuery 表达式仍然会给出相同的结果。
然后您可以使用Jerry(Java 中的jQuery),使用几乎相同的表达式来获取您要查找的文本。因此,它不仅与选择器有关,而且您还有其他用于遍历/过滤 DOM 树的 jQuery 方法。
例如,某些 Director 文本的规则将是(以 sudo-java-jerry-code 的形式):
$.find("div#movie").find("div:nth-child(2)")....text();
规则中可能有更多(和更复杂)的表达式,分布在多行中,例如迭代一些节点等。
如果你是 OO 人,每个规则都可以在自己的实现中定义。如果您是 groovy 人,您甚至可以在需要时重写规则,而无需重新编译您的项目,并且仍然在 java 中。等等。
如您所见,这里的核心思想是定义规则如何找到您的文本;并且不匹配模式,因为这可能对微小的变化很脆弱 - 想象一下如果在两个 div 之间添加了一个空格:)。在我的这个示例中,我使用了类似 jQuery 的语法(实际上,它是类似 Jerry 的语法,因为我们使用的是 Java)来定义规则。这只是因为 jQuery 流行且简单,并且您的 Web 开发人员也知道;最后,您可以定义自己的语法(取决于您使用的解析工具):例如,您可以将 HTML 解析为 DOM 树,然后使用您的辅助方法编写规则,如何将其遍历到感兴趣的地方。 Jerry 还允许您访问底层 DOM 树。
希望这会有所帮助。
【讨论】:
这听起来很有趣,这样的规则可以使用Rhino吗?如果在这种情况下是,我可以以键:值对的形式编写这些规则,例如movie_rating:$.find("div#movie").find("div:nth-child(2)")
一开始我会尝试跳过大犀牛(它很大而且可能很慢)。我会尝试使用 Jerry - 当然,如果这对你有意义 - 因为它在 java 中,你可以用它编写类似 jquery 的语法(参见文档)。如果由于某种原因这对您不起作用,是的,您可能可以使用 Rhino 并触发 javascript 事件。【参考方案5】:
您提供的方法与吉尔伯特的方法非常相似,除了 正则表达式部分。我不想踏入丑陋的正则表达式世界,我是 计划在许多其他领域使用模板方法,除了 电影信息价格、产品规格提取等。
您描述的模板实际上并不是正常意义上的“模板”:一组静态内容被转储到输出,其中插入了一堆动态内容。相反,它是模板的“逆向”——它是一种被吞并丢弃的解析模式,留下需要的参数。
因为您的网页会定期更改,所以您不希望将要解析的内容硬编码得太精确,而是希望“放大”其基本特征,尽量减少假设。即,您希望承诺按字面意思匹配“Rating:”等关键文本,并以更灵活的方式处理诸如"<b/>"
之类的交错标记 - 忽略它并允许它在不中断的情况下更改。
当您结合 (1) 和 (2) 时,您可以给结果起任何您喜欢的名称,但它是使用正则表达式进行解析。即模板方法是使用正则表达式的解析方法 - 它们是相同的。问题是:正则表达式应该采用什么形式?
3A。如果您使用 java 手动编码进行解析,那么显而易见的答案是正则表达式格式应该只是 java.util.regex
格式。其他任何东西都是开发负担并且是“非标准的”并且难以维护。
3B。如果您想使用支持 html 的解析器,那么 jsoup 是一个很好的解决方案。问题是您需要比 jsoup 提供的更多的文本/正则表达式处理和灵活性。它似乎过于锁定特定的 html 标记和结构,因此在页面更改时会中断。
3C。您可以使用更强大的语法控制的通用文本解析器,例如 ANTLR - 一种受 backus-naur 启发的语法形式用于控制解析,并插入生成器代码来处理解析的数据。在这里,解析语法表达式确实非常强大,它具有复杂的规则,用于文本在页面上的排序方式以及文本字段和值如何相互关联。由于您没有处理语言,因此功能超出了您的要求。无可避免的事实是,您仍然需要描述要跳过的丑陋部分 - 例如标记标签等。第一次与 ANTLR 较量涉及教育投资,然后才能获得生产力回报。
3D。是否有一个 java 工具只使用简单的模板类型方法来给出一个简单的答案?好吧,谷歌搜索并没有给太多希望https://www.google.com/search?q=java+template+based+parser&ie=utf-8&oe=utf-8&aq=t&rls=org.mozilla:en-GB:official&client=firefox-a。我相信任何创建这样一个野兽的尝试都会退化为基本的正则表达式解析或更高级的语法控制解析,因为匹配/忽略/替换文本的基本要求推动了这些方向的解决方案。其他任何事情都太简单而无法实际工作。对不起,负面观点 - 它只是反映了问题空间。
我投票赞成 (3A),因为它是满足您需求的最简单、最强大和最灵活的解决方案。
【讨论】:
感谢您的详细回答,您如何看待 Igor Spasic 方法?现在看来我没有太多选择,所以我更倾向于 Igor 的方法,而不是基于正则表达式的方法。 在 Igor 的回答中,他给出了 JQuery 文本匹配的例子 $.find("div#movie").find("div:nth-child(2)")....text( );这完全取决于页面中标签的结构——你不想要什么。使用正则表达式提取字段的示例: String stripped=htmlString.replaceAll("\"); Pattern pattern = Pattern.compile("评分:\s*([1/2])\s导演:([a-zA-Z,.\-'])");匹配器 matcher=pattern.match(stripped); while (matcher.find()) String rating=matcher.group(1);字符串 director=matcher.group(2); 【参考方案6】:我使用以下方法在我的一个个人项目中执行类似的操作,该项目从此处the leading real estate website in spain 生成一个 RSS 提要。
使用这个工具,我找到了我目前居住的租来的地方;-)
-
从页面中获取 HTML 代码
将 HTML 转换为 XHTML。我用了这个this library 我想今天可能会有更好的选择
使用 XPath 将 XHTML 导航到您感兴趣的信息
当然,每次他们更改原始页面时,您都必须更改 XPath 表达式。我能想到的另一种方法——对原始 HTML 源的语义分析——远远超出了我的卑微技能;-)
【讨论】:
以上是关于Java中基于模板提取网页信息的主要内容,如果未能解决你的问题,请参考以下文章