<?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: Tyler Reicks</title>
    <description>The latest articles on Forem by Tyler Reicks (@tyry327).</description>
    <link>https://forem.com/tyry327</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%2F519879%2F9837cebf-0175-4d72-bbd2-814e1522cccd.jpeg</url>
      <title>Forem: Tyler Reicks</title>
      <link>https://forem.com/tyry327</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://forem.com/feed/tyry327"/>
    <language>en</language>
    <item>
      <title>What is data scraping?</title>
      <dc:creator>Tyler Reicks</dc:creator>
      <pubDate>Thu, 20 Oct 2022 18:01:38 +0000</pubDate>
      <link>https://forem.com/tyry327/what-is-data-scraping-4lan</link>
      <guid>https://forem.com/tyry327/what-is-data-scraping-4lan</guid>
      <description>&lt;p&gt;Suppose you want to get large amounts of information from a website as quickly as possible. How can this be done? In this article, we will talk about data scraping and how to scrape the web. Additionally, we'll get into what data scraping is, why you would want to do it, how data scrapers work, and lastly, we'll go over different processes for scraping the web. I'll also include a quick example to reference.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fknjq36ynjrfw8d5gszx0.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fknjq36ynjrfw8d5gszx0.png" alt="data scraping json and csv"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  What is data scraping?
&lt;/h2&gt;

&lt;p&gt;If you've ever copied and pasted content from a website into a different location, you are doing a very manual version of data scraping. In this article, we will be using software applications to do the data scraping for us. &lt;/p&gt;

&lt;p&gt;Data scraping is the process of using an application to extract valuable information from a website. This will allow us to obtain large amounts of data from websites in a short amount of time. Many of the larger websites like Google, Facebook, and GitHub have APIs that allow you to access their data. This is super convenient because the data will be given to you in a structured format that is easy to consume.&lt;/p&gt;

&lt;p&gt;Unfortunately, that isn't always the case. There will be other times when you will have to collect content and data for your specific use case. This is where web and data scraping applications come in handy. You can program these scraping applications to visit websites and extract the content/data that you want. The obvious benefit of this is being able to get the precise data that you want easily and efficiently.&lt;/p&gt;

&lt;p&gt;Data scraping is comprised of two parts, the crawler, and the scraper. The crawler is the algorithm that we can create to browse the web and find the exact data that we want. An example of this would be navigating to a specific website and clicking on the page where the content you want exists. Once you have found that data, we will utilize the scraper. The scraper is used to "scrape" the data from the website. With the scraper, you can detect the data points you want and export them to a format that would work best for you. &lt;/p&gt;

&lt;p&gt;Once the data has been exported the fun can begin. You are free to use that data however you need.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use cases for data scraping
&lt;/h2&gt;

&lt;p&gt;While reading this article you've probably wondered, "what are some good use cases for web/data scraping?" Let's go over a couple of these use cases. &lt;/p&gt;

&lt;p&gt;The first one that we will be talking about is my favorite, price monitoring. You can use price monitoring to keep track of prices and make sure you are finding the best deal. Who doesn't want to save money? I have written a previous &lt;a href="https://www.browserless.io/blog/2022/06/06/amazon-web-scraping/" rel="noopener noreferrer"&gt;blog post&lt;/a&gt; about scraping Amazon.com to monitor the prices of specific products. Companies can also use price scraping to see what competitors are pricing similar products. This provides them with an&lt;br&gt;
advantage of being able to provide optimal pricing for their products so they can obtain maximum revenue. &lt;/p&gt;

&lt;p&gt;Contact scraping is another way data scraping is used. Many companies and individuals can scrape the web for contact information to use for e-mail marketing. A good example of this would be scraping locations like an online employee directory or a bulk mailing list. This use case is very controversial and often requires permission to collect this kind of data. If you have ever visited a website and given them access to your contact information in exchange for using their software, you have permitted them to collect personal data like your e-mail address and phone number.&lt;/p&gt;

&lt;p&gt;The last use case we'll go over is news monitoring. Many individuals and companies can scrape news sites to stay current on stories and issues relevant to them. This could be especially useful if you are trying to create a feed of some type, or if you just need to keep up with day-to-day reports.&lt;/p&gt;
&lt;h2&gt;
  
  
  How do data scrapers work?
&lt;/h2&gt;

&lt;p&gt;Next, let's go over how data scrapers work. Scrapers can take all the content on web pages or just the specific data that you want. In many situations, it is best to pinpoint the specific data you want so that the data scraper can quickly extract it. For example, in the &lt;a href="https://www.browserless.io/blog/2022/06/06/amazon-web-scraping/" rel="noopener noreferrer"&gt;Amazon web scraping blog post&lt;/a&gt; that I mentioned earlier, we look at pricing for office chairs. In that instance, we are only trying to identify the price of the chairs and the title of the item. That allowed the data scraper to swiftly filter out any unneeded clutter resulting in the script being run relatively fast. &lt;/p&gt;

&lt;p&gt;Now when the process of scraping a site first begins there needs to be a URL that is provided to the script or application software. Based on the provided URL the scraper will navigate to that web page. Next, it will load the HTML code of that site. After that code has been loaded, the scraper can then begin to collect the data that is wanted/needed. Lastly, the collected data is outputted in a predefined format determined by the user. Usually, this is a JSON file, but it can also be saved in other formats like an excel spreadsheet or a CSV file.&lt;/p&gt;
&lt;h2&gt;
  
  
  Stepping through the scraping process
&lt;/h2&gt;

&lt;p&gt;Now that we know how a data scraper functions let's identify some preliminary steps that are needed before you try to scrape a website yourself. There are many cool tools and software applications out there that help with scraping websites. Because of this we will stay at a high level and focus on the basics.&lt;/p&gt;

&lt;p&gt;First, you want to find the URLs you want to scrape. This might seem obvious, but it is a key factor and how well your data scrape will work. If the URL you give the scraper is even slightly incorrect the data you get back will not be what you want, or even worse, your scraper won't work at all. For instance, if you're trying to do a &lt;a href="https://www.browserless.io/blog/2022/06/28/google-shopping-scraper/" rel="noopener noreferrer"&gt;price monitoring data scrape&lt;/a&gt; you want to make sure that your URL goes to a relevant site like Amazon or Google shopping.&lt;/p&gt;

&lt;p&gt;Secondly, you want to inspect the webpage (f12 on most keyboards) to identify what your scraper needs to scrape. If we use the same Amazon price monitoring example, you could go to a search result page on Amazon, inspect that page, and locate where the price is in the HTML code.&lt;/p&gt;

&lt;p&gt;Once you have that, you want to identify the unique tags that are around the price so you can use that in your data scraper. Some good tags would be div tags with IDs or very specific class names.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;&amp;lt;div id="price"&amp;gt;$1.99&amp;lt;/div&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;After finding the code you want to collect and use, you'll want to incorporate this into your data scrape. This could be writing a script with the IDs or class names that you found in the previous step or simply inputting the tags into scraping software. You'll also probably want to add supporting information to help when displaying your data. Sticking with our Amazon example, if you are collecting the price of office chairs on Amazon it would be nice to also have the title of the item to accompany the price.&lt;/p&gt;

&lt;p&gt;Once you've specified the tags in your script or scraping application, you'll want to execute the code. This is where all the magic happens. Everything that we talked about in the above section about how data scrapers work comes into play here.&lt;/p&gt;

&lt;p&gt;Hopefully, you now have the data you need to start building your application. Whether that be a dashboard of charts, a cool table, or a sweet content feed the data is yours to do with it what you like. Lots of times you may get data back that you don't expect. That is completely normal. Just like anything else in the engineering world, if one tiny thing is off it can often lead to things being incorrect. Don't get discouraged! Practice makes perfect and you will catch on.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data scraping with Browserless
&lt;/h2&gt;

&lt;p&gt;All the basics have been covered for scraping the web. Before we end, I want to mention a cool tool that allows you to do data scraping. &lt;a href="https://www.browserless.io/" rel="noopener noreferrer"&gt;Browserless&lt;/a&gt; is a headless Chrome browser as a service. You can use Browserless with libraries like &lt;a href="https://pptr.dev/" rel="noopener noreferrer"&gt;puppeteer&lt;/a&gt; or &lt;a href="https://www.selenium.dev/" rel="noopener noreferrer"&gt;Selenium&lt;/a&gt; to automate web-based tasks like data collection. To learn more, make sure to visit the &lt;a href="https://www.browserless.io/" rel="noopener noreferrer"&gt;Browserless website&lt;/a&gt; where you can find blog posts, documentation, debuggers, and other resources.&lt;/p&gt;

&lt;h2&gt;
  
  
  Data Scraping Example with Browserless
&lt;/h2&gt;

&lt;p&gt;In this example we are going to do a simple data scrape of the Y Combinator Hacker News feed. You can also run this example in the &lt;a href="https://chrome.browserless.io/" rel="noopener noreferrer"&gt;Browserless debugger tool&lt;/a&gt;. For this, we will use two main tools, Puppeteer and Browserless. In the above paragraph I mentioned these tools with corresponding links. I highly recommend you check them out before diving into the example.&lt;/p&gt;

&lt;h3&gt;
  
  
  Initial setup
&lt;/h3&gt;

&lt;p&gt;Alrighty, let's get to it! We are going to start with the initial setup. Luckily for us, there aren't many dependencies we need to install. There's only one... Puppeteer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i puppeteer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you've run that command you are good to go! 👍&lt;/p&gt;

&lt;h3&gt;
  
  
  Browserless setup
&lt;/h3&gt;

&lt;p&gt;Let's look at how to set up Browserless real quick. &lt;/p&gt;

&lt;p&gt;For starters, you'll need to set up an account. Once you get your account set up, you'll be directed to your Browserless dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3y210vw80nd2toxsxca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3y210vw80nd2toxsxca.png" alt="Browserless dashboard"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Here you can find your session usage chart, prepaid balance, API key, and all of the other account goodies.&lt;/p&gt;

&lt;p&gt;Keep your API key handy as we'll use that once we start writing our script. Other than that we are ready to start coding!&lt;/p&gt;

&lt;h3&gt;
  
  
  The code
&lt;/h3&gt;

&lt;p&gt;Ok now for the exciting part! Here is the code 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;puppeteer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;scrape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;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;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// const browser = await puppeteer.connect({ browserWSEndpoint: 'wss://chrome.browserless.io?token=[ADD BROWSERLESS API TOKEN HERE]' })&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="nf"&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;goto&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;https://news.ycombinator.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Here, we inject some JavaScript into the page to build a list of results&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;items&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="nf"&gt;evaluate&lt;/span&gt;&lt;span class="p"&gt;(()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;elements&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nb"&gt;document&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;querySelectorAll&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;.athing .titleline a&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;results&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;elements&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;({&lt;/span&gt;
            &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;textContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
            &lt;span class="na"&gt;href&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;link&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;href&lt;/span&gt;&lt;span class="p"&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;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&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="c1"&gt;// Finally, we return an object, which triggers a JSON file download&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;parse&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nf"&gt;scrape&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Lines 4 and 5 are very important in this example. &lt;/p&gt;

