Node Web Scraper

broken image


Get into the world of Web Scraping and Data Mining with NodeJs. Learn modern methods of scraping with NodeJs - Puppeteer and with direct NodeJs Requests. Introduce yourself and improve your knowledge on Scraping Learn Scraping with Puppeteer, NightmareJs or Manual Requests. Nodejs-web-scraper is a simple tool for scraping/crawling server-side rendered pages. It supports features like recursive scraping (pages that 'open' other pages), file download and handling, automatic retries of failed requests, concurrency limitation, pagination, request delay, etc.

  1. Node Web Scraper Login
  2. Node Web Scraping Puppeteer

2013-02-10T21:32:50Z

Posted by Miguel Grinberg under Programming, JavaScript.

Web scraping is a technique used to extract data from websites using a computer program that acts as a web browser. The program requests pages from web servers in the same way a web browser does, and it may even simulate a user logging in to obtain access. It downloads the pages containing the desired data and extracts the data out of the HTML code. Once the data is extracted it can be reformatted and presented in a more useful way.

In this article I'm going to show you how to write web scraping scripts in Javascript using Node.js.

Why use web scraping?

Here are a few of examples where web scraping can be useful:

  • You have several bank accounts with different institutions and you want to generate a combined report that includes all your accounts.
  • You want to see data presented by a website in a different format. For example, the website shows a table, but you want to see a chart.
  • A web site presents related data across multiple pages. You want to see all or part of this data combined in a single report.
  • You are an app developer with apps in iTunes and several Android app stores, and you want to have a report of monthly sales across all app stores.

Web scraping can also be used in ways that are dishonest and sometimes even illegal. Harvesting of email addresses for spam purposes, or sniping Ebay auctions are examples of such uses. As a matter of principle I only use web scraping to collect and organize information that is either available to everyone (stock prices, movie showtimes, etc.) or only available to me (personal bank accounts, etc.). I avoid using this technique for profit, I just do it to simplify the task of obtaining information.

In this article I'm going to show you a practical example that implements this technique. Ready? Then let's get started!

Tools for web scraping

In its most basic form, a web scraping script just needs to have a way to download web pages and then search for data in them. All modern languages provide functions to download web pages, or at least someone wrote a library or extension that can do it, so this is not a problem. Locating and isolating data in HTML pages, however, is difficult. An HTML page has content, layout and style elements all intermixed, so a non trivial effort is required to parse and identify the interesting parts of the page.

For example, consider the following HTML page:

Let's say we want to extract the names of the people that appear in the table with id='data' that is in the page. How do we get to those?

Typically the web page will be downloaded into a string, so it would be simple to just search this string for all the occurrences of and extract what comes after that and until the following .

But this could easily make us find incorrect data. The page could have other tables, either before or after the one we want that use the same CSS classes for some of its cells. Or worst, maybe this simple search algoritm works fine for a while, but one day the layout of the page changes so that the old becomes making our search find nothing.

While there is always a risk that a change to the target web page can break a scraping script, it is a good idea to be smart about how items are located in the HTML so that the script does not need to be revised every time the web site changes.

If you have ever written client-side Javascript for the browser using a library like jQuery then you know how the tricky task of locating DOM elements becomes much easier using CSS selectors.

For example, in the browser we could easily extract the names from the above web page as follows:

The CSS selector is what goes inside jQuery's $ function, #data .name in this example, This is saying that we want to locate all the elements that are children of an element with the id data and have a CSS class name. Note that we are not saying anything about the data being in a table in this case. CSS selectors have great flexibility in how you specify search terms for elements, and you can be as specific or vague as you want.

The each function will just call the function given as an argument for all the elements that match the selector, with the this context set to the matching element. If we were to run this in the browser we would see an alert box with the name John, and then another one with the name 'Susan'.

Wouldn't it be nice if we could do something similar outside of the context of a web browser? Well, this is exactly what we are about to do.

Introducing Node.js

Javascript was born as a language to be embedded in web browsers, but thanks to the open source Node.js project we can now write stand-alone scripts in Javascript that can run on a desktop computer or even on a web server.

Manipulating the DOM inside a web browser is something that Javascript and libraries like jQuery do really well so to me it makes a lot of sense to write web scraping scripts in Node.js, since we can use many techniques that we know from DOM manipulation in the client-side code for the web browser.

If you would like to try the examples I will present in the rest of this article then this is the time to download and install Node.js. Installers for Windows, Linux and OS X are available at http://nodejs.org.

Node.js has a large library of packages that simplify different tasks. For web scraping we will use two packages called request and cheerio. The request package is used to download web pages, while cheerio generates a DOM tree and provides a subset of the jQuery function set to manipulate it. To install Node.js packages we use a package manager called npm that is installed with Node.js. This is equivalent to Ruby's gem or Python's easy_install and pip, it simplifies the download and installation of packages.

So let's start by creating a new directory where we will put our web scraping scripts and install these two modules in it:

Node.js modules will be installed in the scraping/node_modules subdirectory and will only be accessible to scripts that are in the scraping directory. It is also possible to install Node.js packages globally, but I prefer to keep things organized by installing modules locally.

Now that we have all the tools installed let's see how we can implement the above scraping example using cheerio. Let's call this script example.js:

The first line imports the cheerio package into the script. The require statement is similar to #include in C/C++, require in Ruby or import in Python.

In the second line we instantiate a DOM for our example HTML, by sending the HTML string to cheerio.load(). The return value is the constructed DOM, which we store in a variable called $ to match how the DOM is accessed in the browser when using jQuery.

Once we have a DOM created we just go about business as if we were using jQuery on the client side. So we use the proper selector and the each iterator to find all the occurrences of the data we want to extract. In the callback function we use the console.log function to write the extracted data. In Node.js console.log writes to the console, so it is handy to dump data to the screen.

Here is how to run the script and what output it produces:

Easy, right? In the following section we'll write a more complex scraping script.

Real world scraping

Let's use web scraping to solve a real problem.

The Tualatin Hills Park and Recreation District (THPRD) is a Beaverton, Oregon organization that offers area residents a number of recreational options, among them swimming. There are eight swimming pools, all in the area, each offering swimming instruction, lap swimming, open swim and a variety of other programs. The problem is that THPRD does not publish a combined schedule for the pools, it only publishes individual schedules for each pool. But the pools are all located close to each other, so many times the choice of pool is less important than what programs are offered at a given time. If I wanted to find the time slots a given program is offered at any of the pools I would need to access eight different web pages and search eight schedules.

For this example we will say that we want to obtain the list of times during the current week when there is an open swim program offered in any of the pools in the district. This requires obtaining the schedule pages for all the pools, locating the open swim entries and listing them.

Before we start, click here to open one of the pool schedules in another browser tab. Feel free to inspect the HTML for the page to familiarize yourself with the structure of the schedule.

The schedule pages for the eight pools have a URL with the following structure:

The id is what selects which pool to show a schedule for. I took the effort to open all the schedules manually to take note of the names of each pool and its corresponding id, since we will need those in the script. We will also use an array with the names of the days of the week. We can scrape these names from the web pages, but since this is information that will never change we can simplify the script by incorporating the data as constants.

Web scraping skeleton

With the above information we can sketch out the structure of our scraping script. Let's call the script thprd.js:

We begin the script importing the two packages that we are going to use and defining the constants for the eight pools and the days of the week.

Then we download the schedule web pages of each of the pools in a loop. For this we construct the URL of each pool schedule and send it to the request function. This is an asynchronous function that takes a callback as its second argument. If you are not very familiar with Javascript this may seem odd, but in this language asynchronous functions are very common. The request() function returns immediately, so it is likely that the eight request() calls will be issued almost simultaneously and will be processed concurrently by background threads.

When a request completes its callback function will be invoked with three arguments, an error code, a response object and the body of the response. Inside the callback we make sure there is no error and then we just send the body of the response into cheerio to create a DOM from it. When we reach this point we are ready to start scraping.

We will look at how to scrape this content later, for now we just print the name of the pool as a placeholder. If you run this first version of our script you'll get a surprise:

What? Why do we get the same pool name eight times? Shouldn't we see all the pool names here?

