Code Monkey home page Code Monkey logo

crawler4j's Introduction

crawler4j

This repository contains a fork of rzo1/crawler4j which itself is a fork of yasserg/crawler4j. Yasserg is the initial creator of this crawler.


crawler4j is an open source web crawler for Java which provides a simple interface for crawling the Web. Using it, you can setup a multi-threaded web crawler in few minutes.

Table of content

Why you should use this fork?

This fork starts where the development of the previous main repository stalled.

Some highlights include:

  • choice between multiple frontier implementations => avoid using a database with a license that doesn't comply with your use-case
  • easy substitution of various parser implementations (not only for html, but also css, binary, and plain text)
  • dynamic authentication
  • improved exception handling, more versatile to customize
  • fixes various parsing issues
  • more documentation
  • more tests and all tests are JUnit5 based (so no knowledge of Groovy and/or Spock needed anymore to maintain the codebase)
  • uses Apache Maven as build tool
  • provides a clean upgrade path by keeping backward compatibility in mind and deprecating methods before removing them
  • more eyes have gone through the code, so readability and correctness have improved
  • maintained, i.e. dependencies are often updated to their latest versions

Installation

Using Maven

This fork provides no compiled artifacts on Maven. It might somewhere in the future but there are no plans to do so yet.

License hint

Please check, if the Oracle license for Sleepycat database complies with your use-case.

Otherwise, you can use HSQLDB instead or you use an external crawler-commons/url-frontier

Quickstart

Archetype

Since 5.0.1, we provide a Maven archetype to bootstrap crawler4j development. Just run

mvn archetype:generate -DarchetypeGroupId=de.hs-heilbronn.mi -DarchetypeArtifactId=crawler4j-archetype -DarchetypeVersion=5.0.1                

Manual

You need to create a crawler class that extends WebCrawler and provide an (optional) UrlFilter implementation. The WebCrawler implementation handles the downloaded page and the UrlFilter implementation tells which URLs the crawler may fetch. The following is a sample implementation:

public class MyCrawler extends WebCrawler {
    
    public MyCrawler() {
        // set your URL filter in the constructor
        // or call setUrlFilter after creating an instance of the MyCrawler class
        setUrlFilter(new MyFilter());
    }

    /**
     * This function is called when a page is fetched and ready
      * to be processed by your program.
     */
    @Override
    public void visit(Page page) {
        String url = page.getWebURL().getURL();
        System.out.println("URL: " + url);

        if (page.getParseData() instanceof HtmlParseData) {
            HtmlParseData htmlParseData = (HtmlParseData) page.getParseData();
            String text = htmlParseData.getText();
            String html = htmlParseData.getHtml();
            Set<WebURL> links = htmlParseData.getOutgoingUrls();

            System.out.println("Text length: " + text.length());
            System.out.println("Html length: " + html.length());
            System.out.println("Number of outgoing links: " + links.size());
        }
    }
    
    /**
     * Determine whether links found at the given URL should be added to the queue for crawling.
     */
    @Override
    protected boolean shouldFollowLinksIn(WebURL url) {
        return super.shouldFollowLinksIn(url);
    }

    public record MyFilter(Pattern pattern) implements UrlFilter {
        private final static Pattern FILTERS = Pattern.compile(".*(\\.(css|js|gif|jpg"
                + "|png|mp3|mp4|zip|gz))$");
        
        /**
         * This method receives two parameters. The first parameter is the page
         * in which we have discovered this new url and the second parameter is
         * the new url. You should implement this function to specify whether
         * the given url should be crawled or not (based on your crawling logic).
         * In this example, we are instructing the crawler to ignore urls that
         * have css, js, git, ... extensions and to only accept urls that start
         * with "https://www.ics.uci.edu/". In this case, we didn't need the
         * referringPage parameter to make the decision.
         */
        @Override
        public boolean accept(Page referringPage, WebURL url) {
            String href = url.getURL().toLowerCase();
            return !FILTERS.matcher(href).matches()
                    && href.startsWith("https://www.ics.uci.edu/");
        }
    }
    
}

As can be seen in the above code, there are two main functions that should be implemented:

  • MyFilter#accept: This method decides whether the given URL should be crawled or not. In the above example, this example is not allowing .css, .js and media files and only allows pages within 'www.ics.uci.edu' domain.
  • MyCrawler#visit: This method is called after the content of a URL is downloaded successfully. You can easily get the url, text, links, html, and unique id of the downloaded page.
  • (extra) MyCrawler#shouldFollowLinksIn: can also be overridden as needed. false means none of the outgoing links are scheduled to be crawled.

