Help us understand the problem. What is going on with this article?

PuppeteerでReactの公式サイトを巡回してReactハンドブックを自作した

More than 1 year has passed since last update.

ハンドブック作った

IMG_20190326_015314.jpg

Reactの翻訳完成率が100%になり、公式のドキュメントが日本語で読めるようになりました:clap: ありがてぇ。
スクリーンショット 2019-03-19 1.55.48.png
紙で読みたい人のために印刷用に整形してPDFにしたので、2019/03/20時点のものはここから落とせます。
追記:PDFにしちゃえばKindleでも読めるらしい。(参考: PDFの技術書をKindleで快適に読む
)


公式のドキュメントにはAPIや使い方だけでなく、流儀や設計なども書いてあるんで、そういうところが日本語で読めるようになるのはまじであざあすでしかない。

Puppeteerを使えば、PDFで保存できるので、プリントアウトしてハンドブック的な奴をサクッと作っみたいと思います!

全体のコードはここに置いてあります!


これからはPuppeteerのevaluate()pdf()の話しかしませんw さーせん!

他のサイトとかでやりたい人は参考になるかも?


なにはともあれ npm init

とりあえず Puppeteer があればOKなので下記を実行して、
$ npm init -y
$ npm i puppeteer

セットアップ完了。

スクリーンショット 2019-03-20 12.39.51.png

下記の記事を参考にindex.jsを作ってPDF出力にチャレンジしてみる。
参考: ヘッドレスブラウザの Puppeteer を利用して WEB ページを PDF 出力してみる。


PuppeteerでDocsのページをクローリング

css-ではじまるセレクタの部分はすぐに変わるはずなのでソッコーで動かなくなると思いますw

とりあえず、下記の画像の場所のhrefの一覧をPuppeteerで取得して、
そのリストにアクセスしていって、page.pdf()をキメれば多分やりたいことはできるな。
スクリーンショット 2019-03-19 20.14.30.png


とりあえずpage.evaluate()をすればPuppeteerがアクセスしたページでJSを実行してくれるっぽいのでクロームのインスペクタでセレクタ名を調べながら下記のcreateTarget関数を書いた。(使い捨てのスクリプトなので雑です。)

page.evaluate()内でのreturnpage.evaluate()の返り値で取れるからすごい。
あと、page.evaluate()内はコンテキストが違うので、外で定義している変数にはアクセスできない。

// サイドバーの見出しからページの一覧を取得する {見出し: [ページ1, ページ2 ...]} みたいな感じで返す
const createTarget = async page => {
  await page.goto("https://ja.reactjs.org/docs/getting-started.html");
  return await page.evaluate(({}) => {
    const contentsCss = ".css-1j8jxus";
    return Array.prototype.reduce.call(
      document.querySelectorAll(contentsCss),
      (acc, elem) =>
        Object.assign(acc, {
          [elem.getElementsByTagName("div")[0]
            .innerText]: Array.prototype.map.call(
            elem.nextElementSibling.children,
            children =>
              children
                .getElementsByTagName("a")[0]
                .href.replace("https://ja.reactjs.org/docs/", "")
                .replace(".html", "")
          )
        }),
      {}
    );
  }, {});
};

したら下に、見出しがkeyでページのIDの配列がvalueObjectが取得できるのでこれを使ってクローリングしていくっ!

{
    "INSTALLATION": [
        "getting-started",
        "add-react-to-a-website",
        "create-a-new-react-app",
        "cdn-links"
    ]...
}

PDFの作成!でも微調整が必要。

page.pdf()を使えば今いるページをPDFにしてくれる。素直にやると下記の画像みたいな感じになる。
スクリーンショット 2019-03-20 12.46.25.png
スクリーンショット 2019-03-20 12.47.13.png


これでもいいけど、実際に印刷する前に下記の点を改善したい。

  • ヘッダーがいらない
  • フッターがいらない
  • サイドバーがいらない(でもどこを見ているのかは見出しレベルでわかるようにしたい)
  • 文字はもっと小さくしたい
  • ページネーションがいらない

そこで微調整をするためにpage.pdf()にオプションを渡したり、page.evaluate()をしてstyleを変更していく!


page.pdf()にはこんな感じのオプションを設定した。
headerTemplateのプロパティにcreateTarget関数で取れる見出しの情報を見れるようにしたり、scaleプロパティで縮小表示にしたりまぁそんな感じ。

    await page.pdf({
      displayHeaderFooter: true,
      headerTemplate: `<div style="font-size: 10px; margin-left: 40px;"><span>${content}</span> / <span class="title"></span></div>`,
      margin: { top: 50, bottom: 50 },
      scale: 0.5,
      path: `pdf/${content}/${index}-${item}.pdf`
    });

スタイルの微調整はこんな感じ。ヘッダー、フッター、サイドバーなどを消したり、文字の間隔を狭めたり。

    await page.evaluate(({}) => {
      const paginationCss = ".css-uygc5k";
      const sideNavCss = ".css-1kbu8hg";
      const contentText = ".css-7u1i3w p";
      document.getElementsByTagName("header")[0].style.display = "none";
      document.getElementsByTagName("footer")[0].style.display = "none";
      document.querySelector(sideNavCss).style.display = "none";
      document.querySelectorAll(contentText).forEach(p => {
        p.style.maxWidth = "100%";
        p.style.marginTop = "0px";
      });
      const pagination = document.querySelector(paginationCss);
      if (pagination) pagination.style.display = "none"; // ページネーションがあれば非表示
    }, {});

あとはエントリーポイントを作って、実行するだけ!


$ node index.js

うん。結構、本っぽい!

スクリーンショット 2019-03-20 13.07.20.png


createTarget関数は見出しをキーにして各コンテンツを配列で返しているので下記の画像みたいにディレクトリに別れて出力している。
実行結果はこんな感じ。

スクリーンショット 2019-03-20 13.06.09.png

実行可能な全体のコードは全体のコードはここに置いてあります!


プリントアウトして本にする

今回はADVANCED GUIDESが読みたいなと思っていたので、これをPDF結合します。
Webで「PDF結合」とか調べると、下記がヒットしたのでそれを使って結合していきます。
https://smallpdf.com/jp/merge-pdf

83pのPDFができました。

スクリーンショット 2019-03-20 14.37.43.png

さあてネットプリントのクラウドにアップロードしたしコンビニに行って印刷してきますか!
コンビニ高かったんで(1枚 20円)、kinkos(1枚 8円)に行ってやった。


感想

  • 寝る前にソファーで読んだりしてる。いい。
  • kindleでも読みたいという人の意見もあった。できるっぽい。( PDFの技術書をKindleで快適に読む)
  • 久しぶりにdomを触ったら懐かしくもやはり辛かった。
  • ただのPuppeteerを使った話になってしまった :-)
hand-dot
サービスを作ったり、新しい技術を試したりすることが好きです。 基本的にJavaScript,HTML,CSSのフロントエンド周り、SEOやアクセス解析などのWeb全般が好きなのでそういう投稿が多めになると思いますが、よかったらフォーローしてください!
https://labelmake.jp/
sharefull
短期間・短時間の仕事に特化したオンデマンドマッチングプラットフォーム「シェアフル」を開発中の、パーソルグループとランサーズの合弁会社
https://sharefull.com
Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした