<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>Forem: Nikolas Serafini</title>
    <description>The latest articles on Forem by Nikolas Serafini (@emethium).</description>
    <link>https://forem.com/emethium</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F36415%2Fa2551cab-628d-4810-9195-098da8346c78.jpg</url>
      <title>Forem: Nikolas Serafini</title>
      <link>https://forem.com/emethium</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/emethium"/>
    <language>en</language>
    <item>
      <title>A practical and gentle introduction to web scraping with Puppeteer</title>
      <dc:creator>Nikolas Serafini</dc:creator>
      <pubDate>Fri, 13 Mar 2020 20:34:16 +0000</pubDate>
      <link>https://forem.com/zrp/a-practical-and-gentle-introduction-to-web-scraping-with-puppeteer-20jn</link>
      <guid>https://forem.com/zrp/a-practical-and-gentle-introduction-to-web-scraping-with-puppeteer-20jn</guid>
      <description>&lt;p&gt;If you are wondering what that is, &lt;a href="https://github.com/puppeteer/puppeteer"&gt;Puppeteer&lt;/a&gt; is a Google-maintained Node library that provides an API over the DevTools protocol, offering us the ability to take control over Chrome or Chromium and do very nice automation and scraping related things.&lt;/p&gt;

&lt;p&gt;It's very resourceful, widely used, and probably what you should take a look today if you need to develop something of the like. It's use even extends to performing e2e tests with front-end web frameworks such as &lt;a href="https://github.com/angular/angular"&gt;Angular&lt;/a&gt;, it's a very powerful tool.&lt;/p&gt;

&lt;p&gt;In this article we aim to show some of the essential Puppeteer operations along with a very simple example of extracting Google's first page results for a keyword, as a way of wrapping things up.&lt;br&gt;
Oh, and a full and working repository example with all the code shown in this post can be found &lt;a href="https://github.com/Emethium/starting-with-puppeteer"&gt;here if you need!&lt;/a&gt;&lt;/p&gt;
&lt;h3&gt;
  
  
  TL;DR
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;We'll learn how to make Puppeteer's basic configuration&lt;/li&gt;
&lt;li&gt;Also how to access Google's website and scrap the results page&lt;/li&gt;
&lt;li&gt;All of this getting into detail about a couple of commonly used API functions &lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  First step, launching a Browser instance
&lt;/h2&gt;

&lt;p&gt;Before we can attempt to do anything, we need to launch a Browser instance as a matter to actually access a specific website. As the name suggests, we are actually going to launch a full-fledged Chromium browser (or not, we can run in &lt;a href="https://developers.google.com/web/updates/2017/04/headless-chrome"&gt;headless mode&lt;/a&gt;), capable of opening multiple tabs and as feature-rich as the browser you may be using right now.&lt;/p&gt;

&lt;p&gt;Launching a Browser can be simple as typing await puppeteer.launch(), but we should be aware that there is a &lt;a href="https://github.com/puppeteer/puppeteer/blob/master/docs/api.md#puppeteerlaunchoptions"&gt;huge amount of launching options&lt;/a&gt; available, whose use depends on your needs. Since we will be using Docker in the example, some additional tinkering is done here so we can run it inside a container without problems, but still serves as a good example:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nx"&gt;initializePuppeteer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;launchArgs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
  &lt;span class="c1"&gt;// Required for Docker version of Puppeteer&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--no-sandbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--disable-setuid-sandbox&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// Disable GPU&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--disable-gpu&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="c1"&gt;// This will write shared memory files into /tmp instead of /dev/shm,&lt;/span&gt;
  &lt;span class="c1"&gt;// because Docker’s default for /dev/shm is 64MB&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;--disable-dev-shm-usage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
  &lt;span class="p"&gt;];&lt;/span&gt;

  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;executablePath&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/usr/bin/chromium-browser&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;args&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;launchArgs&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;defaultViewport&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;width&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;1024&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="na"&gt;height&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;768&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Working with tabs
&lt;/h2&gt;

&lt;p&gt;Since we have already initialized our Browser, we need to create tabs (or pages) to be able to access our very first website. Using the function we defined above, we can simply do something of the like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;initializePuppeteer&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;newPage&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;scrapSomeSite&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h2&gt;
  
  
  Accessing a website
&lt;/h2&gt;

