<?xml version="1.0" encoding="utf-8"?> 
<feed xmlns="http://www.w3.org/2005/Atom" xml:lang="en">
 <title type="text">SHRIPHANI PALAKODETY: Posts tagged 'scale'</title>
 <link rel="self" href="http://blog.shriphani.com/feeds/scale.atom.xml" />
 <link href="http://blog.shriphani.com/tags/scale.html" />
 <id>urn:http-blog-shriphani-com:-tags-scale-html</id>
 <updated>2016-01-25T05:10:26Z</updated>
 <entry>
  <title type="text">Pegasus: A Modular, Durable Web Crawler For Clojure</title>
  <link rel="alternate" href="http://blog.shriphani.com/2016/01/25/pegasus-a-modular-durable-web-crawler-for-clojure/?utm_source=scale&amp;utm_medium=Atom" />
  <id>urn:http-blog-shriphani-com:-2016-01-25-pegasus-a-modular-durable-web-crawler-for-clojure</id>
  <published>2016-01-25T05:10:26Z</published>
  <updated>2016-01-25T05:10:26Z</updated>
  <author>
   <name>SHRIPHANI PALAKODETY</name></author>
  <content type="html">&lt;html&gt;
&lt;p&gt;&lt;img src="https://raw.githubusercontent.com/shriphani/pegasus/master/pegasus_logo.png" /&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="http://getpegasus.io"&gt;Pegasus&lt;/a&gt; is a durable, multithreaded web-crawler for clojure.&lt;/p&gt;

&lt;p&gt;I wrote Pegasus after the existing choices in the Java ecosystem left me frustrated.&lt;/p&gt;

&lt;p&gt;The more popular crawler projects (&lt;a href="https://webarchive.jira.com/wiki/display/Heritrix/Heritrix"&gt;Heritrix&lt;/a&gt; and &lt;a href="http://nutch.apache.org/"&gt;Nutch&lt;/a&gt;) are clunky and not easy to configure. I have often wanted to be able to supply my own extractors, save payloads directly to a database and so on. Short of digging into large codebases, there isn&amp;rsquo;t much of an option there.&lt;/p&gt;

&lt;p&gt;Tiny crawlers hold all their data structures in memory and are incapable of crawling the entire web. A simple crash somewhere causes you to lose all state built over a long-running crawl. I also want to be able to (at times) modify critical data-structures and functions mid-crawl.&lt;/p&gt;

&lt;p&gt;Pegasus gives you the following:&lt;/p&gt;

&lt;ol&gt;
 &lt;li&gt;Parallelism using the excellent &lt;code&gt;core.async&lt;/code&gt; library.&lt;/li&gt;
 &lt;li&gt;Disk-backed data structures that allow crawls to survive crashes, system restarts etc. (I am still implementing the restart bits).&lt;/li&gt;
 &lt;li&gt;Implements the bare minimum politeness needed in crawlers (support for &lt;code&gt;robots.txt&lt;/code&gt; and &lt;code&gt;rel='nofollow'&lt;/code&gt;).&lt;/li&gt;&lt;/ol&gt;
&lt;!-- more--&gt;

&lt;p&gt;A crawl is configured using a config object - a clojure map. All crawl state such as references to important data-structures, function definitions, crawl-specific vars, is stored in this config.&lt;/p&gt;

&lt;p&gt;The config is available to all critical functions and allows us to (i) maintain important crawl-state, and (ii) update important data-structures on-the-fly.&lt;/p&gt;

&lt;p&gt;Some critical pieces:&lt;/p&gt;

&lt;h1 id="process-pipeline"&gt;Process Pipeline&lt;/h1&gt;

&lt;p&gt;&lt;code&gt;core.async&lt;/code&gt; allows us to specify a pipeline using &lt;code&gt;pipeline-blocking&lt;/code&gt;. The default pipeline in pegasus consists of a set of simple functions. These routines are invoked by producers and consumers that produce and consume from, well &lt;code&gt;core.async&lt;/code&gt; channels.&lt;/p&gt;

