{"id":"design-web-crawler","title":"Design a web crawler","sections":[{"id":"overview","title":"Overview","html":"<p><em>Note: This document links directly to relevant areas found in the <a href=\"/\">system design topics</a> to avoid duplication.  Refer to the linked content for general talking points, tradeoffs, and alternatives.</em></p>","headings":[]},{"id":"step-1-outline-use-cases-and-constraints","title":"Step 1: Outline use cases and constraints","html":"<h2 id=\"step-1-outline-use-cases-and-constraints\">Step 1: Outline use cases and constraints</h2>\n<blockquote>\n<p>Gather requirements and scope the problem.\nAsk questions to clarify use cases and constraints.\nDiscuss assumptions.</p>\n</blockquote>\n<p>Without an interviewer to address clarifying questions, we'll define some use cases and constraints.</p>\n<h3 id=\"use-cases\">Use cases</h3>\n<h4 id=\"well-scope-the-problem-to-handle-only-the-following-use-cases\">We'll scope the problem to handle only the following use cases</h4>\n<ul>\n<li><strong>Service</strong> crawls a list of urls:\n<ul>\n<li>Generates reverse index of words to pages containing the search terms</li>\n<li>Generates titles and snippets for pages\n<ul>\n<li>Title and snippets are static, they do not change based on search query</li>\n</ul>\n</li>\n</ul>\n</li>\n<li><strong>User</strong> inputs a search term and sees a list of relevant pages with titles and snippets  the crawler generated\n<ul>\n<li>Only sketch high level components and interactions for this use case, no need to go into depth</li>\n</ul>\n</li>\n<li><strong>Service</strong> has high availability</li>\n</ul>\n<h4 id=\"out-of-scope\">Out of scope</h4>\n<ul>\n<li>Search analytics</li>\n<li>Personalized search results</li>\n<li>Page rank</li>\n</ul>\n<h3 id=\"constraints-and-assumptions\">Constraints and assumptions</h3>\n<h4 id=\"state-assumptions\">State assumptions</h4>\n<ul>\n<li>Traffic is not evenly distributed\n<ul>\n<li>Some searches are very popular, while others are only executed once</li>\n</ul>\n</li>\n<li>Support only anonymous users</li>\n<li>Generating search results should be fast</li>\n<li>The web crawler should not get stuck in an infinite loop\n<ul>\n<li>We get stuck in an infinite loop if the graph contains a cycle</li>\n</ul>\n</li>\n<li>1 billion links to crawl\n<ul>\n<li>Pages need to be crawled regularly to ensure freshness</li>\n<li>Average refresh rate of about once per week, more frequent for popular sites\n<ul>\n<li>4 billion links crawled each month</li>\n</ul>\n</li>\n<li>Average stored size per web page: 500 KB\n<ul>\n<li>For simplicity, count changes the same as new pages</li>\n</ul>\n</li>\n</ul>\n</li>\n<li>100 billion searches per month</li>\n</ul>\n<p>Exercise the use of more traditional systems - don't use existing systems such as <a href=\"http://lucene.apache.org/solr/\" target=\"_blank\" rel=\"noopener noreferrer\">solr</a> or <a href=\"http://nutch.apache.org/\" target=\"_blank\" rel=\"noopener noreferrer\">nutch</a>.</p>\n<h4 id=\"calculate-usage\">Calculate usage</h4>\n<p><strong>Clarify with your interviewer if you should run back-of-the-envelope usage calculations.</strong></p>\n<ul>\n<li>2 PB of stored page content per month\n<ul>\n<li>500 KB per page * 4 billion links crawled per month</li>\n<li>72 PB of stored page content in 3 years</li>\n</ul>\n</li>\n<li>1,600 write requests per second</li>\n<li>40,000 search requests per second</li>\n</ul>\n<p>Handy conversion guide:</p>\n<ul>\n<li>2.5 million seconds per month</li>\n<li>1 request per second = 2.5 million requests per month</li>\n<li>40 requests per second = 100 million requests per month</li>\n<li>400 requests per second = 1 billion requests per month</li>\n</ul>","headings":[{"id":"use-cases","title":"Use cases","depth":3},{"id":"well-scope-the-problem-to-handle-only-the-following-use-cases","title":"We'll scope the problem to handle only the following use cases","depth":4},{"id":"out-of-scope","title":"Out of scope","depth":4},{"id":"constraints-and-assumptions","title":"Constraints and assumptions","depth":3},{"id":"state-assumptions","title":"State assumptions","depth":4},{"id":"calculate-usage","title":"Calculate usage","depth":4}]},{"id":"step-2-create-a-high-level-design","title":"Step 2: Create a high level design","html":"<h2 id=\"step-2-create-a-high-level-design\">Step 2: Create a high level design</h2>\n<blockquote>\n<p>Outline a high level design with all important components.</p>\n</blockquote>\n<p><img src=\"/repo/solutions/system_design/web_crawler/web_crawler_basic.png\" alt=\"Diagram\" loading=\"lazy\" decoding=\"async\"></p>","headings":[]},{"id":"step-3-design-core-components","title":"Step 3: Design core components","html":"<h2 id=\"step-3-design-core-components\">Step 3: Design core components</h2>\n<blockquote>\n<p>Dive into details for each core component.</p>\n</blockquote>\n<h3 id=\"use-case-service-crawls-a-list-of-urls\">Use case: Service crawls a list of urls</h3>\n<p>We'll assume we have an initial list of <code>links_to_crawl</code> ranked initially based on overall site popularity.  If this is not a reasonable assumption, we can seed the crawler with popular sites that link to outside content such as <a href=\"https://www.yahoo.com/\" target=\"_blank\" rel=\"noopener noreferrer\">Yahoo</a>, <a href=\"http://www.dmoz.org/\" target=\"_blank\" rel=\"noopener noreferrer\">DMOZ</a>, etc.</p>\n<p>We'll use a table <code>crawled_links</code> to store processed links and their page signatures.</p>\n<p>We could store <code>links_to_crawl</code> and <code>crawled_links</code> in a key-value <strong>NoSQL Database</strong>.  For the ranked links in <code>links_to_crawl</code>, we could use <a href=\"https://redis.io/\" target=\"_blank\" rel=\"noopener noreferrer\">Redis</a> with sorted sets to maintain a ranking of page links.  We should discuss the <a href=\"/learn/database#sql-or-nosql\">use cases and tradeoffs between choosing SQL or NoSQL</a>.</p>\n<ul>\n<li>The <strong>Crawler Service</strong> processes each page link by doing the following in a loop:\n<ul>\n<li>Takes the top ranked page link to crawl\n<ul>\n<li>Checks <code>crawled_links</code> in the <strong>NoSQL Database</strong> for an entry with a similar page signature\n<ul>\n<li>If we have a similar page, reduces the priority of the page link\n<ul>\n<li>This prevents us from getting into a cycle</li>\n<li>Continue</li>\n</ul>\n</li>\n<li>Else, crawls the link\n<ul>\n<li>Adds a job to the <strong>Reverse Index Service</strong> queue to generate a <a href=\"https://en.wikipedia.org/wiki/Search_engine_indexing\" target=\"_blank\" rel=\"noopener noreferrer\">reverse index</a></li>\n<li>Adds a job to the <strong>Document Service</strong> queue to generate a static title and snippet</li>\n<li>Generates the page signature</li>\n<li>Removes the link from <code>links_to_crawl</code> in the <strong>NoSQL Database</strong></li>\n<li>Inserts the page link and signature to <code>crawled_links</code> in the <strong>NoSQL Database</strong></li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n</li>\n</ul>\n<p><strong>Clarify with your interviewer how much code you are expected to write</strong>.</p>\n<p><code>PagesDataStore</code> is an abstraction within the <strong>Crawler Service</strong> that uses the <strong>NoSQL Database</strong>:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PagesDataStore</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">):</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, db);</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.db </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> db</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        ...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> add_link_to_crawl</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, url):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Add the given link to `links_to_crawl`.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        ...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> remove_link_to_crawl</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, url):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Remove the given link from `links_to_crawl`.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        ...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> reduce_priority_link_to_crawl</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, url)</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Reduce the priority of a link in `links_to_crawl` to avoid cycles.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        ...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> extract_max_priority_page</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Return the highest priority link in `links_to_crawl`.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        ...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> insert_crawled_link</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, url, signature):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Add the given link to `crawled_links`.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        ...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> crawled_similar</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, signature):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Determine if we've already crawled a page matching the given signature\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        ...</span></span></code></pre>\n<p><code>Page</code> is an abstraction within the <strong>Crawler Service</strong> that encapsulates a page, its contents, child urls, and signature:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Page</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">):</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, url, contents, child_urls, signature):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.url </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> url</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.contents </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> contents</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.child_urls </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> child_urls</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.signature </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> signature</span></span></code></pre>\n<p><code>Crawler</code> is the main class within <strong>Crawler Service</strong>, composed of <code>Page</code> and <code>PagesDataStore</code>.</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Crawler</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">):</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, data_store, reverse_index_queue, doc_index_queue):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> data_store</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.reverse_index_queue </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> reverse_index_queue</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.doc_index_queue </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> doc_index_queue</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> create_signature</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, page):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Create signature based on url and contents.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        ...</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> crawl_page</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, page):</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        for</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> url </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">in</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> page.child_urls:</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.add_link_to_crawl(url)</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        page.signature </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.create_signature(page)</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.remove_link_to_crawl(page.url)</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.insert_crawled_link(page.url, page.signature)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> crawl</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self):</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        while</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> True</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">            page </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.extract_max_priority_page()</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> page </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">is</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">                break</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            if</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.crawled_similar(page.signature):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">                self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.reduce_priority_link_to_crawl(page.url)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            else</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">                self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.crawl_page(page)</span></span></code></pre>\n<h3 id=\"handling-duplicates\">Handling duplicates</h3>\n<p>We need to be careful the web crawler doesn't get stuck in an infinite loop, which happens when the graph contains a cycle.</p>\n<p><strong>Clarify with your interviewer how much code you are expected to write</strong>.</p>\n<p>We'll want to remove duplicate urls:</p>\n<ul>\n<li>For smaller lists we could use something like <code>sort | unique</code></li>\n<li>With 1 billion links to crawl, we could use <strong>MapReduce</strong> to output only entries that have a frequency of 1</li>\n</ul>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RemoveDuplicateUrls</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">MRJob</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">):</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> mapper</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, _, line):</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        yield</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> line, </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">1</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> reducer</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, key, values):</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        total </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> sum</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(values)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> total </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">==</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            yield</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> key, total</span></span></code></pre>\n<p>Detecting duplicate content is more complex.  We could generate a signature based on the contents of the page and compare those two signatures for similarity.  Some potential algorithms are <a href=\"https://en.wikipedia.org/wiki/Jaccard_index\" target=\"_blank\" rel=\"noopener noreferrer\">Jaccard index</a> and <a href=\"https://en.wikipedia.org/wiki/Cosine_similarity\" target=\"_blank\" rel=\"noopener noreferrer\">cosine similarity</a>.</p>\n<h3 id=\"determining-when-to-update-the-crawl-results\">Determining when to update the crawl results</h3>\n<p>Pages need to be crawled regularly to ensure freshness.  Crawl results could have a <code>timestamp</code> field that indicates the last time a page was crawled.  After a default time period, say one week, all pages should be refreshed.  Frequently updated or more popular sites could be refreshed in shorter intervals.</p>\n<p>Although we won't dive into details on analytics, we could do some data mining to determine the mean time before a particular page is updated, and use that statistic to determine how often to re-crawl the page.</p>\n<p>We might also choose to support a <code>Robots.txt</code> file that gives webmasters control of crawl frequency.</p>\n<h3 id=\"use-case-user-inputs-a-search-term-and-sees-a-list-of-relevant-pages-with-titles-and-snippets\">Use case: User inputs a search term and sees a list of relevant pages with titles and snippets</h3>\n<ul>\n<li>The <strong>Client</strong> sends a request to the <strong>Web Server</strong>, running as a <a href=\"/learn/reverse-proxy-web-server\">reverse proxy</a></li>\n<li>The <strong>Web Server</strong> forwards the request to the <strong>Query API</strong> server</li>\n<li>The <strong>Query API</strong> server does the following:\n<ul>\n<li>Parses the query\n<ul>\n<li>Removes markup</li>\n<li>Breaks up the text into terms</li>\n<li>Fixes typos</li>\n<li>Normalizes capitalization</li>\n<li>Converts the query to use boolean operations</li>\n</ul>\n</li>\n<li>Uses the <strong>Reverse Index Service</strong> to find documents matching the query\n<ul>\n<li>The <strong>Reverse Index Service</strong> ranks the matching results and returns the top ones</li>\n</ul>\n</li>\n<li>Uses the <strong>Document Service</strong> to return titles and snippets</li>\n</ul>\n</li>\n</ul>\n<p>We'll use a public <a href=\"/learn/communication#representational-state-transfer-rest\"><strong>REST API</strong></a>:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span>$ curl https://search.com/api/v1/search?query=hello+world</span></span></code></pre>\n<p>Response:</p>\n<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span>{</span></span>\n<span class=\"line\"><span>    \"title\": \"foo's title\",</span></span>\n<span class=\"line\"><span>    \"snippet\": \"foo's snippet\",</span></span>\n<span class=\"line\"><span>    \"link\": \"https://foo.com\",</span></span>\n<span class=\"line\"><span>},</span></span>\n<span class=\"line\"><span>{</span></span>\n<span class=\"line\"><span>    \"title\": \"bar's title\",</span></span>\n<span class=\"line\"><span>    \"snippet\": \"bar's snippet\",</span></span>\n<span class=\"line\"><span>    \"link\": \"https://bar.com\",</span></span>\n<span class=\"line\"><span>},</span></span>\n<span class=\"line\"><span>{</span></span>\n<span class=\"line\"><span>    \"title\": \"baz's title\",</span></span>\n<span class=\"line\"><span>    \"snippet\": \"baz's snippet\",</span></span>\n<span class=\"line\"><span>    \"link\": \"https://baz.com\",</span></span>\n<span class=\"line\"><span>},</span></span></code></pre>\n<p>For internal communications, we could use <a href=\"/learn/communication#remote-procedure-call-rpc\">Remote Procedure Calls</a>.</p>","headings":[{"id":"use-case-service-crawls-a-list-of-urls","title":"Use case: Service crawls a list of urls","depth":3},{"id":"handling-duplicates","title":"Handling duplicates","depth":3},{"id":"determining-when-to-update-the-crawl-results","title":"Determining when to update the crawl results","depth":3},{"id":"use-case-user-inputs-a-search-term-and-sees-a-list-of-relevant-pages-with-titles-and-snippets","title":"Use case: User inputs a search term and sees a list of relevant pages with titles and snippets","depth":3}]},{"id":"step-4-scale-the-design","title":"Step 4: Scale the design","html":"<h2 id=\"step-4-scale-the-design\">Step 4: Scale the design</h2>\n<blockquote>\n<p>Identify and address bottlenecks, given the constraints.</p>\n</blockquote>\n<p><img src=\"/repo/images/bWxPtQA.png\" alt=\"Diagram\" loading=\"lazy\" decoding=\"async\"></p>\n<p><strong>Important: Do not simply jump right into the final design from the initial design!</strong></p>\n<p>State you would 1) <strong>Benchmark/Load Test</strong>, 2) <strong>Profile</strong> for bottlenecks 3) address bottlenecks while evaluating alternatives and trade-offs, and 4) repeat.  See <a href=\"/learn/design-scaling-aws\">Design a system that scales to millions of users on AWS</a> as a sample on how to iteratively scale the initial design.</p>\n<p>It's important to discuss what bottlenecks you might encounter with the initial design and how you might address each of them.  For example, what issues are addressed by adding a <strong>Load Balancer</strong> with multiple <strong>Web Servers</strong>?  <strong>CDN</strong>?  <strong>Master-Slave Replicas</strong>?  What are the alternatives and <strong>Trade-Offs</strong> for each?</p>\n<p>We'll introduce some components to complete the design and to address scalability issues.  Internal load balancers are not shown to reduce clutter.</p>\n<p><em>To avoid repeating discussions</em>, refer to the following <a href=\"/\">system design topics</a> for main talking points, tradeoffs, and alternatives:</p>\n<ul>\n<li><a href=\"/learn/domain-name-system\">DNS</a></li>\n<li><a href=\"/learn/load-balancer\">Load balancer</a></li>\n<li><a href=\"/learn/load-balancer#horizontal-scaling\">Horizontal scaling</a></li>\n<li><a href=\"/learn/reverse-proxy-web-server\">Web server (reverse proxy)</a></li>\n<li><a href=\"/learn/application-layer\">API server (application layer)</a></li>\n<li><a href=\"/learn/cache\">Cache</a></li>\n<li><a href=\"/learn/database#nosql\">NoSQL</a></li>\n<li><a href=\"/learn/consistency-patterns\">Consistency patterns</a></li>\n<li><a href=\"/learn/availability-patterns\">Availability patterns</a></li>\n</ul>\n<p>Some searches are very popular, while others are only executed once.  Popular queries can be served from a <strong>Memory Cache</strong> such as Redis or Memcached to reduce response times and to avoid overloading the <strong>Reverse Index Service</strong> and <strong>Document Service</strong>.  The <strong>Memory Cache</strong> is also useful for handling the unevenly distributed traffic and traffic spikes.  Reading 1 MB sequentially from memory takes about 250 microseconds, while reading from SSD takes 4x and from disk takes 80x longer.<sup>&#x3C;a href=<a href=\"https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know%3E1\" target=\"_blank\" rel=\"noopener noreferrer\">https://github.com/donnemartin/system-design-primer#latency-numbers-every-programmer-should-know>1</a></sup></p>\n<p>Below are a few other optimizations to the <strong>Crawling Service</strong>:</p>\n<ul>\n<li>To handle the data size and request load, the <strong>Reverse Index Service</strong> and <strong>Document Service</strong> will likely need to make heavy use sharding and federation.</li>\n<li>DNS lookup can be a bottleneck, the <strong>Crawler Service</strong> can keep its own DNS lookup that is refreshed periodically</li>\n<li>The <strong>Crawler Service</strong> can improve performance and reduce memory usage by keeping many open connections at a time, referred to as <a href=\"https://en.wikipedia.org/wiki/Connection_pool\" target=\"_blank\" rel=\"noopener noreferrer\">connection pooling</a>\n<ul>\n<li>Switching to <a href=\"/learn/communication#user-datagram-protocol-udp\">UDP</a> could also boost performance</li>\n</ul>\n</li>\n<li>Web crawling is bandwidth intensive, ensure there is enough bandwidth to sustain high throughput</li>\n</ul>","headings":[]},{"id":"additional-talking-points","title":"Additional talking points","html":"<h2 id=\"additional-talking-points\">Additional talking points</h2>\n<blockquote>\n<p>Additional topics to dive into, depending on the problem scope and time remaining.</p>\n</blockquote>\n<h3 id=\"sql-scaling-patterns\">SQL scaling patterns</h3>\n<ul>\n<li><a href=\"/learn/database#master-slave-replication\">Read replicas</a></li>\n<li><a href=\"/learn/database#federation\">Federation</a></li>\n<li><a href=\"/learn/database#sharding\">Sharding</a></li>\n<li><a href=\"/learn/database#denormalization\">Denormalization</a></li>\n<li><a href=\"/learn/database#sql-tuning\">SQL Tuning</a></li>\n</ul>\n<h4 id=\"nosql\">NoSQL</h4>\n<ul>\n<li><a href=\"/learn/database#key-value-store\">Key-value store</a></li>\n<li><a href=\"/learn/database#document-store\">Document store</a></li>\n<li><a href=\"/learn/database#wide-column-store\">Wide column store</a></li>\n<li><a href=\"/learn/database#graph-database\">Graph database</a></li>\n<li><a href=\"/learn/database#sql-or-nosql\">SQL vs NoSQL</a></li>\n</ul>\n<h3 id=\"caching\">Caching</h3>\n<ul>\n<li>Where to cache\n<ul>\n<li><a href=\"/learn/cache#client-caching\">Client caching</a></li>\n<li><a href=\"/learn/cache#cdn-caching\">CDN caching</a></li>\n<li><a href=\"/learn/cache#web-server-caching\">Web server caching</a></li>\n<li><a href=\"/learn/cache#database-caching\">Database caching</a></li>\n<li><a href=\"/learn/cache#application-caching\">Application caching</a></li>\n</ul>\n</li>\n<li>What to cache\n<ul>\n<li><a href=\"/learn/cache#caching-at-the-database-query-level\">Caching at the database query level</a></li>\n<li><a href=\"/learn/cache#caching-at-the-object-level\">Caching at the object level</a></li>\n</ul>\n</li>\n<li>When to update the cache\n<ul>\n<li><a href=\"/learn/cache#cache-aside\">Cache-aside</a></li>\n<li><a href=\"/learn/cache#write-through\">Write-through</a></li>\n<li><a href=\"/learn/cache#write-behind-write-back\">Write-behind (write-back)</a></li>\n<li><a href=\"/learn/cache#refresh-ahead\">Refresh ahead</a></li>\n</ul>\n</li>\n</ul>\n<h3 id=\"asynchronism-and-microservices\">Asynchronism and microservices</h3>\n<ul>\n<li><a href=\"/learn/asynchronism#message-queues\">Message queues</a></li>\n<li><a href=\"/learn/asynchronism#task-queues\">Task queues</a></li>\n<li><a href=\"/learn/asynchronism#back-pressure\">Back pressure</a></li>\n<li><a href=\"/learn/application-layer#microservices\">Microservices</a></li>\n</ul>\n<h3 id=\"communications\">Communications</h3>\n<ul>\n<li>Discuss tradeoffs:\n<ul>\n<li>External communication with clients - <a href=\"/learn/communication#representational-state-transfer-rest\">HTTP APIs following REST</a></li>\n<li>Internal communications - <a href=\"/learn/communication#remote-procedure-call-rpc\">RPC</a></li>\n</ul>\n</li>\n<li><a href=\"/learn/application-layer#service-discovery\">Service discovery</a></li>\n</ul>\n<h3 id=\"security\">Security</h3>\n<p>Refer to the <a href=\"/learn/security\">security section</a>.</p>\n<h3 id=\"latency-numbers\">Latency numbers</h3>\n<p>See <a href=\"/learn/appendix#latency-numbers-every-programmer-should-know\">Latency numbers every programmer should know</a>.</p>\n<h3 id=\"ongoing\">Ongoing</h3>\n<ul>\n<li>Continue benchmarking and monitoring your system to address bottlenecks as they come up</li>\n<li>Scaling is an iterative process</li>\n</ul>","headings":[{"id":"sql-scaling-patterns","title":"SQL scaling patterns","depth":3},{"id":"nosql","title":"NoSQL","depth":4},{"id":"caching","title":"Caching","depth":3},{"id":"asynchronism-and-microservices","title":"Asynchronism and microservices","depth":3},{"id":"communications","title":"Communications","depth":3},{"id":"security","title":"Security","depth":3},{"id":"latency-numbers","title":"Latency numbers","depth":3},{"id":"ongoing","title":"Ongoing","depth":3}]}],"sourceFiles":[{"name":"web_crawler_mapreduce.py","url":"https://github.com/donnemartin/system-design-primer/blob/master/solutions/system_design/web_crawler/web_crawler_mapreduce.py","html":"<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># -*- coding: utf-8 -*-</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">from</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> mrjob.job </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">import</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> MRJob</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> RemoveDuplicateUrls</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\">MRJob</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">):</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> mapper</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, _, line):</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        yield</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> line, </span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">1</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> reducer</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, key, values):</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        total </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> sum</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(values)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> total </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">==</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> 1</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            yield</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> key, total</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> steps</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Run the map and reduce steps.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        return</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> [</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mr(</span><span style=\"color:#E36209;--shiki-dark:#FFAB70\">mapper</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.mapper,</span></span>\n<span class=\"line\"><span style=\"color:#E36209;--shiki-dark:#FFAB70\">                    reducer</span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.reducer)</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">        ]</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">if</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> __name__</span><span style=\"color:#D73A49;--shiki-dark:#F97583\"> ==</span><span style=\"color:#032F62;--shiki-dark:#9ECBFF\"> '__main__'</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">    RemoveDuplicateUrls.run()</span></span></code></pre>"},{"name":"web_crawler_snippets.py","url":"https://github.com/donnemartin/system-design-primer/blob/master/solutions/system_design/web_crawler/web_crawler_snippets.py","html":"<pre class=\"shiki shiki-themes github-light github-dark\" style=\"background-color:#fff;--shiki-dark-bg:#24292e;color:#24292e;--shiki-dark:#e1e4e8\" tabindex=\"0\"><code><span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\"># -*- coding: utf-8 -*-</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> PagesDataStore</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">):</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, db):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.db </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> db</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        pass</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> add_link_to_crawl</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, url):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Add the given link to `links_to_crawl`.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        pass</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> remove_link_to_crawl</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, url):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Remove the given link from `links_to_crawl`.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        pass</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> reduce_priority_link_to_crawl</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, url):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Reduce the priority of a link in `links_to_crawl` to avoid cycles.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        pass</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> extract_max_priority_page</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Return the highest priority link in `links_to_crawl`.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        pass</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> insert_crawled_link</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, url, signature):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Add the given link to `crawled_links`.\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        pass</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> crawled_similar</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, signature):</span></span>\n<span class=\"line\"><span style=\"color:#032F62;--shiki-dark:#9ECBFF\">        \"\"\"Determine if we've already crawled a page matching the given signature\"\"\"</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        pass</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Page</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">):</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, url, contents, child_urls):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.url </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> url</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.contents </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> contents</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.child_urls </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> child_urls</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.signature </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.create_signature()</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> create_signature</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self):</span></span>\n<span class=\"line\"><span style=\"color:#6A737D;--shiki-dark:#6A737D\">        # Create signature based on url and contents</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        pass</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">class</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> Crawler</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">object</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">):</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> __init__</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, pages, data_store, reverse_index_queue, doc_index_queue):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.pages </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> pages</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> data_store</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.reverse_index_queue </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> reverse_index_queue</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.doc_index_queue </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> doc_index_queue</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> crawl_page</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self, page):</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        for</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> url </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">in</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> page.child_urls:</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">            self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.add_link_to_crawl(url)</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.reverse_index_queue.generate(page)</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.doc_index_queue.generate(page)</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.remove_link_to_crawl(page.url)</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">        self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.insert_crawled_link(page.url, page.signature)</span></span>\n<span class=\"line\"></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">    def</span><span style=\"color:#6F42C1;--shiki-dark:#B392F0\"> crawl</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">(self):</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">        while</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> True</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">            page </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.extract_max_priority_page()</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            if</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\"> page </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">is</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> None</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">                break</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            if</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.crawled_similar(page.signature):</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">                self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.reduce_priority_link_to_crawl(page.url)</span></span>\n<span class=\"line\"><span style=\"color:#D73A49;--shiki-dark:#F97583\">            else</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">:</span></span>\n<span class=\"line\"><span style=\"color:#005CC5;--shiki-dark:#79B8FF\">                self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.crawl_page(page)</span></span>\n<span class=\"line\"><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">            page </span><span style=\"color:#D73A49;--shiki-dark:#F97583\">=</span><span style=\"color:#005CC5;--shiki-dark:#79B8FF\"> self</span><span style=\"color:#24292E;--shiki-dark:#E1E4E8\">.data_store.extract_max_priority_page()</span></span></code></pre>"}]}