&lt;p&gt;Now that we have a proper page opened, we can manage to access a website and do something nice. By default, the newly created page always open blank so we must manually navigate to somewhere specific. Again, a very simple operation:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;https://www.google.com/?gl=us&amp;amp;hl=en&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="na"&gt;timeout&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;30000&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
    &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
  &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;There are a couple of options in this operation that requires extra attention and can heavily impact your implementation if misused:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;timeout&lt;/code&gt;: while the default is 30s, if we are dealing with a somewhat slow website or even running behind proxies, we need to set a proper value to avoid undesired execution errors.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;waitUntil&lt;/code&gt;: this guy is really important as different sites have completely different behaviors. It defines the page events that are going to be waited before considering that the page actually loaded, not waiting for the right events can break your scraping code. We can use one or all of them, defaulting to &lt;code&gt;load&lt;/code&gt; . You can find all the available options &lt;a href="https://github.com/puppeteer/puppeteer/blob/v2.0.0/docs/api.md#pagegotourl-options"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Page shenanigans
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Google's first page
&lt;/h3&gt;

&lt;p&gt;So, we finally opened a web page! That's nice. We now have arrived at the actually fun part.&lt;br&gt;
Let's follow the idea of scraping Google's first result page, shall we? Since we have already navigated to the main page we need to do two different things:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Fill the form field with a keyword&lt;/li&gt;
&lt;li&gt;Press the search button&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Before we can interact with any element inside a page, we need to find it by code first, so then we can replicate all the necessary steps to accomplish our goals. This is a little detective work, and it may take some time to figure out.&lt;/p&gt;

&lt;p&gt;We are using the US Google page so we all see the same page, the link is in the code example above. If we take a look at Google's HTML code you'll see that a lot of element properties are properly obfuscated with different hashes that change over time, so we have lesser options to always get the same element we desire.&lt;/p&gt;

&lt;p&gt;But, lucky us, if we inspect the input field, one can find easy-to-spot properties such as &lt;code&gt;title="Search"&lt;/code&gt; on the element. If we check it with a &lt;code&gt;document.querySelectorAll("[title=Search]")&lt;/code&gt; on the browser we'll verify that it is a unique element for this query. One down.&lt;/p&gt;

&lt;p&gt;We could apply the same logic to the submit button, but I'll take a different approach here on purpose. Since everything is inside a form , and we only have one in the page, we can &lt;strong&gt;forcefully submit it&lt;/strong&gt; to instantly navigate to the result screen, by simply calling a form.submit(). Two down.&lt;/p&gt;

&lt;p&gt;And how we can "find" these elements and do these awesome operations by code? Easy-peasy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Filling the form&lt;/span&gt;
&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;inputField&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[title=Search]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;inputField&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;type&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;puppeteer&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;delay&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="mi"&gt;100&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="c1"&gt;// Forces form submission&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;form&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;form&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;submit&lt;/span&gt;&lt;span class="p"&gt;());&lt;/span&gt;
&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForNavigation&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;waitUntil&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;load&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we first grab the input field by executing a &lt;code&gt;page.$(selectorGoesHere)&lt;/code&gt; , function that actually runs &lt;code&gt;document.querySelector&lt;/code&gt; on the browser's context, returning the &lt;strong&gt;first&lt;/strong&gt; element that matches our selector. Being that said you have to make sure that you're fetching the right element with a correct and unique selector, otherwise things may not go the way they should. On a side note, to fetch &lt;strong&gt;all&lt;/strong&gt; the elements that match a specific selector, you may want to run a &lt;code&gt;page.$$(selectorGoesHere)&lt;/code&gt; , that runs a &lt;code&gt;document.querySelectorAll&lt;/code&gt; inside the browser's context.&lt;/p&gt;

&lt;p&gt;As for actually typing the keyword into the element, we can simply use the &lt;code&gt;page.type&lt;/code&gt; function with the content we want to search for. Keep in mind that, depending on the website, you may want to add a typing &lt;strong&gt;delay&lt;/strong&gt; (as we did in the example) to simulate a human-like behavior. Not adding a delay may lead to weird things like input drop downs not showing or a plethora of different strange things that we don't really want to face.&lt;/p&gt;

&lt;p&gt;Want to check if we filled everything correctly? Taking a screenshot and the page's full HTML for inspecting is also very easy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;screenshot&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
  &lt;span class="na"&gt;path&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;./firstpage&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;fullPage&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="na"&gt;type&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;jpeg&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;