Javascript scoping

Remember I said above that the request() function is asynchronous? The for loop will do its eight iterations, spawning a background job in each. The loop then ends, leaving the loop variable set to the pool name that was used in the last iteration. When the callback functions are invoked a few seconds later they will all see this value and print it.

I made this mistake on purpose to demonstrate one of the main sources of confusion among developers that are used to traditional languages and are new to Javascript's asynchronous model.

How can we get the correct pool name to be sent to each callback function then?

The solution is to bind the name of the pool to the callback function at the time the callback is created and sent to the request() function, because that is when the pool variable has the correct value.

As we've seen before the callback function will execute some time in the future, after the loop in the main script completed. But the callback function can still access the loop variable even though the callback runs outside of the context of the main script. This is because the scope of Javascript functions is defined at the time the function is created. When we created the callback function the loop variable was in scope, so the variable is accessible to the callback. The url variable is also in the scope, so the callback can also make use of it if necessary, though the same problem will exist with it, its last value will be seen by all callbacks.

So what I'm basically saying is that the scope of a function is determined at the time the function is created, but the values of the variables in the scope are only retrieved at the time the function is called.

We can take advantage of these seemingly odd scoping rules of Javascript to insert any variable into the scope of a callback function. Let's do this with a simple function:

Can you guess what the output of this script will be? The output will be 2, because that's the value of variable a at the time the function stored in variable f is invoked.

To freeze the value of a at the time f is created we need to insert the current value of a into the scope:

Let's analyze this alternative way to create f one step at a time:

We clearly see that the expression enclosed in parenthesis supposedly returns a function, and we invoke that function and pass the current value of a as an argument. This is not a callback function that will execute later, this is executing right away, so the current value of a that is passed into the function is 1.

Here we see a bit more of what's inside the parenthesis. The expression is, in fact, a function that expects one argument. We called that argument a, but we could have used a different name.

In Javascript a construct like the above is called a self-executing function. You could consider this the reverse of a callback function. While a callback function is a function that is created now but runs later, a self-executing function is created and immediately executed. Whatever this function returns will be the result of the whole expression, and will get assigned to f in our example.

Why would you want to use a self-executing function when you can make any code execute directly without enclosing it inside a function? The difference is subtle. By putting code inside a function we are creating a new scope level, and that gives us the chance to insert variables into that scope simply by passing them as arguments to the self-executing function.

We know f should be a function, since later in the script we want to invoke it. So the return value of our self-executing function must be the function that will get assigned to f:

Does it make a bit more sense now? The function that is assigned to f now has a parent function that received a as an argument. That a is a level closer than the original a in the scope of f, so that is the a that the scope of f sees. When you run the modified script you will get a 1 as output.

Here is how the self-executing trick can be applied to our web scraping script:

This is pretty much identical to the simpler example above using the pool variable instead of a. Running this script again gives us the expected result:

Scraping the swimming pool schedules

To be able to scrape the contents of the schedule tables we need to discover how these schedules are structured. In rough terms the schedule table is located inside a page that looks like this:

Inside each of these elements that hold the daily schedules there is a

wrapper around each scheduled event. Here is a simplified structure for a day:

Each element contains a link at the top that we are not interested in, then a sequence of

elements, each containing the information for an event.

One way we can get to these event

elements is with the following selector:

The problem with the above selector, though, is that we will get all the events of all the days in sequence, so we will not know what events happen on which day.

Instead, we can separate the search in two parts. First we locate the element that defines a day, then we search for

elements within it:

The function that we pass to the each() iterator receives the index number of the found element as a first argument. This is handy because for our outer search this is telling us which day we are in. We do not need an index number in the inner search, so there we do not need to use an argument in our function.

Running the script now shows the pool name, then the day of the week and then the text inside the event

, which has the information that we want. The text() function applied to any element of the DOM returns the constant text filtering out any HTML elements, so this gets rid of the and
elements that exist there and just returns the filtered text.

We are now very close. The only remaining problem is that the text we extracted from the

element has a lot of whitespace in it. There is whitespace at the start and end of the text and also in between the event time and event description. We can eliminate the leading and trailing whitespace with trim():

