<?xml version="1.0" encoding="UTF-8"?>
<feed xmlns="http://www.w3.org/2005/Atom" xml:base="https://illuminatedcomputing.com/">
  <id>https://illuminatedcomputing.com/</id>
  <title>Illuminated Computing</title>
  <updated>2012-11-19T00:00:00Z</updated>
  <link rel="alternate" href="https://illuminatedcomputing.com/" type="text/html"/>
  <link rel="self" href="https://illuminatedcomputing.com/tags/search/atom.xml" type="application/atom+xml"/>
  <author>
    <name>Paul A. Jungwirth</name>
    <uri>https://illuminatedcommputing.com/</uri>
  </author>
  <entry>
    <id>tag:illuminatedcomputing.com,2012-11-19:/posts/2012/11/indexing-html-with-solr/</id>
    <title type="html">Indexing HTML with Solr/Lucene</title>
    <published>2012-11-19T00:00:00Z</published>
    <updated>2012-11-19T00:00:00Z</updated>
    <link rel="alternate" href="https://illuminatedcomputing.com/posts/2012/11/indexing-html-with-solr/" type="text/html"/>
    <content type="html">
&lt;p&gt;&lt;a href="http://stackoverflow.com/questions/5271810/is-there-a-html-analyzer-tokenizer-for-lucene"&gt;Lots&lt;/a&gt; &lt;a href="http://www.jguru.com/faq/view.jsp?EID=1074228"&gt;of&lt;/a&gt; &lt;a href="https://web.archive.org/web/20171011161759/http://lucene.472066.n3.nabble.com:80/Indexing-HTML-files-in-SOLR-td896530.html"&gt;people&lt;/a&gt; want to index HTML files with Solr or Lucene. One great tool that can extract text out of many document formats is &lt;a href="http://tika.apache.org/"&gt;Tika&lt;/a&gt;, but it is very heavy-weight. What if you just want to strip the text from html files and have Lucene index it? There is no out-of-the-box solution for this, but with a tiny customization we can get it working.&lt;/p&gt;

&lt;p&gt;For the actual text extraction, we’re going to use the excellent &lt;a href="https://sourceforge.net/projects/jerichohtml/"&gt;Jericho HTML Parser&lt;/a&gt;. This is a loose HTML parser that can handle all kinds of malformed markup and still give reasonable results. It’s also got a class dedicated to ripping text out of html documents called &lt;a href="https://javadoc.io/static/net.htmlparser.jericho/jericho-html/3.4/net/htmlparser/jericho/TextExtractor.html"&gt;TextExtractor&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;To use this in Solr, we’re going to implement our own &lt;a href="https://web.archive.org/web/20130518085154/http://lucene.apache.org:80/solr/api-3_6_1/org/apache/solr/analysis/TokenizerFactory.html"&gt;TokenizerFactory&lt;/a&gt;. Solr uses this class to get a Tokenizer, which will generate the tokens for your index. The important method here is &lt;code&gt;Tokenizer create(Reader input)&lt;/code&gt;. This method takes a Reader, a standard Java I/O object, opened on the text of whatever document you’re indexing. Your Tokenizer will use this Reader to get the document’s content.&lt;/p&gt;

&lt;p&gt;Our strategy is to return the same Tokenizer we were before (StandardTokenizer for normal text), but to inject Jericho into the mix before passing it the input Reader. So intead of &lt;code&gt;Reader -&amp;gt; StandardTokenizer&lt;/code&gt; we’re going to do &lt;code&gt;Reader -&amp;gt; Jericho -&amp;gt; StandardTokenizer&lt;/code&gt;. Jericho’s TextExtractor makes this easy for us, because it takes a Reader of HTML and returns a Reader of plain text. So we write an HtmlTokenizerFactory with this implementation:&lt;/p&gt;

&lt;div class="CodeRay"&gt;&lt;div class="code"&gt;&lt;pre&gt;&lt;code class="language-java"&gt;&lt;span class="directive"&gt;public&lt;/span&gt; Tokenizer create(&lt;span class="predefined-type"&gt;Reader&lt;/span&gt; input) {
  &lt;span class="keyword"&gt;try&lt;/span&gt; {
    &lt;span class="keyword"&gt;return&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; StandardTokenizer(Version.LUCENE_36, convertReader(input));
  } &lt;span class="keyword"&gt;catch&lt;/span&gt; (&lt;span class="exception"&gt;IOException&lt;/span&gt; e) {
    &lt;span class="keyword"&gt;throw&lt;/span&gt; &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="exception"&gt;RuntimeException&lt;/span&gt;(e);
  }
}

