Node.jsでHTMLを取得してparseして必要なデータを取得する

2021/05/16

Node.js

はじめに

スクレイピングをする機会が多いのでメモとして残す。

htmlを取得する

url を渡して取得する。コメントを外せば保存ができるようにしてある。 エラー対策してないのは割愛

const get = (url) =>{
  return new Promise((resolve, reject)=>{
    request.get({
      uri: url,
      headers: {'Content-type': 'text/html;'},
    }, function(err, req, data){
      if(req.statusCode == 200){
        resolve(data)
        //fs.writeFileSync(path.resolve(__dirname,`./temp-${Date.now()}.html`), data);
      }
      console.log("complate:",url);
      resolve("");
    });
  })
}

htmlをパースする

JSDOM を使ってHTMLをパースして window から色々取れるようになる。
javascript で描画されるものは対応してないっぽいので、ちょっと処理が重くなっちゃうけど Headless Chrome とかの方が良いかも。
試してはない document.addEventListener("DOMContentLoaded",()=>{...}) みたいに取得できたら良いかなと思おうけど非同期処理を入れる必要があるかな。
runScripts: "dangerously" プロパティを設定すると js などが走るっぽい。
resources: "usable" を設定することで外部リソースの利用を許可する。

const htmlParse = (htmlText) =>{
  try{
    const dom = new JSDOM(htmlText,{
      runScripts: "dangerously",
      resources: "usable"
    });
    // get element
    const text = dom.window.document.title;
    console.log(`text:`,text);
  }catch (e){
    console.error(e)
  }
}

全体

url.txt からUrl群を取得してUrlからHTMLを取得する。

const fs = require('fs');
const glob = require('glob');
const request = require('request');
const path = require('path');
const { JSDOM } = require('jsdom')

const urls = fs
  .readFileSync(
    path.resolve(__dirname,"url.txt"),'utf8'
  )
  .split('\n')
  .filter(t=>t.length > 0);

const get = (url) =>{
  return new Promise((resolve, reject)=>{
    request.get({
      uri: url,
      headers: {'Content-type': 'text/html;'},
    }, function(err, req, data){
      if(req.statusCode == 200){
        resolve(data)
        //fs.writeFileSync(path.resolve(__dirname,`./temp-${Date.now()}.html`), data);
      }
      console.log("complate:",url);
      resolve("");
    });
  })
}

const htmlParse = (htmlText) =>{
  try{
    const dom = new JSDOM(htmlText,{
      runScripts: "dangerously",
      resources: "usable"
    });
    // get element
    setTimeout(()=>{
      const text = dom.window.document.title;
      console.log(`text:`,text);
    },1000);
  }catch (e){
    console.error(e)
  }
}

Promise.all(urls.map(url=>get(url))).then((htmls)=>{
  htmls.forEach((html) => {
    htmlParse(html)
  });
});

// glob.sync(path.resolve(__dirname,`./temp-*.html`)).forEach((p)=>{
//   fs.unlinkSync(p);
// });

終わりに

  • JSDOMfromURL() というメソッドがあるのでHTMLファイルとして保存しない場合 request が必要ないかも。
    • fromFile() メソッドもあるので保存したHTMLファイルから再開も fs を使わないでできそう。
JSDOM.fromURL("https://example.com/", options).then(dom => {
  console.log(dom.serialize());
});
  • window.matchMedia がない。
    • Headless Chrome でいいかな…。