This leaves us with a few lines of whitespace in between the event time and the description. To remove that we can use replace():

Note the regular expression that we use to remove the spaces requires at least two whitespace characters. This is because the event description can contain spaces as well, if we search for two or more spaces we will just find the large whitespace block in the middle and not affect the description.

When we run the script now this is what we get:

And this is just a CSV version of all the pool schedules combined!

We said that for this exercise we were only interested in obtaining the open swim events, so we need to add one more filtering layer to just print the targeted events:

And now we have completed our task. Here is the final version of our web scraping script:

Running the script gives us this output:

From this point on it is easy to continue to massage this data to get it into a format that is useful. My next step would be to sort the list by day and time instead of by pool, but I'll leave that as an exercise to interested readers.

Final words

I hope this introduction to web scraping was useful to you and the example script serves you as a starting point for your own projects.

If you have any questions feel free to leave them below in the comments section.

Thanks!

Miguel

Hello, and thank you for visiting my blog! If you enjoyed this article, please consider supporting my work on this blog on Patreon!

68 comments

  • #1Nano said 2013-02-11T01:18:06Z

  • #2fallanic said 2013-02-11T16:02:43Z

  • #3roshan agarwal said 2013-04-24T17:14:09Z

  • #4Kishore said 2013-06-18T15:53:24Z

  • #5Miguel Grinberg said 2013-06-19T04:08:32Z

  • #6Matt said 2013-06-26T00:53:46Z

  • #7Victor said 2013-07-09T12:01:34Z

  • #8Evis said 2013-07-09T12:21:47Z

  • #9Miguel Grinberg said 2013-07-09T16:19:19Z

  • #10Miguel Grinberg said 2013-07-09T16:20:38Z

  • #11Marko said 2013-07-19T18:46:04Z

  • #12Jinjo Johnson said 2013-07-31T12:10:15Z

  • #13Alexandru Cobuz said 2013-08-05T12:26:39Z

  • #14David Konsumer said 2013-08-12T11:20:53Z

  • #15David Konsumer said 2013-08-12T11:30:30Z

  • #16Max said 2013-09-06T18:37:43Z

  • #17ponk said 2013-10-09T21:59:18Z

  • #18Miguel Grinberg said 2013-10-10T06:06:28Z

  • #19Carlos said 2013-10-10T09:45:57Z

  • #20Trevor said 2013-10-17T18:57:47Z

  • #21dhar said 2013-10-18T07:42:05Z

  • #22Miguel Grinberg said 2013-10-18T14:38:29Z

  • #23Mark Thien said 2013-10-31T16:22:28Z

  • #24Miguel Grinberg said 2013-11-01T15:11:53Z

  • #25sotiris said 2013-11-14T12:18:41Z

Leave a Comment

Web scraping is the easiest way to automate the process of extracting data from any website. Puppeteer scrapers can be used when a normal request module based scraper is unable to extract data from a website.

What is Puppeteer?

Puppeteer is a node.js library that provides a powerful but simple API that allows you to control Google's Chrome or Chromium browser. It also allows you to run Chromium in headless mode (useful for running browsers in servers) and can send and receive requests without the need for a user interface. It works in the background, performing actions as instructed by the API. The developer community for puppeteer is very active and new updates are rolled out regularly. With its full-fledged API, it covers most actions that can be done with a Chrome browser. As of now, it is one of the best options to scrape JavaScript-heavy websites.

What can you do with Puppeteer?

Puppeteer can do almost everything Google Chrome or Chromium can do.

  • Click elements such as buttons, links, and images.
  • Type like a user in input boxes and automate form submissions
  • Navigate pages, click on links, and follow them, go back and forward.
  • Take a timeline trace to find out where the issues are in a website.
  • Carry out automated testing for user interfaces and various front-end apps, directly in a browser.
  • Take screenshots and convert web pages to pdf's.

Web Scraping using Puppeteer

In this tutorial, we'll show you how to create a web scraper for Booking.com to scrape the details of hotel listings in a particular city from the first page of results. We will scrape the hotel name, rating, number of reviews, and price for each hotel listing.

Required Tools

