需要做一個(gè)垂直搜索引擎,比較了nekohtml和htmlparser 的功能,盡管nekohtml在容錯(cuò)性、性能等方面的口碑好像比htmlparser好(htmlunit也用的是nekohtml),但感覺 nekohtml的測(cè)試用例和文檔都比htmlparser都少,而且htmlparser基本上能夠滿足垂直搜索引擎頁(yè)面處理分析的需求,因此先研究一 下htmlparser的使用,有空再研究nekohtml和mozilla html parser的使用。 html的功能還是官方說(shuō)得最為清楚,
HTML Parser is a Java library used to parse HTML in either a linear or nested fashion. Primarily used for transformation or extraction, it features filters, visitors, custom tags and easy to use JavaBeans. It is a fast, robust and well tested package.
The two fundamental use-cases that are handled by the parser are extraction and transformation (the syntheses use-case, where HTML pages are created from scratch, is better handled by other tools closer to the source of data). While prior versions concentrated on data extraction from web pages, Version 1.4 of the HTMLParser has substantial improvements in the area of transforming web pages, with simplified tag creation and editing, and verbatim toHtml() method output.
研究的重點(diǎn)還是extraction的使用,有空再研究transformation的使用。
1、htmlparser對(duì)html頁(yè)面處理的數(shù)據(jù)結(jié)構(gòu)
如圖所示,HtmlParser采用了經(jīng)典的Composite模式,通過RemarkNode、TextNode、TagNode、AbstractNode和Tag來(lái)描述HTML頁(yè)面各元素。
- org.htmlparser.Node:
Node接口定義了進(jìn)行樹形結(jié)構(gòu)節(jié)點(diǎn)操作的各種典型操作方法,包括:
節(jié)點(diǎn)到html文本、text文本的方法:toPlainTextString、toHtml
典型樹形結(jié)構(gòu)遍歷的方法:getParent、getChildren、getFirstChild、getLastChild、getPreviousSibling、getNextSibling、getText
獲取節(jié)點(diǎn)對(duì)應(yīng)的樹形結(jié)構(gòu)結(jié)構(gòu)的頂級(jí)節(jié)點(diǎn)Page對(duì)象方法:getPage
獲取節(jié)點(diǎn)起始位置的方法:getStartPosition、getEndPosition
Visitor方法遍歷節(jié)點(diǎn)時(shí)候方法:accept (NodeVisitor visitor)
Filter方法:collectInto (NodeList list, NodeFilter filter)
Object方法:toString、clone
- org.htmlparser.nodes.AbstractNode:
AbstractNode是形成HTML樹形結(jié)構(gòu)抽象基類,實(shí)現(xiàn)了Node接口。
在htmlparser中,Node分成三類:
RemarkNode:代表Html中的注釋
TagNode:標(biāo)簽節(jié)點(diǎn)。
TextNode:文本節(jié)點(diǎn)
這三類節(jié)點(diǎn)都繼承AbstractNode。
- org.htmlparser.nodes.TagNode:
TagNode包含了對(duì)HTML處理的核心的各個(gè)類,是所有TAG的基類,其中有分為包含其他TAG的復(fù)合節(jié)點(diǎn)ComositeTag和不包含其他TAG的葉子節(jié)點(diǎn)Tag。
復(fù)合節(jié)點(diǎn)CompositeTag:
AppletTag,BodyTag,Bullet,BulletList,DefinitionList,DefinitionListBullet,Div,FormTag,FrameSetTag,HeadingTag,
HeadTag,Html,LabelTag,LinkTag,ObjectTag,ParagraphTag,ScriptTag,SelectTag,Span,StyleTag,TableColumn,
TableHeader,TableRow,TableTag,TextareaTag,TitleTag
葉子節(jié)點(diǎn)TAG:
BaseHrefTag,DoctypeTag,FrameTag,ImageTag,InputTag,JspTag,MetaTag,ProcessingInstructionTag,
2、htmlparser對(duì)html頁(yè)面處理的算法
主要是如下幾種方式
2.
3. try {
4. Parser parser = new Parser();
5. parser.setURL(”http://www.google.com”);
6. parser.setEncoding(parser.getEncoding());
7. NodeVisitor visitor = new NodeVisitor() {
8. public void visitTag(Tag tag) {
9. logger.fatal(”testVisitorAll() Tag name is :”
10. + tag.getTagName() + ” \n Class is :”
11. + tag.getClass());
12. }
13.
14. };
15.
16. parser.visitAllNodesWith(visitor);
17. } catch (ParserException e) {
18. e.printStackTrace();
19. }
20.
21. * 采用Filter方式訪問html
22.
23. try {
24.
25. NodeFilter filter = new NodeClassFilter(LinkTag.class);
26. Parser parser = new Parser();
27. parser.setURL(”http://www.google.com”);
28. parser.setEncoding(parser.getEncoding());
29. NodeList list = parser.extractAllNodesThatMatch(filter);
30. for (int i = 0; i < list.size(); i++) {
31. LinkTag node = (LinkTag) list.elementAt(i);
32. logger.fatal(”testLinkTag() Link is :” + node.extractLink());
33. }
34. } catch (Exception e) {
35. e.printStackTrace();
36. }
37.
38. * 采用org.htmlparser.beans方式
39.
40. 另外htmlparser 還在org.htmlparser.beans中對(duì)一些常用的方法進(jìn)行了封裝,以簡(jiǎn)化操作,例如:
41.
42. Parser parser = new Parser();
43.
44. LinkBean linkBean = new LinkBean();
45. linkBean.setURL(”http://www.google.com”);
46. URL[] urls = linkBean.getLinks();
47.
48. for (int i = 0; i < urls.length; i++) {
49. URL url = urls[i];
50. logger.fatal(”testLinkBean() -url is :” + url);
51. }
52.
53.
54. 3、htmlparser關(guān)鍵包結(jié)構(gòu)說(shuō)明
55.
56. htmlparser其實(shí)核心代碼并不多,好好研究一下其代碼,彌補(bǔ)文檔不足的問題。同時(shí)htmlparser的代碼注釋和單元測(cè)試用例還是很齊全的,也有助于了解htmlparser的用法。
57.
58.
59. 3.1、org.htmlparser
60.
61. 定義了htmlparser的一些基礎(chǔ)類。其中最為重要的是Parser類。
62.
63. Parser 是htmlparser的最核心的類,其構(gòu)造函數(shù)提供了如下:Parser.createParser (String html, String charset)、 Parser ()、 Parser (Lexer lexer, ParserFeedback fb)、 Parser (URLConnection connection, ParserFeedback fb)、 Parser (String resource, ParserFeedback feedback)、 Parser (String resource)
64.
65. 各構(gòu)造函數(shù)的具體用法及含義可以查看其代碼,很容易理解。
66.
67. Parser常用的幾個(gè)方法:
68.
69. * elements獲取元素
70.
71. Parser parser = new Parser (”http://www.google.com”);
72. for (NodeIterator i = parser.elements (); i.hasMoreElements (); )
73. processMyNodes (i.nextNode ());
74.
75. * parse (NodeFilter filter):通過NodeFilter方式獲取
76. * visitAllNodesWith (NodeVisitor visitor):通過Nodevisitor方式
77. * extractAllNodesThatMatch (NodeFilter filter):通過NodeFilter方式
78.
79. 3.2、org.htmlparser.beans
80.
81. 對(duì)Visitor和Filter的方法進(jìn)行了封裝,定義了針對(duì)一些常用html元素操作的bean,簡(jiǎn)化對(duì)常用元素的提取操作。
82.
83. 包括:FilterBean、HTMLLinkBean、HTMLTextBean、LinkBean、StringBean、BeanyBaby等。
84. 3.3、org.htmlparser.nodes
85.
86. 定義了基礎(chǔ)的node,包括:AbstractNode、RemarkNode、TagNode、TextNode等。
87. 3.4、org.htmlparser.tags
88.
89. 定義了htmlparser的各種tag。
90. 3.5、org.htmlparser.filters
91.
92. 定義了htmlparser所提供的各種filter,主要通過 extractAllNodesThatMatch (NodeFilter filter)來(lái)對(duì)html頁(yè)面指定類型的元素進(jìn)行過濾,包括:AndFilter、CssSelectorNodeFilter、 HasAttributeFilter、HasChildFilter、 HasParentFilter、HasSiblingFilter、 IsEqualFilter、LinkRegexFilter、 LinkStringFilter、NodeClassFilter、 NotFilter、OrFilter、RegexFilter、 StringFilter、TagNameFilter、XorFilter
93. 3.6、org.htmlparser.visitors
94.
95. 定義了htmlparser所提供的各種visitor,主要通過visitAllNodesWith (NodeVisitor visitor)來(lái)對(duì) html頁(yè)面元素進(jìn)行遍歷,包括:HtmlPage、LinkFindingVisitor、 NodeVisitor、 ObjectFindingVisitor、StringFindingVisitor、 TagFindingVisitor、 TextExtractingVisitor、UrlModifyingVisitor
96.
97.
98. 3.7、org.htmlparser.parserapplications
99.
100. 定義了一些實(shí)用的工具,包括LinkExtractor、SiteCapturer、StringExtractor、WikiCapturer,這幾個(gè)類也可以作為htmlparser使用樣例。
101. 3.8、org.htmlparser.tests
102.
103. 對(duì)各種功能的單元測(cè)試用例,也可以作為htmlparser使用的樣例。
104.
105.
106. 4、htmlparser的使用樣例
107.
108.
109.
110. import java.net.URL;
111.
112. import junit.framework.TestCase;
113.
114. import org.apache.log4j.Logger;
115. import org.htmlparser.Node;
116. import org.htmlparser.NodeFilter;
117. import org.htmlparser.Parser;
118. import org.htmlparser.Tag;
119. import org.htmlparser.beans.LinkBean;
120. import org.htmlparser.filters.NodeClassFilter;
121. import org.htmlparser.filters.OrFilter;
122. import org.htmlparser.filters.TagNameFilter;
123. import org.htmlparser.tags.HeadTag;
124. import org.htmlparser.tags.ImageTag;
125. import org.htmlparser.tags.InputTag;
126. import org.htmlparser.tags.LinkTag;
127. import org.htmlparser.tags.OptionTag;
128. import org.htmlparser.tags.SelectTag;
129. import org.htmlparser.tags.TableColumn;
130. import org.htmlparser.tags.TableRow;
131. import org.htmlparser.tags.TableTag;
132. import org.htmlparser.tags.TitleTag;
133. import org.htmlparser.util.NodeIterator;
134. import org.htmlparser.util.NodeList;
135. import org.htmlparser.util.ParserException;
136. import org.htmlparser.visitors.HtmlPage;
137. import org.htmlparser.visitors.NodeVisitor;
138. import org.htmlparser.visitors.ObjectFindingVisitor;
139.
140. public class ParserTestCase extends TestCase {
141.
142. private static final Logger logger = Logger.getLogger(ParserTestCase.class);
143.
144. public ParserTestCase(String name) {
145. super(name);
146. }
147. /*
148. * 測(cè)試ObjectFindVisitor的用法
149. */
150. public void testImageVisitor() {
151. try {
152. ImageTag imgLink;
153. ObjectFindingVisitor visitor = new ObjectFindingVisitor(
154. ImageTag.class);
155. Parser parser = new Parser();
156. parser.setURL(”http://www.google.com”);
157. parser.setEncoding(parser.getEncoding());
158. parser.visitAllNodesWith(visitor);
159. Node[] nodes = visitor.getTags();
160. for (int i = 0; i < nodes.length; i++) {
161. imgLink = (ImageTag) nodes[i];
162. logger.fatal(”testImageVisitor() ImageURL = “
163. + imgLink.getImageURL());
164. logger.fatal(”testImageVisitor() ImageLocation = “
165. + imgLink.extractImageLocn());
166. logger.fatal(”testImageVisitor() SRC = “
167. + imgLink.getAttribute(”SRC”));
168. }
169. }
170. catch (Exception e) {
171. e.printStackTrace();
172. }
173. }
174. /*
175. * 測(cè)試TagNameFilter用法
176. */
177. public void testNodeFilter() {
178. try {
179. NodeFilter filter = new TagNameFilter(”IMG”);
180. Parser parser = new Parser();
181. parser.setURL(”http://www.google.com”);
182. parser.setEncoding(parser.getEncoding());
183. NodeList list = parser.extractAllNodesThatMatch(filter);
184. for (int i = 0; i < list.size(); i++) {
185. logger.fatal(”testNodeFilter() ” + list.elementAt(i).toHtml());
186. }
187. } catch (Exception e) {
188. e.printStackTrace();
189. }
190.
191. }
192. /*
193. * 測(cè)試NodeClassFilter用法
194. */
195. public void testLinkTag() {
196. try {
197.
198. NodeFilter filter = new NodeClassFilter(LinkTag.class);
199. Parser parser = new Parser();
200. parser.setURL(”http://www.google.com”);
201. parser.setEncoding(parser.getEncoding());
202. NodeList list = parser.extractAllNodesThatMatch(filter);
203. for (int i = 0; i < list.size(); i++) {
204. LinkTag node = (LinkTag) list.elementAt(i);
205. logger.fatal(”testLinkTag() Link is :” + node.extractLink());
206. }
207. } catch (Exception e) {
208. e.printStackTrace();
209. }
210.
211. }
212. /*
213. * 測(cè)試<link href=” text=’text/css’ rel=’stylesheet’ />用法
214. */
215. public void testLinkCSS() {
216. try {
217.
218. Parser parser = new Parser();
219. parser
220. .setInputHTML(”<head><title>Link Test</title>”
221. + “<link href=’/test01/css.css’ text=’text/css’ rel=’stylesheet’ />”
222. + “<link href=’/test02/css.css’ text=’text/css’ rel=’stylesheet’ />”
223. + “</head>” + “<body>”);
224. parser.setEncoding(parser.getEncoding());
225. NodeList nodeList = null;
226.
227. for (NodeIterator e = parser.elements(); e.hasMoreNodes();) {
228. Node node = e.nextNode();
229. logger
230. .fatal(”testLinkCSS()” + node.getText()
231. + node.getClass());
232.
233. }
234. } catch (Exception e) {
235. e.printStackTrace();
236. }
237. }
238. /*
239. * 測(cè)試OrFilter的用法
240. */
241. public void testOrFilter() {
242. NodeFilter inputFilter = new NodeClassFilter(InputTag.class);
243. NodeFilter selectFilter = new NodeClassFilter(SelectTag.class);
244. Parser myParser;
245. NodeList nodeList = null;
246.
247. try {
248. Parser parser = new Parser();
249. parser
250. .setInputHTML(”<head><title>OrFilter Test</title>”
251. + “<link href=’/test01/css.css’ text=’text/css’ rel=’stylesheet’ />”
252. + “<link href=’/test02/css.css’ text=’text/css’ rel=’stylesheet’ />”
253. + “</head>”
254. + “<body>”
255. + “<input type=’text’ value=’text1′ name=’text1′/>”
256. + “<input type=’text’ value=’text2′ name=’text2′/>”
257. + “<select><option id=’1′>1</option><option id=’2′>2</option><option id=’3′></option></select>”
258. + “<a href=’http://www.yeeach.com’>yeeach.com</a>”
259. + “</body>”);
260.
261. parser.setEncoding(parser.getEncoding());
262. OrFilter lastFilter = new OrFilter();
263. lastFilter.setPredicates(new NodeFilter[] { selectFilter,
264. inputFilter });
265. nodeList = parser.parse(lastFilter);
266. for (int i = 0; i <= nodeList.size(); i++) {
267. if (nodeList.elementAt(i) instanceof InputTag) {
268. InputTag tag = (InputTag) nodeList.elementAt(i);
269. logger.fatal(”OrFilter tag name is :” + tag.getTagName()
270. + ” ,tag value is:” + tag.getAttribute(”value”));
271. }
272. if (nodeList.elementAt(i) instanceof SelectTag) {
273. SelectTag tag = (SelectTag) nodeList.elementAt(i);
274. NodeList list = tag.getChildren();
275.
276. for (int j = 0; j < list.size(); j++) {
277. OptionTag option = (OptionTag) list.elementAt(j);
278. logger
279. .fatal(”OrFilter Option”
280. + option.getOptionText());
281. }
282.
283. }
284. }
285.
286. } catch (ParserException e) {
287. e.printStackTrace();
288. }
289. }
290. /*
291. * 測(cè)試對(duì)<table><tr><td></td></tr></table>的解析
292. */
293. public void testTable() {
294. Parser myParser;
295. NodeList nodeList = null;
296. myParser = Parser.createParser(”<body> ” + “<table id=’table1′ >”
297. + “<tr><td>1-11</td><td>1-12</td><td>1-13</td>”
298. + “<tr><td>1-21</td><td>1-22</td><td>1-23</td>”
299. + “<tr><td>1-31</td><td>1-32</td><td>1-33</td></table>”
300. + “<table id=’table2′ >”
301. + “<tr><td>2-11</td><td>2-12</td><td>2-13</td>”
302. + “<tr><td>2-21</td><td>2-22</td><td>2-23</td>”
303. + “<tr><td>2-31</td><td>2-32</td><td>2-33</td></table>”
304. + “</body>”, “GBK”);
305. NodeFilter tableFilter = new NodeClassFilter(TableTag.class);
306. OrFilter lastFilter = new OrFilter();
307. lastFilter.setPredicates(new NodeFilter[] { tableFilter });
308. try {
309. nodeList = myParser.parse(lastFilter);
310. for (int i = 0; i <= nodeList.size(); i++) {
311. if (nodeList.elementAt(i) instanceof TableTag) {
312. TableTag tag = (TableTag) nodeList.elementAt(i);
313. TableRow[] rows = tag.getRows();
314.
315. for (int j = 0; j < rows.length; j++) {
316. TableRow tr = (TableRow) rows[j];
317. TableColumn[] td = tr.getColumns();
318. for (int k = 0; k < td.length; k++) {
319. logger.fatal(”<td>” + td[k].toPlainTextString());
320. }
321.
322. }
323.
324. }
325. }
326.
327. } catch (ParserException e) {
328. e.printStackTrace();
329. }
330. }
331. /*
332. * 測(cè)試NodeVisitor的用法,遍歷所有節(jié)點(diǎn)
333. */
334. public void testVisitorAll() {
335. try {
336. Parser parser = new Parser();
337. parser.setURL(”http://www.google.com”);
338. parser.setEncoding(parser.getEncoding());
339. NodeVisitor visitor = new NodeVisitor() {
340. public void visitTag(Tag tag) {
341. logger.fatal(”testVisitorAll() Tag name is :”
342. + tag.getTagName() + ” \n Class is :”
343. + tag.getClass());
344. }
345.
346. };
347.
348. parser.visitAllNodesWith(visitor);
349. } catch (ParserException e) {
350. e.printStackTrace();
351. }
352. }
353. /*
354. * 測(cè)試對(duì)指定Tag的NodeVisitor的用法
355. */
356. public void testTagVisitor() {
357. try {
358.
359. Parser parser = new Parser(
360. “<head><title>dddd</title>”
361. + “<link href=’/test01/css.css’ text=’text/css’ rel=’stylesheet’ />”
362. + “<link href=’/test02/css.css’ text=’text/css’ rel=’stylesheet’ />”
363. + “</head>” + “<body>”
364. + “<a href=’http://www.yeeach.com’>yeeach.com</a>”
365. + “</body>”);
366. NodeVisitor visitor = new NodeVisitor() {
367. public void visitTag(Tag tag) {
368. if (tag instanceof HeadTag) {
369. logger.fatal(”visitTag() HeadTag : Tag name is :”
370. + tag.getTagName() + ” \n Class is :”
371. + tag.getClass() + “\n Text is :”
372. + tag.getText());
373. } else if (tag instanceof TitleTag) {
374. logger.fatal(”visitTag() TitleTag : Tag name is :”
375. + tag.getTagName() + ” \n Class is :”
376. + tag.getClass() + “\n Text is :”
377. + tag.getText());
378.
379.
380. } else if (tag instanceof LinkTag) {
381. logger.fatal(”visitTag() LinkTag : Tag name is :”
382. + tag.getTagName() + ” \n Class is :”
383. + tag.getClass() + “\n Text is :”
384. + tag.getText() + ” \n getAttribute is :”
385. + tag.getAttribute(”href”));
386. } else {
387. logger.fatal(”visitTag() : Tag name is :”
388. + tag.getTagName() + ” \n Class is :”
389. + tag.getClass() + “\n Text is :”
390. + tag.getText());
391. }
392.
393. }
394.
395. };
396.
397. parser.visitAllNodesWith(visitor);
398. } catch (Exception e) {
399. e.printStackTrace();
400. }
401. }
402. /*
403. * 測(cè)試HtmlPage的用法
404. */
405. public void testHtmlPage() {
406. String inputHTML = “<html>” + “<head>”
407. + “<title>Welcome to the HTMLParser website</title>”
408. + “</head>” + “<body>” + “Welcome to HTMLParser”
409. + “<table id=’table1′ >”
410. + “<tr><td>1-11</td><td>1-12</td><td>1-13</td>”
411. + “<tr><td>1-21</td><td>1-22</td><td>1-23</td>”
412. + “<tr><td>1-31</td><td>1-32</td><td>1-33</td></table>”
413. + “<table id=’table2′ >”
414. + “<tr><td>2-11</td><td>2-12</td><td>2-13</td>”
415. + “<tr><td>2-21</td><td>2-22</td><td>2-23</td>”
416. + “<tr><td>2-31</td><td>2-32</td><td>2-33</td></table>”
417. + “</body>” + “</html>”;
418. Parser parser = new Parser();
419. try {
420. parser.setInputHTML(inputHTML);
421. parser.setEncoding(parser.getURL());
422. HtmlPage page = new HtmlPage(parser);
423. parser.visitAllNodesWith(page);
424. logger.fatal(”testHtmlPage -title is :” + page.getTitle());
425. NodeList list = page.getBody();
426.
427. for (NodeIterator iterator = list.elements(); iterator
428. .hasMoreNodes();) {
429. Node node = iterator.nextNode();
430. logger.fatal(”testHtmlPage -node is :” + node.toHtml());
431. }
432.
433. } catch (ParserException e) {
434. // TODO Auto-generated catch block
435. e.printStackTrace();
436. }
437. }
438. /*
439. * 測(cè)試LinkBean的用法
440. */
441. public void testLinkBean() {
442. Parser parser = new Parser();
443.
444. LinkBean linkBean = new LinkBean();
445. linkBean.setURL(”http://www.google.com”);
446. URL[] urls = linkBean.getLinks();
447.
448. for (int i = 0; i < urls.length; i++) {
449. URL url = urls[i];
450. logger.fatal(”testLinkBean() -url is :” + url);
451. }
452.
453. }
454.
455. }
5、相關(guān)的項(xiàng)目
nekohtml :評(píng)價(jià)比htmlparser好,把html正規(guī)化標(biāo)準(zhǔn)的xml文檔,用xerces處理,但文檔較少。
mozilla htmlparser:http://www.dapper.net/網(wǎng)站采用的html解析器,開源了,基于mozilla的解析器,值得研究一下。
http://jerichohtml.sourceforge.net/
http://htmlcleaner.sourceforge.net/
http://html.xamjwg.org/cobra.jsp
https://xhtmlrenderer.dev.java.net
其他一些html parser可以參考相關(guān)的匯總文章:
http://www.manageability.org/blog/stuff/screen-scraping-tools-written-in-java/view
http://java-source.net/open-source/html-parsers
http://www.open-open.com/30.htm
6、參考文檔
http://www.aygfsteel.com/lostfire/archive/2006/07/02/56212.html
http://blog.csdn.net/scud/archive/2005/08/11/451397.aspx
http://chasethedevil.blogspot.com/2006/05/java-html-parsing-example-with.html
http://javaboutique.internet.com/tutorials/HTMLParser/
Technorati 標(biāo)簽: nekohtml,htmlparser,scraping,scrape,spider,爬蟲,crawler
站內(nèi)標(biāo)簽:crawler,爬蟲,htmlparser,nekohtml,scrape,scraping,spider