使用正则expression式来parsingHTML:为什么不呢?
这似乎是每个问题在提交者正在使用正则expression式从HTML获取一些信息将不可避免地有一个“答案”,说不使用正则expression式来parsingHTML。
为什么不? 我知道那里有像“ 美味汤 ”这样的引用不引人注目的“真正的”HTMLparsing器,我相信它们是强大而有用的,但是如果你只是在做一些简单,快速或肮脏的事情,那么为什么打扰一些正则expression式可以正常工作的时候使用这么复杂的东西?
此外,是否有一些基本的东西,我不明白正则expression式,使他们通常是一个不好的selectparsing?
整个HTMLparsing对于正则expression式来说是不可能的,因为它依赖于匹配开始和结束标记,这对于正则expression式是不可能的。
正则expression式只能匹配常规语言,但HTML是一种上下文无关的语言 。 HTML上的正则expression式可以做的唯一的事情就是启发式,但是在任何情况下都不行。 应该可以呈现一个HTML文件,这个文件将被任何正则expression式错误地匹配。
对于quick'n'dirty正则expression式将会很好。 但要知道的基本知识是,构build正确parsingHTML的正则expression式是不可能的 。
原因是正则expression式无法处理任意嵌套的expression式。 请参阅可以使用正则expression式来匹配嵌套的模式吗?
就parsing而言,正则expression式在“词法分析”(词法分析器)阶段可能非常有用,input被分解为标记。 在实际的“构build一个分析树”阶段中,它没有那么有用。
对于一个HTMLparsing器,我希望它只接受格式良好的HTML,并且需要超出正则expression式所能做的function(它们不能“计数”并确保给定数量的开始元素由相同数量closures元素)。
两个简单的理由:
- 写一个能忍受恶意input的正则expression式是很难的; 比使用预先构build的工具更难
- 写一个正则expression式,可以用你会不可避免地被卡住的荒谬标记工作很难; 比使用预先构build的工具更难
关于正则expression式的适用性一般来说:它们是不合适的。 你有没有看过你需要parsing大多数语言的那种正则expression式?
(来自http://htmlparsing.com/regexes )
假设你有一个HTML文件,你试图从<img>标签中提取URL。
<img src="http://example.com/whatever.jpg">
所以你在Perl中写这样的正则expression式:
if ( $html =~ /<img src="(.+)"/ ) { $url = $1; }
在这种情况下, $url
确实会包含http://example.com/whatever.jpg
。 但是当你开始获得这样的HTML时会发生什么:
<img src='http://example.com/whatever.jpg'>
要么
<img src=http://example.com/whatever.jpg>
要么
<img border=0 src="http://example.com/whatever.jpg">
要么
<img src="http://example.com/whatever.jpg">
或者你开始从中得到误报
<!-- // commented out <img src="http://example.com/outdated.png"> -->
它看起来非常简单,对于一个单一的,不变的文件可能很简单,但是对于任何你将要对任意HTML数据进行的操作,正则expression式只是未来心痛的一个秘诀。
因为有很多方法可以让浏览器以相当宽松的方式“搞砸”HTML,但是要重现浏览器的自由行为来覆盖所有使用正则expression式的情况需要付出很大努力,所以你的正则expression式在某些特殊情况下将不可避免地失败案件,这可能会在您的系统中引入严重的安全漏洞。
问题是,大多数用户提出一个与HTML和正则expression式有关的问题,因为他们找不到自己的正则expression式。 然后,人们不得不考虑在使用DOM或SAXparsing器或类似的东西时是否会更容易一些。 为了处理类似XML的文档结构,对它们进行了优化和构build。
当然,有些问题可以用正则expression式轻松解决。 但重点在于轻松 。
如果你只是想find所有类似http://.../
URL,那么使用正则http://.../
就可以了。 但是,如果你想查找所有具有类“mylink”的a元素中的URL,最好使用适当的parsing器。
正则expression式不是用来处理一个嵌套的标签结构的,它最好是复杂的(在最坏的情况下是不可能的),以处理所有可能的边缘情况。
我相信答案在于计算理论。 对于要使用正则expression式parsing的语言,必须按照定义“常规”( 链接 )。 HTML不是一种常规的语言,因为它不符合许多常规语言的标准(与HTML代码中固有的多层嵌套有很大关系)。 如果你对计算理论感兴趣,我会推荐这本书。
“这取决于”虽然。 诚然,正则expression式不会,也不能以真正的准确性parsingHTML,因为这里给出的所有原因。 但是,如果错误的后果(例如不处理嵌套标签)是次要的,并且正则expression式在您的环境中超级方便(例如,当您正在窃取Perl)时,请继续。
假设你可能正在parsing链接到你网站的网页 – 也许你是通过Google链接searchfind的 – 而且你想要一个快速的方法来获得围绕你的链接的上下文的总体思路。 您正在尝试运行一些可能会提醒您链接垃圾邮件的小报告。
在这种情况下,错误地分析一些文件不会有什么大不了的。 除了你之外,没有人会看到这些错误,如果你非常幸运的话,你可以单独跟进。
我想我是说这是一个折衷。 有时,实施或使用正确的parsing器 – 就像这样简单 – 如果准确性不重要,可能不值得麻烦。
只要小心你的假设。 例如,如果您试图parsing公开显示的内容,则可以考虑几种方法,正则expression式的快捷方式可能会适得其反。
肯定有使用正则expression式来parsingHTML中某些信息的正确方法 – 这取决于具体的情况。
上面的共识是一般来说这是一个坏主意。 但是,如果HTML结构是已知的(不太可能改变),那么它仍然是一个有效的方法。
这个expression式从HTML元素中检索属性。 它支持:
- 没有引用/引用的属性,
- 单/双引号,
- 在属性里面逃脱了引号,
- 周围的空间等于标志,
- 任何数量的属性,
- 只检查标签内的属性,
- 逃避评论,和
- 在属性值中pipe理不同的引号。
(?:\<\!\-\-(?:(?!\-\-\>)\r\n?|\n|.)*?-\-\>)|(?:<(\S+)\s+(?=.*>)|(?<=[=\s])\G)(?:((?:(?!\s|=).)*)\s*?=\s*?[\"']?((?:(?<=\")(?:(?<=\\)\"|[^\"])*|(?<=')(?:(?<=\\)'|[^'])*)|(?:(?!\"|')(?:(?!\/>|>|\s).)+))[\"']?\s*)
检查出来 。 在“gisx”标志中效果更好,就像在演示中一样。
请记住,虽然HTML本身并不固定,但您正在查看的页面部分可能是常规的。
例如, <form>
标签嵌套错误; 如果网页工作正常,那么使用正则expression式来抓取<form>
将是完全合理的。
我最近做了一些只使用selenium和正则expression式的网页抓取。 我逃避了,因为我想要的数据被放在一个<form>
,并以一个简单的表格格式(所以我甚至可以指望<table>
, <tr>
和<td>
是非嵌套的 -这实际上是非常不寻常的)。 在某种程度上,正则expression式甚至几乎是必要的,因为我需要访问的一些结构被评论界定。 (美丽的汤可以给你意见,但要用美丽的汤来抓住<!-- BEGIN -->
和<!-- END -->
块是很困难的。
如果我不得不担心嵌套表,我的方法根本无法工作! 我将不得不倒回美丽的汤。 然而,即便如此,有时候您可以使用正则expression式来抓取所需的块,然后从那里钻取。
实际上,使用正则expression式的HTMLparsing在PHP中是完全可能的。 你只需要使用strrpos
向后parsing整个string来find<
并且每次使用ungreedy说明符从那里重复正则expression式来获得嵌套标记。 对大件事情不是很花哨,很慢,但是我把它用于我自己的个人模板编辑器。 我实际上并没有parsingHTML,而是为了查询数据库条目来显示数据表(我的<#if()>
标记可以用这种方式突出显示特殊条目)而制作的一些自定义标记。 我并不准备在这里和那里仅仅使用一对自创的标签(其中非常非XML数据)来使用XMLparsing器。
所以,即使这个问题已经死了,它仍然显示在Googlesearch中。 我读它,并认为“挑战接受”,并完成修复我简单的代码,而不必更换一切。 决定给寻求类似原因的人提供不同的意见。 也是最后一个答案是4小时前发布,所以这仍然是一个热门话题。
我也尝试了一个正则expression式。 这对find与下一个HTML标签配对的内容块来说非常有用,它不会查找匹配的closures标签,但是它会selectclosures标签。 用您自己的语言翻阅一个堆栈来检查这些堆栈。
与'sx'选项一起使用。 如果你感觉幸运的话,也是“g”
(?P<content>.*?) # Content up to next tag (?P<markup> # Entire tag <!\[CDATA\[(?P<cdata>.+?)]]>| # <![CDATA[ ... ]]> <!--(?P<comment>.+?)-->| # <!-- Comment --> </\s*(?P<close_tag>\w+)\s*>| # </tag> <(?P<tag>\w+) # <tag ... (?P<attributes> (?P<attribute>\s+ # <snip>: Use this part to get the attributes out of 'attributes' group. (?P<attribute_name>\w+) (?:\s*=\s* (?P<attribute_value> [\w:/.\-]+| # Unquoted (?=(?P<_v> # Quoted (?P<_q>['\"]).*?(?<!\\)(?P=_q))) (?P=_v) ))? # </snip> )* )\s* (?P<is_self_closing>/?) # Self-closing indicator >) # End of tag
这是为Pythondevise的(它可能适用于其他语言,还没有尝试过,它使用积极的lookaheads,反向lookbehinds和命名的反向引用)。 支持:
- 打开标签 –
<div ...>
- closures标签 –
</div>
- 评论 –
<!-- ... -->
- CDATA –
<![CDATA[ ... ]]>
- 自闭合标签 –
<div .../>
- 可选的属性值 –
<input checked>
- 未加引号/引用属性值 –
<div style='...'>
- 单/双引号 –
<div style="...">
- Escaped Quotes –
<a title='John\'s Story'>
(这不是真的有效的HTML,但我是一个很好的人) - 等于标志的空间 –
<a href = '...'>
- 命名捕获有趣的位
在格式不正确的标签上触发也很不错,比如当你忘记一个<
或>
。
如果你的正则expression式支持重复的命名捕获,那么你是金,但Python re
不(我知道正则expression式,但我需要使用香草Python)。 以下是你所得到的:
-
content
– 直到下一个标签的所有内容。 你可以离开这个。 -
markup
– 包含所有内容的整个标记。 -
comment
– 如果是评论,评论内容。 -
cdata
– 如果它是<![CDATA[...]]>
,CDATA内容。 -
close_tag
– 如果它是一个closures标记(</div>
),标记名称。 -
tag
– 如果是开放标签(<div>
),标签名称。 -
attributes
– 标签内的所有属性。 如果你没有得到重复的组,那么使用它来获得所有的属性。 -
attribute
– 重复,每个属性。 -
attribute_name
– 重复,每个属性的名称。 -
attribute_value
– 重复的每个属性值。 这包括报价,如果它是引用。 -
is_self_closing
– 这是/
如果它是一个自我closures的标签,否则没有。 -
_q
和_v
– 忽略这些; 它们在内部用于反向引用。
如果您的正则expression式引擎不支持重复的命名捕获,那么可以使用一个叫做out的部分来获取每个属性。 只要在attributes
组上运行该正则expression式就可以得到每个attribute
, attribute_name
和attribute_value
。
在这里演示: https : //regex101.com/r/mH8jSu/11
对于像HTML这样的语言,正则expression式不够强大。 当然,有一些例子可以使用正则expression式。 但总的来说,parsing并不合适。
你知道吗?你有很多的心态不能这样做,我认为围栏两边的每个人都是非对错。 你可以做到这一点,但它只需要运行一个正则expression式就需要多一点的处理。 拿这个 (我在一个小时内写这个)为例。 它假定HTML是完全有效的,但根据你使用什么语言来应用前面提到的正则expression式,你可以修改一下HTML以确保它能成功。 例如,删除不应该在那里的结束标记: </img>
例如。 然后,将closures的单个HTML正斜杠添加到缺less它们的元素等
我将在编写一个允许我执行类似于JavaScript的[x].getElementsByTagName()
HTML元素检索的库的上下文中使用它。 我只是拼凑了我在正则expression式的DEFINE部分编写的function,并用它来逐步在元素树中进行步进。
那么,这将是validationHTML的最后100%的答案吗? 不,但是这是一个开始,多一点工作,可以做到。 然而,试图在一个正则expression式执行内部执行是不实际的,也不是有效的。
HTML / XML分为标记和内容。
正则expression式只对词法标签parsing有用。
我想你可以推断出内容。
这对于SAXparsing器来说是个不错的select。
标签和内容可以交付给用户
定义函数嵌套/closures的元素
可以跟踪。
只要parsing标签,就可以完成
正则expression式并用于从文档中去除标签。
经过多年的testing,我发现了这个秘密
双向浏览器parsing标签,既好又坏。
正常的元素用这种formsparsing:
这些标签的核心使用这个正则expression式
(?: " [\S\s]*? " | ' [\S\s]*? ' | [^>]? )+
你会注意到这个[^>]?
作为其中一个变化。
这将匹配来自不合格标记的不平衡引号。
它也是正则expression式的唯一最邪恶的根源 。
它的使用方式将触发一个冲击,以满足它的贪婪,必须匹配
量化容器。
如果被动地使用,从来没有问题。
但是,如果你强迫某物匹配穿插它
一个想要的属性/值对,并没有提供足够的保护
从回溯来看,这是一场失控的噩梦。
这是普通的旧标签的一般forms。
注意表示标签名称的[\w:]
?
实际上,代表标签名称的合法字符
是一个令人难以置信的Unicode字符列表。
< (?: [\w:]+ \s+ (?: " [\S\s]*? " | ' [\S\s]*? ' | [^>]? )+ \s* /? ) >
继续前进,我们也看到你只是不能search一个特定的标签
无需parsing所有标签。
我的意思是你可以,但它将不得不使用的组合
(* SKIP)(* FAIL)这样的动词,但是仍然需要parsing所有的标签。
原因是标签语法可能隐藏在其他标签内等。
所以,为了被动地parsing所有的标签,需要像下面这样的正则expression式。
这个特别的匹配不可见的内容 。
作为新的HTML或XML或任何其他开发新的构造,只需将其添加为
其中一个变化。
网页笔记 – 我从来没有见过一个网页(或xhtml / xml)这个
遇到了麻烦。 如果你find一个,让我知道。
性能注意事项 – 速度很快。 这是我见过的最快的标签parsing器
(有可能会更快,谁知道)。
我有几个特定的版本。 作为刮刀也很好
(如果你是动手型)。
完整的原始正则expression式
<(?:(?:(?:(script|style|object|embed|applet|noframes|noscript|noembed)(?:\s+(?>"[\S\s]*?"|'[\S\s]*?'|(?:(?!/>)[^>])?)+)?\s*>)[\S\s]*?</\1\s*(?=>))|(?:/?[\w:]+\s*/?)|(?:[\w:]+\s+(?:"[\S\s]*?"|'[\S\s]*?'|[^>]?)+\s*/?)|\?[\S\s]*?\?|(?:!(?:(?:DOCTYPE[\S\s]*?)|(?:\[CDATA\[[\S\s]*?\]\])|(?:--[\S\s]*?--)|(?:ATTLIST[\S\s]*?)|(?:ENTITY[\S\s]*?)|(?:ELEMENT[\S\s]*?))))>
格式化的外观
< (?: (?: (?: # Invisible content; end tag req'd ( # (1 start) script | style | object | embed | applet | noframes | noscript | noembed ) # (1 end) (?: \s+ (?> " [\S\s]*? " | ' [\S\s]*? ' | (?: (?! /> ) [^>] )? )+ )? \s* > ) [\S\s]*? </ \1 \s* (?= > ) ) | (?: /? [\w:]+ \s* /? ) | (?: [\w:]+ \s+ (?: " [\S\s]*? " | ' [\S\s]*? ' | [^>]? )+ \s* /? ) | \? [\S\s]*? \? | (?: ! (?: (?: DOCTYPE [\S\s]*? ) | (?: \[CDATA\[ [\S\s]*? \]\] ) | (?: -- [\S\s]*? -- ) | (?: ATTLIST [\S\s]*? ) | (?: ENTITY [\S\s]*? ) | (?: ELEMENT [\S\s]*? ) ) ) ) >