The flow is as follows:
fetch url -> parse => outgoing links are detected -> "shouldFollowLinksIn": decide for all outgoing urls -> "accept": a more fine grained (per url) decision

You should also implement a controller class which specifies the seeds of the crawl, the folder in which intermediate crawl data should be stored and the number of concurrent threads:

public class Controller {
    public static void main(String[] args) throws Exception {
        String crawlStorageFolder = "/data/crawl/root";
        int numberOfCrawlers = 7;

        CrawlConfig config = new CrawlConfig();
        config.setCrawlStorageFolder(crawlStorageFolder);
        
        // Instantiate the controller for this crawl 
        BasicURLNormalizer normalizer = BasicURLNormalizer.newBuilder().idnNormalization(BasicURLNormalizer.IdnNormalization.NONE).build();
        PageFetcher pageFetcher = new PageFetcher(config, normalizer);
        RobotstxtConfig robotstxtConfig = new RobotstxtConfig();
        FrontierConfiguration frontierConfiguration = new SleepycatFrontierConfiguration(config);
        // OR use
        // FrontierConfiguration frontierConfiguration = new HSQLDBFrontierConfiguration(config, 10);
        RobotstxtServer robotstxtServer = new RobotstxtServer(robotstxtConfig, pageFetcher, frontierConfiguration.getWebURLFactory());
        CrawlController controller = new CrawlController(config, normalizer, pageFetcher, robotstxtServer, frontierConfiguration);

        // For each crawl, you need to add some seed urls. These are the first
        // URLs that are fetched and then the crawler starts following links
        // which are found in these pages
        controller.addSeed("https://www.ics.uci.edu/~lopes/");
        controller.addSeed("https://www.ics.uci.edu/~welling/");
    	controller.addSeed("https://www.ics.uci.edu/");
    	
    	// The factory which creates instances of crawlers.
        CrawlController.WebCrawlerFactory<BasicCrawler> factory = MyCrawler::new;
        
        // Start the crawl. This is a blocking operation, meaning that your code
        // will reach the line after this only when crawling is finished.
        controller.start(factory, numberOfCrawlers);
    }
}

More Examples

  • Basic crawler: the full source code of the above example with more details.
  • Image crawler: a simple image crawler that downloads image content from the crawling domain and stores them in a folder. This example demonstrates how binary content can be fetched using crawler4j.
  • Collecting data from threads: this example demonstrates how the controller can collect data/statistics from crawling threads.
  • Multiple crawlers: this is a sample that shows how two distinct crawlers can run concurrently. For example, you might want to split your crawling into different domains and then take different crawling policies for each group. Each crawling controller can have its own configurations.
  • Shutdown crawling: this example shows how crawling can be terminated gracefully by sending the 'shutdown' command to the controller.
  • Postgres/JDBC integration: this shows how to save the crawled content into a Postgres database (or any other JDBC repository), thanks rzo1.

Configuration Details

The controller class has a mandatory parameter of type CrawlConfig. Instances of this class can be used for configuring crawler4j. The following sections describe some details of configurations.

Crawl depth

By default there is no limit on the depth of crawling. But you can limit the depth of crawling. For example, assume that you have a seed page "A", which links to "B", which links to "C", which links to "D". So, we have the following link structure:

A -> B -> C -> D

Since, "A" is a seed page, it will have a depth of 0. "B" will have depth of 1 and so on. You can set a limit on the depth of pages that crawler4j crawls. For example, if you set this limit to 2, it won't crawl page "D". To set the maximum depth you can use:

crawlConfig.setMaxDepthOfCrawling(maxDepthOfCrawling);

Maximum number of pages to crawl

Although by default there is no limit on the number of pages to crawl, you can set a limit on this:

crawlConfig.setMaxPagesToFetch(maxPagesToFetch);

Enable Binary Content Crawling

By default crawling binary content (i.e. images, audio etc.) is turned off. To enable crawling these files:

crawlConfig.setIncludeBinaryContentInCrawling(true);

See an example here for more details.

Politeness