&lt;p&gt;Line 4 uses Puppeteer when running your script. This is good for testing so you can view how the browser is interacting with your script.&lt;/p&gt;

&lt;p&gt;Line 5 is the Browserless connection. This is where you can add your API key which will link up to your Browserless account and allow you to run your script with Browserless.&lt;/p&gt;

&lt;p&gt;Make sure one of these two lines are commented out. You only need one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Final product
&lt;/h3&gt;

&lt;p&gt;Alrighty, that is all you need for this example. Once things are installed and code is implemented, you can open up your preferred command line interface in your project and run &lt;code&gt;node [insert name of js file here]&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The output should be a json file with real time titles and links from the Hacker News feed!&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqm29x6v93puqtw5izqh9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqm29x6v93puqtw5izqh9.png" alt="Finished hacker news example"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hoped this article on data scraping was intriguing and exciting. There are endless possibilities as to what you can accomplish with web and data scraping. I hope this sparks some cool projects for some of you.&lt;/p&gt;

&lt;p&gt;Happy coding! ❤️&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>beginners</category>
      <category>data</category>
    </item>
    <item>
      <title>Getting started with Website Test Automation</title>
      <dc:creator>Tyler Reicks</dc:creator>
      <pubDate>Mon, 29 Aug 2022 17:46:00 +0000</pubDate>
      <link>https://forem.com/tyry327/getting-started-with-website-test-automation-1633</link>
      <guid>https://forem.com/tyry327/getting-started-with-website-test-automation-1633</guid>
      <description>&lt;p&gt;The software development ecosystem is in a constant state of movement. This is especially true when it comes to building websites and other applications. Not only is the technology that you are using changing, but so are the requirements and acceptance criteria. This is where technologies like headless architecture can help with continuous integration and continuous delivery (CI/CD).&lt;/p&gt;

&lt;p&gt;In this article, we will be going over website test automation. Specifically, we will be talking about UI automation testing using Puppeteer and Browserless. There are other libraries that you can use for website test automation alongside Browserless (i.e. Playwright), but for the sake of this article, we will be using Puppeteer. If you want more info on other browser automation tools &lt;a href="https://www.browserless.io/blog/2022/08/19/playwright-vs-puppeteer/"&gt;check out this other article on the Browserless blog&lt;/a&gt;. Browserless and Puppeteer integrate seamlessly within your project and allow you to stay on top of testing as well as changing requirements. We'll go through what both tools are, and do some simple examples.  &lt;/p&gt;

&lt;h2&gt;
  
  
  Headless Browser Testing
&lt;/h2&gt;

&lt;p&gt;First, let's explain what headless browser testing is. Headless browser testing allows for automated control of a webpage without any graphical user interface (GUI). This allows a tester/user/QA/developer to do automated testing on a web application without having to manually test functionality on browsers that we visually see on our monitors. Imagine having a robot test your application for you! That's pretty much what headless browser testing is only often times it's a simple script. Headless browser testing speeds up the testing process while also providing quick feedback during development.&lt;/p&gt;

&lt;h2&gt;
  
  
  Puppeteer and Browserless
&lt;/h2&gt;

&lt;p&gt;So, you may be thinking how do Puppeteer and Browserless fit into this equation? &lt;/p&gt;

&lt;p&gt;Puppeteer provides a great high-level API to control Chrome (both headless and non-headless). Puppeteer is maintained by the Chrome DevTools team. Most things you can do manually in the browser can be accomplished using Puppeteer. This lets us easily perform actions to test our UI's functionality. Puppeteer also allows us to generate screenshots and PDFs of the pages while performing actions on the application for testing.&lt;/p&gt;

&lt;p&gt;Puppeteer alone is a great tool to use for website test automation. However, Browserless can take your website test automation to the next level. Let's say you want to run tests in your production environment, but you don't want to add Puppeteer as an extra dependency. This is where Browserless comes to the rescue. With Browserless there is no need to install extra packages or dependencies, and it's compatible with Puppeteer. &lt;a href="https://www.browserless.io/docs/start"&gt;All you need to do is plug and play&lt;/a&gt;. This takes a huge load off your application. You can run your tests with Puppeteer in your dev environments, and meanwhile, run Browserless in your prod environments without having to worry about the extra baggage.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Before we get to the examples let's set up our environments. Luckily, there aren't many dependencies we need to install. There is only one ... Puppeteer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i puppeteer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have run that command you are good to go!&lt;/p&gt;

&lt;p&gt;The great part about the other tool we're using, Browserless, is that there's not any setup involved other than creating an account. Let's go to &lt;a href="http://www.browserless.io"&gt;www.browserless.io&lt;/a&gt; and create an account. Once you get your account set up you will be directed to your Browserless dashboard. &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--Rc_tf_wA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/irhsj23l0mjf4psxdsno.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--Rc_tf_wA--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/irhsj23l0mjf4psxdsno.png" alt="Browserless dashboard" width="880" height="481"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here you can find your session usage chart, prepaid balance, API key, and all of the other account goodies. Keep your API key handy as we will use that for our examples.&lt;/p&gt;

&lt;p&gt;I should also mention, Browserless provides a great &lt;a href="https://chrome.browserless.io/"&gt;online debugger tool&lt;/a&gt; that lets you test out your scripts to see how they work. If you don't want to go through the hassle of setting up your local machine, this is a great option to use.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 1:
&lt;/h2&gt;

&lt;p&gt;In this example, we are going to test the functionality of a cool website created by Tim Holman (@twholman) and Tobias van Schneider (@vanschneider) called "The Passive Aggressive Password Machine".  Their website asks you to enter a password, and it will tell you how good your password is. This test is going to be super simple. We are going to test the functionality of the website by using Puppeteer to type a password in the input area. We will then take a screenshot to verify the website gave the user feedback. Taking a screenshot is the simplest form of validation. If you wanted to make this test even more robust, you could programmatically check to make sure the website was giving updated feedback on the password that was entered.&lt;/p&gt;

&lt;p&gt;See the code for example 1 below. Notice the commented section on line 5. This is where you would add in your Browserless API token to run in your Browserless environment.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const puppeteer = require("puppeteer");

const scrape = async () =&amp;gt; {
    const browser = await puppeteer.launch({headless: false});
    // const browser = await puppeteer.connect({ browserWSEndpoint: 'wss://chrome.browserless.io?token=[ADD BROWSERLESS API TOKEN HERE]' })

    const page = await browser.newPage();

    await page.goto('https://trypap.com/');

    await page.setViewport({ width: 1920, height: 1080 });

    await page.type('input[placeholder="Please Enter a Password"]', 'BrowserlessIsCool1234');

    await page.waitForTimeout(2000);

    await page.screenshot({ path: "screenshot.png", fullPage: true });

    await browser.close();
};
scrape();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have this script on your local machine, run &lt;code&gt;node [title of your script]&lt;/code&gt; in your terminal and you should get a screenshot outputted into the root of your directory.&lt;/p&gt;

&lt;p&gt;Here is what it looks like:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--8WOs7eFF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t7fwhoza6c6ddh310cfg.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--8WOs7eFF--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/t7fwhoza6c6ddh310cfg.gif" alt="web automation GIF" width="600" height="338"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Congrats! You've officially done a simple UI test.&lt;/p&gt;

&lt;h2&gt;
  
  
  Example 2:
&lt;/h2&gt;

&lt;p&gt;In this example, I want to show off the &lt;a href="https://www.browserless.io/docs/stats"&gt;/stats API&lt;/a&gt; that Browserless provides to us. The /stats API is powered by Google's Lighthouse project and gives you metrics about performance, accessibility, size, and much more. As mentioned in the documentation, the /stats API is pretty simple to use. To gather this data, send a &lt;code&gt;POST&lt;/code&gt; request to &lt;code&gt;https://chrome.browserless.io/stats&lt;/code&gt; with a simple JSON payload containing a valid URL. This will output a large amount of data, about 370kb. You can narrow down what gets outputted by adding a &lt;code&gt;config&lt;/code&gt; object with specific categories you want to see. This is similar to how you would use the Lighthouse node API. For our example, we will only be checking the performance category. &lt;/p&gt;

&lt;p&gt;We are going to run a curl command to receive the output, but if you wanted to run this in JavaScript you could accomplish the same thing with the &lt;a href="https://developer.mozilla.org/en-US/docs/Web/API/Fetch_API/Using_Fetch"&gt;fetch API&lt;/a&gt;. For this example we'll run the Lighthouse tests on the &lt;a href="https://trypap.com/"&gt;Passive Aggressive Password Machine website&lt;/a&gt; we used in the first example. Below is the curl command.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;curl -X POST \
  https://chrome.browserless.io/stats?token=[ADD BROWSERLESS API TOKEN HERE] \
  -H 'Cache-Control: no-cache' \
  -H 'Content-Type: application/json' \
  -d '
{
  "url": "https://trypap.com/",
    "config": {
        "extends": "lighthouse:default",
        "settings": {
            "onlyCategories": ["performance"]
        }
    }
}'  
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This should return a JSON object with the Lighthouse performance score. At the time of writing this, trypap.com received a 0.9 performance score (on a scale from 0-1). Pretty good! &lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--s0owp1p6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/79do95usi99wl14ub1ci.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--s0owp1p6--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/79do95usi99wl14ub1ci.png" alt="performance score" width="288" height="194"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;I hope this article helped open up a whole new world of website test automation for you. As always, make sure to check out the &lt;a href="https://www.browserless.io/blog/"&gt;Browserless blog&lt;/a&gt; and Browserless YouTube channel for more exciting web browser automation content! Happy coding!❤️&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>webdev</category>
      <category>browserless</category>
      <category>testing</category>
    </item>
    <item>
      <title>Scraping Google Shopping using Puppeteer and Browserless</title>
      <dc:creator>Tyler Reicks</dc:creator>
      <pubDate>Wed, 29 Jun 2022 03:23:32 +0000</pubDate>
      <link>https://forem.com/tyry327/scraping-google-shopping-using-puppeteer-and-browserless-46ae</link>
      <guid>https://forem.com/tyry327/scraping-google-shopping-using-puppeteer-and-browserless-46ae</guid>
      <description>&lt;p&gt;In this article, we are going to completely automate the process of searching for a product on Google Shopping. Specifically, we are going to search for board games and scrape the Google Shopping results page for the product name and price. Below is a visual of what we are going to be targeting on the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxkkahbnwz67i0ed65fj.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fpxkkahbnwz67i0ed65fj.png" alt="Google Shopping results page"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the kicker, all we are going to use is one script and exactly 50 lines of code. How are we going to do this you may ask? The magic of JavaScript. Furthermore, a node library called &lt;a href="https://pptr.dev/" rel="noopener noreferrer"&gt;Puppeteer&lt;/a&gt; and a web service called &lt;a href="https://www.browserless.io/" rel="noopener noreferrer"&gt;Browserless&lt;/a&gt;. If you don't know much about these tools I'd highly recommend you check them out further. They offer a lot of neat capabilities! We will go over some of them in this article. &lt;/p&gt;

&lt;p&gt;For further info on Puppeteer, I'd encourage watching &lt;a href="https://developers.google.com/web/tools/puppeteer" rel="noopener noreferrer"&gt;Eric Bidelman's talk at the Google I/O conference&lt;/a&gt; in 2018. Also, if you're hungry for more Browserless material after reading this article checkout &lt;a href="https://www.youtube.com/channel/UCIGMawm-wZEeb9IhML8XyVg" rel="noopener noreferrer"&gt;the Browserless YouTube channel&lt;/a&gt; and &lt;a href="https://www.browserless.io/blog/" rel="noopener noreferrer"&gt;the Browserless blog&lt;/a&gt;. Both the channel and blog offer great tips and tricks to use in your Browserless and Puppeteer environments.&lt;/p&gt;

&lt;h3&gt;Why Puppeteer?&lt;/h3&gt;

&lt;p&gt;Next, let's very quickly explain why we are using Puppeteer to accomplish our task of automating a Google Shopping search. First, Puppeteer provides a great high-level API to control Chrome (both headless and non-headless). Puppeteer is maintained by the Chrome DevTools team. These engineers know what they are doing. Lastly, most things you can do manually in the browser can be accomplished using Puppeteer. This will allow us to easily manipulate our script to perform the actions we want in the browser.&lt;/p&gt;

&lt;h3&gt;Initial setup&lt;/h3&gt;

&lt;p&gt;Alrighty, let's get to it! We are going to start with the initial setup. Luckily for us, there aren't many dependencies we need to install. There's only one... Puppeteer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i puppeteer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you've run that command you are good to go! 👍&lt;/p&gt;

&lt;h3&gt;Browserless&lt;/h3&gt;

&lt;p&gt;Technically you are all set to start coding, but I want to use Browserless in tandem with Puppeteer. Why am I going to use this tool you're wondering? Let's get into that. &lt;/p&gt;

&lt;p&gt;The script that we are going to be coding is one of many things that can be done with Puppeteer and headless Chrome. Let's say you want to run this script in one of your production applications, but you don't want to have to add Puppeteer as an extra dependency. What can you do? This is one scenario where Browserless comes to the rescue. With Browserless there is no need to install extra packages or dependencies, and it's compatible with Puppeteer. All you need to do is plug and play (We'll get more into the details of setting up Browserless below). This takes a huge load off of your application. You can test and run Puppeteer scripts in your dev environments and meanwhile run Browserless in your prod environments without having to worry about the extra baggage. Talk about a performance boost!&lt;/p&gt;

&lt;p&gt;Another area where Browserless shines is in remote development environments (i.e. repl.it, gitpod, codepen, codespaces, etc.). Since there is no need to install any software you can easily hook up to the Browserless API and run your Puppeteer scripts in all your remote environments.&lt;/p&gt;

&lt;p&gt;Before we get to the actual script, let's look at how to set up Browserless real quick. &lt;/p&gt;

&lt;p&gt;For starters, you'll need to set up an account. Once you get your account set up, you'll be directed to your Browserless dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3y210vw80nd2toxsxca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd3y210vw80nd2toxsxca.png" alt="Browserless dashboard"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Here you can find your session usage chart, prepaid balance, API key, and all of the other account goodies.&lt;/p&gt;

&lt;p&gt;Keep your API key handy as we'll use that once we start writing our script. Other than that we are ready to start coding!&lt;/p&gt;

&lt;h3&gt;The code&lt;/h3&gt;

&lt;p&gt;Ok now for the exciting part! I like to show the final product at the beginning and then dissect each piece from top to bottom. Here is our script:&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;puppeteer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&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;scrape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async &lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;try&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;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;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;&lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="c1"&gt;// const browser = await puppeteer.connect({&lt;/span&gt;
    &lt;span class="c1"&gt;//   browserWSEndpoint:&lt;/span&gt;
    &lt;span class="c1"&gt;//     "wss://chrome.browserless.io?token=[ADD BROWSERLESS API TOKEN HERE]",&lt;/span&gt;
    &lt;span class="c1"&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="nf"&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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://shopping.google.com/&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;input[placeholder='What are you looking for?']&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;board game&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button[aria-label='Google 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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#pnnext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Go to next results page&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="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#pnnext&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#pnnext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="c1"&gt;// Gather product title&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&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="nf"&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;div.sh-dgr__grid-result h4&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;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&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="c1"&gt;// Gather price&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&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="nf"&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;div.sh-dgr__grid-result a.shntl div span span[aria-hidden='true'] span:nth-child(1)&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;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&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="c1"&gt;// Consolidate product search data&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;googleShoppingSearchArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;googleShoppingSearchArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;googleShoppingSearchResults.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="c1"&gt;// await browser.close();&lt;/span&gt;
  &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&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;span class="nf"&gt;scrape&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okie doke, let's dive into this. The first 2 lines are pulling in JavaScript modules. We'll want to pull in Puppeteer for obvious reasons. The second module is what we will use to export our scraped data to a JSON file.&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;puppeteer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next section is the start of our actual &lt;code&gt;scrape()&lt;/code&gt; function. At the very beginning of this arrow function, we hook up to the Browserless API using the API key you obtained from the Browserless dashboard.&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;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;browserWSEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://chrome.browserless.io?token=[ADD BROWSERLESS API TOKEN HERE]&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;Alternatively, you could replace this section of code with &lt;code&gt;puppeteer.launch&lt;/code&gt; like so:&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;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;launch&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is good to do in your dev environment to test out the script. That will save your Browserless resources and help with debugging if there is any issue with your code. One thing I wanted to point out was the &lt;code&gt;{headless: false}&lt;/code&gt; property that is being passed in. This tells Puppeteer to launch Chrome on the screen so you can visually see how the code is interacting with the webpage. Pretty cool to watch, and extra useful when building out scripts!&lt;/p&gt;

&lt;p&gt;In the next section, we create a new browser page, navigate to Google Shopping's website, find the search bar, type in whatever we want to search for (in this case we are searching for a board game), click the search button, and finally waiting for the page to load. More specifically, waiting for the pagination at the bottom of the page to load. You'll see why in a minute.&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;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="nf"&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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://shopping.google.com/&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&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;input[placeholder='What are you looking for?']&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;board game&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;button[aria-label='Google 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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#pnnext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This following section is kind of a bonus. What if you wanted to navigate a couple of pages deep into the search results? That is what we accomplish in the next code block.&lt;/p&gt;

&lt;p&gt;Remember how we waited for the pagination to load in the last part of the code? In this section, we click on that "next page" button that we were waiting on. This will navigate our browser to the next page of board games.&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="nf"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#pnnext&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#pnnext&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alrighty, now to gather the data we want from the page! The following piece of code is what finds the product title and the price of the board game. I do want to add a little disclaimer here. When inspecting Google Shopping's search page, you'll notice their compiled code is a little convoluted. If these specific attributes aren't working for you, make sure to inspect the search results page yourself and find the HTML attributes that work for your scenario. This is where using that &lt;code&gt;puppeteer.launch({ headless: false })&lt;/code&gt; property can help.&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;// Gather product title&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&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="nf"&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;div.sh-dgr__grid-result h4&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;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
      &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&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="c1"&gt;// Gather price&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&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="nf"&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;div.sh-dgr__grid-result a.shntl div span span[aria-hidden='true'] span:nth-child(1)&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;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;nodes&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, so once we've gotten our product titles and prices it's time to consolidate our data. For this I'm only going to take the first 10 results I gathered.&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;// Consolidate product search data&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;googleShoppingSearchArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;10&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
        &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&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;p&gt;Lastly, we want to display or save our data. If you simply want to see what results get returned, you can &lt;code&gt;console.log(googleShoppingSearchArray);&lt;/code&gt;. But let's say you want to export this data to a JSON file and maybe use that JSON to make a chart of the results. To save your data into a JSON file you'll use the code in our 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;googleShoppingSearchArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;googleShoppingSearchResults.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added extra parameters to the &lt;code&gt;stringify&lt;/code&gt; method for formatting purposes. The last param is a &lt;code&gt;space&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;And to finish off our script, we close the browser.&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below is a visual display of what our script is doing in the browser. &lt;br&gt;
&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9s1xlf8acxaxb131jfk.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fo9s1xlf8acxaxb131jfk.gif" alt="Google Shopping search"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, Puppeteer is a pretty cool API. Pair that with Browserless and you have a super scalable, web scraping, automation tool. Did this article spark an idea or thought? Let me know in the comments, or on Twitter at @tylerreicks. Happy coding ❤️!&lt;/p&gt;

</description>
      <category>browserless</category>
      <category>javascript</category>
      <category>puppeteer</category>
      <category>googleshopping</category>
    </item>
    <item>
      <title>Scraping Amazon using Puppeteer and Browserless</title>
      <dc:creator>Tyler Reicks</dc:creator>
      <pubDate>Tue, 07 Jun 2022 17:51:49 +0000</pubDate>
      <link>https://forem.com/tyry327/scraping-amazon-using-puppeteer-and-browserless-3n24</link>
      <guid>https://forem.com/tyry327/scraping-amazon-using-puppeteer-and-browserless-3n24</guid>
      <description>&lt;p&gt;In this article, we are going to completely automate the process of searching for a product on Amazon. Specifically, we are going to search for office chairs and scrape the Amazon results page for the product name and price. Below is a visual of what we are going to be targeting on the page.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--o4WpTZN---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/btz1kcgfd8j9a0vyvlgc.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--o4WpTZN---/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/btz1kcgfd8j9a0vyvlgc.png" alt="Amazon results page - office chair" width="880" height="396"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Here's the kicker, all we are going to use is one script and under 50 lines of code. How are we going to do this you may ask? The magic of JavaScript. Furthermore, a node library called &lt;a href="https://pptr.dev/"&gt;Puppeteer&lt;/a&gt; and a web service called &lt;a href="https://www.browserless.io/"&gt;Browserless&lt;/a&gt;. If you don't know much about these tools I'd highly recommend you check them out further. They offer a lot of neat capabilities! We will go over some of them in this article. &lt;/p&gt;

&lt;p&gt;For further info on Puppeteer, I'd highly recommend watching &lt;a href="https://developers.google.com/web/tools/puppeteer"&gt;Eric Bidelman's talk at the Google I/O conference&lt;/a&gt; in 2018. Also, but if you're hungry for more Browserless after reading this article checkout &lt;a href="https://www.youtube.com/watch?v=MF52-nd2Si8"&gt;Joel Griffith's webinar about cool ways to optimize web scraping&lt;/a&gt;. Joel is the CEO of Browserless and offers great tips and tricks to use in your Browserless and Puppeteer environments.&lt;/p&gt;