&lt;span class="p"&gt;});&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;html&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;content&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;To submit the form, we are introduced to a &lt;strong&gt;very&lt;/strong&gt; useful function: &lt;code&gt;page.$eval(selector, pageFunction)&lt;/code&gt; . It actually runs a &lt;code&gt;document.querySelector&lt;/code&gt; for it's first argument, and passes the element result as the first argument of the provided page function. This is really useful if you have to run code that &lt;strong&gt;needs to be inside the browser's context to work&lt;/strong&gt;, as our &lt;code&gt;form.submit()&lt;/code&gt; . As the previous function we mentioned, we also have the alternate &lt;code&gt;page.$$eval(selector, pageFunction)&lt;/code&gt; that works the same way but differs by running a &lt;code&gt;document.querySelectorAll&lt;/code&gt; for the selector provided instead.&lt;/p&gt;

&lt;p&gt;As forcing the form submission causes a page navigation, we need to be explicit in what conditions we should wait for it before we continue with the scraping process. In this case, waiting until the navigated page launches a &lt;code&gt;load&lt;/code&gt; event is sufficient.&lt;/p&gt;

&lt;h3&gt;
  
  
  The result page
&lt;/h3&gt;

&lt;p&gt;With the result page loaded we can finally extract some data from it! We are looking only for the textual results, so we need to scope them down first.&lt;br&gt;
If we take a very careful look, the entire results container can be found with the &lt;code&gt;[id=search] &amp;gt; div &amp;gt; [data-async-context]&lt;/code&gt; selector. There are probably different ways to reach the same element, so that's not a definitive answer. If you find a easier path, let me know.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--CF0wDw4F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kh1nvlqigh7wvbzwb38z.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--CF0wDw4F--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/kh1nvlqigh7wvbzwb38z.png" alt="The text result container" width="664" height="834"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;And, lucky us, every text entry here has the weird &lt;code&gt;.g&lt;/code&gt; class! So, if we query this container element we found for every sub-element that has this specific class (yes, this is also supported) we can have direct access to all the results! And we can do all that with stuff we already mentioned:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;rawResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;[id=search] &amp;gt; div &amp;gt; [data-async-context]&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;filteredResults&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;rawResults&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;$$eval&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.g&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;results&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nb"&gt;Array&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="k"&gt;from&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;results&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;innerText&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
      &lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;r&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;log&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;filteredResults&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;So we use the &lt;code&gt;page.$&lt;/code&gt; function to take a hold on that beautiful container we just saw, so then a &lt;code&gt;.$$eval&lt;/code&gt; function can be used on this container to fetch all the sub-elements that have the &lt;code&gt;.g&lt;/code&gt; class, applying a custom function for these entries. As for the function, we just retrieved the &lt;code&gt;innerText&lt;/code&gt; for every element and removed the empty strings on the end, to tidy up our results.&lt;/p&gt;

&lt;p&gt;One thing that should not be overlooked here is that we had to use &lt;code&gt;Array.from()&lt;/code&gt; on the returning &lt;code&gt;results&lt;/code&gt; so we could actually make use of functions like &lt;code&gt;map&lt;/code&gt; , &lt;code&gt;filter&lt;/code&gt; and &lt;code&gt;reduce&lt;/code&gt; . The returning element from a &lt;code&gt;.$$eval&lt;/code&gt; call is a &lt;strong&gt;&lt;code&gt;NodeList&lt;/code&gt;&lt;/strong&gt; , not an &lt;code&gt;Array&lt;/code&gt;, and it does not offer support for some of the functions that we otherwise would find on the last.&lt;/p&gt;

&lt;p&gt;If we check on the filtered results, we'll find something of the like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;[
  '\n' +
    'puppeteer/puppeteer: Headless Chrome Node.js API - GitHub\n' +
    'github.com › puppeteer › puppeteer\n' +
    'Puppeteer runs headless by default, but can be configured to run full (non-headless) Chrome or Chromium. What can I do? Most things that you can do manually ...\n' +
    '‎Puppeteer API · ‎37 releases · ‎Puppeteer for Firefox · ‎How do I get puppeteer to ...',
  '\n' +
    'Puppeteer | Tools for Web Developers | Google Developers\n' +
    'developers.google.com › web › tools › puppeteer\n' +
    'Jan 28, 2020 - Puppeteer is a Node library which provides a high-level API to control headless Chrome or Chromium over the DevTools Protocol. It can also be configured to use full (non-headless) Chrome or Chromium.\n' +
    '‎Quick start · ‎Examples · ‎Headless Chrome: an answer · ‎Debugging tips',
  'People also ask\n' +
    'What is puppeteer used for?\n' +
    'How does a puppeteer work?\n' +
    'What is puppeteer JS?\n' +
    'Does puppeteer need Chrome installed?\n' +
    'Feedback',
