[译文]使用 Mojo::DOM 来解析和处理 HTML
每个人都会用到的就是解析 HTML, 很多人都是使用正则来进行解析. 当然我们是可以使用正则, 但是相比起我最喜欢的方案使用 Mojo::DOM 这个模块所提供的 CSS3 的选择器可以直接进行 DOM 元素的操作来讲, 这个方案有意思多了.
相比起早期我来尝试记住和使用 XPATH 来讲, 这个 Mojo 也更好.
这的 DOM 是指 "文档对象模型". 它可以用于解析和组织信息, 并用来访问和查询其中的一些内容, 如 "找到全面的 div 的标记" 或 "找到指定类的全部内容". 这样不需要我们自己来操作文本文件本身.
如果我们使用 Mojo::UserAgent, 我们可以直接从 HTTP 的响应中取得 DOM 对象:
useMojo::UserAgent; my$ua= Mojo::UserAgent->new; my$dom= $ua->get( 'http://search.cpan.org/~bdfoy/') ->res ->dom;
在这魔咒 (Mojolicious) 是使用的方法链风格的方式进行方法调用. 对于复杂的任务, 这个非常有优势.
我们这时并不一定需要靠 Mojo::UserAgent 才能得到 DOM 对象. 我们也可以解析本地 (非服务器) 的 HTML 文件. 这也非常容易.
我们还能手动处理和删除我们并不想要的部分. 这并不需要使用正则就能做到. 这也不需要保存中间的状态. 直接使用 DOM 的操作就行, 没问题.
还是上面这个例子, 假设, 我们给 http://search.cpan.org/~bdfoy/' 这个链接的内容下载到本地 (这是 CPAN 搜索作者的页面), 并读出来了存到 $string 变量中.
useMojo::DOM; use5.010; my$string= ...; my$dom= Mojo::DOM->new( $string); say $dom ->find('a') ->join("\n");
在这, 我们有一个 $dom 的对象, 我们使用 find 的方式来选择元素, 这是使用的 CSS3 的选择器, 这时会找到所有的 a 标签的所有链接. 因为内容是一堆链接, 所以会返回 Mojo::Collection 对象(多个结果),
我们可以给这个对象想象成一个数组一样的列表和它能做些什么.
魔咒 (Mojolicious) 使用了大量的这种方法链的方式来调用, 这时我们对于处理的结果, 需要输出来, 这时, 我们使用 join 来加入了一些换行符. 输出的结果如下:
<a href="/"><img alt="CPAN"src="http://st.pimg.net/tucs/img/cpan_banner.png"></a> <a href="/">Home</a> <a href="/author/">Authors</a> <a href="/recent">Recent</a> <a href="http://log.perl.org/cpansearch/">News</a> <a href="/mirror">Mirrors</a> <a href="/faq.html">FAQ</a> <a href="/feedback">Feedback</a> <a href="Acme-BDFOY-0.01/">Acme-BDFOY-0.01</a> <a href="/CPAN/authors/id/B/BD/BDFOY/Acme-BDFOY-0.01.tar.gz">Download</a> <a href="/src/BDFOY/Acme-BDFOY-0.01/">Browse</a>
这是一个非常好的开始, 接着我们可能想对提取出来的所有的这些链接, 来进行限制, 从上面看得到, 我们要的是模块名和链接地址, 但上面还输出了网页中的 HEAD 中一些分类之类的休息.
所以这时我们要对提取的内容进行限制, 由下可以见到, 我们是需要的是 tr 标签中有 td 的内容下面的 a 标签的链接.
<trclass=s> <td><ahref="Data-Constraint-1.17/">Data-Constraint-1.17</a></td> <td>prototypical value checking</td> <td><small>[<ahref="/CPAN/authors/id/B/BD/BDFOY/Data-Constraint-1.17.tar.gz">Download</a>] [<a href="/src/BDFOY/Data-Constraint-1.17/">Browse</a>]</small></td> <tdnowrap>26 Aug 2014</td> </tr>
我们这时修改我们的选择器, 对于链接我们需的是 tr 表格中第一行中的第一个单元格 td:
say $dom ->find('tr td:first-child a:first-child') ->join("\n");
使用 'tr td:first-child a:first-child' 之后(这是 CSS 选择器的语法, 很简单, 值得看看), 现在我得到的新的列表, 是象下面这样. 这是 HTML 的文本:
<ahref="Acme-BDFOY-0.01/">Acme-BDFOY-0.01</a> <ahref="Apache-Htaccess-1.4/">Apache-Htaccess-1.4</a> <ahref="Apache-iTunes-0.11/">Apache-iTunes-0.11</a> <ahref="App-Module-Lister-0.15/">App-Module-Lister-0.15</a> <ahref="App-PPI-Dumper-1.02/">App-PPI-Dumper-1.02</a>
所以这时我们还有一些工作需要做, 我们只需要信息, 给无用的标签去掉, 我们可以直接提取 href 的属性. 我们可以通过 map 的方法来操作 Mojo::Collection 对象中每个元素:
say $dom ->find('tr td:first-child a:first-child') ->map( attr => 'href') ->join("\n");
上面的列表产生的每一个 collection 的内容, 其实也是一个 Mojo::DOM 对象. 所以在这的 map 方法会调用会默认使用 Mojo::DOM 对象, 第一个参数会是这个对象的方法, 其它的内容会以参数传递给方法.
在这个例子中, 我们每行在对象上调用的是 attr('href') 方法.现在我们可以得到如下的值:
Acme-BDFOY-0.01/ Apache-Htaccess-1.4/ Apache-iTunes-0.11/ App-Module-Lister-0.15/ App-PPI-Dumper-1.02/
我们并不想要这个斜线, 我们还可以接着使用 map 在处理一次, 这次直接使用匿名函数, 来对结果集中所有元素进行替换.
say $dom ->find('tr td:first-child a:first-child') ->map( attr => 'href') ->map(sub{s|\/||;$_}) ->join("\n");
这时我们可以取到的列表是这样:
Acme-BDFOY-0.01 Apache-Htaccess-1.4 Apache-iTunes-0.11 App-Module-Lister-0.15 App-PPI-Dumper-1.02
我们并不想按行换行, 我们可能想给结果存起来做为一个列表, 我们只需要在方法链中使用 each 就行.
my@module_list= $dom ->find('tr td:first-child a:first-child') ->map( attr => 'href') ->map(sub{s|\/||;$_}) ->each; printjoin"\n", @module_list;
我可以更酷些, 我们同时得到名称, 并且加上版本号, 我们可以使用 CPAN::DistnameInfo 来加上这个.
我们给找到的每个链接转换成一对名称和版本的数组. 这个模块就是做这个. 因为这个模块需要文件的名字, 所以我们需要加上 .tar.gz 的名字.
useData::Printer; useCPAN::DistnameInfo; my$dom= Mojo::DOM->new( $string); my@module_list= $dom ->find('tr td:first-child a:first-child') ->map( attr => 'href') ->map(sub{s|\/||;$_}) ->map( sub{ my$d= CPAN::DistnameInfo->new( "$_.tar.gz"); [ map{ $d->$_() } qw(dist version) ]; } ) ->each; p @module_list;
这时我们解析收集出来的每个元素会是下面这样, 这是使用 Data::Printer 的 p 函数输出的显示结果:
[ [0] [ [0] "Acme-BDFOY", [1] 0.01 ], [1] [ [0] "Apache-Htaccess", [1] 1.4 ], [2] [ [0] "Apache-iTunes", [1] 0.11 ], [3] [ [0] "App-Module-Lister", [1] 0.15 ],
这时我只想要看开发版本的模块, 我们可以在 Mojo::Collection 上使用 grep 方法:
my@module_list= $dom ->find('tr td:first-child a:first-child') ->map( attr => 'href') ->map(sub{s|\/||;$_}) ->map( sub{ my$d= CPAN::DistnameInfo->new( "$_.tar.gz"); [ map{ $d->$_() } qw(dist version) ]; } ) ->grep( sub{ $_->[-1] =~ /_/ } ) ->each;
我们可以使用 grep 来选择 collection 对象中来进行过滤, 给相应的内容返回真:
[ [0] [ [0] "Brick", [1] "0.227_01" ], [1] [ [0] "Distribution-Guess-BuildSystem", [1] "0.12_02" ], [2] [ [0] "File-Fingerprint", [1] "0.10_02" ], [3] [ [0] "Geo-GeoNames", [1] "1.01_01" ],
本教程到这就完了, 这时没有 HTML 的代码, 只有我们想要的元素了.
PS: 译者注: 这个文档, 有些代码为了方便和容易读, 我做了简单的修改, 其实 Mojo::DOM 比这个教程要强大得多, 很早我就想写这个文章, 但不知道从那开始.这个模块还可以做为服务器端来对网页的 DOM 结构进行修改然后输出之类,还可以单行来实现这些功能, 还有命令行版本的这个选择器.非常非常强大.
原文地址: Extracting from HTML with Mojo::DOM
建议继续学习:
- PHP Simple HTML DOM Parser 是一个不错的html/xml分析类 (阅读:5789)
- Ruby 解析 HTML (Nokogiri) (阅读:3924)
- HTML5是什么东东 我们为什么要关注 (阅读:3868)
- HTML页面布局基础 (阅读:3700)
- 为什么不压缩 HTML (阅读:3535)
- phpQuery:像jQuery一样处理DOM (阅读:3333)
- 仅100行的JavaScript DOM操作类库 (阅读:3437)
- 对大量子节点DOM操作的最佳实践方式 (阅读:3160)
- HTML优化 (阅读:3140)
- Ajax和WEB服务数据格式:XML SOAP HTML (阅读:3096)
扫一扫订阅我的微信号:IT技术博客大学习
- 作者:扶 凯 来源: 扶凯
- 标签: DOM HTML Mojo
- 发布时间:2015-02-14 14:15:24
- [68] Twitter/微博客的学习摘要
- [66] IOS安全–浅谈关于IOS加固的几种方法
- [64] android 开发入门
- [64] 如何拿下简短的域名
- [62] find命令的一点注意事项
- [61] Go Reflect 性能
- [60] 流程管理与用户研究
- [59] Oracle MTS模式下 进程地址与会话信
- [58] 图书馆的世界纪录
- [56] 读书笔记-壹百度:百度十年千倍的29条法则