&lt;h3&gt;Why Puppeteer?&lt;/h3&gt;

&lt;p&gt;Next, let's very quickly explain why we are using Puppeteer to accomplish our task of automating an Amazon search. First, Puppeteer provides a great high-level API to control Chrome (both headless and non-headless). Puppeteer is maintained by the Chrome DevTools team. These engineers know what they are doing. Lastly, most things you can do manually in the browser can be accomplished using Puppeteer. This will allow us to easily manipulate our script to perform the actions we want in the browser.&lt;/p&gt;

&lt;h3&gt;Initial setup&lt;/h3&gt;

&lt;p&gt;Alrighty, let's get to it! We are going to start with the initial setup. Luckily for us, there aren't many dependencies we need to install. There's only one... Puppeteer.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;npm i puppeteer
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you've run that command you are good to go! 👍&lt;/p&gt;

&lt;h3&gt;Browserless&lt;/h3&gt;

&lt;p&gt;Technically you are all set to start coding, but there is another tool I want to use in tandem with Puppeteer. That tool is a web service called Browserless. Why am I going to use this tool you're wondering? Let's get into that. &lt;/p&gt;

&lt;p&gt;The script that we are going to be coding is one of many things that can be done with Puppeteer and headless Chrome. Let's say you want to run this script in one of your production applications, but you don't want to have to add Puppeteer as an extra dependency. What can you do? This is one scenario where Browserless comes to the rescue. With Browserless there is no need to install extra packages or dependencies, and it's compatible with Puppeteer. All you need to do is plug and play (We'll get more into the details of setting up Browserless below). This takes a huge load off of your application. You can test and run Puppeteer scripts in your dev environments and meanwhile run Browserless in your prod environments without having to worry about the extra baggage. Talk about a performance boost!&lt;/p&gt;

&lt;p&gt;Another area where Browserless shines is in remote development environments (i.e. repl.it, gitpod, codepen, codespaces, etc.). Since there is no need to install any software you can easily hook up to the Browserless API and run your Puppeteer scripts in all your remote environments.&lt;/p&gt;

&lt;p&gt;Before we get to the actual script, let's look at how to set up Browserless real quick. &lt;/p&gt;

&lt;p&gt;For starters, you'll need to set up an account. Once you get your account set up, you'll be directed to your Browserless dashboard.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--5kLxJTCa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d3y210vw80nd2toxsxca.png" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--5kLxJTCa--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_auto%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/d3y210vw80nd2toxsxca.png" alt="Browserless dashboard" width="880" height="481"&gt;&lt;/a&gt; &lt;/p&gt;

&lt;p&gt;Here you can find your session usage chart, prepaid balance, API key, and all of the other account goodies.&lt;/p&gt;

&lt;p&gt;Keep your API key handy as we'll use that once we start writing our script. Other than that we are ready to start coding!&lt;/p&gt;

&lt;h3&gt;The code&lt;/h3&gt;

&lt;p&gt;Ok now for the exciting part! I like to show the final product at the beginning and then dissect each piece from top to bottom. Here is our script:&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;puppeteer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&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;scrape&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="p"&gt;()&lt;/span&gt; &lt;span class="o"&gt;=&amp;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;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;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;browserWSEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://chrome.browserless.io?token=[ADD BROWSERLESS API TOKEN HERE]&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="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;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.amazon.com&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;page&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;#twotabsearchtextbox&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;office chair&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#nav-search-submit-button&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.s-pagination-next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Go to next results page&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;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.s-pagination-next&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.s-pagination-next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

  &lt;span class="c1"&gt;// Gather product title&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&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;$$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;h2 span.a-color-base&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;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;nodes&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;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&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="c1"&gt;// Gather price&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&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;$$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;[data-component-type='s-search-result'] span.a-price[data-a-color='base'] span.a-offscreen&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;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;nodes&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;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&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="c1"&gt;// Consolidate product search data&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amazonSearchArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&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;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amazonSearchArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amazonSearchResults.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jsonData&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;span class="p"&gt;};&lt;/span&gt;
&lt;span class="nx"&gt;scrape&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Okie doke, let's dive into this. The first 2 lines are pulling in JavaScript modules. We'll want to pull in Puppeteer for obvious reasons. The second module is what we will use to export our scraped data to a JSON file.&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;puppeteer&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;fs&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;require&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;fs&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The next section is the start of our actual &lt;code&gt;scrape()&lt;/code&gt; function. At the very beginning of this arrow function, we hook up to the Browserless API using the API key you obtained from the Browserless dashboard.&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;puppeteer&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;connect&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt;
    &lt;span class="na"&gt;browserWSEndpoint&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;wss://chrome.browserless.io?token=[ADD BROWSERLESS API TOKEN HERE]&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;Alternatively, you could replace this section of code with &lt;code&gt;puppeteer.launch&lt;/code&gt; like so:&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;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;headless&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This is good to do in your dev environment to test out the script. That will save your Browserless resources and help with debugging if there is any issue with your code. One thing I wanted to point out was the &lt;code&gt;{headless: false}&lt;/code&gt; property that is being passed in. This tells Puppeteer to launch Chrome on the screen so you can visually see how the code is interacting with the webpage. Pretty cool to watch, and extra useful when building out scripts!&lt;/p&gt;

&lt;p&gt;In the next section, we create a new browser page, navigate to Amazon's website, find the search bar, type in whatever we want to search for (in this case we are searching for an office chair), click the search button, and finally waiting for the page to load. More specifically, waiting for the pagination at the bottom of the page to load. You'll see why in a minute.&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;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;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.amazon.com&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;page&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;#twotabsearchtextbox&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;office chair&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;#nav-search-submit-button&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.s-pagination-next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;This following section is kind of a bonus. What if you wanted to navigate a couple of pages deep into the search results? That is what we accomplish in the next code block.&lt;/p&gt;

&lt;p&gt;Remember how we waited for the pagination to load in the last part of the code? In this section, we click on that "next page" button that we were waiting on. This will navigate our browser to the next page of office chairs.&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;click&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.s-pagination-next&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;page&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;waitForSelector&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;.s-pagination-next&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alrighty, now to gather the data we want from the page! The following piece of code is what finds the product title and the price of the office chair. I do want to add a little disclaimer here. When inspecting Amazon's search page, you'll notice their compiled code is a little convoluted. If these specific attributes aren't working for you, make sure to inspect the search results page yourself and find the HTML attributes that work for your scenario. This is where using that &lt;code&gt;puppeteer.launch({ headless: false })&lt;/code&gt; property can help.&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;// Gather product title&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;title&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;$$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;h2 span.a-color-base&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;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt;
    &lt;span class="nx"&gt;nodes&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;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&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="c1"&gt;// Gather price&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;price&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;$$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;[data-component-type='s-search-result'] span.a-price[data-a-color='base'] span.a-offscreen&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;nodes&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;nodes&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;n&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nx"&gt;n&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Ok, so once we've gotten our product titles and prices it's time to consolidate our data. For this I'm only going to take the first 5 results I gathered.&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;// Consolidate product search data&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;amazonSearchArray&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;slice&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;5&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;map&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;value&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="na"&gt;title&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;title&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;
      &lt;span class="na"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;index&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;p&gt;Lastly, we want to display or save our data. If you simply want to see what results get returned, you can &lt;code&gt;console.log(amazonSearchArray);&lt;/code&gt;. But let's say you want to export this data to a JSON file and maybe use that JSON to make a chart of the results. To save your data into a JSON file you'll use the code in our 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="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;JSON&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;stringify&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;amazonSearchArray&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
  &lt;span class="nx"&gt;fs&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;writeFileSync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;amazonSearchResults.json&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;jsonData&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I added extra parameters to the &lt;code&gt;stringify&lt;/code&gt; method for formatting purposes. The last param is a &lt;code&gt;space&lt;/code&gt; parameter.&lt;/p&gt;

&lt;p&gt;And to finish off our script, we close the browser.&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;browser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;close&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Below is a visual display of what our script is doing in the browser. &lt;br&gt;
&lt;a href="https://res.cloudinary.com/practicaldev/image/fetch/s--pfRwsf5h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2wlhoy2soxglsntcphru.gif" class="article-body-image-wrapper"&gt;&lt;img src="https://res.cloudinary.com/practicaldev/image/fetch/s--pfRwsf5h--/c_limit%2Cf_auto%2Cfl_progressive%2Cq_66%2Cw_880/https://dev-to-uploads.s3.amazonaws.com/uploads/articles/2wlhoy2soxglsntcphru.gif" alt="Amazon search" width="800" height="450"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;As you can see, Puppeteer is a pretty cool API. Pair that with Browserless and you have a super scalable, web scraping, automation tool. Did this article spark an idea or thought? Let me know in the comments, or on Twitter at @tylerreicks. Happy coding ❤️!&lt;/p&gt;

</description>
      <category>javascript</category>
      <category>puppeteer</category>
      <category>browserless</category>
      <category>automation</category>
    </item>
    <item>
      <title>Creating a Slack clone with Supabase and Next.js</title>
      <dc:creator>Tyler Reicks</dc:creator>
      <pubDate>Thu, 11 Mar 2021 23:00:01 +0000</pubDate>
      <link>https://forem.com/tyry327/creating-a-slack-clone-with-supabase-and-next-js-1ni5</link>
      <guid>https://forem.com/tyry327/creating-a-slack-clone-with-supabase-and-next-js-1ni5</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;Recently, I came across a new cloud backend service, called Supabase, that claimed to be a firebase alternative. I think Firebase is a great tool but I have had my fair share of headaches working with it. So I decided to try out Supabase and see what it was all about.&lt;/p&gt;

&lt;p&gt;On &lt;a href="https://github.com/supabase/supabase/tree/master/examples" rel="noopener noreferrer"&gt;Supabase's GitHub page&lt;/a&gt; they have a lot of cool example projects to help you learn their platform. One that really caught my eye was their &lt;a href="https://github.com/supabase/supabase/tree/master/examples/nextjs-slack-clone" rel="noopener noreferrer"&gt;Slack clone project&lt;/a&gt;. In this article, I'm going to go over the steps in that example. I'll talk about my experience and what my opinions are. Enjoy!&lt;/p&gt;

&lt;p&gt;Here is &lt;a href="https://github.com/tyry327/slack-clone" rel="noopener noreferrer"&gt;the repo&lt;/a&gt; that I forked from Supabase to make this project. You can follow along there with the README if you want as well.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Not much setup was needed for this project. The three things you need are all accounts: GitHub account, Vercel account (Next.js), and a Supabase account. Sidenote, you can use your GitHub credentials to make a Vercel and a Supabase account, so if you want to be technical, you really only need one account. Either way, once you have all of that setup you are ready to start the project!&lt;/p&gt;

&lt;h2&gt;
  
  
  Supabase things
&lt;/h2&gt;

