<?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 'dsl'</title>
 <link rel="self" href="http://blog.shriphani.com/feeds/dsl.atom.xml" />
 <link href="http://blog.shriphani.com/tags/dsl.html" />
 <id>urn:http-blog-shriphani-com:-tags-dsl-html</id>
 <updated>2016-11-16T06:00:20Z</updated>
 <entry>
  <title type="text">A Clojure DSL for Web-Crawling</title>
  <link rel="alternate" href="http://blog.shriphani.com/2016/11/16/a-clojure-dsl-for-web-crawling/?utm_source=dsl&amp;utm_medium=Atom" />
  <id>urn:http-blog-shriphani-com:-2016-11-16-a-clojure-dsl-for-web-crawling</id>
  <published>2016-11-16T06:00:20Z</published>
  <updated>2016-11-16T06:00:20Z</updated>
  <author>
   <name>SHRIPHANI PALAKODETY</name></author>
  <content type="html">&lt;html&gt;
&lt;p&gt;When building crawlers, most of the effort is expended in guiding them through a website. For example, if we want to crawl all pages and individual posts on this blog, we extract links like so:&lt;/p&gt;

&lt;ol&gt;
 &lt;li&gt;Visit current webpage&lt;/li&gt;
 &lt;li&gt;Extract pagination links&lt;/li&gt;
 &lt;li&gt;Extract link to each blog post&lt;/li&gt;
 &lt;li&gt;Enqueue extracted links&lt;/li&gt;
 &lt;li&gt;Continue&lt;/li&gt;&lt;/ol&gt;

&lt;p&gt;In this blog post, I present a new DSL that allows you to concisely describe this process.&lt;/p&gt;

&lt;p&gt;This DSL is now part of this crawler: &lt;a href="https://github.com/shriphani/pegasus"&gt;https://github.com/shriphani/pegasus&lt;/a&gt;&lt;/p&gt;
&lt;!-- more--&gt;

&lt;p&gt;Enlive provides an idiomatic way to select elements and it forms the foundation of most of the work here. Let us implement the crawler I discussed above:&lt;/p&gt;

&lt;p&gt;Since the focus (in this blog post at least) in on extracting links, let us look at the DSL:&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&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="nf"&gt;defextractors&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt; &lt;span class="ss"&gt;:at-selector&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:article&lt;/span&gt; &lt;span class="ss"&gt;:header&lt;/span&gt; &lt;span class="ss"&gt;:h2&lt;/span&gt; &lt;span class="ss"&gt;:a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

           &lt;span class="ss"&gt;:follow&lt;/span&gt; &lt;span class="ss"&gt;:href&lt;/span&gt;

           &lt;span class="ss"&gt;:with-regex&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="s"&gt;"blog.shriphani.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                       
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt; &lt;span class="ss"&gt;:at-selector&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:ul.pagination&lt;/span&gt; &lt;span class="ss"&gt;:a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

           &lt;span class="ss"&gt;:follow&lt;/span&gt; &lt;span class="ss"&gt;:href&lt;/span&gt;
                                
           &lt;span class="ss"&gt;:with-regex&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="s"&gt;"blog.shriphani.com"&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;And that is it! We specified an elive selector to pull tags, the attribute entry to follow and then filter these URLs with a regex.&lt;/p&gt;

&lt;p&gt;With &lt;a href="https://github.com/shriphani/pegasus"&gt;pegasus&lt;/a&gt;, the full crawler is expressed in under 10 lines as:&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&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="kd"&gt;defn &lt;/span&gt;&lt;span class="nv"&gt;crawl-sp-blog-custom-extractor&lt;/span&gt;
  &lt;span class="p"&gt;[]&lt;/span&gt;
  &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;crawl&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="ss"&gt;:seeds&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="s"&gt;"http://blog.shriphani.com"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;
          &lt;span class="ss"&gt;:user-agent&lt;/span&gt; &lt;span class="s"&gt;"Pegasus web crawler"&lt;/span&gt;
          &lt;span class="ss"&gt;:extractor&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;defextractors&lt;/span&gt;
                       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt; &lt;span class="ss"&gt;:at-selector&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:article&lt;/span&gt; &lt;span class="ss"&gt;:header&lt;/span&gt; &lt;span class="ss"&gt;:h2&lt;/span&gt; &lt;span class="ss"&gt;:a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

                                &lt;span class="ss"&gt;:follow&lt;/span&gt; &lt;span class="ss"&gt;:href&lt;/span&gt;

                                &lt;span class="ss"&gt;:with-regex&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="s"&gt;"blog.shriphani.com"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
                       
                       &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nf"&gt;extract&lt;/span&gt; &lt;span class="ss"&gt;:at-selector&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="ss"&gt;:ul.pagination&lt;/span&gt; &lt;span class="ss"&gt;:a&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

                                &lt;span class="ss"&gt;:follow&lt;/span&gt; &lt;span class="ss"&gt;:href&lt;/span&gt;
                                
                                &lt;span class="ss"&gt;:with-regex&lt;/span&gt; &lt;span class="o"&gt;#&lt;/span&gt;&lt;span class="s"&gt;"blog.shriphani.com"&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
          
          &lt;span class="ss"&gt;:corpus-size&lt;/span&gt; &lt;span class="mi"&gt;20&lt;/span&gt; &lt;span class="c1"&gt;;; crawl 20 documents&lt;/span&gt;
          &lt;span class="ss"&gt;:job-dir&lt;/span&gt; &lt;span class="s"&gt;"/tmp/sp-blog-corpus"&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;Essentially, under 20 lines.&lt;/p&gt;

&lt;p&gt;The DSL was partially inspired by the great work done in this crawler for Node.js: &lt;a href="https://github.com/rchipka/node-osmosis"&gt;https://github.com/rchipka/node-osmosis&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Links:&lt;/p&gt;
&lt;!-- Place this tag where you want the button to render.--&gt;

&lt;p&gt;&lt;a href="https://github.com/shriphani/pegasus"&gt;Pegasus&lt;/a&gt;: &lt;a class="github-button" href="https://github.com/shriphani/pegasus" data-icon="octicon-star" data-style="mega" data-count-href="/shriphani/pegasus/stargazers" data-count-api="/repos/shriphani/pegasus#stargazers_count" data-count-aria-label="# stargazers on GitHub" aria-label="Star shriphani/pegasus on GitHub"&gt;Star&lt;/a&gt;&lt;/p&gt;

&lt;div&gt;&lt;!-- Place this tag in your head or just before your close body tag.--&gt; 
 &lt;script async="async" defer="defer" src="https://buttons.github.io/buttons.js"&gt;&lt;/script&gt;&lt;/div&gt;&lt;/html&gt;</content></entry></feed>