概要
SPAサイト全体をスクレイピングし、サイトマップを自動生成します。因みにSPAのフレームワークは、vue.jsを使用しました。
実装には、以下のNode.jsライブラリを使っています。
処理の流れ
全体の流れを簡単に説明すると、スクレイピングのスタート地点を指定し、そこから全てのページ内のa
タグのhref
属性を取得していき、最終的にsitemap.xmlを生成します。
Chromyでスクレイピング
const Chromy = require('chromy')
async function scraping(chromy, array, to) {
await chromy.blockUrls(['*.ttf', '*.gif', '*.png', '*.jpg', '*.jpeg', '*.webp'])
await chromy.goto(to)
// 指定の要素が現れるまで処理を中断
await chromy.wait('body.is-content-loaded')
// ページ内のaタグのhref属性を全て取得
const anchors = await chromy.evaluate(()=> {
const result = []
for(const item of document.body.getElementsByTagName('a')) {
if(item.target != '_blank') {
result.push(item.href)
}
}
return result
})
// 配列に格納
for(let item of anchors) {
if(array.indexOf(item) == -1) > -1) {
array.push(item)
}
}
await chromy.close()
}
async function start() {
const chromy = new Chromy()
const urls = ['https://sample.com']
let count = 0
do {
await scraping(chromy, urls, urls[count])
count++
} while (count < urls.length - 1)
}
start()
scraping()
関数は、Webページへのスクレイピングと、スクレイピングした結果からa
タグのhref
属性を全ての取得し、urls
配列に格納しています。
SPAではAPIからコンテンツデータを取得し、それをレンダリングする必要があるため、データを取得するにはレンダリングを待たなければなりません。そこでchromy.wait()
関数で指定した要素(今回はbody.is-content-loaded
)が現れるまで処理を停止しています。
またurls
配列には、既に配列に格納されていない値のみ格納します。
start()
関数は、urls
配列に格納された値をdo while
文でループし、scraping()
関数に渡すことでサイト内の全てのページをスクレイピングします。
ループしつつ、scraping()
関数がurls
配列に重複のない値を格納していくため、最終的に格納できる値がなくなりスクレイピングは終了されます。
xml2jsでsitemap.xmlを生成
const xml2js = require('xml2js')
const fs = require('fs')
function xmlBuild(object) {
const array = []
// 配列を入れ子に
for(const item of object) {
array.push({ url: { loc: item } })
}
const builder = new xml2js.Builder({ rootName: 'urlset' }).buildObject({ $: { xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9' } })
const xml = builder.buildObject(array)
fs.writeFile('sitemap.xml', xml, 'utf8')
}
const urls = ['https://sample.com']
xmlBuild(urls)
配列の構造通りにXMLファイルが生成されるように、urls
配列の値を入れ子にした新しい配列を生成しています。
今回のソースコード
const Chromy = require("chromy")
const xml2js = require("xml2js")
const fs = require("fs")
async function scrap(chromy, pushArray, to) {
await chromy.blockUrls(["*.ttf", "*.gif", "*.png", "*.jpg", "*.jpeg", "*.webp"])
await chromy.goto(to)
await chromy.wait("body.is-content-loaded")
const anchors = await chromy.evaluate(()=> {
const result = []
for(const item of document.body.getElementsByTagName("a")) {
if(item.target != "_blank") {
result.push(item.href)
}
}
return result
})
for(let item of anchors) {
if(pushArray.indexOf(item) == -1 && item.indexOf("http://") > -1) {
pushArray.push(item)
}
}
await chromy.close()
}
function xmlBuild(object) {
const array = []
for(const item of object) {
array.push({ url: { loc: item } })
}
const builder = new xml2js.Builder({ rootName: "urlset" }).buildObject({ $: { xmlns: "http://www.sitemaps.org/schemas/sitemap/0.9" } })
const xml = builder.buildObject(array)
fs.writeFile("sitemap.xml", xml, "utf8")
}
async function scrapingStart() {
const chromy = new Chromy()
const urls = ['https://sample.com']
let count = 0
do {
await scrap(chromy, urls, urls[count])
count++
} while (count < urls.length - 1)
await xmlBuild(urls)
}
start()
今回は以上です。
実は、スクレイピングを試す前に既存のサイトマップ生成サービス、ライブラリも試してみたのですがどれも上手くいかなかったので、生成できるものを知ってる方がいれば、是非教えてください!