&lt;p&gt;Once you have set up your Supabase account you'll see a dashboard page. When you first sign up you won't have any projects, but we shall change that soon. On the left side of the screen, you'll see a vertical toolbar. Select the third icon that looks like a code icon/terminal logo. This will bring you to a page with specific scripts and quick start guides to help you get up and running with the Supabase platform. Select the "Slack Clone" option in the Quick start section.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdnixe3gi1%2Fimage%2Fupload%2Fv1615272326%2Fquick_start_8115ea4d95.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdnixe3gi1%2Fimage%2Fupload%2Fv1615272326%2Fquick_start_8115ea4d95.png" alt="quick-start.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Once you click on the Slack clone script you'll be directed to a page where you can see the code for the script query. On the right side of that screen, there will be a "Run" button. Make sure to give that a click. Look at that! You're well on your way to making your own Slack clone with Supabase!&lt;/p&gt;

&lt;p&gt;Next, we are going to click on the Settings button on the left vertical toolbar. It will be the last icon in the column and looks like your typical settings gear. Once there, you'll want to click on the API tab from the project menu on the left. There you will find the API URL and the &lt;code&gt;anon&lt;/code&gt; key for your project.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdnixe3gi1%2Fimage%2Fupload%2Fv1615273004%2FScreen_Shot_2021_03_08_at_11_54_50_PM_0d0742efed.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fres.cloudinary.com%2Fdnixe3gi1%2Fimage%2Fupload%2Fv1615273004%2FScreen_Shot_2021_03_08_at_11_54_50_PM_0d0742efed.png" alt="Screen Shot 2021-03-08 at 11.54.50 PM.png"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you're wondering what the &lt;code&gt;anon&lt;/code&gt; key is here is how the Supabase documentation explains it: &lt;/p&gt;

&lt;p&gt;"&lt;em&gt;The &lt;code&gt;anon&lt;/code&gt; key is your client-side API key. It allows "anonymous access" to your database until the user has logged in. Once they have logged in, the keys will switch to the user's own login token. This enables row-level security for your data.&lt;/em&gt;"&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploy to Next.js
&lt;/h2&gt;

