序章
Webスクレイピングは、Webからのデータ収集を自動化するプロセスです。 このプロセスは通常、Webを自動的にサーフィンし、選択したページからデータを取得する「クローラー」をデプロイします。 データをスクレイピングする理由はたくさんあります。 主に、手動のデータ収集プロセスを排除することにより、データ収集をはるかに高速化します。 スクレイピングは、データ収集が必要または必要であるが、WebサイトがAPIを提供していない場合の解決策でもあります。
このチュートリアルでは、Node.jsとPuppeteerを使用してWebスクレイピングアプリケーションを構築します。 進行するにつれて、アプリは複雑になります。 まず、 Chromium を開き、ウェブスクレイピングサンドボックスとして設計された特別なウェブサイトbooks.toscrape.comを読み込むようにアプリをコーディングします。 次の2つのステップでは、books.toscrapeの1ページにあるすべての本をスクレイプしてから、複数のページにまたがるすべての本をスクレイプします。 残りの手順では、本のカテゴリでスクレイピングをフィルタリングしてから、データをJSONファイルとして保存します。
警告:ウェブスクレイピングの倫理と合法性は非常に複雑で、常に進化しています。 また、場所、データの場所、および問題のWebサイトによっても異なります。 このチュートリアルでは、スクレーパーアプリケーションをテストするために特別に設計された特別なWebサイトbooks.toscrape.comをスクレイピングします。 他のドメインをスクレイピングすることは、このチュートリアルの範囲外です。
前提条件
- 開発マシンにインストールされているNode.js。 このチュートリアルは、Node.jsバージョン12.18.3およびnpmバージョン6.14.6でテストされました。 このガイドに従ってmacOSまたはUbuntu18.04にNode.jsをインストールするか、このガイドに従ってPPAを使用してUbuntu18.04にNode.jsをインストールできます。
ステップ1—Webスクレイパーを設定する
Node.jsをインストールすると、Webスクレイパーのセットアップを開始できます。 まず、プロジェクトのルートディレクトリを作成してから、必要な依存関係をインストールします。 このチュートリアルに必要な依存関係は1つだけで、Node.jsのデフォルトのパッケージマネージャーnpmを使用してインストールします。 npmにはNode.jsがプリインストールされているため、インストールする必要はありません。
このプロジェクトのフォルダーを作成してから、次の場所に移動します。
- mkdir book-scraper
- cd book-scraper
以降のすべてのコマンドは、このディレクトリから実行します。
npmまたはノードパッケージマネージャーを使用して1つのパッケージをインストールする必要があります。 最初にnpmを初期化して、 packages.json
ファイル。プロジェクトの依存関係とメタデータを管理します。
プロジェクトのnpmを初期化します。
- npm init
npmは一連のプロンプトを表示します。 押すことができます ENTER
すべてのプロンプトに追加するか、パーソナライズされた説明を追加できます。 必ず押してください ENTER
プロンプトが表示されたら、デフォルト値のままにします entry point:
と test command:
. または、合格することもできます y
フラグを立てる npm
—npm init -y
—そしてそれはあなたのためにすべてのデフォルト値を提出します。
出力は次のようになります。
Output{
"name": "sammy_scraper",
"version": "1.0.0",
"description": "a web scraper",
"main": "index.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "sammy the shark",
"license": "ISC"
}
Is this OK? (yes) yes
タイプ yes
を押して ENTER
. npmはこの出力をあなたの package.json
ファイル。
次に、npmを使用してPuppeteerをインストールします。
- npm install --save puppeteer
このコマンドは、Puppeteerと、PuppeteerチームがAPIで動作することがわかっているバージョンのChromiumの両方をインストールします。
Linuxマシンでは、Puppeteerにいくつかの追加の依存関係が必要になる場合があります。
Ubuntu 18.04を使用している場合は、Puppeteerのトラブルシューティングドキュメントの「ChromeヘッドレスがUNIXで起動しない」セクション内の「Debian依存関係」ドロップダウンを確認してください。 次のコマンドを使用して、不足している依存関係を見つけることができます。
- ldd chrome | grep not
npm、Puppeteer、およびその他の依存関係がインストールされている場合、 package.json
コーディングを開始する前に、ファイルに最後の構成が1つ必要です。 このチュートリアルでは、コマンドラインからアプリを起動します。 npm run start
. これに関する情報を追加する必要があります start
スクリプトに package.json
. 具体的には、下に1行追加する必要があります scripts
あなたに関する指令 start
指図。
お好みのテキストエディタでファイルを開きます。
- nano package.json
を見つける scripts:
セクションを作成し、次の構成を追加します。 の最後にコンマを置くことを忘れないでください test
スクリプト行、またはファイルが正しく解析されません。
Output{
. . .
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1",
"start": "node index.js"
},
. . .
"dependencies": {
"puppeteer": "^5.2.1"
}
}
また、次のことに気付くでしょう puppeteer
下に表示されます dependencies
ファイルの終わり近く。 君の package.json
ファイルはこれ以上のリビジョンを必要としません。 変更を保存して、エディターを閉じます。
これで、スクレーパーのコーディングを開始する準備が整いました。 次のステップでは、ブラウザインスタンスを設定し、スクレーパーの基本機能をテストします。
ステップ2—ブラウザインスタンスの設定
従来のブラウザを開くと、ボタンをクリックしたり、マウスでナビゲートしたり、入力したり、開発ツールを開いたりすることができます。 Chromiumのようなヘッドレスブラウザを使用すると、これらと同じことができますが、プログラムによって、ユーザーインターフェイスなしで実行できます。 このステップでは、スクレーパーのブラウザーインスタンスを設定します。 アプリケーションを起動すると、Chromiumが自動的に開き、books.toscrape.comに移動します。 これらの最初のアクションは、プログラムの基礎を形成します。
Webスクレイパーには4つ必要です .js
ファイル: browser.js
, index,js
, pageController.js
、 と pageScraper.js
. このステップでは、4つのファイルすべてを作成し、プログラムが高度化するにつれてそれらを継続的に更新します。 皮切りに browser.js
; このファイルには、ブラウザを起動するスクリプトが含まれています。
プロジェクトのルートディレクトリから、作成して開きます browser.js
テキストエディタの場合:
- nano browser.js
まず、あなたは require
Puppeteerを作成してから、 async
と呼ばれる関数 startBrowser()
. この関数はブラウザを起動し、そのインスタンスを返します。 次のコードを追加します。
const puppeteer = require('puppeteer');
async function startBrowser(){
let browser;
try {
console.log("Opening the browser......");
browser = await puppeteer.launch({
headless: false,
args: ["--disable-setuid-sandbox"],
'ignoreHTTPSErrors': true
});
} catch (err) {
console.log("Could not create a browser instance => : ", err);
}
return browser;
}
module.exports = {
startBrowser
};
Puppeteerには、ブラウザのインスタンスを起動する.launch()メソッドがあります。 このメソッドはPromiseを返すため、.thenまたはawaitブロックを使用してPromiseが解決されることを確認する必要があります。
使用しています await
Promiseが確実に解決されるようにするには、このインスタンスを try-catchコードブロックでラップしてから、ブラウザのインスタンスを返します。
に注意してください .launch()
メソッドは、いくつかの値を持つJSONパラメーターを取ります。
- ヘッドレス–
false
これは、ブラウザがインターフェイスで実行されるため、スクリプトの実行を監視できることを意味します。true
ブラウザがヘッドレスモードで実行されることを意味します。 ただし、スクレーパーをクラウドにデプロイする場合は、headless
戻るtrue
. ほとんどの仮想マシンはヘッドレスであり、ユーザーインターフェイスが含まれていないため、ブラウザはヘッドレスモードでのみ実行できます。 Puppeteerにはheadful
モードですが、テスト目的でのみ使用する必要があります。 - ignoreHTTPSErrors–
true
安全なHTTPSプロトコルでホストされていないWebサイトにアクセスし、HTTPS関連のエラーを無視することができます。
ファイルを保存して閉じます。
次に、2番目を作成します .js
ファイル、 index.js
:
- nano index.js
ここであなたは require
browser.js
と pageController.js
. 次に、 startBrowser()
関数を実行し、作成されたブラウザインスタンスをページコントローラに渡します。ページコントローラはそのアクションを指示します。 次のコードを追加します。
const browserObject = require('./browser');
const scraperController = require('./pageController');
//Start the browser and create a browser instance
let browserInstance = browserObject.startBrowser();
// Pass the browser instance to the scraper controller
scraperController(browserInstance)
ファイルを保存して閉じます。
3番目を作成します .js
ファイル、 pageController.js
:
- nano pageController.js
pageController.js
スクレイピングプロセスを制御します。 ブラウザインスタンスを使用して、 pageScraper.js
ファイル。これは、すべてのスクレイピングスクリプトが実行される場所です。 最終的には、これを使用して、スクレイプする本のカテゴリを指定します。 ただし、今のところ、Chromiumを開いてWebページに移動できることを確認する必要があります。
const pageScraper = require('./pageScraper');
async function scrapeAll(browserInstance){
let browser;
try{
browser = await browserInstance;
await pageScraper.scraper(browser);
}
catch(err){
console.log("Could not resolve the browser instance => ", err);
}
}
module.exports = (browserInstance) => scrapeAll(browserInstance)
このコードは、ブラウザインスタンスを取り込んで、それをと呼ばれる関数に渡す関数をエクスポートします。 scrapeAll()
. 次に、この関数はこのインスタンスをに渡します pageScraper.scraper()
ページをスクレイプするためにそれを使用する引数として。
ファイルを保存して閉じます。
最後に、最後を作成します .js
ファイル、 pageScraper.js
:
- nano pageScraper.js
ここでは、オブジェクトリテラルを作成します url
プロパティと scraper()
方法。 The url
は、スクレイピングするWebページのWeb URLですが、 scraper()
メソッドには、実際のスクレイピングを実行するコードが含まれていますが、この段階ではURLに移動するだけです。 次のコードを追加します。
const scraperObject = {
url: 'http://books.toscrape.com',
async scraper(browser){
let page = await browser.newPage();
console.log(`Navigating to ${this.url}...`);
await page.goto(this.url);
}
}
module.exports = scraperObject;
Puppeteerには、ブラウザに新しいページインスタンスを作成するnewPage()メソッドがあり、これらのページインスタンスはかなりの数のことを実行できます。 私たちの中で scraper()
メソッドでは、ページインスタンスを作成し、 page.goto()メソッドを使用してbooks.toscrape.comホームページに移動しました。
ファイルを保存して閉じます。
これで、プログラムのファイル構造が完成しました。 プロジェクトのディレクトリツリーの最初のレベルは次のようになります。
Output.
├── browser.js
├── index.js
├── node_modules
├── package-lock.json
├── package.json
├── pageController.js
└── pageScraper.js
コマンドを実行します npm run start
スクレーパーアプリケーションの実行を監視します。
- npm run start
Chromiumブラウザインスタンスが自動的に開き、ブラウザで新しいページが開き、books.toscrape.comに移動します。
In this step, you created a Puppeteer application that opened Chromium and loaded the homepage for a dummy online bookstore—books.toscrape.com. 次のステップでは、そのホームページ上のすべての本のデータをスクレイピングします。
ステップ3—単一のページからデータをスクレイピングする
スクレーパーアプリケーションに機能を追加する前に、お好みのWebブラウザーを開き、ブックに手動で移動してホームページをスクレイプします。 サイトを閲覧して、データがどのように構造化されているかを理解してください。
左側にカテゴリセクションがあり、右側に本が表示されています。 書籍をクリックすると、ブラウザはその特定の書籍に関連する情報を表示する新しいURLに移動します。
このステップでは、この動作を複製しますが、コードを使用します。 あなたはウェブサイトをナビゲートしてそのデータを消費するビジネスを自動化します。
まず、ブラウザ内の開発ツールを使用してホームページのソースコードを調べると、ページに各書籍のデータが一覧表示されていることがわかります。 section
鬼ごっこ。 内部 section
すべての本の下にタグを付ける list
(li
)タグを付けると、本の専用ページへのリンク、価格、在庫状況がここに表示されます。
これらの本のURLをスクレイピングし、在庫のある本をフィルタリングし、個々の本のページに移動して、その本のデータをスクレイピングします。
再開します pageScraper.js
ファイル:
- nano pageScraper.js
次の強調表示されたコンテンツを追加します。 別のネストします await
内部のブロック await page.goto(this.url);
:
const scraperObject = {
url: 'http://books.toscrape.com',
async scraper(browser){
let page = await browser.newPage();
console.log(`Navigating to ${this.url}...`);
// Navigate to the selected page
await page.goto(this.url);
// Wait for the required DOM to be rendered
await page.waitForSelector('.page_inner');
// Get the link to all the required books
let urls = await page.$$eval('section ol > li', links => {
// Make sure the book to be scraped is in stock
links = links.filter(link => link.querySelector('.instock.availability > i').textContent !== "In stock")
// Extract the links from the data
links = links.map(el => el.querySelector('h3 > a').href)
return links;
});
console.log(urls);
}
}
module.exports = scraperObject;
このコードブロックでは、 page.waitForSelector()メソッドを呼び出しました。 これは、すべての本関連情報を含むdivがDOMにレンダリングされるのを待ってから、ページを呼び出しました。$$ eval()メソッド。 このメソッドは、セレクターでURL要素を取得します section ol li
(常に文字列または数値のみを返すようにしてください page.$eval()
と page.$$eval()
メソッド)。
すべての本には2つのステータスがあります。 本はどちらかです In Stock
また Out of stock
. あなたはある本だけをこすりたい In Stock
. なぜなら page.$$eval()
一致するすべての要素の配列を返します。この配列をフィルタリングして、在庫のある本のみを操作していることを確認します。 クラスを検索して評価することでこれを行いました .instock.availability
. 次に、 href
ブックリンクのプロパティを取得し、メソッドから返しました。
ファイルを保存して閉じます。
アプリケーションを再実行します。
- npm run start
ブラウザが開き、Webページに移動し、タスクが完了すると閉じます。 次に、コンソールを確認します。 削り取られたすべてのURLが含まれます。
Output> book-scraper@1.0.0 start /Users/sammy/book-scraper
> node index.js
Opening the browser......
Navigating to http://books.toscrape.com...
[
'http://books.toscrape.com/catalogue/a-light-in-the-attic_1000/index.html',
'http://books.toscrape.com/catalogue/tipping-the-velvet_999/index.html',
'http://books.toscrape.com/catalogue/soumission_998/index.html',
'http://books.toscrape.com/catalogue/sharp-objects_997/index.html',
'http://books.toscrape.com/catalogue/sapiens-a-brief-history-of-humankind_996/index.html',
'http://books.toscrape.com/catalogue/the-requiem-red_995/index.html',
'http://books.toscrape.com/catalogue/the-dirty-little-secrets-of-getting-your-dream-job_994/index.html',
'http://books.toscrape.com/catalogue/the-coming-woman-a-novel-based-on-the-life-of-the-infamous-feminist-victoria-woodhull_993/index.html',
'http://books.toscrape.com/catalogue/the-boys-in-the-boat-nine-americans-and-their-epic-quest-for-gold-at-the-1936-berlin-olympics_992/index.html',
'http://books.toscrape.com/catalogue/the-black-maria_991/index.html',
'http://books.toscrape.com/catalogue/starving-hearts-triangular-trade-trilogy-1_990/index.html',
'http://books.toscrape.com/catalogue/shakespeares-sonnets_989/index.html',
'http://books.toscrape.com/catalogue/set-me-free_988/index.html',
'http://books.toscrape.com/catalogue/scott-pilgrims-precious-little-life-scott-pilgrim-1_987/index.html',
'http://books.toscrape.com/catalogue/rip-it-up-and-start-again_986/index.html',
'http://books.toscrape.com/catalogue/our-band-could-be-your-life-scenes-from-the-american-indie-underground-1981-1991_985/index.html',
'http://books.toscrape.com/catalogue/olio_984/index.html',
'http://books.toscrape.com/catalogue/mesaerion-the-best-science-fiction-stories-1800-1849_983/index.html',
'http://books.toscrape.com/catalogue/libertarianism-for-beginners_982/index.html',
'http://books.toscrape.com/catalogue/its-only-the-himalayas_981/index.html'
]
これは素晴らしいスタートですが、URLだけでなく、特定の本に関連するすべてのデータを取得する必要があります。 次に、これらのURLを使用して各ページを開き、書籍のタイトル、著者、価格、在庫状況、UPC、説明、画像のURLを取得します。
再開する pageScraper.js
:
- nano pageScraper.js
次のコードを追加します。このコードは、スクレイプされた各リンクをループし、新しいページインスタンスを開いてから、関連するデータを取得します。
const scraperObject = {
url: 'http://books.toscrape.com',
async scraper(browser){
let page = await browser.newPage();
console.log(`Navigating to ${this.url}...`);
// Navigate to the selected page
await page.goto(this.url);
// Wait for the required DOM to be rendered
await page.waitForSelector('.page_inner');
// Get the link to all the required books
let urls = await page.$$eval('section ol > li', links => {
// Make sure the book to be scraped is in stock
links = links.filter(link => link.querySelector('.instock.availability > i').textContent !== "In stock")
// Extract the links from the data
links = links.map(el => el.querySelector('h3 > a').href)
return links;
});
// Loop through each of those links, open a new page instance and get the relevant data from them
let pagePromise = (link) => new Promise(async(resolve, reject) => {
let dataObj = {};
let newPage = await browser.newPage();
await newPage.goto(link);
dataObj['bookTitle'] = await newPage.$eval('.product_main > h1', text => text.textContent);
dataObj['bookPrice'] = await newPage.$eval('.price_color', text => text.textContent);
dataObj['noAvailable'] = await newPage.$eval('.instock.availability', text => {
// Strip new line and tab spaces
text = text.textContent.replace(/(\r\n\t|\n|\r|\t)/gm, "");
// Get the number of stock available
let regexp = /^.*\((.*)\).*$/i;
let stockAvailable = regexp.exec(text)[1].split(' ')[0];
return stockAvailable;
});
dataObj['imageUrl'] = await newPage.$eval('#product_gallery img', img => img.src);
dataObj['bookDescription'] = await newPage.$eval('#product_description', div => div.nextSibling.nextSibling.textContent);
dataObj['upc'] = await newPage.$eval('.table.table-striped > tbody > tr > td', table => table.textContent);
resolve(dataObj);
await newPage.close();
});
for(link in urls){
let currentPageData = await pagePromise(urls[link]);
// scrapedData.push(currentPageData);
console.log(currentPageData);
}
}
}
module.exports = scraperObject;
すべてのURLの配列があります。 この配列をループして、新しいページでURLを開き、そのページのデータを取得し、そのページを閉じて、配列内の次のURLの新しいページを開きます。 このコードをPromiseでラップしていることに注意してください。 これは、ループ内の各アクションが完了するのを待つことができるようにするためです。 したがって、各Promiseは新しいURLを開き、プログラムがURL上のすべてのデータをスクレイプし、そのページインスタンスが閉じるまで解決されません。
警告:プロミスを使用してプロミスを待ったことに注意してください for-in
ループ。 他のループでも十分ですが、次のような配列反復法を使用してURL配列を反復処理することは避けてください。 forEach
、またはコールバック関数を使用するその他のメソッド。 これは、コールバック関数が最初にコールバックキューとイベントループを通過する必要があるためです。したがって、複数のページインスタンスが一度に開きます。 これはあなたの記憶にはるかに大きな負担をかけます。
あなたの pagePromise
関数。 スクレーパーは最初にURLごとに新しいページを作成し、次に page.$eval()
新しいページでスクレイプしたい関連する詳細のセレクターをターゲットにする関数。 一部のテキストには、正規表現を使用して削除した空白、タブ、改行、およびその他の英数字以外の文字が含まれています。 次に、このページでスクレイピングされたすべてのデータの値をオブジェクトに追加し、そのオブジェクトを解決しました。
ファイルを保存して閉じます。
スクリプトを再度実行します。
- npm run start
ブラウザはホームページを開き、次に各本のページを開き、それらの各ページからスクレイピングされたデータをログに記録します。 この出力はコンソールに出力されます。
OutputOpening the browser......
Navigating to http://books.toscrape.com...
{
bookTitle: 'A Light in the Attic',
bookPrice: '£51.77',
noAvailable: '22',
imageUrl: 'http://books.toscrape.com/media/cache/fe/72/fe72f0532301ec28892ae79a629a293c.jpg',
bookDescription: "It's hard to imagine a world without A Light in the Attic. [...]',
upc: 'a897fe39b1053632'
}
{
bookTitle: 'Tipping the Velvet',
bookPrice: '£53.74',
noAvailable: '20',
imageUrl: 'http://books.toscrape.com/media/cache/08/e9/08e94f3731d7d6b760dfbfbc02ca5c62.jpg',
bookDescription: `"Erotic and absorbing...Written with starling power."--"The New York Times Book Review " Nan King, an oyster girl, is captivated by the music hall phenomenon Kitty Butler [...]`,
upc: '90fa61229261140a'
}
{
bookTitle: 'Soumission',
bookPrice: '£50.10',
noAvailable: '20',
imageUrl: 'http://books.toscrape.com/media/cache/ee/cf/eecfe998905e455df12064dba399c075.jpg',
bookDescription: 'Dans une France assez proche de la nôtre, [...]',
upc: '6957f44c3847a760'
}
...
このステップでは、 books.toscrape.com のホームページにあるすべての本の関連データをスクレイプしましたが、さらに多くの機能を追加することができます。 たとえば、本の各ページはページ付けされています。 これらの他のページからどのように本を入手しますか? また、ウェブサイトの左側には、本のカテゴリがあります。 すべての本が必要ではなく、特定のジャンルの本だけが必要な場合はどうなりますか? 次に、これらの機能を追加します。
ステップ4—複数のページからデータをスクレイピングする
books.toscrape.com のページ付けされたページには、 next
コンテンツの下にボタンがありますが、ページが表示されていないページは表示されません。
このボタンの存在を使用して、ページがページ分割されているかどうかを判断します。 各ページのデータは同じ構造であり、同じマークアップを持っているため、すべての可能なページにスクレーパーを作成することはありません。 むしろ、再帰のプラクティスを使用します。
まず、コードの構造を少し変更して、複数のページに再帰的に移動できるようにする必要があります。
再開する pagescraper.js
:
- nano pagescraper.js
と呼ばれる新しい関数を追加します scrapeCurrentPage()
あなたに scraper()
方法。 この関数には、特定のページからデータを取得し、存在する場合は[次へ]ボタンをクリックするすべてのコードが含まれます。 次の強調表示されたコードを追加します。
const scraperObject = {
url: 'http://books.toscrape.com',
async scraper(browser){
let page = await browser.newPage();
console.log(`Navigating to ${this.url}...`);
// Navigate to the selected page
await page.goto(this.url);
let scrapedData = [];
// Wait for the required DOM to be rendered
async function scrapeCurrentPage(){
await page.waitForSelector('.page_inner');
// Get the link to all the required books
let urls = await page.$$eval('section ol > li', links => {
// Make sure the book to be scraped is in stock
links = links.filter(link => link.querySelector('.instock.availability > i').textContent !== "In stock")
// Extract the links from the data
links = links.map(el => el.querySelector('h3 > a').href)
return links;
});
// Loop through each of those links, open a new page instance and get the relevant data from them
let pagePromise = (link) => new Promise(async(resolve, reject) => {
let dataObj = {};
let newPage = await browser.newPage();
await newPage.goto(link);
dataObj['bookTitle'] = await newPage.$eval('.product_main > h1', text => text.textContent);
dataObj['bookPrice'] = await newPage.$eval('.price_color', text => text.textContent);
dataObj['noAvailable'] = await newPage.$eval('.instock.availability', text => {
// Strip new line and tab spaces
text = text.textContent.replace(/(\r\n\t|\n|\r|\t)/gm, "");
// Get the number of stock available
let regexp = /^.*\((.*)\).*$/i;
let stockAvailable = regexp.exec(text)[1].split(' ')[0];
return stockAvailable;
});
dataObj['imageUrl'] = await newPage.$eval('#product_gallery img', img => img.src);
dataObj['bookDescription'] = await newPage.$eval('#product_description', div => div.nextSibling.nextSibling.textContent);
dataObj['upc'] = await newPage.$eval('.table.table-striped > tbody > tr > td', table => table.textContent);
resolve(dataObj);
await newPage.close();
});
for(link in urls){
let currentPageData = await pagePromise(urls[link]);
scrapedData.push(currentPageData);
// console.log(currentPageData);
}
// When all the data on this page is done, click the next button and start the scraping of the next page
// You are going to check if this button exist first, so you know if there really is a next page.
let nextButtonExist = false;
try{
const nextButton = await page.$eval('.next > a', a => a.textContent);
nextButtonExist = true;
}
catch(err){
nextButtonExist = false;
}
if(nextButtonExist){
await page.click('.next > a');
return scrapeCurrentPage(); // Call this function recursively
}
await page.close();
return scrapedData;
}
let data = await scrapeCurrentPage();
console.log(data);
return data;
}
}
module.exports = scraperObject;
あなたは nextButtonExist
最初は変数をfalseに設定してから、ボタンが存在するかどうかを確認します。 の場合 next
ボタンが存在し、設定します nextButtonExists
に true
クリックして next
ボタンをクリックしてから、この関数を再帰的に呼び出します。
もしも nextButtonExists
falseの場合、 scrapedData
いつものように配列。
ファイルを保存して閉じます。
スクリプトを再度実行します。
- npm run start
これは完了するまでに時間がかかる場合があります。 結局のところ、アプリケーションは800冊を超える本からデータをスクレイピングしています。 ブラウザを閉じるか、を押してください CTRL + C
プロセスをキャンセルします。
これでスクレーパーの機能が最大化されましたが、その過程で新しい問題が発生しました。 ここで問題となるのは、データが少なすぎることではなく、データが多すぎることです。 次のステップでは、アプリケーションを微調整して、本のカテゴリでスクレイピングをフィルタリングします。
ステップ5—カテゴリ別のデータのスクレイピング
カテゴリ別にデータをスクレイピングするには、両方を変更する必要があります pageScraper.js
ファイルとあなたの pageController.js
ファイル。
開ける pageController.js
テキストエディタの場合:
nano pageController.js
旅行の本だけをこすり取るようにスクレーパーを呼び出します。 次のコードを追加します。
const pageScraper = require('./pageScraper');
async function scrapeAll(browserInstance){
let browser;
try{
browser = await browserInstance;
let scrapedData = {};
// Call the scraper for different set of books to be scraped
scrapedData['Travel'] = await pageScraper.scraper(browser, 'Travel');
await browser.close();
console.log(scrapedData)
}
catch(err){
console.log("Could not resolve the browser instance => ", err);
}
}
module.exports = (browserInstance) => scrapeAll(browserInstance)
これで、2つのパラメータを pageScraper.scraper()
メソッド。2番目のパラメーターは、スクレイプする本のカテゴリーです。この例では、 Travel
. しかし、あなたの pageScraper.js
ファイルはまだこのパラメータを認識していません。 このファイルも調整する必要があります。
ファイルを保存して閉じます。
開ける pageScraper.js
:
- nano pageScraper.js
次のコードを追加します。これにより、カテゴリパラメータが追加され、そのカテゴリページに移動して、ページ付けされた結果のスクレイピングを開始します。
const scraperObject = {
url: 'http://books.toscrape.com',
async scraper(browser, category){
let page = await browser.newPage();
console.log(`Navigating to ${this.url}...`);
// Navigate to the selected page
await page.goto(this.url);
// Select the category of book to be displayed
let selectedCategory = await page.$$eval('.side_categories > ul > li > ul > li > a', (links, _category) => {
// Search for the element that has the matching text
links = links.map(a => a.textContent.replace(/(\r\n\t|\n|\r|\t|^\s|\s$|\B\s|\s\B)/gm, "") === _category ? a : null);
let link = links.filter(tx => tx !== null)[0];
return link.href;
}, category);
// Navigate to the selected category
await page.goto(selectedCategory);
let scrapedData = [];
// Wait for the required DOM to be rendered
async function scrapeCurrentPage(){
await page.waitForSelector('.page_inner');
// Get the link to all the required books
let urls = await page.$$eval('section ol > li', links => {
// Make sure the book to be scraped is in stock
links = links.filter(link => link.querySelector('.instock.availability > i').textContent !== "In stock")
// Extract the links from the data
links = links.map(el => el.querySelector('h3 > a').href)
return links;
});
// Loop through each of those links, open a new page instance and get the relevant data from them
let pagePromise = (link) => new Promise(async(resolve, reject) => {
let dataObj = {};
let newPage = await browser.newPage();
await newPage.goto(link);
dataObj['bookTitle'] = await newPage.$eval('.product_main > h1', text => text.textContent);
dataObj['bookPrice'] = await newPage.$eval('.price_color', text => text.textContent);
dataObj['noAvailable'] = await newPage.$eval('.instock.availability', text => {
// Strip new line and tab spaces
text = text.textContent.replace(/(\r\n\t|\n|\r|\t)/gm, "");
// Get the number of stock available
let regexp = /^.*\((.*)\).*$/i;
let stockAvailable = regexp.exec(text)[1].split(' ')[0];
return stockAvailable;
});
dataObj['imageUrl'] = await newPage.$eval('#product_gallery img', img => img.src);
dataObj['bookDescription'] = await newPage.$eval('#product_description', div => div.nextSibling.nextSibling.textContent);
dataObj['upc'] = await newPage.$eval('.table.table-striped > tbody > tr > td', table => table.textContent);
resolve(dataObj);
await newPage.close();
});
for(link in urls){
let currentPageData = await pagePromise(urls[link]);
scrapedData.push(currentPageData);
// console.log(currentPageData);
}
// When all the data on this page is done, click the next button and start the scraping of the next page
// You are going to check if this button exist first, so you know if there really is a next page.
let nextButtonExist = false;
try{
const nextButton = await page.$eval('.next > a', a => a.textContent);
nextButtonExist = true;
}
catch(err){
nextButtonExist = false;
}
if(nextButtonExist){
await page.click('.next > a');
return scrapeCurrentPage(); // Call this function recursively
}
await page.close();
return scrapedData;
}
let data = await scrapeCurrentPage();
console.log(data);
return data;
}
}
module.exports = scraperObject;
このコードブロックは、渡されたカテゴリを使用して、そのカテゴリの書籍が存在するURLを取得します。
The page.$$eval()
引数を3番目のパラメータとして $$eval()
メソッド、およびそれをコールバックの3番目のパラメーターとして定義します。
page.$$eval('selector', function(elem, args){
// .......
}, args)
これはあなたがあなたのコードでしたことでした。 削りたい本のカテゴリを渡し、すべてのカテゴリをマッピングして、どれが一致するかを確認してから、このカテゴリのURLを返しました。
次に、このURLを使用して、スクレイプする書籍のカテゴリを表示するページに移動します。 page.goto(selectedCategory)
方法。
ファイルを保存して閉じます。
アプリケーションを再度実行します。 あなたはそれがにナビゲートすることに気付くでしょう Travel
カテゴリ、そのカテゴリの本をページごとに再帰的に開き、結果をログに記録します。
- npm run start
このステップでは、複数のページにまたがってデータをスクレイピングしてから、1つの特定のカテゴリから複数のページにまたがってデータをスクレイピングしました。 最後のステップでは、スクリプトを変更して複数のカテゴリにまたがるデータをスクレイピングし、このスクレイピングされたデータを文字列化されたJSONファイルに保存します。
ステップ6—複数のカテゴリからデータをスクレイピングし、データをJSONとして保存する
この最後のステップでは、スクリプトで必要な数のカテゴリからデータを取得し、出力の方法を変更します。 結果をログに記録するのではなく、と呼ばれる構造化ファイルに保存します data.json
.
スクレイプにカテゴリをすばやく追加できます。 これを行うには、ジャンルごとに1行追加するだけで済みます。
開ける pageController.js
:
- nano pageController.js
追加のカテゴリを含めるようにコードを調整します。 以下の例は追加します HistoricalFiction
と Mystery
私たちの既存に Travel
カテゴリー:
const pageScraper = require('./pageScraper');
async function scrapeAll(browserInstance){
let browser;
try{
browser = await browserInstance;
let scrapedData = {};
// Call the scraper for different set of books to be scraped
scrapedData['Travel'] = await pageScraper.scraper(browser, 'Travel');
scrapedData['HistoricalFiction'] = await pageScraper.scraper(browser, 'Historical Fiction');
scrapedData['Mystery'] = await pageScraper.scraper(browser, 'Mystery');
await browser.close();
console.log(scrapedData)
}
catch(err){
console.log("Could not resolve the browser instance => ", err);
}
}
module.exports = (browserInstance) => scrapeAll(browserInstance)
ファイルを保存して閉じます。
スクリプトを再度実行し、3つのカテゴリすべてのデータを取得するのを確認します。
- npm run start
スクレーパーが完全に機能するようになったら、最後のステップとして、データをより便利な形式で保存します。 ここで、Node.jsのfsモジュールを使用してJSONファイルに保存します。
まず、再開します pageController.js
:
- nano pageController.js
次の強調表示されたコードを追加します。
const pageScraper = require('./pageScraper');
const fs = require('fs');
async function scrapeAll(browserInstance){
let browser;
try{
browser = await browserInstance;
let scrapedData = {};
// Call the scraper for different set of books to be scraped
scrapedData['Travel'] = await pageScraper.scraper(browser, 'Travel');
scrapedData['HistoricalFiction'] = await pageScraper.scraper(browser, 'Historical Fiction');
scrapedData['Mystery'] = await pageScraper.scraper(browser, 'Mystery');
await browser.close();
fs.writeFile("data.json", JSON.stringify(scrapedData), 'utf8', function(err) {
if(err) {
return console.log(err);
}
console.log("The data has been scraped and saved successfully! View it at './data.json'");
});
}
catch(err){
console.log("Could not resolve the browser instance => ", err);
}
}
module.exports = (browserInstance) => scrapeAll(browserInstance)
まず、Node、jsが必要です fs
のモジュール pageController.js
. これにより、データをJSONファイルとして保存できるようになります。 次に、コードを追加して、スクレイピングが完了してブラウザを閉じると、プログラムがという新しいファイルを作成するようにします。 data.json
. の内容に注意してください data.json
文字列化されたJSONです。 したがって、の内容を読むとき data.json
、データを再利用する前に、常にそれをJSONとして解析します。
ファイルを保存して閉じます。
これで、複数のカテゴリにわたって本をスクレイピングし、スクレイピングしたデータをJSONファイルに保存するWebスクレイピングアプリケーションを構築しました。 アプリケーションの複雑さが増すにつれて、このスクレイピングされたデータをデータベースに保存するか、APIを介して提供することをお勧めします。 このデータがどのように消費されるかは、本当にあなた次第です。
結論
このチュートリアルでは、複数のページにまたがるデータを再帰的にスクレイピングするWebクローラーを作成し、それをJSONファイルに保存しました。 つまり、Webサイトからのデータ収集を自動化する新しい方法を学びました。
Puppeteerには、このチュートリアルの範囲外の機能がたくさんあります。 詳細については、ヘッドレスクロームを簡単に制御するためのPuppeteerの使用をご覧ください。 Puppeteerの公式ドキュメントにもアクセスできます。