TL;DR
- Client-side
jQueryscraping hits CORS; do it server-side instead. - In Node.js, use
jsdom+jQuery: fetch with$.get, select elements, extract fields. - Add pagination (next page) and export data to
CSV/JSONviafs. - For scale / anti-bot reliability, the post suggests Scrapingdog.
In this tutorial, youβll learn how to use jQuery for web scraping. While jQuery is traditionally used for client-side tasks, this guide will also demonstrate its use in server-side scraping, including a complete example of scraping data from the Hacker News website.
What Is Client-Side Scraping?
Client-side scraping involves fetching and processing web content directly from a browser. Or executing JavaScript code within the browser to access and modify the DOM. Typically, this is done using JavaScript, including libraries like jQuery.
Client-side scraping can be achieved by either using a public API or parsing the HTML content of the page. Since most websites do not offer public APIs, you will often need to opt for the latter: downloading HTML documents and extracting data by parsing the content.
You can perform client-side scraping by either calling a public API or parsing the pageβs HTML content. Since most websites donβt provide public APIs, you will often need to resort to the latter: downloading HTML documents and extracting data by parsing the content.
Now, letβs explore how to perform client-side scraping with jQuery!
Scraping with jQuery on the Client Side
First, you need to fetch the HTML content of the target page. We will retrieve the HTML content from Hacker News. To do this, you can utilize the get() method in jQuery. The get() method in jQuery performs an HTTP GET request and provides the serverβs response through a callback function. Hereβs how to use it:
1$.get("https://news.ycombinator.com/", function(html) {2 console.log(html);3});Now, letβs write some HTML code and add a reference to the jQuery library using the <script> tag.
1!DOCTYPE html>2< html lang="en">3 < head>4 < meta charset="UTF-8" />5 < meta name="viewport" content="width=device-width, initial-scale=1.0" />6 7 < title>Web Scraping with jQuery< /title>8 < script src="https://code.jquery.com/jquery-3.6.0.min.js">< /script>9 < /head >10 11 < body >12 JavaScript code 13 < /body >14< /html >Add the following JavaScript code to fetch and log HTML content from a URL using jQuery. Paste this code inside <body> the tag of your HTML document:
1< script >2 $(document).ready(function() {3 $.get("https://news.ycombinator.com/", function(html) {4 console.log(html);5 });6 }); <7/script>Now, your full HTML should look like this:
1< !DOCTYPE html>2< html lang="en">3 < head>4 < meta charset="UTF-8" />5 < meta name="viewport" content="width=device-width, initial-scale=1.0" />6 < title>Web Scraping with jQuery7 < script src="https://code.jquery.com/jquery-3.6.0.min.js">8 < /head>9 10 < body>11 < script>12 $(document).ready(function () {13 $.get("https://news.ycombinator.com/", function (html) {14 console.log(html);15 });16 });17 < /script>18 < /body>19< /html>To see the output, open the HTML file in your web browser and open the browserβs console:
The output indicates that our code wonβt work due to a βNo βAccess-Control-Allow-Originβ header is present on the requested resourceβ error, which is related to Cross-Origin Resource Sharing (CORS).
Why does this error occur?
When your browser makes a request to a different domain, it includes an Origin HTTP header for security reasons. The server must respond with an Access-Control-Allow-Origin header to allow access. If this header is missing or incorrect, the browser will block the response to prevent unauthorized access.
To follow CORS rules, servers have security measures in place to block unauthorized requests and allow trusted ones. If the server doesnβt allow requests from your domain, youβll encounter a CORS error. These CORS restrictions mean that you cannot use JavaScript to scrape web content from a different domain on the client side.
Okay, so how do we scrape a website using jQuery? To bypass the CORS restriction, we can shift our scraping logic to the server side. This allows us to make requests from the server to any website without being blocked by the browserβs security policies. Letβs explore this in more detail in the next section.
Why Choose jQuery for Web Scraping?
jQuery is one of the most widely adopted libraries for DOM manipulation. It provides various selectors to accurately target specific elements on the page, making it easy to extract text content, attributes, or other data from selected elements. For dynamic content, jQueryβs AJAX methods can retrieve additional data without having to reload the entire page.
Scraping with jQuery on the Server Side
To use jQuery in a Node.js environment, you can leverage the jsdom NPM package. By combining jQuery with jsdom, you can effectively query and manipulate the DOM on the server side, just as you would do in a browser.
Setting Up the Environment
Before we start working on the code, letβs make sure we have the right environment set up. Youβll need to have Node.js installed on your computer. If you havenβt installed it yet, you can download it from the official Node.js website.
Once Node.js is installed, follow these steps to set up your project:
1. Initialize a New Node.js Project Open the terminal, create a new directory for your project, navigate to this directory, and run the npm init -y command to initialize a new Node.js project.
1 mkdir jquery-scraper2cd jquery-scraper3npm init -yThis will create a package.json file.
2. Install jsdom and jQuery Install the required packages, jsdom and jquery, using npm:
1npm install jsdom jqueryNow you can use jQuery in Node.js for scraping! Letβs put it to use by building a scraper to extract data from the Hacker News website.
Hereβs what our target page looks like:
Fetching HTML with jQuery get()
The first step in any web scraping process is to fetch the HTML of the target web page. The jQuery get() method loads data from the server using an HTTP GET request.
Letβs write a Node.js script that uses the jsdom library to create a simulated browser environment and the jquery library to perform an HTTP GET request to fetch the HTML content of the Hacker News:
1const { JSDOM } = require("jsdom");2 3// Initialize JSDOM on the "https://news.ycombinator.com/" page to avoid CORS problems4const { window } = new JSDOM("", {5 url: "https://news.ycombinator.com/",6});7 8const $ = require("jquery")(window);9 10$.get("https://news.ycombinator.com/", function (html) {11 console.log(html);12});In the code, we first start by importing the JSDOM class from the jsdom library. jsdom allows us to create a simulated browser environment within Node.js. Then, we create a jsdom instance by providing the URLs we want to simulate. The result is stored in a new variable called window.
This window object represents the simulated browser window, similar to the window object you would have access to in a real browser.
Next, we import the jQuery library and pass the window object. This integrates jQuery into the simulated environment, allowing us to utilize its functionalities.
Finally, we use jQueryβs get() method to fetch the HTML content from the specified URL.
Run the code and youβll see the following output:
Thatβs the HTML content of the Hacker News!
Extracting Data with jQuery find()
Given a jQuery object representing a set of DOM elements, theΒ .find()Β method allows you to search for descendant elements within the DOM tree and create a new jQuery object containing the matching elements.
TheΒ .find()Β andΒ .children()Β methods are similar, butΒ .children()Β only select elements that are direct children of the original set. As shown in the image below, theΒ CSSΒ selectorΒ for identifying news elements isΒ tr.athing. You can use theΒ .find()Β method to retrieve a list of these HTML elements.
The jQueryΒ .find()Β method returns a set of DOM elements that match the CSS selector, jQuery object, or HTML element passed as the parameter.
In our case, we use jQuery to find all elements with the classΒ tr.athingΒ within the retrieved HTML. These elements represent individual news items on the Hacker News website. Take a look at the below code snippet:
1 $.get("https://news.ycombinator.com/", function(html) {2 const newsItemElements = $(html).find("tr.athing");3});Next, we iterate over each news item element to individually extract its rank, title, and URL. We use jQueryβs .each() method to process these elements one by one.
1$.get("https://news.ycombinator.com/", function(html) {2 const newsHTMLElements = $(html).find("tr.athing");3 4 newsHTMLElements.each((i, newsItemElement) => {5 // Code to extract data from each news item will go here6 });7});For each news item, we locate the element containing the rank. This is a span element with the class βrankβ. The .find() method is used to locate the span, and the .text() method extracts its content.
1$.get("https://news.ycombinator.com/", function(html) {2 const newsHTMLElements = $(html).find("tr.athing");3 4 newsHTMLElements.each((i, newsItemElement) => {5 const rank = $(newsItemElement).find("span.rank").text().replace('.', '');6 });7});Finally, we find the element containing the title and URL of the news item. This is an anchor (<a>) element within aΒ spanΒ with the classΒ βtitlelineβ. We extract the text of the title directly from the anchor and the URL from itsΒ hrefΒ attribute.
1$.get("https://news.ycombinator.com/", function(html) {2 const newsHTMLElements = $(html).find("tr.athing");3 4 const newsItems = [];5 newsHTMLElements.each((i, newsItemElement) => {6 const rank = $(newsItemElement).find("span.rank").text().replace('.', '');7 const titleElement = $(newsItemElement).find("span.titleline > a");8 const title = titleElement.text();9 const url = titleElement.attr("href");10 11 // Store the extracted data12 newsItems.push({13 rank,14 title,15 url16 });17 });18 19 // Log the extracted data20 console.log(newsItems);21});After running the code, you should see the extracted news items logged in the console, like this:
You can extract additional details for each news item, such as theΒ author name,Β points, andΒ post time, from the next trΒ element after the currentΒ tr.athingΒ element. This subsequentΒ trΒ element has aΒ tdΒ with the classΒ βsubtextβΒ where the data is present.
Hereβs the code to extract additional details:
1$.get("https://news.ycombinator.com/", function(html) {2 const newsHTMLElements = $(html).find("tr.athing");3 4 const newsItems = [];5 newsHTMLElements.each((i, newsItemElement) => {6 const rank = $(newsItemElement).find("span.rank").text().replace('.', '');7 const titleElement = $(newsItemElement).find("span.titleline > a");8 const title = titleElement.text().trim();9 const url = titleElement.attr("href");10 11 // Extract additional details12 const subtext = $(newsItemElement).next().find(".subtext");13 const author = subtext.find("a.hnuser").text();14 const points = subtext.find("span.score").text();15 const postTime = subtext.find("span.age").text();16 17 // Store the extracted data18 newsItems.push({19 rank,20 title,21 url,22 author,23 points,24 postTime25 });26 });27 28 console.log(newsItems);29});We use theΒ next().find(".subtext")Β method, which selects theΒ tr element immediately after the current tr.athingΒ element and then finds the element with the classΒ subtext.
To extract additional details about each news item, you need to locate the subtextΒ class, which contains information such as the authorβs name, points, and post time.
TheΒ subtextΒ class is located in the nextΒ trΒ element following the currentΒ tr.athingΒ element.
You can use theΒ next().find(".subtext")Β method, which selects theΒ tr element immediately after the current tr.athingΒ element and then finds the element with the classΒ subtext.
Once you have theΒ subtext element, you can extract desired data from it. To get the authorβs name, you can use subtext.find("a.hnuser"). Similarly, you can find the number of points and the post time.
Run the code and you will see the extracted news items logged in the console, like this:
Nice! Weβve successfully extracted all the desired data from the first page of Hacker News. However, this is not enough, as we need to fetch data from multiple pages. Letβs explore how to scrape multiple pages by handling pagination.
Handling Pagination to Scrape Multiple Pages
Hacker News only displays a limited number of items on each page. Pagination allows you to navigate through multiple pages to collect all available news articles, not just those on the first page.
On the Hacker News website, if you scroll to the bottom of the page, you will see a βmoreβ button. Clicking this button loads additional news items and updates the URL.
For example, if the initial URL is:
Clicking βmoreβ would change the URL to:
https://news.ycombinator.com/?p=2
With each subsequent click, the page number (p) will increase by 1 until there are no more pages to display.
Take a look at the below image:
How will we handle pagination? Weβll handle pagination by checking for a βnext pageβ link. If found, weβll fetch the content of the next page. See the code snippet below:
1// Check for next page link2const nextPageLink = $(html).find("a.morelink").attr("href");3if (nextPageLink) {4 currentPage += 1;5 fetchPage(currentPage);6} else {7 console.log(newsItems);8}The code shows that, if the next page link is present, the script increments theΒ currentPageΒ variable by 1. It then recursively calls theΒ fetchPageΒ function with the updatedΒ currentPageΒ value to fetch and process the next page.
If no βnext pageβ link is found, it means there are no more pages to fetch. At this point, the script stops fetching further pages and logs the collected news items to the console.
Hereβs the complete code:
1const { JSDOM } = require("jsdom");2 3// Initialize JSDOM with the target URL4const { window } = new JSDOM("", {5 url: "https://news.ycombinator.com/",6});7 8const $ = require("jquery")(window);9 10const baseUrl = "https://news.ycombinator.com/";11let currentPage = 1;12const newsItems = [];13 14// Function to fetch a page of news items15function fetchPage(page) {16 $.get(`${baseUrl}?p=${page}`, function (html) {17 const newsHTMLElements = $(html).find("tr.athing");18 19 newsHTMLElements.each((i, newsItemElement) => {20 const rank = $(newsItemElement).find("span.rank").text().replace('.', '');21 const titleElement = $(newsItemElement).find("span.titleline > a");22 const title = titleElement.text().trim();23 const url = titleElement.attr("href");24 25 // Extract additional details26 const subtext = $(newsItemElement).next().find(".subtext");27 const author = subtext.find("a.hnuser").text();28 const points = subtext.find("span.score").text();29 const postTime = subtext.find("span.age").text();30 31 // Store the extracted data32 newsItems.push({33 rank,34 title,35 url,36 author,37 points,38 postTime39 });40 });41 42 // Check for next page link43 const nextPageLink = $(html).find("a.morelink").attr("href");44 if (nextPageLink) {45 currentPage += 1;46 fetchPage(currentPage);47 } else {48 console.log(newsItems);49 }50 }).fail(function (error) {51 console.error(`Error fetching page ${page}:`, error);52 });53}54 55// Start fetching from the first page56fetchPage(currentPage);When the code runs, this would print:
Great! Weβve successfully extracted data from multiple pages using the pagination technique.
Exporting Scraped Data
Scraping data from the web is often the initial step in a larger workflow. The scraped data can be used for multiple purposes. To make the most of the gathered data, it is crucial to convert it into a format that is easy to read, analyze, and manipulate. Some of the common formats include CSV and JSON.
Letβs start by discussing how to store the scraped data in a CSV file.
To export the scraped data to a CSV file in Node.js, you can utilize theΒ fsΒ (File System) module to write the data. Hereβs how to modify your script:
Import the necessaryΒ
fsΒ module.Convert theΒ
newsItemsΒ array into CSV format.Write the CSV data to a file.
Hereβs the full code:
1const { JSDOM } = require("jsdom");2const fs = require("fs");3 4const { window } = new JSDOM("", {5 url: "https://news.ycombinator.com/",6});7const $ = require("jquery")(window);8 9const baseUrl = "https://news.ycombinator.com/";10let currentPage = 1;11const newsItems = [];12 13// Function to fetch a page of news items14function fetchPage(page) {15 $.get(`${baseUrl}?p=${page}`, function(html) {16 const newsHTMLElements = $(html).find("tr.athing");17 18 newsHTMLElements.each((i, newsItemElement) => {19 const rank = $(newsItemElement).find("span.rank").text().replace('.', '');20 const titleElement = $(newsItemElement).find("span.titleline > a");21 const title = titleElement.text().trim();22 const url = titleElement.attr("href");23 24 // Extract additional details25 const subtext = $(newsItemElement).next().find(".subtext");26 const author = subtext.find("a.hnuser").text();27 const points = subtext.find("span.score").text();28 const postTime = subtext.find("span.age").text();29 30 // Store the extracted data31 newsItems.push({32 rank,33 title,34 url,35 author,36 points,37 postTime38 });39 });40 41 // Check for next page link42 const nextPageLink = $(html).find("a.morelink").attr("href");43 if (nextPageLink) {44 currentPage += 1;45 fetchPage(currentPage);46 } else {47 exportToCSV(newsItems);48 }49 }).fail(function(error) {50 console.error(`Error fetching page ${page}:`, error);51 });52}53 54// Function to export the data to a CSV file55function exportToCSV(data) {56 const csvHeaders = ['Rank', 'Title', 'URL', 'Author', 'Points', 'Post Time'];57 58 const csvRows = [59 csvHeaders.join(','),60 ...data.map(item => [61 item.rank,62 `"${item.title}"`,63 item.url,64 item.author,65 item.points,66 item.postTime67 ].join(','))68 ].join('\n'); // Join all rows with newline characters69 70 // Write the CSV string to a file71 fs.writeFile("newsItems.csv", csvRows, (err) => {72 if (err) {73 console.error("Error writing to CSV file:", err);74 } else {75 console.log("Data successfully exported to newsItems.csv");76 }77 });78}79 80fetchPage(currentPage);Run the code to create a new CSV file in your directory.
Great! The data looks clean and readable in a CSV file. Letβs explore storing scraped data in a JSON file. To save the scraped data to a JSON file, you need to create a function that converts the newsItems array into a JSON format and then saves it to a file. In the function, youβll need to convert the newsItems array into a JSON string by using JSON.stringify, and then write the JSON string to a file named newsItems.json using the fs.writeFile method from the fs module of the Node.js. Hereβs the full code:
1const { JSDOM } = require("jsdom");2const fs = require("fs");3 4const { window } = new JSDOM("", {5 url: "https://news.ycombinator.com/",6});7const $ = require("jquery")(window);8 9const baseUrl = "https://news.ycombinator.com/";10let currentPage = 1;11const newsItems = [];12 13// Function to fetch a page of news items14function fetchPage(page) {15 $.get(`${baseUrl}?p=${page}`, function (html) {16 const newsHTMLElements = $(html).find("tr.athing");17 18 newsHTMLElements.each((i, newsItemElement) => {19 const rank = $(newsItemElement).find("span.rank").text().replace('.', '');20 const titleElement = $(newsItemElement).find("span.titleline > a");21 const title = titleElement.text().trim();22 const url = titleElement.attr("href");23 24 // Extract additional details25 const subtext = $(newsItemElement).next().find(".subtext");26 const author = subtext.find("a.hnuser").text();27 const points = subtext.find("span.score").text();28 const postTime = subtext.find("span.age").text();29 30 // Store the extracted data31 newsItems.push({ rank, title, url, author, points, postTime });32 });33 34 // Check for next page link35 const nextPageLink = $(html).find("a.morelink").attr("href");36 if (nextPageLink) {37 currentPage += 1;38 fetchPage(currentPage);39 } else {40 exportToJSON(newsItems);41 }42 }).fail(function (error) {43 console.error(`Error fetching page ${page}:`, error);44 });45}46 47// Function to export data to a JSON file48function exportToJSON(data) {49 const jsonData = JSON.stringify(data, null, 2);50 51 // Write the JSON string to a file52 fs.writeFile("newsItems.json", jsonData, (err) => {53 if (err) {54 console.error("Error writing to JSON file:", err);55 } else {56 console.log("Data successfully exported to newsItems.json");57 }58 });59}60 61// Start fetching from the first page62fetchPage(currentPage);Run the code to create a new JSON file in your directory.
Conclusion
We tried to make this tutorial beginner-friendly. However, there are limitations when doing it on the client side. To understand more, you can read this full tutorial on Web scraping with JavaScript & NodeJs.
Also, if you are looking for a scraping solution without any limitations you can try Scrapingdog web scraping API.