&lt;p&gt;Ok, so now that we have created our slack clone backend, we have to host it somewhere. The easiest way to do this is by following &lt;a href="https://github.com/tyry327/slack-clone#4-deploy-the-nextjs-client" rel="noopener noreferrer"&gt;step 4 in the GitHub repo&lt;/a&gt; that Supabase provides. Essentially what you will do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fork their repo into your own GitHub account.&lt;/li&gt;
&lt;li&gt;After you fork, click the deploy button on step 4 in your newly created repo.&lt;/li&gt;
&lt;li&gt;This will take you to the Vercel website where you will have to log in with your Vercel account (It's way easier if you just log in with your GitHub credentials).&lt;/li&gt;
&lt;li&gt;Once you log in, it will ask you to create a project name. I kept it the same as what was generated, but you can change it to whatever you want.&lt;/li&gt;
&lt;li&gt; This is where I got a little confused but hang in there. In the previous step, you created the project, for this step you are now creating the repository. You'll want to link your GitHub again by clicking on the GitHub button. The steps look very identical and it almost feels like your doing the same thing over again, but trust me you're on the right track.&lt;/li&gt;
&lt;li&gt;Once you have created your project and repo, you'll be brought to an "import project" screen where you will be asked for a &lt;code&gt;NEXT_PUBLIC_SUPABASE_URL&lt;/code&gt; and &lt;code&gt;NEXT_PUBLIC_SUPABASE_KEY&lt;/code&gt;. Use the API URL and &lt;code&gt;anon&lt;/code&gt; key from &lt;a href="https://github.com/tyry327/slack-clone#3-get-the-url-and-key" rel="noopener noreferrer"&gt;step 3&lt;/a&gt; of the GitHub README. This is also what we went over just previously at the end of the &lt;strong&gt;Supabase things&lt;/strong&gt; section above.&lt;/li&gt;
&lt;li&gt;Lastly, all you have to do is click the "Deploy" button on the Vercel website!&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Final touches
&lt;/h2&gt;

&lt;p&gt;Ok so after you click the deploy button, everything will build and hopefully succeed. You'll see a congratulations message and two buttons. One to go to the dashboard and the other to visit your newly created Slack clone! &lt;/p&gt;

&lt;p&gt;Before I get into the actual Slack clone, I wanted to touch on the Vercel dashboard really quickly. The only change I made here was the domain name. When on the dashboard, head over to the settings tab in the top navigation. Then click on "Domains" to see and edit what domain your site is under. Ok, that's it for the dashboard stuff.&lt;/p&gt;

&lt;p&gt;Now to the actual, newly created Slack clone site! Initially, you will be prompted with a login screen. I was initially confused when trying to sign up for an account because when I clicked the "Sign up" button nothing happened. It took me a couple of minutes to realize that when I clicked the "Sign up" button it sent me a confirmation email to confirm my account. I think a good fun exercise for this app would be to give some sort of feedback to the user when they sign up for an account. Just something to let them know to check their email for a confirmation link. Anyways, after I figured that out I was able to log in and start messaging away!&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Supabase seems like a really well-built tool. I can definitely see it competing with Firebase. It is very easy to use, and setup. I think the key differentiator for me will be the price. Currently, Supabase is in its alpha version. In other words, they are still not a final product yet. Since it's not officially in its final version, Supabase is offering its service for free. But when they do finally get to a version one I'm curious to see how much it costs compared to Firebase. &lt;/p&gt;

&lt;p&gt;That's my only outstanding question so far. I think Supabase is really cool and will be a force to be reckoned with. I can't wait to see how it evolves in the future! &lt;/p&gt;

&lt;p&gt;That's all for now. Thank you everyone for following along, and happy coding!&lt;/p&gt;

</description>
      <category>supabase</category>
      <category>nextjs</category>
      <category>slack</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Create A Data Visualization Map Using Mapbox</title>
      <dc:creator>Tyler Reicks</dc:creator>
      <pubDate>Sun, 14 Feb 2021 23:03:46 +0000</pubDate>
      <link>https://forem.com/tyry327/create-a-data-visualization-map-using-mapbox-2h5e</link>
      <guid>https://forem.com/tyry327/create-a-data-visualization-map-using-mapbox-2h5e</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;In this article, we are going to make a map with a software called Mapbox. This won't involve any coding at all, but I think it is a cool tool to use if you want to add a professional-looking map to your website or app. Mapbox's software is used by apps of many popular companies that all of us utilize every day.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Snap Inc.&lt;/li&gt;
&lt;li&gt;The Weather Company&lt;/li&gt;
&lt;li&gt;Shopify&lt;/li&gt;
&lt;li&gt;Facebook&lt;/li&gt;
&lt;li&gt;AllTrails&lt;/li&gt;
&lt;li&gt;Ancestry&lt;/li&gt;
&lt;li&gt;Peloton&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.mapbox.com/showcase"&gt;And many more...&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For the map that we make today, we will be using the data visualization component that is provided to us in Mapbox Studio (aka Mapbox's map editor). The data we will be working with is the temperature change for US counties from 1895 to 2019. This is already pre-loaded into Mapbox studio which is really handy. Without further ado, let's get into it!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;The setup for this is pretty simple. All you need is an internet connection and a &lt;a href="https://docs.mapbox.com/help/how-mapbox-works/"&gt;Mapbox account&lt;/a&gt;. Once you have those 2 things you are ready to go!&lt;/p&gt;

&lt;h2&gt;
  
  
  The Process
&lt;/h2&gt;

&lt;p&gt;Now that you are signed in you'll want to go to the &lt;a href="https://studio.mapbox.com/"&gt;Styles page&lt;/a&gt;. This is where all of your different designed maps would be housed. If you just set up your account you won't have anything in here.&lt;/p&gt;

&lt;p&gt;Next, we are going to click the "New style" button on this page. This will take you to a screen where you can choose a map template you'll want to use. For this scenario I'm going to choose the Basic map template, I think any template will work here (other than the blank template of course). After you have chosen your template, click the "Customize" button at the bottom of the modal.&lt;/p&gt;

&lt;p&gt;This will direct you to your new Mapbox map! Pro tip: your map will be centered in Paris. Make sure to navigate to the United State before moving on to the rest of the steps.&lt;/p&gt;

&lt;p&gt;This next paragraph is completely optional, in my opinion, but to make the map feel a little less cluttered we will delete some of the components that are added on by default. Click on the component in the menu on the left-hand side. Then click the trash can icon at the top of the menu. Below is the list of components that I deleted.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Buildings&lt;/li&gt;
&lt;li&gt;Place labels&lt;/li&gt;
&lt;li&gt;Points of interest&lt;/li&gt;
&lt;li&gt;Road network&lt;/li&gt;
&lt;li&gt;Transit&lt;/li&gt;
&lt;li&gt;Walking, cycling, etc.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Adding the data
&lt;/h2&gt;

&lt;p&gt;For this exercise, we are going to create a &lt;a href="https://en.wikipedia.org/wiki/Choropleth_map"&gt;choropleth map&lt;/a&gt; with data from &lt;a href="https://github.com/washingtonpost/data-2C-beyond-the-limit-usa"&gt;The Washington Post's "2ºC: Beyond the Limit" series about rising temperatures&lt;/a&gt;, which analyzes warming temperatures in the United States. Our choropleth map will visualize the change in average annual temperature from 1895 to 2019.&lt;/p&gt;

&lt;p&gt;In the Components panel, click the plus icon and then select "Data visualization" from the menu that pops up. You'll then be shown a dark map of the United States. Click on where it says "None selected" under Source. Then click "Add source by ID". Paste &lt;code&gt;mapbox.brt3djy1&lt;/code&gt; into the search bar and click "Find".&lt;/p&gt;

&lt;p&gt;Yay! We have pulled in the data. It was that easy. Now we have to decide how we want our data to look on our map. Click "Select data visualization type". Then click the choropleth option, and lastly, the "Select Choropleth" button.&lt;/p&gt;

&lt;p&gt;There you have it. The component will be added to your map and you'll see the United States illuminate with color.&lt;/p&gt;

&lt;h2&gt;
  
  
  Finishing touches
&lt;/h2&gt;

&lt;p&gt;The rest is just messing around with the styles and colors. You'll notice initially that your map will have 3 colors populated. The field &lt;code&gt;tempchg&lt;/code&gt; has values above and below 0. To make the visualization reflect divergence from 0 with color, the second stop should be 0 instead of the default value of 1.774. So let us change the middle value (2) in the Color section to 0.&lt;/p&gt;

&lt;p&gt;Also, it would be nice to make this look a little bit more like a heat map. We can do that by going down to the bottom section of the Components panel under the Colors section. Click on "Choropleth" with the 3 colors to the left. This will expand and display a dropdown. In that dropdown, select the "Temperate" option (the "Adverse" color palette is a good choice too). Finally, our colors are switched around so we need to click the "Reverse palette" button next to the dropdown.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;There you have it! A super slick looking map that shows temperature change data of the United States. How easy was that? I encourage you to play around a little more in the Mapbox studio and see what other cool manipulations you can make. Some easy ones are: adding more colors to show more details of the data, change the stroke to show or hide the polygon outlines, toggle labels on or off, and even change the opacity of your map.&lt;/p&gt;

&lt;p&gt;You can preview your map by copying the share link in the top right corner, as well as publish it so you can include it in your dev project.&lt;/p&gt;

&lt;p&gt;Lastly, if you're curious about diving deeper into this, Mapbox has lots of great lessons and tutorials to help guide you through all of the available functionality. I think a logical next step after this would be to &lt;a href="https://docs.mapbox.com/help/how-mapbox-works/uploading-data/"&gt;add your own data&lt;/a&gt; to a Mapbox map. Maybe you could look at one of my &lt;a href="https://hackernoon.com/how-to-scrape-wikipedia-by-using-puppeteer-and-nodejs-sp1731g0"&gt;previous articles about scraping websites for data&lt;/a&gt; then including that data into your very own custom map!&lt;/p&gt;

&lt;p&gt;That's all for now. I hope you enjoyed this article. Happy coding!&lt;/p&gt;

</description>
      <category>mapbox</category>
      <category>javascript</category>
      <category>frontend</category>
      <category>webdev</category>
    </item>
    <item>
      <title>Create a Chess game with React and Chessboardjsx ♟️</title>
      <dc:creator>Tyler Reicks</dc:creator>
      <pubDate>Fri, 22 Jan 2021 05:53:41 +0000</pubDate>
      <link>https://forem.com/tyry327/create-a-chess-game-with-react-and-chessboardjsx-214e</link>
      <guid>https://forem.com/tyry327/create-a-chess-game-with-react-and-chessboardjsx-214e</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;In this article we are going to create a chess game with React. I based this article off of another article that I read recently by Varun Pujari. Here's the &lt;a href="https://levelup.gitconnected.com/how-to-create-a-simple-chess-app-with-react-e18c0179b167"&gt;link&lt;/a&gt; to that if you want to check it out. &lt;/p&gt;

&lt;p&gt;We'll use a package called &lt;a href="https://github.com/willb335/chessboardjsx"&gt;chessboardjsx&lt;/a&gt;, which will give us an easy way to display the chess game. On top of that we will use the &lt;a href="https://github.com/jhlywa/chess.js"&gt;chess.js&lt;/a&gt; library to implement moves and how the game should be played. &lt;/p&gt;

&lt;p&gt;This chess game will have one player playing against an AI that will make a random move for every turn. Lastly, we will add a timer to our chess game so we can time how fast we beat the AI!&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Setup is pretty simple. First, we'll run a couple commands in the terminal/command prompt to get everything installed. &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;yarn create react-app chess-game --template typescript&lt;/code&gt;. You can also run &lt;code&gt;npx create-react-app chess-game --template typescript&lt;/code&gt; but yarn worked better for me. I was getting an error saying my create-react-app was out of date. Every time I would uninstall and try to run the npx command I would get the same out of date error. So yarn was what I went with.&lt;/li&gt;
&lt;li&gt;Next we'll install &lt;code&gt;chessboard.jsx&lt;/code&gt; with this &lt;code&gt;yarn add chessboardjsx&lt;/code&gt; command.&lt;/li&gt;
&lt;li&gt;Now let's install the brains of the game. Run &lt;code&gt;yarn add chess.js&lt;/code&gt;. This package is what we will use for the AI logic.&lt;/li&gt;
&lt;li&gt;Since we are also using typescript we have to add types for &lt;code&gt;chess.js&lt;/code&gt;. We can do this by running &lt;code&gt;yarn add @types/chess.js&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt; Lastly, I was thinking it would be cool to add a timer to this game so we can see how long the game took. Let's set that up by running &lt;code&gt;yarn add react-compound-timer&lt;/code&gt;.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Code
&lt;/h2&gt;

&lt;p&gt;Now for the fun part, the actual code behind the game. Below you will find the code for the only file you'll need to edit in this project, the &lt;code&gt;App.tsx&lt;/code&gt; file. I've tried to comment on the main parts so it's easier to understand what is going on.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import React, { useState } from "react";
import "./App.css";
import Timer from "react-compound-timer";
// Lines 5-8: Bring in chessboard and chess.js stuff
import Chessboard from "chessboardjsx";
import { ChessInstance, ShortMove } from "chess.js";

const Chess = require("chess.js");

const paddingStyle = {
  padding: 5
}

const marginStyle = {
  margin: 5
}

const App: React.FC = () =&amp;gt; {
  const [chess] = useState&amp;lt;ChessInstance&amp;gt;(
    // Set initial state to FEN layout
    new Chess("rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1")
  );

  const [fen, setFen] = useState(chess.fen());

  // Logic for the setting up the random computer move.
  const handleMove = (move: ShortMove) =&amp;gt; {
    // Line 29 validates the user move.
    if (chess.move(move)) {
      setTimeout(() =&amp;gt; {
        const moves = chess.moves();
        // Lines 33-28: Computer random move.
        if (moves.length &amp;gt; 0) {
          const computerMove = moves[Math.floor(Math.random() * moves.length)];
          chess.move(computerMove);
          setFen(chess.fen());
        }
      }, 300);
      // Sets state of chess board
      setFen(chess.fen());
    }
  };

  return (
    &amp;lt;div className="flex-center"&amp;gt;
      &amp;lt;h1&amp;gt;Random Chess Game&amp;lt;/h1&amp;gt;
      &amp;lt;Chessboard
        width={400}
        position={fen}
        // onDrop prop tracks everytime a piece is moved.
        // The rest is handled in the the handleMove function.
        onDrop={(move) =&amp;gt;
          handleMove({
            from: move.sourceSquare,
            to: move.targetSquare,
            // This promotion attribute changes pawns to a queen if they reach the other side of the board.
            promotion: "q",
          })
        }
      /&amp;gt;
      {/* Timer code */}
      &amp;lt;Timer initialTime={0} startImmediately={false}&amp;gt;
        {/* I thought this was weird. Definitely a better way to do this, but I just wanted it to work. */}
        {({ start, resume, pause, stop, reset, timerState } : {start:any, resume:any, pause:any, stop:any, reset:any, timerState:any}) =&amp;gt; (
            &amp;lt;&amp;gt;
                &amp;lt;div&amp;gt;
                    &amp;lt;span style={paddingStyle}&amp;gt;&amp;lt;Timer.Minutes /&amp;gt; minutes&amp;lt;/span&amp;gt;
                    &amp;lt;span style={paddingStyle}&amp;gt;&amp;lt;Timer.Seconds /&amp;gt; seconds&amp;lt;/span&amp;gt;
                    &amp;lt;span style={paddingStyle}&amp;gt;&amp;lt;Timer.Milliseconds /&amp;gt; milliseconds&amp;lt;/span&amp;gt;
                &amp;lt;/div&amp;gt;
                &amp;lt;div style={paddingStyle}&amp;gt;{timerState}&amp;lt;/div&amp;gt;
                &amp;lt;br /&amp;gt;
                &amp;lt;div&amp;gt;
                    &amp;lt;button style={marginStyle} onClick={start}&amp;gt;Start&amp;lt;/button&amp;gt;
                    &amp;lt;button style={marginStyle} onClick={pause}&amp;gt;Pause&amp;lt;/button&amp;gt;
                    &amp;lt;button style={marginStyle} onClick={resume}&amp;gt;Resume&amp;lt;/button&amp;gt;
                    &amp;lt;button style={marginStyle} onClick={stop}&amp;gt;Stop&amp;lt;/button&amp;gt;
                    &amp;lt;button style={marginStyle} onClick={reset}&amp;gt;Reset&amp;lt;/button&amp;gt;
                &amp;lt;/div&amp;gt;
            &amp;lt;/&amp;gt;
        )}
      &amp;lt;/Timer&amp;gt;
    &amp;lt;/div&amp;gt;
  );
};

export default App;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Once you have updated your &lt;code&gt;App.tsx&lt;/code&gt; file to look like this you should be able to run your project with &lt;code&gt;yarn start&lt;/code&gt; and play chess against an AI that you created. Don't forget to start the timer and see how quick you can win!&lt;/p&gt;

&lt;h2&gt;
  
  
  FEN (Forsyth-Edwards Notation)
&lt;/h2&gt;

&lt;p&gt;One thing I thought was really interesting about this project was &lt;a href="https://www.chessprogramming.org/Forsyth-Edwards_Notation"&gt;Forsyth-Edwards Notation&lt;/a&gt;, or FEN. It is the notation that describes a chess position. You'll notice it being used on line 21 of the &lt;code&gt;App.tsx&lt;/code&gt; code. It stood out to me because when I first saw it I was certain it was just a bunch of gibberish. Can you figure out what the letters on the initial starting state of the Forsyth-Edwards Notation mean? I'm sure you'll pick it up quick, but if you need a hint, it has to do with names of the pieces on the chess board.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Welp, that’s about it. Pretty short and simple. I hope you enjoyed making this chess game and hopefully have more fun playing it. If you want a little bit of extra credit, try deploying this out online somewhere and see how fast your friends and family can beat the AI. I would recommend deploying it to Netlify. That's my go to hosting service.&lt;/p&gt;

&lt;p&gt;Like always, happy coding! Love y'all. Peace.&lt;/p&gt;

</description>
      <category>react</category>
      <category>typescript</category>
      <category>chess</category>
      <category>netlify</category>
    </item>
    <item>
      <title>Scraping Wikipedia for data using Puppeteer and Node</title>
      <dc:creator>Tyler Reicks</dc:creator>
      <pubDate>Wed, 06 Jan 2021 04:41:23 +0000</pubDate>
      <link>https://forem.com/tyry327/scraping-wikipedia-for-data-using-puppeteer-and-node-1f0l</link>
      <guid>https://forem.com/tyry327/scraping-wikipedia-for-data-using-puppeteer-and-node-1f0l</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;In this article, we'll go through scraping a Wikipedia table with COVID-19 data using Puppeteer and Node. The original article that I used for this project is located &lt;a href="https://analyticsindiamag.com/puppeteer-web-scraping/"&gt;here&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;I have never scraped a website before. I've always seen it as a hacky thing to do. But, after going through this little project, I can see the value of something like this. Data is hard to find and if you can scrape a website for it, in my opinion, by all means, do it. &lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;Setting up this project was extremely easy. All you have to do is install Puppeteer with the command &lt;code&gt;npm install puppeteer&lt;/code&gt;. There was one confusing issue I had during setup, however. The puppeteer package was not unzipped correctly when I initially installed it. I found this out while running the initial example in the article. If you get an error that states &lt;code&gt;Failed to launch browser process&lt;/code&gt; or something similar follow these steps: &lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Unzip &lt;code&gt;chrome-win&lt;/code&gt; from &lt;code&gt;node_modules/puppeteer/.local-chromium/&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Then add that folder to the &lt;code&gt;win64&lt;/code&gt; folder in that same &lt;code&gt;.local-chromium&lt;/code&gt; folder.&lt;/li&gt;
&lt;li&gt;Make sure the &lt;code&gt;chrome.exe&lt;/code&gt; is in this path &lt;code&gt;node_modules/puppeteer/.local-chromium/win64-818858/chrome-win/chrome.exe&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;This is for windows specifically. Mac might be similar, but not sure.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Here is the &lt;a href="https://github.com/puppeteer/puppeteer/issues/5662#issuecomment-625788716"&gt;link&lt;/a&gt; that lead me to the answer. It might be a good idea to do this no matter what to make sure everything is functioning properly.&lt;/p&gt;

&lt;h2&gt;
  
  
  The code
&lt;/h2&gt;

&lt;p&gt;I had to make a couple of small changes to the existing code. &lt;/p&gt;

&lt;h4&gt;
  
  
  First example
&lt;/h4&gt;

&lt;p&gt;The first example didn't work for me. To fix the problem I assigned the async function to a variable then invoked that variable after the function. I'm not sure this is the best way to handle the issue but hey, it works. Here is the code:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const puppeteer = require('puppeteer');

const takeScreenShot = async () =&amp;gt; {
    const browser = await puppeteer.launch();
    const page = await browser.newPage();
    await page.goto('https://www.stem-effect.com/');
    await page.screenshot({path: 'output.png'});

    await browser.close();
};

takeScreenShot();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Wikipedia scraper
&lt;/h4&gt;

&lt;p&gt;I also had an issue with the Wikipedia scraper code. For some reason, I was getting null values for the country names. This screwed up all of my data in the JSON file I was creating. &lt;/p&gt;

&lt;p&gt;Also, the scraper was 'scraping' every table on the Wikipedia page. I didn't want that. I only wanted the first table with the total number of cases and deaths caused by COVID-19. Here is the modified code I used:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;const puppeteer = require('puppeteer');
const fs = require('fs')

const scrape = async () =&amp;gt;{
    const browser = await puppeteer.launch({headless : false}); //browser initiate
    const page = await browser.newPage();  // opening a new blank page
    await page.goto('https://en.wikipedia.org/wiki/2019%E2%80%9320_coronavirus_pandemic_by_country_and_territory', {waitUntil : 'domcontentloaded'}) // navigate to url and wait until page loads completely

    // Selected table by aria-label instead of div id
    const recordList = await page.$$eval('[aria-label="COVID-19 pandemic by country and territory table"] table#thetable tbody tr',(trows)=&amp;gt;{
        let rowList = []    
        trows.forEach(row =&amp;gt; {
                let record = {'country' : '','cases' :'', 'death' : '', 'recovered':''}
                record.country = row.querySelector('a').innerText; // (tr &amp;lt; th &amp;lt; a) anchor tag text contains country name
                const tdList = Array.from(row.querySelectorAll('td'), column =&amp;gt; column.innerText); // getting textvalue of each column of a row and adding them to a list.
                record.cases = tdList[0];        
                record.death = tdList[1];       
                record.recovered = tdList[2];   
                if(tdList.length &amp;gt;= 3){         
                    rowList.push(record)
                }
            });
        return rowList;
    })
    console.log(recordList)
    // Commented out screen shot here
    // await page.screenshot({ path: 'screenshots/wikipedia.png' }); //screenshot 
    browser.close();

    // Store output
    fs.writeFile('covid-19.json',JSON.stringify(recordList, null, 2),(err)=&amp;gt;{
        if(err){console.log(err)}
        else{console.log('Saved Successfully!')}
    })
};
scrape();
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I wrote comments on the subtle changes I made, but I'll also explain them here. &lt;/p&gt;

&lt;p&gt;First, instead of identifying the table I wanted to use by the &lt;code&gt;div#covid19-container&lt;/code&gt;, I pinpointed the table with the aria-label. This was a little more precise. Originally, the reason the code was scraping over all of the tables on the page was because the IDs were the same (I know, not a good practice. That's what classes are for, right?). Identifying the table via aria-label helped ensure that I only scraped the exact table I wanted, at least in this scenario.&lt;/p&gt;

&lt;p&gt;Second, I commented out the screenshot command. It broke the code for some reason and I didn't see the need for it if we were just trying to create a JSON object from table data.&lt;/p&gt;

&lt;p&gt;Lastly, after I obtained the data from the correct table I wanted to actually use it in a chart. I created an HTML file and displayed the data using Google charts. You can see the full project on my &lt;a href="https://github.com/tyry327/scraper"&gt;Github&lt;/a&gt; if you are curious. Fair warning, I got down and dirty (very hacky) putting this part together, but at the end of the day, I just wanted an easier way to consume the data that I had just mined for. There could be a whole separate article on the amount of refactoring that can be done on my HTML page.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;This project was really fun. Thank you to the author, Mohit Maithani, for putting it together. It opened my eyes to the world of web scraping and a whole new realm of possibilities! At a high level, web scraping enables you to grab data from anywhere you want. &lt;/p&gt;

&lt;p&gt;Like one of my favorite Youtubers, Ben Sullins likes to say, "When you free the data, your mind will follow". &lt;/p&gt;

&lt;p&gt;Love y'all. Happy coding!&lt;/p&gt;

</description>
      <category>webdev</category>
      <category>javascript</category>
      <category>node</category>
      <category>puppeteer</category>
    </item>
    <item>
      <title>Creating a Todo app with Svelte and Meteor</title>
      <dc:creator>Tyler Reicks</dc:creator>
      <pubDate>Thu, 24 Dec 2020 00:27:10 +0000</pubDate>
      <link>https://forem.com/tyry327/creating-a-todo-app-with-svelte-and-meteor-5f09</link>
      <guid>https://forem.com/tyry327/creating-a-todo-app-with-svelte-and-meteor-5f09</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;For my next project, I decided to create a todo list app using Svelte and Meteor. I initially got the motivation to do this through Meteor's documentation site. On their site, they have a &lt;a href="https://www.meteor.com/tutorials/svelte/creating-an-app"&gt;tutorial&lt;/a&gt; on how to create a svelte app with a meteor back end (and &lt;a href="https://www.meteor.com/tutorials"&gt;lots of other great tutorials&lt;/a&gt;). Which is exactly what I was looking for! In this article, I'll take you through my experience of the tutorial and a couple of extra things that I added.&lt;/p&gt;

&lt;p&gt;Before I start, I want to share a great quote I heard about Meteor from Scott Tolinski on the Syntax podcast:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Meteor is a back end tool that allows you to "Bring your own front end".&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;I couldn't agree more, and Meteor shows this off with all of the different front end frameworks you can use with their platform. This is comparable to how Next.js operates. Next allows you to bring in whatever back end platform you want to hook up to your Next front end.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setup
&lt;/h2&gt;

&lt;p&gt;The setup for Meteor was weird to me. First, I had to install some software called &lt;a href="https://chocolatey.org/install#"&gt;Chocolatey&lt;/a&gt;. I'm not sure if this a popular software among the dev community, but I had definitely never heard of it. &lt;/p&gt;

&lt;p&gt;Once I had that installed, I was able to run the command &lt;code&gt;choco install meteor&lt;/code&gt; to actually get Meteor on my machine. After that, it was a pretty straightforward setup process. Running the &lt;code&gt;meteor create &amp;lt;name of project&amp;gt;&lt;/code&gt; was all I needed to do to get going. &lt;/p&gt;

&lt;p&gt;There were a couple of odd/cool things during this setup process that I want to note. Meteor forces you to run their commands in Powershell. Which is another practice that I had never experienced before. I'd be curious to know why that is. Also, while in a Meteor project, Meteor replaces npm in a lot of ways. For example, instead of using &lt;code&gt;npm install &amp;lt;package&amp;gt;&lt;/code&gt; you would use &lt;code&gt;meteor add &amp;lt;package&amp;gt;&lt;/code&gt;. Lastly, I was super stoked to find out that Meteor automatically hooks up your project to a MongoDB back end! Right out of the box!&lt;/p&gt;

&lt;h2&gt;
  
  
  Cool things I learned
&lt;/h2&gt;

&lt;p&gt;Here are a couple of things that I learned or thought were cool about Svelte and Meteor.&lt;/p&gt;

&lt;h4&gt;
  
  
  Meteor
&lt;/h4&gt;

&lt;p&gt;Meteor offers a lot of really cool features (on top of the automatic MongoDB hook up) that I haven't really encountered with other projects I've done. I'm still very new to development so these might not be too special but they stood out to me.&lt;/p&gt;

&lt;p&gt;Running your app with Meteor makes it extremely easy to develop for mobile environments. In this tutorial, they actually go over how to run your app on iOS and Android emulators. They explain the steps very well, and it's a nice added functionality bonus.&lt;/p&gt;

&lt;p&gt;Meteor has built-in account login. No need for additional setup. I thought that was really handy. Other cloud-based platforms have made hooking up logins from other accounts pretty easy nowadays, but what made Meteor stand out to me was their default username and password login functionality. It was very straightforward and easy to understand. Other login accounts you could hook up in your Meteor project included: Google, GitHub, and Facebook.&lt;/p&gt;

&lt;p&gt;Because Meteor is such a focused back end solution they include a great security section in their tutorial. In this section, Meteor goes over how to set up your data calls correctly. This covers how you submit, update, and delete data. Really beneficial especially in times like these where hacking is a huge problem in the tech world.&lt;/p&gt;

&lt;p&gt;Last but certainly not least, testing. Every developer loves writing tests! (subtle sarcasm) In this Meteor tutorial, they walk you through writing some tests. It was actually pretty easy and helped me understand writing tests better overall. Side note: One of my tests didn't work but that's not the point! The point is, I wrote my own tests.&lt;/p&gt;

&lt;h4&gt;
  
  
  Svelte
&lt;/h4&gt;

&lt;p&gt;The main reason I did this tutorial was to get more exposure to Svelte so I thought I should share something I learned about that framework as well.&lt;/p&gt;

&lt;p&gt;The way Svelte uses reactivity was very confusing for me at first, but this tutorial helped me understand it a little better. I also looked at some &lt;a href="https://svelte.dev/tutorial/reactive-assignments"&gt;Svelte documentation&lt;/a&gt; to help guide me as well. Long story short, the &lt;code&gt;$&lt;/code&gt; is awesome!&lt;/p&gt;

&lt;p&gt;This doesn't really have to do with Svelte, but I'm going to add it in here anyway. I had never used the double bang syntax &lt;code&gt;!!&lt;/code&gt; until this tutorial. What does the double bang syntax do? It casts a javascript variable to a boolean. Pretty cool, huh? If you're still confused here's a &lt;a href="https://brianflove.com/2014-09-02/whats-the-double-exclamation-mark-for-in-javascript/"&gt;great article&lt;/a&gt; that helped me grasp the concept. Running the author's example in the web console dev tools helped me visualize it even better.&lt;/p&gt;

&lt;h2&gt;
  
  
  Not so great things
&lt;/h2&gt;

&lt;p&gt;There were a couple of hiccups during this tutorial. Luckily only one code hiccup which is always nice. &lt;/p&gt;

&lt;h4&gt;
  
  
  Code Error
&lt;/h4&gt;

&lt;p&gt;When I finished the project my to-do list wasn't functioning correctly. The checkboxes weren't checking and the delete button wasn't deleting. Here is what I had to change in my code. This change occurred in the &lt;code&gt;api/tasks.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Lines 35-53 in my tasks.js file
    'tasks.remove'(taskId) {
        check(taskId, String);

        const task = Tasks.findOne(taskId);
        if (task.private &amp;amp;&amp;amp; task.owner === this.userId) {
            // If the task is private, make sure only the owner can delete it
            Tasks.remove(taskId);
        } 
    },
    'tasks.setChecked'(taskId, setChecked) {
        check(taskId, String);
        check(setChecked, Boolean);

        const task = Tasks.findOne(taskId);
        if (task.owner === this.userId) {
            // If the task is private, make sure only the owner can delete it
            Tasks.update(taskId, { $set: { checked: setChecked } });
        } 
    },
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h4&gt;
  
  
  Deployment
&lt;/h4&gt;

&lt;p&gt;The last downside for me was deploying this Meteor app. Meteor provides a wonderful service called &lt;a href="https://www.meteor.com/hosting"&gt;Galaxy&lt;/a&gt;. It looks like it handles all of your Meteor deployment needs. What's the downside? There isn't a free tier. The cheapest you can get is 7 dollars a month. Which, for me, is too expensive to host a tutorial project.&lt;/p&gt;

&lt;p&gt;So naturally, I looked into other ways of deploying this out to the public. The next best resource seemed to be Heroku. There are a couple of GitHub repos and articles that explain how to get this deployed onto the web. The major hurdle that I ran into was the recent discontinuation of the mongoLab Heroku add-on. Since Meteor uses a mongo database under the hood a lot of the ways to deploy this via Heroku involved using this recently non-existent Heroku add-on. So that was a bummer. I'm sure there's still a way to deploy this thing, but there was no easy way. The easy way was what I was looking for.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Meteor seems like a really great platform. I like how structured it is, and how it does all the complicated back end work for you while allowing you to use whatever front end framework you want. I can definitely see why many companies rely on Meteor for their web needs. However, the lack of easy deployment puts a wrench in the possibility of casual devs latching onto this. Unless I'm really dedicated to a project I'm working on, I'm not shelling out 7 bucks a month to host my fun web project. Especially when there are so many awesome free tools out there. I rate this dev experience a 3.4 out of 5.&lt;/p&gt;

&lt;p&gt;That's all folks! Happy coding!&lt;/p&gt;

</description>
      <category>svelte</category>
      <category>meteor</category>
      <category>javascript</category>
    </item>
    <item>
      <title>Building a blog with Strapi, Netlify, and React</title>
      <dc:creator>Tyler Reicks</dc:creator>
      <pubDate>Wed, 09 Dec 2020 06:08:09 +0000</pubDate>
      <link>https://forem.com/tyry327/building-a-blog-with-strapi-netlify-and-react-5d8b</link>
      <guid>https://forem.com/tyry327/building-a-blog-with-strapi-netlify-and-react-5d8b</guid>
      <description>&lt;h2&gt;
  
  
  Intro
&lt;/h2&gt;

&lt;p&gt;For this blog post, I am going to go over how I built my blog site with Strapi and React. I'm going to keep this as short and simple as possible. I followed along with the tutorial from Strapi themselves but there were hiccups along the way that I will address. Also, the tutorial only goes over how to make the blog locally. In this post, I'll explain what I did to deploy it out to the worldwide web.&lt;/p&gt;

&lt;p&gt;Here is &lt;a href="https://strapi.io/blog/build-a-blog-with-react-strapi-and-apollo"&gt;the link&lt;/a&gt; to the Strapi blog post that I followed along with. Overall it was great. It clearly described every step of the process and explained why things were done the way they were.&lt;/p&gt;

&lt;h2&gt;
  
  
  Initial hurdles
&lt;/h2&gt;

&lt;p&gt;The first small issue I noticed was in their instructions. One of the first commands you have to run is &lt;code&gt;yarn strapi install graphql&lt;/code&gt;. Before this command is run in the terminal you must ensure you are in the correct directory. Make sure to &lt;code&gt;cd backend&lt;/code&gt; in your terminal. You make this backend folder in the step before this, they just don't tell you to navigate to the newly created folder before the next step. If you don't do this you will install graphql in the root folder of the project and your backend for the blog will not work.&lt;/p&gt;

&lt;p&gt;Another problem I ran into was how to organize the project for version control. I use VSCode's built-in source control almost 100% of the time for my projects. The way this tutorial gets setup (I'm starting to learn many projects are set up this way), you'll end up with two separate directories for your frontend and backend code. Since the backend was largely already set up for me by Strapi I only committed my frontend code to GitHub. My backend directory also has version control but it is managed through the Heroku CLI. I'm sure there is a better way to manage this, but at the time I was too lazy to look into other options. Especially, when my current setup was working perfectly fine.&lt;/p&gt;

