
難易度の高いスクレイピング処理として、サイト内検索や、入力フォームによる結果ページの情報取得があります。
今回は、データベースの内部を検索して、その検索結果を表示するようなサイトをスクレイピングする時のサンプルパターンを紹介したいと思います。
サンプル対象ページ
厚生労働省の薬品管理を目的とする、安全衛生データベースサイトの「SDSモデル」の検索サイトで、クローリングを行ってみたいと思います。
参考サイト :
https://anzeninfo.mhlw.go.jp/anzen_pg/ghs_msd_fnd.aspx
下の図のように、検索文字列を入力して、「検索開始」ボタンを押す事で、結果ページに遷移するという流れになります。
検索結果は、次の様な表示が行われるので、赤枠の中の情報をリスト取得する事で、情報取得が可能になります。
そのリストの1つをクリックすると、さらに詳細画面に遷移するので、ここまでをクロール対象にしてみたいと思います。
サンプルコード
sample.js
const puppeteer = require("puppeteer");
const argv = require("minimist")(process.argv.slice(2));
(async () => {
const url = argv.url;
const word = argv.word;
if (!url || !word) {
console.log("Usage: node js/sample.js --url=https://example.com --word=検索ワード");
process.exit(1);
}
const browser = await puppeteer.launch({
headless: "new",
executablePath: "/usr/bin/chromium",
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
const page = await browser.newPage();
await page.goto(url, { waitUntil: "networkidle2" });
// セレクタが出てくるまで待機
await page.waitForSelector(`input[name="chemicalName_J"]`);
// 「検索ワード」を検索ボックスに入力
await page.type('input[name="chemicalName_J"]', word);
// 検索ボタンをクリック
await Promise.all([
page.click(`input[name="bs_search_1"]`), // 検索ボタンのID
page.waitForNavigation({ waitUntil: 'networkidle2' }) // 遷移待ち
]);
// リストの取得
const datas = await page.$$eval(`form[name="form1"] table tbody tr:nth-of-type(n+1)`, trs =>
trs.map(tr => {
const elm = tr.querySelector(`td.list_02 a`)
if(!elm){return null}
const label_elm = tr.querySelector(`td.list_05 a`);
const onclick = elm ? elm.getAttribute("onclick") : null;
const showDetail = onclick ? onclick.match(/showDetail\((.+)\)/) : null;
return {
title : elm ? elm.textContent : null,
num : showDetail ? showDetail[1] : null,
elm : elm,
link : label_elm.href
}
}).filter(e => e !== null)
);
// キーワード検索結果の表示
console.log(`キーワード 検索結果件数: ${datas.length}`);
datas.forEach((data, i) => {
console.log(`${i + 1}. ${JSON.stringify(data)}`);
});
// ラベルページのスクレイピング
const new_datas = [];
for(const data of datas){
const new_data = {};
await page.goto(data.link, { waitUntil: "networkidle2" });
// タイトル取得
new_data.title = await page.title();
// ラベル取得
new_data.labels = await page.$$eval(`#lab03 > *`, elms => elms.map(elm => {
return {
name : elm.getAttribute("alt"),
img : elm.src,
}
}).filter(e => e !== null));
// 危険データ
new_data.danger = await page.$$eval(`#lab05 table`, tables => tables.map(table => {
return table.textContent.trim();
}).filter(e => e !== null));
new_datas.push(new_data)
}
console.log(JSON.stringify(new_datas, null, 2));
await browser.close();
})();
上記のファイルをコピペで作り、以下のコマンドを実行します。
docker-compose run --rm scraper node js/sample.js --url=https://anzeninfo.mhlw.go.jp/anzen_pg/ghs_msd_fnd.aspx --word=硫化水素
実行結果が以下のようになれば成功です。
キーワード 検索結果件数: 2
1. {"title":"硫化水素","num":"673","elm":{},"link":"https://anzeninfo.mhlw.go.jp/anzen/gmsds_label/lab7783-06-4.html"}
2. {"title":"硫化水素ナトリウム","num":"674","elm":{},"link":"https://anzeninfo.mhlw.go.jp/anzen/gmsds_label/lab0999.html"}
[
{
"title": "職場のあんぜんサイト:化学物質:硫化水素",
"labels": [
{
"name": "炎",
"img": "https://anzeninfo.mhlw.go.jp/anzen/gmsds_label/image/flamme.gif"
},
{
"name": "ガスボンベ",
"img": "https://anzeninfo.mhlw.go.jp/anzen/gmsds_label/image/bottle.gif"
},
{
"name": "どくろ",
"img": "https://anzeninfo.mhlw.go.jp/anzen/gmsds_label/image/skull.gif"
},
{
"name": "健康有害性",
"img": "https://anzeninfo.mhlw.go.jp/anzen/gmsds_label/image/silouete.gif"
},
{
"name": "環境",
"img": "https://anzeninfo.mhlw.go.jp/anzen/gmsds_label/image/pollu.gif"
}
],
"danger": [
"危険有害性情報\n 極めて可燃性又は引火性の高いガス高圧ガス:熱すると爆発のおそれ吸入すると生命に危険強い眼刺激中枢神経系、心臓血管系、呼吸器系の障害水生生物に非常に強い毒性長期継続的影響により水生生物に非常に強い毒性",
"注意書き\n 【安全対策】\n 熱/火花/裸火/高温のもののような着火源から遠ざけること。-禁煙。粉じん/煙/ガス/ミスト/蒸気/スプレーを吸入しないこと。屋外又は換気の良い場所でのみ使用すること。【換気が不十分な場合】呼吸用保護具を着用すること。取扱い後は...よく洗うこと。保護手袋/保護衣/保護眼鏡/保護面を着用すること。この製品を使用する時に、飲食又は喫煙をしないこと。環境への放出を避けること。\n \n 【応急措置】\n 漏えい(洩)ガス火災の場合:漏えい(洩)が安全に停止されない限り消火しないこと。安全に対処できるならば着火源を除去すること。吸入した場合:空気の新鮮な場所に移動し、呼吸しやすい姿勢で休息させること。直ちに医師に連絡すること。特別な処置が緊急に必要である (このラベルの...を見よ)。注) ”…”は、ラベルに解毒剤等中毒時の情報提供を受けるための連絡先などが記載されている場合のものです。ラベル作成時には、”…”を適切に置き換えてください。眼に入った場合:水で数分間注意深く洗うこと。次にコンタクトレンズを着用していて容易に外せる場合は外すこと。その後も洗浄を続けること。眼の刺激が続く場合:医師の診断/手当てを受けること。ばく露またはばく露の懸念がある場合:医師の診断/手当を受けること。特別な処置が必要である (このラベルの...を見よ)。注) ”…”は、ラベルに解毒剤等中毒時の情報提供を受けるための連絡先などが記載されている場合のものです。ラベル作成時には、”…”を適切に置き換えてください。漏出物を回収すること。\n \n 【保管】\n 換気の良い場所で保管すること。日光から遮断し、換気の良い場所で保管すること。換気の良い場所で保管すること。容器を密閉しておくこと。施錠して保管すること。\n \n 【廃棄】\n 内容物/容器を都道府県知事の許可を受けた専門の廃棄物処理業者に依頼して廃棄すること。\n \n 【その他の危険有害性】\n -"
]
},
{
"title": "職場のあんぜんサイト:化学物質:硫化水素ナトリウム",
"labels": [
{
"name": "炎",
"img": "https://anzeninfo.mhlw.go.jp/anzen/gmsds_label/image/flamme.gif"
},
{
"name": "どくろ",
"img": "https://anzeninfo.mhlw.go.jp/anzen/gmsds_label/image/skull.gif"
},
{
"name": "腐食性",
"img": "https://anzeninfo.mhlw.go.jp/anzen/gmsds_label/image/acide.gif"
},
{
"name": "健康有害性",
"img": "https://anzeninfo.mhlw.go.jp/anzen/gmsds_label/image/silouete.gif"
},
{
"name": "環境",
"img": "https://anzeninfo.mhlw.go.jp/anzen/gmsds_label/image/pollu.gif"
}
],
"danger": []
}
]
解説
今回紹介したプログラムは、厚生労働省のSDS薬品の詳細を取得するプログラムでしたが、
このサイトの特性は以下のようになっていました。
1. キーワード検索
2. 検索結果リスト表示 -> リンクをクリック
3. 詳細ページの表示
詳細ページが2種類あり、URLリンクで遷移するものと、POSTクエリを送信してページ表示される2パターンでした。
今回のコードでは、URLで簡単遷移できる方の情報を取得するようにしていますが、詳細データを文字列で取得したい場合は、POSTクエリにトライしなければいけません。
あと、アイコンラベルの一覧は大体どのページも同じ仕様で取得できるようでしたが、
文字列データなどは、ページ毎に、HTMLの内容が事なっており、これらを判別するパターンを見つけなければいけなくなるので、今回はそこまでは行なっていません。
プログラム解説
puppeteerモジュールの呼び込み処理
const puppeteer = require("puppeteer");
コマンド引数情報の取得
const argv = require("minimist")(process.argv.slice(2));
ブラウザの準備(Puppeteer)
const browser = await puppeteer.launch({
headless: "new",
executablePath: "/usr/bin/chromium",
args: ["--no-sandbox", "--disable-setuid-sandbox"],
});
URLアクセス(url変数を事前にセットしておきます。)
const page = await browser.newPage();
await page.goto(url, { waitUntil: "networkidle2" });
※今回はurlはコマンド引数から取得しています。
ページの読み込みを待つ処理(指定のセレクタが表示されるまでwaitする処理)
await page.waitForSelector(`input[name="chemicalName_J"]`);
入力フォームに文字列をセットする処理
await page.type('input[name="chemicalName_J"]', word);
ボタンのクリック処理(ボタンのセレクタを指定しています)
await Promise.all([
page.click(`input[name="bs_search_1"]`), // 検索ボタンのID
page.waitForNavigation({ waitUntil: 'networkidle2' }) // 遷移待ち
]);
要素一覧からデータを取得する処理($$evalは、querySelectorAllのような機能です)
const datas = await page.$$eval(`form[name="form1"] table tbody tr:nth-of-type(n+1)`, trs => {...})
あとがき
かなり実践的なスクレイピングデータ取得を実行しましたが、基本的な使い方が理解できたら、
あとは、Puppeteerと、Javascriptのスキル次第で、たいていのデータは取得できるようになります。
少し工夫すると、Webページの自動テストなども作れちゃうかもしれませんね。
スクレイピングは、業務効率アップの大きな便利ツールに成り得ますので、是非とも習得して、自身の作業効率を爆上げしてしまいましょう。
0 件のコメント:
コメントを投稿