To install Puppeteer you need to first install node.js and write the code to control the browser a.k.a scraper in JavaScript. Node.js runs the script and lets you control the Chrome browser using the puppeteer library. Puppeteer requires at least Node v7.6.0 or greater but for this tutorial, we will go with Node v9.0.0.

Node Web Scraper Login

Installing Node.js

Linux

You can head over to Nodesource and choose the distribution you want. Here are the steps to install node.js in Ubuntu 16.04 :

1. Open a terminal run – sudo apt install curl in case it's not installed.

2. Then run – curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -

3. Once that's done, install node.js by running, sudo apt install nodejs. This will automatically install npm.

Windows and Mac

To install node.js in Windows or Mac, download the package for your OS from Nodes JS's website https://nodejs.org/en/download/

Obtaining the URL

Let's start by obtaining the booking URL. Go to booking.com and search for a city with the inputs for check-in and check-out dates. Click the search button and copy the URL that has been generated. This will be your booking URL.

The gif below shows how to obtain the booking URL for hotels available in Singapore.

After you have completed the installation of node.js we will install the project requirements, which will also download the puppeteer library that will be used in the scraper. Download both the files app.js and package.json from below and place it inside a folder. We have named our folder booking_scraper.

The script below is the scraper. We have named it app.js. This script will scrape the results for a single listing page:

The script below is package.json which contains the libraries needed to run the scraper

Installing the project dependencies, which will also install Puppeteer.

  • Install the project directory and make sure it has the package.json file inside it.
  • Use npm install to install the dependencies. This will also install puppeteer and download the Chromium browser to run the puppeteer code. By default, puppeteer works with the Chromium browser but you can also use Chrome.

Now copy the URL that was generated from booking.com and paste it in the bookingUrl variable in the provided space (line 3 in app.js). You should make sure the URL is inserted within quotes otherwise, the script will not work.

Running the Puppeteer Scraper

To run a node.js program you need to type:

For this script, it will be:

Turning off Headless Mode

The script above runs the browser in headless mode. To turn the headless mode off, just modify this line

Tutorial

const browser = await puppeteer.launch({ headless: true }); to const browser = await puppeteer.launch({ headless: false});

You should then be able to see what is going on.

The program will run and fetch all the hotel details and display it in the terminal. If you want to scrape another page you can change the URL in the bookingUrl variable and run the program again.

Here is how the output for hotels in Singapore will look like:

Debug Using Screenshots

In case you are stuck, you could always try taking a screenshot of the webpage and see if you are being blocked or if the structure of the website has changed. Here is something to get started

Learn More:

Speed Up Puppeteer Web Scraping

Loading a web page with images could slow down web scraping due to reduced page speed. To speed up browsing and data scraping, disabling CSS and images could help with that while also reducing bandwidth consumption.

Learn More:

Known Limitations

When using Puppeteer you should keep some things in mind. Since Puppeteer opens up a browser it takes a lot of memory and CPU to run in comparison to script-based approaches like Selenium for JavaScript.

Using Selenium as a Headless Browser: How to Prevent getting Blacklisted while scraping

If you want to scrape a simple website that does not use JavaScript-heavy frontends, use a simple Python Scraper. There are plenty of open source javascript web scraping tools you can try such as Apidfy SDK, Nodecrawler, Playwright, and more.

You will find Puppeteer to be a bit slow as it only opens one page at a time and starts scraping a page once it has been fully loaded. Pupetteer scripts can only be written in JavaScript and do not support any other language.

If you need professional help with scraping complex websites, contact us by filling up the form below.

Node Web Scraping Puppeteer

We can help with your data or automation needs

Turn the Internet into meaningful, structured and usable data



Disclaimer:Any code provided in our tutorials is for illustration and learning purposes only. We are not responsible for how it is used and assume no liability for any detrimental usage of the source code. The mere presence of this code on our site does not imply that we encourage scraping or scrape the websites referenced in the code and accompanying tutorial. The tutorials only help illustrate the technique of programming web scrapers for popular internet websites. We are not obligated to provide any support for the code, however, if you add your questions in the comments section, we may periodically address them.



broken image