LoginSignup
7
6

More than 5 years have passed since last update.

SPAサイトのサイトマップをスクレイピングで自動生成した話

Last updated at Posted at 2018-04-22

概要

SPAサイト全体をスクレイピングし、サイトマップを自動生成します。因みにSPAのフレームワークは、vue.jsを使用しました。
実装には、以下のNode.jsライブラリを使っています。

  • Chromy(Chromeをヘッドレスブラウザとして操作するためのライブラリ)
  • xml2js(XMLの解析と生成を行えるライブラリ)

処理の流れ

全体の流れを簡単に説明すると、スクレイピングのスタート地点を指定し、そこから全てのページ内の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()

今回は以上です。
実は、スクレイピングを試す前に既存のサイトマップ生成サービス、ライブラリも試してみたのですがどれも上手くいかなかったので、生成できるものを知ってる方がいれば、是非教えてください!

7
6
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
6