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

##ハンドブック作った


![IMG_20190326_015314.jpg](https://qiita-image-store.s3.amazonaws.com/0/204698/262a3d85-c18b-5c0f-554e-31c4569ff81c.jpeg)


[Reactの翻訳完成率が100%](https://isreacttranslatedyet.com/)になり、[公式のドキュメントが日本語](https://ja.reactjs.org/)で読めるようになりました:clap: ありがてぇ。
![スクリーンショット 2019-03-19 1.55.48.png](https://qiita-image-store.s3.amazonaws.com/0/204698/4009c1f5-b437-542f-5b40-641ba9317e88.png)
紙で読みたい人のために印刷用に整形してPDFにしたので、2019/03/20時点のものは[ここ](https://drive.google.com/open?id=1JEMPyKTTAHq9G-yT9QlEHmHufRPflEGF)から落とせます。
追記：PDFにしちゃえばKindleでも読めるらしい。(参考: [PDFの技術書をKindleで快適に読む
](https://qiita.com/golang/items/b7d2b3ea171f5b3d223f))


---

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

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

全体のコードは[ここに](https://github.com/hand-dot/dopu)置いてあります！

---

### これからはPuppeteerの`evaluate()`と`pdf()`の話しかしませんw さーせん！
他のサイトとかでやりたい人は参考になるかも？

---

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

セットアップ完了。

![スクリーンショット 2019-03-20 12.39.51.png](https://qiita-image-store.s3.amazonaws.com/0/204698/8a607480-3681-49ec-f4c3-a6ad8bbe756b.png)


下記の記事を参考に`index.js`を作ってPDF出力にチャレンジしてみる。
参考: [ヘッドレスブラウザの Puppeteer を利用して WEB ページを PDF 出力してみる。](https://qiita.com/t-toyota/items/8c7aa6fe8ada2775ea2c)

---

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

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

とりあえず、下記の画像の場所の`href`の一覧を`Puppeteer`で取得して、
そのリストにアクセスしていって、`page.pdf()`をキメれば多分やりたいことはできるな。
![スクリーンショット 2019-03-19 20.14.30.png](https://qiita-image-store.s3.amazonaws.com/0/204698/6cbe93e9-9efd-8b4e-0d38-e79b826a5caa.png)


---

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

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

```js
// サイドバーの見出しからページの一覧を取得する {見出し: [ページ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の配列が`value`の`Object`が取得できるのでこれを使ってクローリングしていくっ！

```
{
    "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](https://qiita-image-store.s3.amazonaws.com/0/204698/f7bdb198-3f7d-2290-f0d6-a10a02814fbd.png)
![スクリーンショット 2019-03-20 12.47.13.png](https://qiita-image-store.s3.amazonaws.com/0/204698/9b67a1fd-e056-62c1-4052-74994d718cee.png)

---


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

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

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

---


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

```js
    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`
    });
```


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

```js
    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](https://qiita-image-store.s3.amazonaws.com/0/204698/28983362-e871-51f8-0ebf-96aadd18038d.png)

---


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

![スクリーンショット 2019-03-20 13.06.09.png](https://qiita-image-store.s3.amazonaws.com/0/204698/3df23cba-42ff-89ce-a6a1-945b8fd2d969.png)



実行可能な全体のコードは全体のコードは[ここに](https://github.com/hand-dot/dopu)置いてあります！

---


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

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

83pのPDFができました。

<img width="1422" alt="スクリーンショット 2019-03-20 14.37.43.png" src="https://qiita-image-store.s3.amazonaws.com/0/204698/0a5fc722-82d0-354e-eb17-81e376a3902c.png">

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

---

## 感想
- 寝る前にソファーで読んだりしてる。いい。
- kindleでも読みたいという人の意見もあった。できるっぽい。( [PDFの技術書をKindleで快適に読む)
](https://qiita.com/golang/items/b7d2b3ea171f5b3d223f)
- 久しぶりにdomを触ったら懐かしくもやはり辛かった。
- ただのPuppeteerを使った話になってしまった :-)