...
]
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;And we have all the data we want right here! We could parse every entry here on several different ways, and create full-fledged objects for further processing, but I'll leave this up to you. &lt;/p&gt;

&lt;p&gt;Our objective was to get our hands into the text data, and we managed just that. Congratulations to us, we finished!&lt;/p&gt;




&lt;h2&gt;
  
  
  Wrapping things up
&lt;/h2&gt;

&lt;p&gt;Our scope here was to present Puppeteer itself along with a series of operations that could be considered basic for almost every web scraping context. This is most probably a mere start for more complex and deeper operations one may find during a page's scraping process.&lt;/p&gt;

&lt;p&gt;We barely managed to scratch the surface of &lt;a href="https://github.com/puppeteer/puppeteer/blob/master/docs/api.md"&gt;Puppeteer's extensive API&lt;/a&gt;, one that you should really consider taking a serious look into. It's pretty well-written and loaded with easy-to-understand examples for almost everything.&lt;/p&gt;

&lt;p&gt;This is just the first of a series of posts regarding Web scraping with Puppeteer that will (probably) come to fruition in the future. Stay tuned!&lt;/p&gt;

</description>
      <category>node</category>
      <category>beginners</category>
      <category>puppeteer</category>
    </item>
    <item>
      <title>Running your first Docker Image on ECS</title>
      <dc:creator>Nikolas Serafini</dc:creator>
      <pubDate>Wed, 04 Mar 2020 17:28:40 +0000</pubDate>
      <link>https://forem.com/zrp/running-your-first-docker-image-on-ecs-17c4</link>
      <guid>https://forem.com/zrp/running-your-first-docker-image-on-ecs-17c4</guid>
      <description>&lt;p&gt;Working with containers has become a hefty trend in Software Engineering in this past couple of years. Containers can offer several advantages for software development and application deployment, possibly taking away a lot of problems faced by development and devops teams. We'll peek a little bit on the basics and hows of running your first Docker image on Amazon Web Services.&lt;/p&gt;

&lt;p&gt;Of the gruesome volume of different services offered by Amazon, the Elastic Container Service (ECS) is the one to go when we commonly need to run and deploy dockerized applications.&lt;/p&gt;

&lt;p&gt;For one to fully deploy a application in said service, at least three little things are needed: an configured &lt;strong&gt;ECS Cluster&lt;/strong&gt;, at least &lt;strong&gt;one&lt;/strong&gt; image on ECR, and a &lt;strong&gt;Task Definition&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Cluster&lt;/strong&gt; is a grouping of tasks and services, whose creation is free of charge by itself. Tasks must be placed inside a cluster to run, so their creation is mandatory. You'll probably want to create different clusters for different applications or execution contexts to keep everything tied up and organized.&lt;/p&gt;

&lt;p&gt;A &lt;strong&gt;Task Definition&lt;/strong&gt; is an AWS resource used to describe containers and volume definitions of ECR tasks. There one can define what docker images to use, environment and network configuration, instance requirements for task placement among other configurations. Which lead us to the definition of &lt;strong&gt;Task&lt;/strong&gt;, a Task Definition instance, the running entity executing all the software in the images included, in the way we defined.&lt;/p&gt;

&lt;h2&gt;
  
  
  Prerequisites
&lt;/h2&gt;

&lt;p&gt;Before we can jump into the Task Definition configuration shenanigans we must first have at &lt;strong&gt;least&lt;/strong&gt; one container image (since a Task Definition can make use of multiple containers at once) on one ECR repository and a already-configured (or empty) Cluster.&lt;/p&gt;

