この記事の内容は古いです
時々いいねされて気になったのですが、いま(2019/10現在)からだと puppeteer
を使うのがいいと思います
https://qiita.com/tags/puppeteer
Headlessブラウザとは
JavaScript
やSPA
の普及によりAjax
など動的なページが増え、Webサイトをスクレイピングする際にcurl
などでHTMLを取得しても取得したいデータが見つからないことが多々ある。
そのためページ内のデータを取得したい場合はHeadless
(画面に表示されない)ブラウザを裏で実行し、普通のブラウザと同じようJavaScript
を実行してページの内容を参照する必要がある。
Headlessブラウザにはどんなものがあるか
Phantom.js
-
Nightmare.js
など
いままではPhantom.js
と、それを自動化するCasper.js
を何度か使っていたが、Chromeが公式でHeadless対応したためPhantom.js
のメンテナが辞任することを発表し一部で話題になりHeadless Chrome
への乗り換えを決意した。
Headless Chrome でスクレイピング
今回はmacOS
環境のNode.js
からQiitaの投稿内容を取得してみる。
環境のインストール
Node.js
は既にインストール済みと想定して、
$ npm install chromy
ブラウザはGoogle Chrome
かGoogle Chrome Canary
を予めインストールしておく。
実際のコード
以下のコードはQiitaのプロフィール画面から記事のURLを取得し、
各記事に設定されているタイトル、タグ、投稿日時を取得する。
const Chromy = require('chromy')
async function scrap() {
let chromy = new Chromy()
await chromy.blockUrls(['*.ttf', '*.gif', '*.png', '*.jpg', '*.jpeg', '*.webp'])
await chromy.goto('http://qiita.com/Ria0130')
const urls = await chromy.evaluate(() => {
let urls = []
document.querySelectorAll("#main .tableList .media .ItemLink__title a").forEach(item => {
urls.push('http://qiita.com/' + item.getAttribute('href'));
})
return urls
})
for(let url of urls){
await chromy.goto(url)
let json = await chromy.evaluate(() => {
let title = document.querySelector("h1.ArticleMainHeader__title").innerHTML
let tags = []
document.querySelectorAll("#main .TagList a span").forEach(elem => {
tags.push(elem.innerHTML)
})
let date = document.querySelector(".container .ArticleAsideHeader__date span time").getAttribute('datetime')
return {
title: title,
tags: tags,
date: date,
}
})
console.log(json)
}
await chromy.close()
}
scrap()
解説
async function scrap() {
記事一覧
→記事A
→記事B
→記事C
と逐次処理が必要になるのでasync/await
を使用した。
async/await
についてはpromise
を書くよりも数段楽だったので後日記事にまとめたい。
await chromy.blockUrls(['*.ttf', '*.gif', '*.png', '*.jpg', '*.jpeg', '*.webp'])
ブロックするURLを指定。ここでは画像等のファイルを開かずにスキップするよう指定している。
await chromy.goto('http://qiita.com/Ria0130')
const urls = await chromy.evaluate(() => {
let urls = []
document.querySelectorAll("#main .tableList .media .ItemLink__title a").forEach(item => {
urls.push('http://qiita.com/' + item.getAttribute('href'));
})
return urls
})
.goto()
でhttp://qiita.com/Ria0130
を開き、
evaluate()
でページ内容の操作を行っている。
for(let url of urls){
await chromy.goto(url)
その場でブラウザを閉じずに次(記事)のURLを開き同等の処理をforで回す。
await chromy.close()
.close()
しないとブラウザのプロセスが残り続け、macOSの場合だとDockにブラウザアイコンが表示されたままになる。
感想
Node.js
からだとJavaScript
なのもあって非同期処理にさえ気をつければ簡単なスクレイピングは容易にできた。
ただ今回ページ内でJavaScript
のクリックイベント等動的な部分は試していないので難しいことをしようとするとどうなるかはまだわからない。
Headless Chrome
+ Python
+ ChromeDriver
+ Selenium
で試したときは
ChromeDriver
が原因の可能性もあるが、ウインドウ(タブ)の切り替えやファイルのダウンロードが上手く行かなかったので、普段のブラウザですることをなんでも出来るわけではなさそう。
この辺りは今後バージョンアップで改善してくれると嬉しいな。