&lt;p&gt;Lastly, I ran into an issue that I couldn't quite understand while running the frontend locally. Here was the error:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Could not find "client" in the context or passed in as an option. Wrap the root component in an , or pass an ApolloClient instance in via options&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Luckily a couple of users had already solved this problem and put it in the comments. &lt;a href="http://disq.us/p/2bp5pa3"&gt;Link to comment here.&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Everything else in the tutorial was pretty self-explanatory though! If I did have any confusion on something Strapi was so kind to add a video to the tutorial which also guided people through the process of creating the blog. I was able to have the blog finished and running locally on my machine pretty quickly. The next step was deploying the blog out to Heroku and Netlify.&lt;/p&gt;

&lt;h2&gt;
  
  
  Deploying to Heroku and Netlify
&lt;/h2&gt;

&lt;p&gt;If you haven't used this stack with other projects before, from what I've gathered, Heroku is used to host your backend server and Netlify is used to host the frontend (at least that is what the tutorial recommended). &lt;/p&gt;

&lt;h4&gt;
  
  
  Heroku
&lt;/h4&gt;

&lt;p&gt;Strapi has great documentation on how to deploy their backend to Heroku. &lt;a href="https://strapi.io/documentation/3.0.0-beta.x/deployment/heroku.html"&gt;Here is the link to that.&lt;/a&gt; Quick note, I created a Heroku project for this blog while I was still going through the tutorial. Pro tip: don't do that. The Strapi documentation shows you how to create the Heroku project in your backend folder which is so much easier. But if you took the same route I did, make sure to skip those create Heroku project steps (step 6) in the Strapi Heroku docs and connect to your existing project. &lt;/p&gt;