&lt;p&gt;&lt;code&gt;schema&lt;/code&gt;&amp;rsquo;s are used to enforce structural requirements for all components.&lt;/p&gt;

&lt;p&gt;Here&amp;rsquo;s a simple example of the pipeline:&lt;/p&gt;

&lt;div class="brush: clojure"&gt;
 &lt;table class="sourcetable"&gt;
  &lt;tbody&gt;
   &lt;tr&gt;
    &lt;td class="linenos"&gt;
     &lt;div class="linenodiv"&gt;
      &lt;pre&gt; 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34&lt;/pre&gt;&lt;/div&gt;&lt;/td&gt;
    &lt;td class="code"&gt;
     &lt;div class="source"&gt;
      &lt;pre&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:frontier&lt;/span&gt; &lt;span class="nv"&gt;default-frontier-fn&lt;/span&gt;
   &lt;span class="ss"&gt;:extractor&lt;/span&gt; &lt;span class="nv"&gt;default-extractor-fn&lt;/span&gt;
   &lt;span class="ss"&gt;:writer&lt;/span&gt; &lt;span class="nv"&gt;default-writer-fn&lt;/span&gt;
   &lt;span class="ss"&gt;:enqueue&lt;/span&gt; &lt;span class="nv"&gt;queue/enqueue-pipeline&lt;/span&gt;
   &lt;span class="ss"&gt;:update-state&lt;/span&gt; &lt;span class="nv"&gt;default-update-state&lt;/span&gt;
   &lt;span class="ss"&gt;:update-stats&lt;/span&gt; &lt;span class="nv"&gt;default-update-stats&lt;/span&gt;
   &lt;span class="ss"&gt;:test-and-halt&lt;/span&gt; &lt;span class="nv"&gt;default-stop-check&lt;/span&gt;
   &lt;span class="ss"&gt;:filter&lt;/span&gt; &lt;span class="nv"&gt;default-filter&lt;/span&gt;
   &lt;span class="ss"&gt;:stop-sequence&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;close-wrtr&lt;/span&gt; &lt;span class="nv"&gt;mark-stop&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
   &lt;span class="ss"&gt;:pipeline&lt;/span&gt; &lt;span class="p"&gt;[[&lt;/span&gt;&lt;span class="ss"&gt;:frontier&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:extractor&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt;,
                           &lt;span class="ss"&gt;:body&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt;,
                           &lt;span class="ss"&gt;:time&lt;/span&gt; &lt;span class="nv"&gt;s/Int&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:update-state&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt;,
                              &lt;span class="ss"&gt;:body&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt;,
                              &lt;span class="ss"&gt;:time&lt;/span&gt; &lt;span class="nv"&gt;s/Int&lt;/span&gt;
                              &lt;span class="ss"&gt;:extracted&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;s/Str&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:filter&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt;
                        &lt;span class="ss"&gt;:body&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt;
                        &lt;span class="ss"&gt;:time&lt;/span&gt; &lt;span class="nv"&gt;s/Int&lt;/span&gt;
                        &lt;span class="ss"&gt;:extracted&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;s/Str&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:writer&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt;
                        &lt;span class="ss"&gt;:body&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt;
                        &lt;span class="ss"&gt;:time&lt;/span&gt; &lt;span class="nv"&gt;s/Int&lt;/span&gt;
                        &lt;span class="ss"&gt;:extracted&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;s/Str&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:enqueue&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt;
                         &lt;span class="ss"&gt;:body&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt;
                         &lt;span class="ss"&gt;:time&lt;/span&gt; &lt;span class="nv"&gt;s/Int&lt;/span&gt;
                         &lt;span class="ss"&gt;:extracted&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;s/Str&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:update-stats&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:url&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt;
                              &lt;span class="ss"&gt;:body&lt;/span&gt; &lt;span class="nv"&gt;s/Str&lt;/span&gt;
                              &lt;span class="ss"&gt;:time&lt;/span&gt; &lt;span class="nv"&gt;s/Int&lt;/span&gt;
                              &lt;span class="ss"&gt;:extracted&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nv"&gt;s/Str&lt;/span&gt;&lt;span class="p"&gt;]}&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
              &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:test-and-halt&lt;/span&gt; &lt;span class="nv"&gt;s/Any&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;]]}&lt;/span&gt;
