CLOVER🍀

That was when it all began.

Apache Solr 5.x/crawler4j/Apache Tikaを使って、HTMLとPDFをクローリングしてインデックスを作る

これまでに、Apache Solrとcrawler4jでHTMLをクローリングしてインデックスするのと、Apache Tikaを使ってPDFを読んでみるエントリを書いてみました。

Apache Solr 5.x+crawler4jで、Webサイトをクロールしてインデックス化する - CLOVER

Apache TikaでPDFを読む - CLOVER

今度は、これらを使ってHTMLとPDFをクローリングしてSolrのインデックスを作ってみたいと思います。

やり方

全体の流れは、以下のようにします。

  • VMwareのドキュメントサイトの一部(http://info.vmware.com/content/apac_jp_co_techresources)をクローリング
  • HTMLとPDFを対象にする
  • インデックス作成は、いきなりSolrjでドキュメントを追加するのではなく、いったんJSONをファイルで作成
  • クローリング終了後に、作成したJSONファイルから一気にUpdateHandlerを使ってSolrのインデックス更新

こんな感じで。

準備

Maven依存関係は、このように。

    <dependencies>
        <dependency>
            <groupId>edu.uci.ics</groupId>
            <artifactId>crawler4j</artifactId>
            <version>4.1</version>
        </dependency>

        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-simple</artifactId>
            <version>1.7.12</version>
        </dependency>
        <dependency>
            <groupId>org.apache.solr</groupId>
            <artifactId>solr-solrj</artifactId>
            <version>5.3.1</version>
        </dependency>
        <dependency>
            <groupId>org.jsoup</groupId>
            <artifactId>jsoup</artifactId>
            <version>1.8.3</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.6.3</version>
        </dependency>
    </dependencies>

HTML解析にjsoupを使い、JSON作成にJackson2を使用。Apache Tikaは、crawler4jの依存関係に含まれているので、明示的には入れていません。

Solrのインデックス定義

Solrのインデックス定義は、このようにしました。

    <field name="url" type="string" indexed="true" stored="true" required="true" multiValued="false" />
    <field name="title" type="text_ja" indexed="true" stored="true" required="true" multiValued="false" />
    <field name="description" type="text_ja" indexed="true" stored="true" required="false" multiValued="false" />
    <field name="contents" type="text_ja" indexed="true" stored="true" required="true" multiValued="false" />
    <field name="file_type" type="string" indexed="true" stored="true" required="true" multiValued="false" />
    <uniqueKey>url</uniqueKey>

URLとドキュメントのタイトル、description(HTMLしかありませんが…)、コンテンツの中身、そしてファイルの種類(html or pdf)とします。

この中に、解析したHTMLとPDFを放り込みます。

クローラーの実装

それでは、各種Javaコードを書いていきます。

クロール時に作成する、ドキュメントとなるクラスをこのように定義。
src/main/java/org/littlewings/mycrawler/CrawlDocument.java

package org.littlewings.mycrawler;

public class CrawlDocument {
    private String url;
    private String title;
    private String description;
    private String contents;
    private String fileType;

    CrawlDocument(String url, String title, String description, String contents, String fileType) {
        this.url = url;
        this.title = title;
        this.description = description;
        this.contents = contents;
        this.fileType = fileType;
    }

    public static CrawlDocument create(String url, String title, String description, String contents, String fileType) {
        return new CrawlDocument(url, title, description, contents, fileType);
    }

    public String getUrl() {
        return url;
    }

    public String getTitle() {
        return title;
    }

    public String getDescription() {
        return description;
    }

    public String getContents() {
        return contents;
    }

    public String getFileType() {
        return fileType;
    }
}

まあ、Solrのインデックス定義の写しですね。

JSONを作成するためのクラスは、こんな感じで。クローリングしつつ、JSONファイルを作成するという関係上、ObjectMapperではなくJsonGeneratorでStreaming的に書き出します。
※だいぶ乱暴な作りですが…
src/main/java/org/littlewings/mycrawler/JsonWriter.java

package org.littlewings.mycrawler;

import java.io.IOException;
import java.io.Writer;

import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.core.JsonGenerator;
import com.fasterxml.jackson.core.util.DefaultPrettyPrinter;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.PropertyNamingStrategy;

public class JsonWriter implements AutoCloseable {
    private static JsonWriter instance;

    private ObjectMapper objectMapper;
    private JsonGenerator jsonGenerator;

    JsonWriter(Writer writer) throws IOException {
        objectMapper = new ObjectMapper()
                .configure(DeserializationFeature.FAIL_ON_IGNORED_PROPERTIES, false)
                .setSerializationInclusion(JsonInclude.Include.NON_NULL)
                .setSerializationInclusion(JsonInclude.Include.NON_EMPTY)
                .setPropertyNamingStrategy(PropertyNamingStrategy.CAMEL_CASE_TO_LOWER_CASE_WITH_UNDERSCORES);
        jsonGenerator =
                objectMapper
                        .getFactory()
                        .createGenerator(writer)
                        .setPrettyPrinter(new DefaultPrettyPrinter());
    }

    public static synchronized JsonWriter getInstance() {
        return instance;
    }

    public static synchronized JsonWriter getInstance(Writer writer) throws IOException {
        if (instance == null) {
            instance = new JsonWriter(writer);
        }

        return instance;
    }

    public synchronized void init() throws IOException {
        jsonGenerator.writeStartArray();
    }

    public synchronized void write(Object target) throws IOException {
        jsonGenerator.writeObject(target);
    }

    public synchronized void end() throws IOException {
        jsonGenerator.writeEndArray();
    }

    @Override
    public synchronized void close() throws Exception {
        jsonGenerator.close();
    }
}

クローラーの実装。
src/main/java/org/littlewings/mycrawler/MyLittleCrawler.java

package org.littlewings.mycrawler;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.UncheckedIOException;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.Set;
import java.util.regex.Pattern;

import edu.uci.ics.crawler4j.crawler.Page;
import edu.uci.ics.crawler4j.crawler.WebCrawler;
import edu.uci.ics.crawler4j.parser.HtmlParseData;
import edu.uci.ics.crawler4j.url.WebURL;
import org.apache.tika.exception.TikaException;
import org.apache.tika.metadata.Metadata;
import org.apache.tika.metadata.TikaCoreProperties;
import org.apache.tika.parser.ParseContext;
import org.apache.tika.parser.Parser;
import org.apache.tika.parser.pdf.PDFParser;
import org.apache.tika.sax.BodyContentHandler;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.xml.sax.ContentHandler;
import org.xml.sax.SAXException;

public class MyLittleCrawler extends WebCrawler {
    private static final Pattern FILTERS = Pattern.compile(".*(\\.(css|js|gif|jpg"
            + "|png|mp3|mp3|zip|gz))$");

    private static final Pattern[] INTERST_PATTERNS = {
            Pattern.compile("^http://info\\.vmware\\.com/content/apac_jp_co_techresources"),
            Pattern.compile("^http://www\\.vmware\\.com/files/jp/pdf"),
            Pattern.compile("^http://info\\.vmware\\.com/content/")
    };

    private Logger logger = LoggerFactory.getLogger(getClass());

    @Override
    public boolean shouldVisit(Page referringPage, WebURL url) {
        String hrefUrl = url.getURL();

        return !FILTERS.matcher(hrefUrl).matches() &&
                Arrays.stream(INTERST_PATTERNS).anyMatch(pattern -> pattern.matcher(hrefUrl).find());
    }

    @Override
    public void visit(Page page) {
        String url = page.getWebURL().getURL();
        logger.info("URL: {}, Content-Type: {}", url, page.getContentType());

        if (page.getParseData() instanceof HtmlParseData) {
            logger.info("Contents is HTML: URL = {}", url);

            HtmlParseData htmlParseData = (HtmlParseData) page.getParseData();
            String html = htmlParseData.getHtml();
            Set<WebURL> links = htmlParseData.getOutgoingUrls();

            logger.info("Number of outgoing links: {}", links.size());

            Document jsoupDoc = Jsoup.parse(html);
            String title = jsoupDoc.title();
            String description = jsoupDoc.select("meta[name=\"description\"]").attr("content");
            String contents = jsoupDoc.body().text();

            if (title != null && !title.isEmpty() && contents != null && !contents.isEmpty()) {
                try {
                    JsonWriter.getInstance().write(CrawlDocument.create(url, title, description, contents, "html"));
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            } else {
                logger.info("Invalid HTML title: {}, contents: {}, URL = {}", title, contents, url);
            }
        } else if (page.getContentType().startsWith("application/pdf") || url.endsWith(".pdf")) {
            logger.info("Contents is PDF: URL = {}", url);

            InputStream is = new ByteArrayInputStream(page.getContentData());

            Parser parser = new PDFParser();
            ContentHandler contentHandler = new BodyContentHandler();
            Metadata metadata = new Metadata();
            ParseContext context = new ParseContext();

            try {
                parser.parse(is, contentHandler, metadata, context);

                String title =
                        Optional
                                .ofNullable(metadata.get(TikaCoreProperties.TITLE))
                                .flatMap(s -> s.isEmpty() ? Optional.empty() : Optional.of(s))
                                .orElse(new File(page.getWebURL().getPath()).getName());
                String descriptionOrContents = contentHandler.toString();

                if (title != null && !title.isEmpty() && descriptionOrContents != null && !descriptionOrContents.isEmpty()) {
                    JsonWriter
                            .getInstance()
                            .write(CrawlDocument
                                    .create(url,
                                            title,
                                            descriptionOrContents,
                                            descriptionOrContents,
                                            "pdf"));
                } else {
                    logger.info("Invalid PDF title: {}, contents: {}, URL = {}", title, descriptionOrContents, url);
                }
            } catch (IOException | SAXException | TikaException e) {
                e.printStackTrace();
            }
        } else {
            logger.info("Unknown Contents: URL = {}", url);
        }
    }
}

クロールしたドキュメントがHTMLだった場合は、以下のような処理になります。

        if (page.getParseData() instanceof HtmlParseData) {
            logger.info("Contents is HTML: URL = {}", url);

            HtmlParseData htmlParseData = (HtmlParseData) page.getParseData();
            String html = htmlParseData.getHtml();
            Set<WebURL> links = htmlParseData.getOutgoingUrls();

            logger.info("Number of outgoing links: {}", links.size());

            Document jsoupDoc = Jsoup.parse(html);
            String title = jsoupDoc.title();
            String description = jsoupDoc.select("meta[name=\"description\"]").attr("content");
            String contents = jsoupDoc.body().text();

            if (title != null && !title.isEmpty() && contents != null && !contents.isEmpty()) {
                try {
                    JsonWriter.getInstance().write(CrawlDocument.create(url, title, description, contents, "html"));
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            } else {
                logger.info("Invalid HTML title: {}, contents: {}, URL = {}", title, contents, url);
            }

とりあえず、タイトルとdescriptionとbodyをぶち抜く、と。

            Document jsoupDoc = Jsoup.parse(html);
            String title = jsoupDoc.title();
            String description = jsoupDoc.select("meta[name=\"description\"]").attr("content");
            String contents = jsoupDoc.body().text();

ここではタイトルとbodyの中身があれば、ドキュメントとしてはOKとしています。

            if (title != null && !title.isEmpty() && contents != null && !contents.isEmpty()) {
                try {
                    JsonWriter.getInstance().write(CrawlDocument.create(url, title, description, contents, "html"));
                } catch (IOException e) {
                    throw new UncheckedIOException(e);
                }
            } else {
                logger.info("Invalid HTML title: {}, contents: {}, URL = {}", title, contents, url);
            }

PDFの場合は、Apache Tikaでパースします。

        } else if (page.getContentType().startsWith("application/pdf") || url.endsWith(".pdf")) {
            logger.info("Contents is PDF: URL = {}", url);

            InputStream is = new ByteArrayInputStream(page.getContentData());

            Parser parser = new PDFParser();
            ContentHandler contentHandler = new BodyContentHandler();
            Metadata metadata = new Metadata();
            ParseContext context = new ParseContext();

            try {
                parser.parse(is, contentHandler, metadata, context);

                String title =
                        Optional
                                .ofNullable(metadata.get(TikaCoreProperties.TITLE))
                                .flatMap(s -> s.isEmpty() ? Optional.empty() : Optional.of(s))
                                .orElse(new File(page.getWebURL().getPath()).getName());
                String descriptionOrContents = contentHandler.toString();

                if (title != null && !title.isEmpty() && descriptionOrContents != null && !descriptionOrContents.isEmpty()) {
                    JsonWriter
                            .getInstance()
                            .write(CrawlDocument
                                    .create(url,
                                            title,
                                            descriptionOrContents,
                                            descriptionOrContents,
                                            "pdf"));
                } else {
                    logger.info("Invalid PDF title: {}, contents: {}, URL = {}", title, descriptionOrContents, url);
                }
            } catch (IOException | SAXException | TikaException e) {
                e.printStackTrace();
            }

WebページからのレスポンスをInputStreamにしておいて

            InputStream is = new ByteArrayInputStream(page.getContentData());

            Parser parser = new PDFParser();
            ContentHandler contentHandler = new BodyContentHandler();
            Metadata metadata = new Metadata();
            ParseContext context = new ParseContext();

タイトルはメタデータから取得、設定されていなければURLのファイル名から取るようにしました。descriptionはPDFにあったものでもないので、とりあえずコンテンツそのものと同じということで…。

                String title =
                        Optional
                                .ofNullable(metadata.get(TikaCoreProperties.TITLE))
                                .flatMap(s -> s.isEmpty() ? Optional.empty() : Optional.of(s))
                                .orElse(new File(page.getWebURL().getPath()).getName());
                String descriptionOrContents = contentHandler.toString();

HTMLと同じように、タイトルとコンテンツがあればOKとしています。

                if (title != null && !title.isEmpty() && descriptionOrContents != null && !descriptionOrContents.isEmpty()) {
                    JsonWriter
                            .getInstance()
                            .write(CrawlDocument
                                    .create(url,
                                            title,
                                            descriptionOrContents,
                                            descriptionOrContents,
                                            "pdf"));
                } else {
                    logger.info("Invalid PDF title: {}, contents: {}, URL = {}", title, descriptionOrContents, url);
                }

HTMLでもPDFでもなければ、破棄と。

        } else {
            logger.info("Unknown Contents: URL = {}", url);
        }

mainメソッドを持ったクラスは、こんな感じになりました。
src/main/java/org/littlewings/mycrawler/Launcher.java

package org.littlewings.mycrawler;

import java.io.BufferedWriter;
import java.io.File;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;

import edu.uci.ics.crawler4j.crawler.CrawlConfig;
import edu.uci.ics.crawler4j.crawler.CrawlController;
import edu.uci.ics.crawler4j.fetcher.PageFetcher;
import edu.uci.ics.crawler4j.robotstxt.RobotstxtConfig;
import edu.uci.ics.crawler4j.robotstxt.RobotstxtServer;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.impl.HttpSolrClient;
import org.apache.solr.client.solrj.request.ContentStreamUpdateRequest;
import org.apache.solr.common.util.NamedList;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class Launcher {
    public static void main(String... args) throws Exception {
        Logger logger = LoggerFactory.getLogger(Launcher.class);

        // JSONファイルの作成
        String indexJsonFile = "index-data.json";
        BufferedWriter writer = Files.newBufferedWriter(Paths.get(indexJsonFile), StandardCharsets.UTF_8);

        JsonWriter jsonWriter = JsonWriter.getInstance(writer);
        jsonWriter.init();

        // Crawlerの設定
        String crawlStorageFolder = "data/crawl/strage";
        int numberOfCrawlers = 7;  // スレッド数

        CrawlConfig config = new CrawlConfig();
        config.setCrawlStorageFolder(crawlStorageFolder);
        config.setPolitenessDelay(2 * 1000);  // 2秒ごとにリクエスト
        config.setIncludeBinaryContentInCrawling(true);  // バイナリファイルも対象に
        config.setMaxDownloadSize(15 * 1024 * 1024);  // 最大ダウンロードサイズを調整

        // HttpClientの設定
        PageFetcher pageFetcher = new PageFetcher(config);
        RobotstxtConfig robotstxtConfig = new RobotstxtConfig();
        RobotstxtServer robotstxtServer = new RobotstxtServer(robotstxtConfig, pageFetcher);
        CrawlController controller = new CrawlController(config, pageFetcher, robotstxtServer);

        // クロール開始ポイント設定
        logger.info("add Seed URL under... {}", "http://info.vmware.com/content/apac_jp_co_techresources");
        controller.addSeed("http://info.vmware.com/content/apac_jp_co_techresources");

        // クロール開始
        logger.info("Start crawler.");
        controller.start(MyLittleCrawler.class, numberOfCrawlers);

        // クロール後、JSONファイル書き出し終了
        jsonWriter.end();
        jsonWriter.close();
        writer.close();

        logger.info("JSON file[{}] created.", indexJsonFile);

        // Solrへドキュメント更新
        SolrClient solrClient = new HttpSolrClient("http://localhost:8983/solr/mycore");
        ContentStreamUpdateRequest updateRequest = new ContentStreamUpdateRequest("/update");

        updateRequest.addFile(new File(indexJsonFile), "application/json");

        NamedList<Object> named = solrClient.request(updateRequest);
        logger.info("Solr request result = {}.", named);

        // コミットおよびオプティマイズ
        logger.info("commit = {}", solrClient.commit().getResponse());
        logger.info("optimize = {}", solrClient.optimize().getResponse());

        // 後始末
        solrClient.close();
    }
}

最初に、JSONファイルを開いておきます。

        // JSONファイルの作成
        String indexJsonFile = "index-data.json";
        BufferedWriter writer = Files.newBufferedWriter(Paths.get(indexJsonFile), StandardCharsets.UTF_8);

        JsonWriter jsonWriter = JsonWriter.getInstance(writer);
        jsonWriter.init();

このファイルは、クローラーインスタンスによって随時書き込まれていきます。

crawler4jの設定時に、CrawlConfig#setIncludeBinaryContentInCrawlingでバイナリファイルもクローリング対象にしておかないと、PDFを取得してくれません。また、今回のクローリング対象はドキュメントサイズがcrawler4jのデフォルトの最大ダウンロードサイズよりも大きかったので、広げています。

        // Crawlerの設定
        String crawlStorageFolder = "data/crawl/strage";
        int numberOfCrawlers = 7;  // スレッド数

        CrawlConfig config = new CrawlConfig();
        config.setCrawlStorageFolder(crawlStorageFolder);
        config.setPolitenessDelay(2 * 1000);  // 2秒ごとにリクエスト
        config.setIncludeBinaryContentInCrawling(true);  // バイナリファイルも対象に
        config.setMaxDownloadSize(15 * 1024 * 1024);  // 最大ダウンロードサイズを調整

クローリングを開始して、終了すればJSONファイルを閉じます。

        // クロール開始
        logger.info("Start crawler.");
        controller.start(MyLittleCrawler.class, numberOfCrawlers);

        // クロール後、JSONファイル書き出し終了
        jsonWriter.end();
        jsonWriter.close();
        writer.close();

そして、最後に作成したJSONファイルを、UpdateHandlerで叩き込みます。

        // Solrへドキュメント更新
        SolrClient solrClient = new HttpSolrClient("http://localhost:8983/solr/mycore");
        ContentStreamUpdateRequest updateRequest = new ContentStreamUpdateRequest("/update");

        updateRequest.addFile(new File(indexJsonFile), "application/json");

        NamedList<Object> named = solrClient.request(updateRequest);
        logger.info("Solr request result = {}.", named);

        // コミットおよびオプティマイズ
        logger.info("commit = {}", solrClient.commit().getResponse());
        logger.info("optimize = {}", solrClient.optimize().getResponse());

        // 後始末
        solrClient.close();

これで、終了。

動かしてみる

これで動作させてみると、クローリングが始まりHTMLやPDFの取得、解析が始まります。

[main] INFO edu.uci.ics.crawler4j.crawler.CrawlController - Deleted contents of: data/crawl/strage/frontier ( as you have configured resumable crawling to false )
[main] INFO org.littlewings.mycrawler.Launcher - add Seed URL under... http://info.vmware.com/content/apac_jp_co_techresources
[main] INFO org.littlewings.mycrawler.Launcher - Start crawler.
[main] INFO edu.uci.ics.crawler4j.crawler.CrawlController - Crawler 1 started
[main] INFO edu.uci.ics.crawler4j.crawler.CrawlController - Crawler 2 started
[main] INFO edu.uci.ics.crawler4j.crawler.CrawlController - Crawler 3 started
[main] INFO edu.uci.ics.crawler4j.crawler.CrawlController - Crawler 4 started
[main] INFO edu.uci.ics.crawler4j.crawler.CrawlController - Crawler 5 started
[main] INFO edu.uci.ics.crawler4j.crawler.CrawlController - Crawler 6 started
[main] INFO edu.uci.ics.crawler4j.crawler.CrawlController - Crawler 7 started
[Crawler 1] INFO org.littlewings.mycrawler.MyLittleCrawler - URL: http://info.vmware.com/content/apac_jp_co_techresources, Content-Type: text/html; charset=utf-8
[Crawler 1] INFO org.littlewings.mycrawler.MyLittleCrawler - Contents is HTML: URL = http://info.vmware.com/content/apac_jp_co_techresources
[Crawler 1] INFO org.littlewings.mycrawler.MyLittleCrawler - Number of outgoing links: 292
[Crawler 3] INFO org.littlewings.mycrawler.MyLittleCrawler - URL: http://info.vmware.com/content/APAC_JP_VMware_Careers/?src=WWW_Careers_JP_CompanyMegaBanner_CareersSearchJob, Content-Type: text/html; charset=utf-8
[Crawler 3] INFO org.littlewings.mycrawler.MyLittleCrawler - Contents is HTML: URL = http://info.vmware.com/content/APAC_JP_VMware_Careers/?src=WWW_Careers_JP_CompanyMegaBanner_CareersSearchJob
[Crawler 3] INFO org.littlewings.mycrawler.MyLittleCrawler - Number of outgoing links: 42

そして、クローリングが終了すると

[Thread-1] INFO edu.uci.ics.crawler4j.crawler.CrawlController - It looks like no thread is working, waiting for 10 seconds to make sure...
[Thread-1] INFO edu.uci.ics.crawler4j.crawler.CrawlController - No thread is working and no more URLs are in queue waiting for another 10 seconds to make sure...
[Thread-1] INFO edu.uci.ics.crawler4j.crawler.CrawlController - All of the crawlers are stopped. Finishing the process...
[Thread-1] INFO edu.uci.ics.crawler4j.crawler.CrawlController - Waiting for 10 seconds before final clean up...

JSONファイルを閉じて、Solrのインデックスを更新します。

[main] INFO org.littlewings.mycrawler.Launcher - JSON file[index-data.json] created.
[main] INFO org.littlewings.mycrawler.Launcher - Solr request result = {responseHeader={status=0,QTime=885}}.
[main] INFO org.littlewings.mycrawler.Launcher - commit = {responseHeader={status=0,QTime=118}}
[main] INFO org.littlewings.mycrawler.Launcher - optimize = {responseHeader={status=0,QTime=1}}

できあがった、JSONファイルの一部はこんな感じです。

$ head index-data.json 
[ {
  "url" : "http://info.vmware.com/content/apac_jp_co_techresources",
  "title" : "テクニカル リソース センター - VMware",
  "description" : "Technical Papers, Technical Resources",
  "contents" : "VMware テクニカル リソース センター クラウドコンピューティング Title Revised VMware とクラウド コンピューティング 14/01/2011 VMware 企業データシート:ITの革新を加速する“Your Cloud.”
〜省略〜

それでは、Solrに対して検索してみます。

$ curl 'http://localhost:8983/solr/mycore/select?wt=json&indent=true' -d '{ "query": "*:*" }'
{
  "responseHeader":{
    "status":0,
    "QTime":0,
    "params":{
      "indent":"true",
      "json":"{ \"query\": \"*:*\" }",
      "wt":"json"}},
  "response":{"numFound":40,"start":0,"docs":[
      {
        "url":"http://info.vmware.com/content/apac_jp_co_techresources",
        "title":"テクニカル リソース センター - VMware",
        "description":"Technical Papers, Technical Resources",
        "contents":"VMware テクニカル リソース センター クラウドコンピューティング Title Revised VMware とクラウド コンピューティング 14/01/2011 VMware 企業データシート:ITの革新を加速する“

〜省略〜

全部で40ドキュメントあるみたいです。

範囲をHTMLに絞ってみます。

$ curl 'http://localhost:8983/solr/mycore/select?wt=json&indent=true' -d '{ "query": "file_type:html" }'
{
  "responseHeader":{
    "status":0,
    "QTime":0,
    "params":{
      "indent":"true",
      "json":"{ \"query\": \"file_type:html\" }",
      "wt":"json"}},
  "response":{"numFound":29,"start":0,"docs":[
      {
        "url":"http://info.vmware.com/content/apac_jp_co_techresources",
        "title":"テクニカル リソース センター - VMware",
Your Cloud.” 22/01/2013 VMware のクラウド運用支援サービス 30/10/2012 \"Your Cloud.\" の実現に向けて 事後対応型の組織から革新型の組織への変革 30/10/2012 オンデマンドサービス 30/10/2012 VMwar
〜省略〜

29件ですね。

今度は、PDFの方で見てみます。

$ curl 'http://localhost:8983/solr/mycore/select?wt=json&indent=true' -d '{ "query": "file_type:pdf" }'
{
  "responseHeader":{
    "status":0,
    "QTime":1,
    "params":{
      "indent":"true",
      "json":"{ \"query\": \"file_type:pdf\" }",
      "wt":"json"}},
  "response":{"numFound":11,"start":0,"docs":[
      {
        "url":"http://www.vmware.com/files/jp/pdf/products/vsphere/VMware-vSphere-Evaluation-Guide-2-Advanced-Storage.pdf",
        "title":"VMware-vSphere-Evaluation-Guide-2-Advanced-Storage.pdf",
        "description":"\nVMware vSphere&#174; 5.0 \n評価ガイド\nVol.2: 高度なストレージ機能\n\nテクニカル  ホワイト  ペーパー\n\n\n\nVMware vSphere 5.0 評価ガイド  \nVol.2\n\nテクニカル  ホワイ
〜省略〜

こちらは、残りの11件ですね。

というわけで、なんとなくそれっぽいのができあがりました。