&lt;span class="directive"&gt;private&lt;/span&gt; &lt;span class="directive"&gt;static&lt;/span&gt; &lt;span class="predefined-type"&gt;Reader&lt;/span&gt; convertReader(&lt;span class="predefined-type"&gt;Reader&lt;/span&gt; r) &lt;span class="directive"&gt;throws&lt;/span&gt; &lt;span class="exception"&gt;IOException&lt;/span&gt; {
  &lt;span class="predefined-type"&gt;Source&lt;/span&gt; s = &lt;span class="keyword"&gt;new&lt;/span&gt; &lt;span class="predefined-type"&gt;Source&lt;/span&gt;(r);
  &lt;span class="predefined-type"&gt;Element&lt;/span&gt; elem = s.getNextElement(&lt;span class="integer"&gt;0&lt;/span&gt;, &lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;html&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;);
  TextExtractor te = &lt;span class="keyword"&gt;new&lt;/span&gt; TextExtractor(elem);
  &lt;span class="keyword"&gt;return&lt;/span&gt; CharStreamSourceUtil.getReader(te);
}&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;That’s it! Our HtmlTokenizerFactory returns a StandardTokenizer, but it pre-processes the HTML to extract just the text.&lt;/p&gt;

&lt;p&gt;The final step is to define a new Field Type in Solr’s schema.xml file, so we can index fields that have HTML. Just add this snippet to the file:&lt;/p&gt;

&lt;div class="CodeRay"&gt;&lt;div class="code"&gt;&lt;pre&gt;&lt;code class="language-xml"&gt;&lt;span class="tag"&gt;&amp;lt;fieldType&lt;/span&gt; &lt;span class="attribute-name"&gt;name&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;text_html&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;solr.TextField&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
  &lt;span class="tag"&gt;&amp;lt;analyzer&lt;/span&gt; &lt;span class="attribute-name"&gt;type&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;index&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;&amp;gt;&lt;/span&gt;
    &lt;span class="tag"&gt;&amp;lt;tokenizer&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;your.package.name.HtmlTokenizerFactory&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="tag"&gt;&amp;lt;filter&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;solr.StopFilterFactory&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="attribute-name"&gt;ignoreCase&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;true&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="attribute-name"&gt;words&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;stopwords.txt&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="attribute-name"&gt;enablePositionIncrements&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;true&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="tag"&gt;/&amp;gt;&lt;/span&gt;
    &lt;span class="tag"&gt;&amp;lt;filter&lt;/span&gt; &lt;span class="attribute-name"&gt;class&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;solr.LowerCaseFilterFactory&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;/&amp;gt;&lt;/span&gt;
  &lt;span class="tag"&gt;&amp;lt;/analyzer&amp;gt;&lt;/span&gt;
&lt;span class="tag"&gt;&amp;lt;/fieldType&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;Here we’re using the same Filters as Solr’s out-of-the-box &lt;code&gt;text_general&lt;/code&gt; Field Type, but you could change them to whatever you like. By converting the HTML to plain text right at the beginning, you retain all the power and flexibility of Lucene’s indexing workflow.&lt;/p&gt;

&lt;p&gt;To actually define a Field using our new Field Type, add something like this to your schema.xml:&lt;/p&gt;

&lt;div class="CodeRay"&gt;&lt;div class="code"&gt;&lt;pre&gt;&lt;code class="language-xml"&gt;&lt;span class="tag"&gt;&amp;lt;field&lt;/span&gt; &lt;span class="attribute-name"&gt;name&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;html_content&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="attribute-name"&gt;type&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;text_html&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="attribute-name"&gt;indexed&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;true&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt; &lt;span class="attribute-name"&gt;stored&lt;/span&gt;=&lt;span class="string"&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;span class="content"&gt;false&lt;/span&gt;&lt;span class="delimiter"&gt;"&lt;/span&gt;&lt;/span&gt;&lt;span class="tag"&gt;/&amp;gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;/div&gt;

&lt;p&gt;There are many improvements you can make to this approach. For instance, you could have Jericho process just the &lt;code&gt;body&lt;/code&gt; tag instead of the the whole page, perhaps also extracting certain meta tags and adding them to the token stream. Or you could follow Google’s lead and boost the weight of tokens that come from the &lt;code&gt;&amp;lt;title&amp;gt;&lt;/code&gt;, &lt;code&gt;&amp;lt;h1&amp;gt;&lt;/code&gt;, or similar tags. But hopefully this is a good starting point for your Solr/Lucene applications!&lt;/p&gt;
</content>
  </entry>
</feed>