&lt;p&gt;Docker images must be built and pushed to another AWS Service, the Elastic Container Registry (ECR). In the pretty straightforward service, each different Docker image must be tagged and pushed to a separate repository so they can be referenced in our task definitions. It's a must.&lt;br&gt;
Amazon is friendly enough to even give us all the necessary commands to do all the basic operations: all you have to do is push that beautiful "&lt;strong&gt;View push commands&lt;/strong&gt;" button, as every needed step is explained in detail over there.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--iW5z9W_8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lq2qqp5vo9v23k7iatms.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--iW5z9W_8--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/lq2qqp5vo9v23k7iatms.png" alt="ECR Repository page" width="800" height="118"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Clusters can be created by clicking on the intuitive "&lt;strong&gt;Create Cluster&lt;/strong&gt;" button on the ECS main page. Make sure to select the correct template for your needs, depending on the OS you'll want to use, but you'll probably want to stick with the "&lt;strong&gt;Linux + Networking&lt;/strong&gt;" one. Do not choose the "&lt;strong&gt;Networking only&lt;/strong&gt;" unless you're really sure on about what you're doing, as you will be dealing with yet another completely different service, &lt;strong&gt;AWS Fargate&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;We have the option of creating an empty cluster, thus not allocating any kind of on-demand or spot instances, or not doing that. Since creating an empty cluster gives us the hassle of providing running machines in some way so our tasks can actually run (and that's completely out of our scope here), I suggest you to just pick a &lt;code&gt;t2.micro&lt;/code&gt; as a ECS Instance Type as it's almost free. Keep in mind that if you want to meddle with Fargate no instances need to be picked as Amazon will take care of machine allocation for you, so you can just create an empty cluster.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Task Definition
&lt;/h2&gt;

&lt;p&gt;With all the needed configuration at hand, we can finally safely access the "&lt;strong&gt;Create new Task Definition&lt;/strong&gt;" section found no the ECS Task Definition main page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Cf43QV7Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gizi8tjy87e1jn16lu0x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Cf43QV7Z--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/gizi8tjy87e1jn16lu0x.png" alt="Create task definition location" width="633" height="168"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;At first, Amazon will ask you to choose your Task Definition launch type based on where you want run your tasks, giving you two choices: &lt;strong&gt;Fargate&lt;/strong&gt; and &lt;strong&gt;ECS&lt;/strong&gt;. Fargate is an AWS alternate service that allows us to execute tasks without the hassle of explicitly allocating machines to run your containers, whereas ECS is a launch type that requires you to manually configure several execution aspects, a lower level operation. Both launch types work and are billed differently, you can find more detailed information &lt;a href="https://medium.com/r/?url=https%3A%2F%2Fdocs.aws.amazon.com%2FAmazonECS%2Flatest%2Fdeveloperguide%2Flaunch_types.html"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--0yGHZPM6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8cbmicwrmcjb50amm765.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--0yGHZPM6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/8cbmicwrmcjb50amm765.png" alt="Launch Type" width="800" height="468"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the following page we will find the more meaningful and deeper configuration:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Task Definition Name&lt;/strong&gt;: the name of your Task Definition. Obligatory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Requires Compatibilities&lt;/strong&gt;: is set following the Launch Type chosen in the first configuration page.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network Mode&lt;/strong&gt;: set network mode ECS will use to start our containers. If Fargate is used, we have no other option as to set the &lt;strong&gt;awsvpc&lt;/strong&gt; mode. If you are not really sure what this means, &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_definition_parameters.html#network_mode"&gt;take a look here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task Execution Role&lt;/strong&gt;: necessary role to be given to each task execution as to allow it to have access and sufficient permissions to run correctly inside the AWS ecosystem. An Execution Role has access to several different permission policies to give specific access to the applications running under it. For instance, a common use case for ECS-executed tasks are to have access to ECR (so we can pull our images) and to have permission to use Cloudwatch (Amazon logging and monitoring service), meaning we would have to attach to our Role policies like: "&lt;strong&gt;ecr:BatchGetImage&lt;/strong&gt;" and "&lt;strong&gt;logs:PutLogEvents&lt;/strong&gt;". Do not confuse this with the Task Role, the Task Execution Role wraps the permissions to be conceded to the ECS agent responsible for placing the tasks, not to the tasks themselves. Detailed information of Task Execution Roles can be found &lt;a href="https://docs.aws.amazon.com/AmazonECS/latest/developerguide/task_execution_IAM_role.html"&gt;here&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task Role&lt;/strong&gt;: necessary role to be given to the task itself. It follows the same principles listed above, so if your application needs to send messages to a SQS queue, communicate to Redis cluster or save data into a RDS database, you'll need to set all the specific policies into your custom Task Role configuration.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Task size&lt;/strong&gt;: a pretty straightforward section, allows you to set specific memory and CPU requirements for running your task. Keep in mind that if you try to run your task on a machine with lower specs than specified here, it won't start at all.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container Definitions&lt;/strong&gt;: the fun part. You'll have to pass through all the steps listed below for each container you want to include inside the same Task Definition. You can set up any number of containers you need.

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Container name&lt;/strong&gt;: self-explanatory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Image&lt;/strong&gt;: the ECR repository image URL from which you'll pull the application.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Memory Limits&lt;/strong&gt;: the definition hard or soft memory limits (Mib) for the container. &lt;a href="https://stackoverflow.com/questions/44764135/aws-ecs-task-memory-hard-and-soft-limits"&gt;Link to universal knowledge if you do not know what this means&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Port mappings&lt;/strong&gt;: if your application will make use of specific ports for any kind of outgoing communication, you'll probably have to bind the host ports to your container ones. Otherwise, nothing is gonna actually work.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Healthcheck&lt;/strong&gt;: sets up a container health check routine in specific timed intervals. Useful to continuously monitor your container health and used by ECS to know when your application actually started running. If you defined a specific route for this in your application, you'll probably have something of the like as your command: &lt;code&gt;CMD-SHELL,curl -f http://localhost:port/healthcheck || exit 1&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Environment&lt;/strong&gt;: Lets you set up the amount of CPU units your container is going to use, configure all the environment variables you need so your application runs correctly among other configuration. Also allows you to set the container as essential, meaning in the scenario that if it dies for some reason, the entire task is going to be killed shortly after.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Startup Dependency Ordering&lt;/strong&gt;: allows you to control the order the containers are going to start, and when they are going to. Not mandatory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Container Timeouts&lt;/strong&gt;: self-explanatory and not mandatory.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Network Settings&lt;/strong&gt;: also self-explanatory. Not really mandatory, unless you have some advanced network shenanigans going on.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Storage and Logging&lt;/strong&gt;: Couple of advanced setups. At a basic level you'll probably want to configure the Cloudwatch Log configuration, or simply let Amazon handle everything with the beautiful "&lt;strong&gt;Auto-configure CloudWatch Logs&lt;/strong&gt;" button.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Security, Resource Limits, and Docker Labels&lt;/strong&gt;: advanced an context-specific configurations. Not mandatory. We'll not cover them here.&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And that's it. There a couple of other options listed in the task definition main page, as Mesh and FireLens integration, but they are very specific and not really needed for your everyday task definitions. You can skip the rest and press the "&lt;strong&gt;Create&lt;/strong&gt;" button.&lt;/p&gt;