&lt;p&gt;I used PostgreSQL for this. They also give you an option to use MongoDB. I'm sure both are great, but seemed like the PostgreSQL route was simpler. &lt;/p&gt;

&lt;p&gt;Other than that the Heroku backend set up was pretty straight forward. Let's get to deploying the frontend.&lt;/p&gt;

&lt;h4&gt;
  
  
  Netlify
&lt;/h4&gt;

&lt;p&gt;I think the Netlify set up might have been the easiest part of the project. Like I mentioned above, I had already pushed up my frontend directory to GitHub. All I had to do was make a Netlify account and connect my GitHub repo to my Netlify server (they step you through this process when you make a Netlify account). &lt;/p&gt;

&lt;p&gt;Just like that my frontend code was being hosted on Netlify, but there was one problem. My Heroku backend wasn't connected to my Netlify frontend. That issue can easily be solved in your &lt;code&gt;.env&lt;/code&gt; file in your frontend directory. Originally, your backend is set to your localhost URL. I think by default in the Strapi tutorial it is &lt;code&gt;https://localhost:1337&lt;/code&gt;. You'll need to change that to the link of your Heroku app you just created. Pro tip: Make sure there is no trailing slash in the URL. For example, your &lt;code&gt;.env&lt;/code&gt; file should have this one line in it: &lt;code&gt;REACT_APP_BACKEND_URL="https://*your-app-name*.herokuapp.com"&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;There we go! Now you should have your blog uploaded to the internet for the whole world to see. Next, I'll go over what I'll call an "optional issue".&lt;/p&gt;

&lt;h2&gt;
  
  
  Image upload with Cloudinary
&lt;/h2&gt;

&lt;p&gt;Figuring out how to upload an image for a blog post was a real pain. What is the issue you may ask? Here is the answer straight from the Strapi docs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;em&gt;Heroku's file system doesn't support local uploading of files as they will be wiped when Heroku "Cycles" the dyno. This type of file system is called ephemeral, which means the file system only lasts until the dyno is restarted (with Heroku this happens any time you redeploy or during their regular restart which can happen every few hours or every day). Due to Heroku's file system, you will need to use an upload provider such as AWS S3, Cloudinary, or Rackspace.&lt;/em&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;One very easy way to get around this is to simply not add any pictures to your blog posts (this is why I consider this an optional issue). If you do go this route, make sure an image is not a required field in the Strapi admin portal (it was by default when I created my Articles content type).&lt;/p&gt;

&lt;p&gt;I decided to use Cloudinary, but in hindsight, I think AWS would have been easier. I'm not giving Cloudinary enough credit though. It really wasn't that hard. I was just being stupid.&lt;/p&gt;

&lt;p&gt;First, you want to head over to the &lt;a href="https://www.npmjs.com/package/strapi-provider-upload-cloudinary"&gt;Strapi Provider for Cloudinary&lt;/a&gt; npm package page. This page will show you how to add the Cloudinary provider to your Strapi blog.&lt;/p&gt;

&lt;p&gt;Second, I set up my configuration differently than they recommended. Before I get into what I did, I should disclose that this is probably not a best practice. Instead of using an &lt;code&gt;.env&lt;/code&gt; file for the Cloudinary config variables I just put them in the &lt;code&gt;plugins.js&lt;/code&gt; file statically as strings. Yes, I know. For some reason when I was trying to use environment variables Cloudinary wasn't getting hooked up correctly. Here is a code example of what I did in my &lt;code&gt;backend/config/plugins.js&lt;/code&gt; file:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;module.exports = ({ env }) =&amp;gt; ({
  // ...
  upload: {
    provider: "cloudinary",
    providerOptions: {
      cloud_name: "add_cloudinary_name_here",
      api_key: "add_api_key_here",
      api_secret: "add_api_secret_here",
    },
  },
  // ...
});
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Definitely not the best way to solve that problem but it indeed worked.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Overall this was a really fun project. If you're new to coding, this tutorial (along with the video) walks you through the process very well. There may be a couple of hiccups to figure out, but hopefully, this article solves most of them. Like always, peace ✌️ and happy coding!&lt;/p&gt;

</description>
      <category>strapi</category>
      <category>netlify</category>
      <category>react</category>
      <category>blog</category>
    </item>
  </channel>
</rss>
