放牧代码和思想
专注自然语言处理、机器学习算法
    Why join the Navy if you can be a pirate?

《智能Web算法》2.1 用Lucene构建搜索引擎

这是《智能Web算法》的笔记,备忘备查。

Lucene是一个成功的开源IR(信息获取)库,可以快速地分析、索引和搜索文档(网页和电子文档)。

Lucene现在最新版本已经有4.6了,由于《智能Web算法》的配书代码用的是2.3.0,所以我依然使用2.3.0,只不过用源码替换掉了jar,这样能够更加接近核心。

这一节的完整BeanShell脚本被我写成了class,方便调试:

package com.hankcs;

import iweb2.ch2.shell.FetchAndProcessCrawler;
import iweb2.ch2.shell.LuceneIndexer;
import iweb2.ch2.shell.MySearcher;

/**
 * @author hankcs
 */
public class ch2_1_LuceneSearch
{
    public static void main(String[] args)
    {
// ------------------------------------------------------
//   Collecting data and searching with Lucene
// ------------------------------------------------------


//
// -- Data (default URL list)
//
        // 创建一个抓取爬行处理者
        FetchAndProcessCrawler crawler = new FetchAndProcessCrawler("C:/iWeb2/data/ch02",5,200);
        // 设置默认的链接,也就是c:/iWeb2/data/ch02/下的全部html文件
        crawler.setDefaultUrls();
        // 添加垃圾网页
//        crawler.addUrl("file:///c:/iWeb2/data/ch02/spam-01.html");
        crawler.run();

//
// -- Lucene
//
        LuceneIndexer luceneIndexer = new LuceneIndexer(crawler.getRootDir());
        luceneIndexer.run();

        MySearcher oracle = new MySearcher(luceneIndexer.getLuceneDir());

        oracle.search("armstrong",5);


    }
}

我画了个图:

搜索引擎的第一部工作是采集,需要创建一个爬行者:

    /**
     * 创建一个抓取处理爬行者,支持抓取文件链接和http链接
     * @param dir 路径,比如C:/iWeb2/data/ch02
     * @param maxDepth 最大深度,比如5
     * @param maxDocs 最大文档数
     */
    public FetchAndProcessCrawler(String dir, int maxDepth, int maxDocs) {
    	
    	rootDir = dir;

        // 检验参数是否有效
    	if ( rootDir == null || rootDir.trim().length() == 0) {
    		
    		String prefix = System.getProperty("iweb2.home");
    		if (prefix == null) {
    			prefix = "..";
    		}

    		rootDir = System.getProperty("iweb2.home")+System.getProperty("file.separator")+"data";
    	}

        // 生成本次的根目录 比如 C:/iWeb2/data/ch02\crawl-1389795965623
    	rootDir = rootDir+System.getProperty("file.separator")+"crawl-" + System.currentTimeMillis();
    	
    	this.maxDepth = maxDepth;
    	
    	this.maxDocs = maxDocs;
    	
    	this.seedUrls = new ArrayList<String>();
    	
    	/* default url filter configuration */
    	this.urlFilter = new URLFilter();
    	urlFilter.setAllowFileUrls(true);
    	urlFilter.setAllowHttpUrls(true);
    }

然后运行抓取作业:

/**
     * 开始抓取
     */
    public void run() {
    	        
    	crawlData = new CrawlData(rootDir);

        BasicWebCrawler webCrawler = new BasicWebCrawler(crawlData);
        webCrawler.addSeedUrls(getSeedUrls());

        webCrawler.setURLFilter(urlFilter);
        
    	long t0 = System.currentTimeMillis();

        /* run crawl */
        webCrawler.fetchAndProcess(maxDepth, maxDocs);

        System.out.println("Timer (s): [Crawler processed data] --> " + 
                (System.currentTimeMillis()-t0)*0.001);
    	
    }

其中fetchAndProcess使用了代理的设计模式,可以根据协议的不同处理文件和http两种页面:

/**
     * 抓取文件或页面
     * @param urls
     * @param fetchedDocsDB
     * @param groupId
     */
    private void fetchPages(List<String> urls, FetchedDocsDB fetchedDocsDB, String groupId) {
        DocumentIdUtils docIdUtils = new DocumentIdUtils();
        int docSequenceInGroup = 1;
        List<UrlGroup> urlGroups = UrlUtils.groupByProtocolAndHost(urls);
        for( UrlGroup urlGroup : urlGroups ) {
            // 通过文件类型返回不同的代理
            // 有http抓取代理和file抓取代理两种
            Transport t = getTransport(urlGroup.getProtocol());
            try {
                t.init();
                for(String url : urlGroup.getUrls() ) {
                    try {
                        FetchedDocument doc = t.fetch(url);
                        String documentId = docIdUtils.getDocumentId(groupId, docSequenceInGroup);
                        doc.setDocumentId(documentId);
                        fetchedDocsDB.saveDocument(doc);
                        if( t.pauseRequired() ) {
                            pause();
                        }
                    }
                    catch(Exception e) {
                    	System.out.println("Failed to fetch document from url: '" + url + "'.\n"+
                    			e.getMessage());
                        crawlData.getKnownUrlsDB().updateUrlStatus(
                                url, KnownUrlEntry.STATUS_PROCESSED_ERROR);
                    }
                    docSequenceInGroup++;
                }
            }
            finally {
                t.clear();                
            }
        }
    }

抓取并且解析和分析的结果存放在工作目录下,这是原始的未索引的数据

然后索引它们,数据的输入借助ProcessedDocsDB,数据的索引并输出借助IndexWriter

org.apache.lucene.index.IndexWriter
public void addDocument(Document doc)
                throws CorruptIndexException,
                       IOException

下图高亮的是索引存放目录:

索引完毕即可使用MySearcher搜索:

iweb2.ch2.shell.MySearcher
public SearchResult[] search(String query,
                             int numberOfMatches)
public SearchResult[] search(String query, int numberOfMatches)
    {

        SearchResult[] docResults = new SearchResult[0];

        // Lucene索引搜索器
        IndexSearcher is = null;

        try
        {

            // C:/iWeb2/data/ch02\crawl-1390578798123\lucene-index
            // 打开Lucene索引
            is = new IndexSearcher(FSDirectory.getDirectory(indexDir));

        } catch (IOException ioX)
        {
            System.out.println("ERROR: " + ioX.getMessage());
        }
        // 创建查询解析器
        QueryParser qp = new QueryParser(
                "content", // 要查询的域
                new StandardAnalyzer() // 分词器,默认是英文分词器,使用空格分词
                // 因为是英文,就算用new WhitespaceAnalyzer()也是一样的结果
        );

        Query q = null;
        try
        {
            // 将文本查询转换为Lucene查询
            q = qp.parse(query);

        } catch (ParseException pX)
        {
            System.out.println("ERROR: " + pX.getMessage());
        }

        // 搜索结果,其实是一个排名表
        Hits hits = null;
        try
        {
            hits = is.search(q);

            // 限制最大结果数
            int n = Math.min(hits.length(), numberOfMatches);
            docResults = new SearchResult[n];

            for (int i = 0; i < n; i++)
            {

                docResults[i] = new SearchResult(hits.doc(i).get("docid"),
                                                 hits.doc(i).get("doctype"),
                                                 hits.doc(i).get("title"),
                                                 hits.doc(i).get("url"),
                                                 hits.score(i));
            }

            is.close();

        } catch (IOException ioX)
        {
            System.out.println("ERROR: " + ioX.getMessage());
        }

        String header = "Search results using Lucene index scores:";
        boolean showTitle = true;
        printResults(header, "Query: " + query, docResults, showTitle);

        return docResults;
    }

知识共享许可协议 知识共享署名-非商业性使用-相同方式共享码农场 » 《智能Web算法》2.1 用Lucene构建搜索引擎

分享到:更多 ()

评论 2

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址
  1. #1

    大神,我照着你的代码运行了下,报错啊(如下)
    ERROR:
    Failed to load properties from resource: ‘/iweb2.properties’.
    null
    是不是要把iweb2.properties这个文件放在根目录下,我试了还是不行。给支个招吧 [可怜]

    white ink3年前 (2015-03-11)回复

我的开源项目

HanLP自然语言处理包基于DoubleArrayTrie的Aho Corasick自动机