&lt;/pre&gt;&lt;/div&gt;
&lt;/td&gt;&lt;/tr&gt;&lt;/tbody&gt;&lt;/table&gt;
&lt;/div&gt;

&lt;p&gt;Each component specifies a function-name whose implementation is available from the config, a schema that specifies the input structure and the parallelism (i.e. the number of concurrent threads).&lt;/p&gt;

&lt;h1 id="politeness"&gt;Politeness&lt;/h1&gt;

&lt;p&gt;Politeness is achieved using a combination of:&lt;/p&gt;

&lt;ol&gt;
 &lt;li&gt;A minimum delay between successive requests to a host.&lt;/li&gt;
 &lt;li&gt;Robots.txt directives.&lt;/li&gt;
 &lt;li&gt;Not following &lt;code&gt;rel="nofollow"&lt;/code&gt; urls.&lt;/li&gt;&lt;/ol&gt;

&lt;h1 id="durability"&gt;Durability&lt;/h1&gt;

&lt;p&gt;A web crawl is a breadth-first search on the web-graph. The critical data-structure is a queue. I decided to use factual&amp;rsquo;s excellent &lt;a href="https://github.com/Factual/durable-queue"&gt;durable-queue&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;Every host gets its own named queue and a custom worker pulls from each queue and pushes it through the pipeline.&lt;/p&gt;

&lt;p&gt;Each component accepts an object and attaches a key to it.&lt;/p&gt;

&lt;p&gt;In addition, it is important to be able to access the queue&amp;rsquo;s contents at random. For instance to check if a URL has already been crawled. The typical approach involves a bloom filter but I chose to go with a key-value store whose results can be saved to disk - &lt;a href="https://commons.apache.org/proper/commons-jcs/"&gt;JCS&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;The Heritrix project has successfully used BerkeleyDB. I might write a variant of pegasus that uses BerkeleyDB in the future.&lt;/p&gt;

&lt;h1 id="examples"&gt;Examples&lt;/h1&gt;

&lt;p&gt;In this section, I am going to implement some simple crawl tasks. These should give you a flavor for what&amp;rsquo;s possible.&lt;/p&gt;

&lt;h2 id="enlive-selectors-for-crawling-this-blog"&gt;Enlive selectors for crawling this blog&lt;/h2&gt;

&lt;p&gt;This snippet crawls all submissions on this blog and the pagination.&lt;/p&gt;

&lt;script src="https://gist.github.com/shriphani/35407ab0d43644e56887.js"&gt;&lt;/script&gt;

&lt;h2 id="xpath-selectors-for-crawling-this-blog"&gt;XPath selectors for crawling this blog&lt;/h2&gt;

&lt;p&gt;This snippet performs the exact same task but with XPaths.&lt;/p&gt;

&lt;script src="https://gist.github.com/shriphani/fef9bd1f73abcbdeb2c1.js"&gt;&lt;/script&gt;

&lt;p&gt;Pegasus is still a WIP and the interfaces exposed are subject to some change but I should have them fleshed out soon (and I will be spending time on better docs).&lt;/p&gt;

&lt;h1 id="resources"&gt;Resources&lt;/h1&gt;

&lt;ul&gt;
 &lt;li&gt;Github Project: &lt;a href="https://github.com/shriphani/pegasus"&gt;[link]&lt;/a&gt;&lt;/li&gt;
 &lt;li&gt;Issues: &lt;a href="https://github.com/shriphani/pegasus/issues"&gt;[link]&lt;/a&gt;&lt;/li&gt;&lt;/ul&gt;

&lt;p&gt;If there are any features you want to see or suggestions you have, do let me know.&lt;/p&gt;&lt;/html&gt;</content></entry></feed>