crawler4j is designed very efficiently and has the ability to crawl domains very fast (e.g., it has been able to crawl 200 Wikipedia pages per second). However, since this is against crawling policies and puts huge load on servers (and they might block you!), since version 1.3, by default crawler4j waits at least 200 milliseconds between requests. However, this parameter can be tuned:

crawlConfig.setPolitenessDelay(politenessDelay);

Proxy

Should your crawl run behind a proxy? If so, you can use:

crawlConfig.setProxyHost("proxyserver.example.com");
crawlConfig.setProxyPort(8080);

If your proxy also needs authentication:

crawlConfig.setProxyUsername(username);
crawlConfig.setProxyPassword(password);

Resumable Crawling

Sometimes you need to run a crawler for a long time. It is possible that the crawler terminates unexpectedly. In such cases, it might be desirable to resume the crawling. You would be able to resume a previously stopped/crashed crawl using the following settings:

crawlConfig.setResumableCrawling(true);

However, you should note that it might make the crawling slightly slower.

User agent string

User-agent string is used for representing your crawler to web servers. See here for more details. By default crawler4j uses the following user agent string:

"crawler4j (https://github.com/rzo1/crawler4j/)"

However, you can overwrite it:

crawlConfig.setUserAgentString(userAgentString);

Reconstructing extra urls to crawl

In these heydays of JavaScript frameworks not all links can always be easily detected.
Following is a naive implementation to add more links to crawl after prying them out of fetched content:

public class MyWebCrawler extends WebCrawler {
	
	private static final Logger LOGGER = LoggerFactory.getLogger(MyWebCrawler.class);
	
	@Override
	public void visit(final Page page) {
		
		final List<WebURL> pageUrls = new ArrayList<>();
		// ... -> gathering/reconstructing urls from page.getContentData() or page.getParseData() (more likely)
		pageUrls.addAll(page.getParseData().getOutgoingUrls()); // Useless, these are already scheduled automatically!!
		
		// Technically possible, but it's better to define extra urls to fetch inside a custom HtmlParser.
		// That way the parent url will automatically be linked with it as well as the crawl depth.
		getMyController().getFrontier().scheduleAll(pageUrls);
		
		// Also possible, but again has no notion of crawl depth, parent url, no "shouldFollowLinksIn(...)"-functionality...
		final List<String> stringUrls = new ArrayList<>();
		try {
			getMyController().addSeeds(stringUrls);
		} catch (InterruptedException e) {
			LOGGER.error("Unable to add seeds -> exception: ", e);
		}
	}
}

A better approach is detecting the extra links while parsing. The links will get added to the list of page.getParseData().outgoingUrls():

// Use this constructor:
new CrawlController(config, normalizer, pageFetcher, parser, robotstxtServer, tldList, frontierConfiguration);

// Of course first create a custom parser:
new Parser(config, normalizer, htmlParser, tldList, frontierConfiguration.getWebURLFactory());
// -> the magic should happen inside a custom htmlParser implementation.

Authentication

Different forms of authentication are supported:

  • BASIC_AUTHENTICATION
  • FORM_AUTHENTICATION
  • NT_AUTHENTICATION.

An example on how to configure form authentication in its most simple form:

		final CrawlConfig config = new CrawlConfig();
		config.addAuthInfo(new FormAuthInfo(
				"myUser", "myReallyGoodPwd"
				, "https://www.test.com/login"
				, "username", "password"
		));

Overriding FormAuthInfo.doFormLogin(...) allows implementing more dynamic form authentication (entering more fields, fetching dynamic login forms, ...).

High-level design diagrams

Below activity diagram highlights the most important steps and vocabulary to know as user of the library. crawler4j activity diagram

License

Published under Apache License 2.0, see LICENSE

crawler4j's People

Contributors

dependabot[bot] avatar rzo1 avatar yasserg avatar s17t avatar chaiavi avatar pgalbraith avatar ewaldbenes avatar egbertw avatar jcotton1123 avatar hkasera avatar brbog avatar valfirst avatar bgoldowsky avatar panthro avatar raymanrt avatar anebrithien avatar dariusgm avatar albert0815 avatar rvenutolo avatar natemago avatar marshalium avatar jcarlile avatar masterejay avatar dhirajhr avatar alunwcom avatar djp3 avatar igor-sokolov avatar juarezr avatar michaellutaaya avatar skalca avatar

Stargazers

 avatar

Watchers

 avatar

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.