&lt;h2&gt;
  
  
  Running your task
&lt;/h2&gt;

&lt;p&gt;After all this explanation, we want to see if the containerized application will actually run right? So click on your created ECS cluster, hit the bottom "&lt;strong&gt;Tasks&lt;/strong&gt;" tab and click on the "&lt;strong&gt;Run new Task&lt;/strong&gt;" button.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--_YnDFMk0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dvo6i8nkts2msvc9dnoh.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--_YnDFMk0--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/dvo6i8nkts2msvc9dnoh.png" alt="Manually running a task" width="717" height="294"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;On the new screen, select "&lt;strong&gt;EC2&lt;/strong&gt;" as a Launch Type, fill in your task definition name and the name of the cluster where we'll run it and click on "Run Task". If you had set the &lt;code&gt;t2.micro&lt;/code&gt; as we suggested, your application will boot in no time.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--rQt_3Xug--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/y27xdcmizs4volmv1yox.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--rQt_3Xug--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/i/y27xdcmizs4volmv1yox.png" alt="Manually running a task" width="556" height="375"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;You can check all the running information of your task in the "&lt;strong&gt;Task&lt;/strong&gt;" tab of your cluster.&lt;/p&gt;

&lt;p&gt;Since we did not enter into the merits of what kind of application you are trying to run, it's up to yourself to check if your application is running as it should. You can check the logs of your application by clicking on the task (inside the aforementioned "Task" tab) and searching for the "&lt;strong&gt;View logs in CloudWatch&lt;/strong&gt;" under the desired configured container.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wrapping up
&lt;/h2&gt;

&lt;p&gt;Our main objective here was to show the simplest (and somewhat lengthy) path of how one can deploy a containerized application on Amazon Web Services without any kind of context what one could actually run over there.&lt;/p&gt;

&lt;p&gt;A lot of deeper points were omitted since they serve no purpose in a introduction, a couple of other complementary ones were also not mentioned now (such as configuring services and making spot fleet requests) but are going to be featured in future following articles. Those additional content will be complementary and crucial for a more consistent understanding of the multitude of ECS services and overall environment.&lt;/p&gt;

&lt;p&gt;I invite you all to stay tuned for the next articles. Feel free to use the comments section below to post any questions or commentaries, we'll take a look on them all, promise.&lt;br&gt;
Until next time, happy deploying.&lt;/p&gt;

</description>
      <category>aws</category>
      <category>docker</category>
      <category>beginners</category>
    </item>
  </channel>
</rss>
