instagramの情報をスクレイピングして自動で整理できないかと目論んで、調べてみた。
結果としてinstagramのウェブページからはスクレイピングはできなかったが、スクレイピングの手法だけでもメモとして残す。
Nuxtプロジェクト作成
スクレイピングテスト用のnuxtのプロジェクトを作成
create-nuxt-appの初期設定の中で、modulesにAxiosを明示的に指定しておく。
? Nuxt.js modules: Axios
puppeteer
node.jsではpuppetterというライブラリを使用するのがいいらしい。apiでヘッドレスでブラウザを呼び出してウェブページからその内容を取得するイメージ。
$ npm i puppeteer
API実装
Apiを実装するためのサーバーミドルウェアとしてExpressを使用する。
実は書いててよくわかってはいないけれど、APIを呼び出したりなどサーバとのやり取りを担当する仕組みの部分が必要で、NuxtだとExpressをいうライブラリを介してAPIを実現する。
nuxt.config.jsに以下を追加
export default { ... , serverMiddleware: { '/api': '~/api' } }
上記により/apiを叩いたとき~/api/index.jsを参照するようになる。
api/index.jsと、実処理を担当するapi/scraping.jsを作成。
$ mkdir api $ touch api/index.js api/scraping.js
それぞれのファイルの中身は以下の通り。
api/index.js(参考サイトのソースほぼそのまま)
const app = require('express')() const scraping = require('./scraping') app.get('/get_posts', async(req, res) => { const image = await scraping.getPosts() res.send(image) }) module.exports = { path: '/api', handler: app }
api/scraping.js(このブログの記事のタイトル一覧を取得するよう記述)
const puppeteer = require('puppeteer') async function getPosts() { const browser = await puppeteer.launch({ args: [ '--no-sandbox', '--disable-dev-shm-usage' ] }) const page = await browser.newPage() await page.goto("http://blog.wald-grun.biz",{waitUntil: 'domcontentloaded'}) const image = await page.evaluate(() => { const posts = document.getElementsByClassName("c-entries__item") const listcount = posts.length const list = [] for(i=0;i<listcount;i++) { list.push(posts[i].getElementsByClassName("c-entry-summary__title")[0].textContent) } return list }) return image } module.exports = { getPosts }
page.evaluateでページの内容を取得している。
取得にあたっては通常のjsの記述を使う。今回は.c-entries__itemを持った要素(1記事)のリストを取得し、その後にループでタイトル一覧の配列にして返している。
この辺は初見で適当に書いたけど、要素を取得して配列にする記述とかありそうだから要勉強。
フロント側実装
index.vue
<template> <div class="container"> <ul> <li v-for="(item,index) in src" :key="index"> {{ item }} </li> </ul> </div> </template> <script> export default { data() { return { src: "" } }, methods: { async showBird() { this.src = await this.$axios.$get("/api/get_posts") } }, mounted() { this.showBird() } } </script> <style> .container { margin: 0 auto; min-height: 100vh; display: flex; justify-content: center; align-items: center; text-align: center; } .title { font-family: 'Quicksand', 'Source Sans Pro', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; display: block; font-weight: 300; font-size: 100px; color: #35495e; letter-spacing: 1px; } .subtitle { font-weight: 300; font-size: 42px; color: #526488; word-spacing: 5px; padding-bottom: 15px; } .links { padding-top: 15px; } </style>
api/get_postsをaxiosでgetし、取得した値をv-forで展開。
無事に表示されました。
instagramはうまくいかない
さて、上記のやり方でinstagramの内容を取得しようとしたところ、まずログインが必要でした。
調べたところ、まずログインの手順を踏んでcookieを取得し、そのcookieを使ってログイン状態としてページを参照する、という流れらしい。
cookie取得
ログインしてcookieを取得するまでは試しました。(成功してませんが)
dotenvの導入
まず事前に、環境変数的にプロジェクトごとの.envで定数を管理するためのライブラリを導入。
$npm install @nuxtjs/dotenv
.envファイルを用意して設定値を記述
.env(インスタのアカウント等々)
ACCOUNT_NAME=kng_s ACCOUNT_PASS=XXXXXXXXX LOGIN_URL=https://www.instagram.com/accounts/login/
cookieを取得するjsを用意。
getCookie.js
require('dotenv').config(); const puppeteer = require('puppeteer'); const fs = require('fs'); const ACCOUNT_NAME = process.env.ACCOUNT_NAME; const ACCOUNT_PASS = process.env.ACCOUNT_PASS; const LOGIN_URL = process.env.LOGIN_URL; const requireEnvs = [ ACCOUNT_NAME, ACCOUNT_PASS, LOGIN_URL ]; console.log(requireEnvs) const COOKIES_PATH = 'token/cookies.json'; // entry point (async () => { for (let requireEnv of requireEnvs) { if (requireEnv == undefined) { console.log('local env is not set.'); return; } } console.log('try to get cookie...'); const browser = await puppeteer.launch({ headless: true }); const page = await browser.newPage(); // パスワードを使ってログイン await page.goto(LOGIN_URL, {waitUntil: 'networkidle0'}); await page.type('input[name="username"]', ACCOUNT_NAME); await page.type('input[name="password"]', ACCOUNT_PASS); page.click('button[type="submit"]'); await page.waitForNavigation({timeout: 60000, waitUntil: 'domcontentloaded'}); // Cookieをローカルに保存 const afterCookies = await page.cookies(); fs.writeFileSync(COOKIES_PATH, JSON.stringify(afterCookies)); browser.close(); console.log('success!'); })();
こちらをnodeで実行するとcookieが取れるはず…なのですが、instagramだとログインできず。うまくいきませんでした。
$ node getCookie.js
inputoのNodeが存在しないというエラーになるのですが、instagramは基本的にすべて動的にコンテンツ生成がされており、微妙なタイミングなどでNodeが取得できなかったりする?のかと解釈しています。
こちらも要勉強です。
取得後の流れは参考サイトを参照。