クローリングをシュッとやるのに、Crawleeが便利だった
スクレイピングしたいときって、あると思います。 Crawlee という OSS が便利だったので、共有します。
背景
スクレイピングしようと思うと、得意な言語でクローリングプログラムを書いて、html をスクレイピングすると思います。 私は、Node.js が得意なので、fetch + jsdom で書くことが多いです。ブラウザレンダリングが必要な場合、ヘッドレスブラウザを使うこともあります。 毎回これを組み立てるのが、ちょっと面倒だなと思います。そういうときに、Crawle という OSS が便利でした。
Crawle
https://crawlee.dev/ より引用します。
Crawlee is a web scraping and browser automation library. It helps you build reliable crawlers. Fast. Crawlee won't fix broken selectors for you (yet), but it helps you build and maintain your crawlers faster.
Crawlee は壊れたセレクタを直せませんが、クローラーを素早く作ることができます。
Crawle の良いところ
Crawle の良いなとおもった特徴を挙げます。
crawlee のテンプレートがある
crawlee は、npx crawlee create
でコード生成できます。
$ npx crawlee create my-crawler ? Please select the template for your new Crawlee project (Use arrow keys) ❯ Getting started example [TypeScript] Getting started example [JavaScript] CheerioCrawler template project [TypeScript] PlaywrightCrawler template project [TypeScript] PuppeteerCrawler template project [TypeScript] CheerioCrawler template project [JavaScript] PlaywrightCrawler template project [JavaScript]
TypeScript のサポートがあります。 また、Crawler はデフォルトで plain HTTP crawler である Cherrio を採用しています。 必要に応じて、Playwright や Puppeteer を使うことができますし、Crawler の切り替えもインターフェースが揃っているため、簡単にできます。
RequestQueue という仕組み
クローラで、復数の URL にアクセスすることは、よくあると思います。 リクエストは、RequestQueue というキューで管理され、自動的にクローラがアクセスしていきます。 キューはユニークな URL で管理されるため、重複したアクセスはありません。
この仕組みは、次のような簡単なコードで実現できます。
import { RequestQueue } from "crawlee"; const requestQueue = await RequestQueue.open(); await requestQueue.addRequest({ url: "https://crawlee.dev" });
さらに、enqueueLinks という機能があります。これは、アクセスしているページの anchor の URL を RequestQueue に追加します。 次のコードが、enqueueLinks の例です。
import { CheerioCrawler } from "crawlee"; const crawler = new CheerioCrawler({ async requestHandler({ enqueueLinks }) { await enqueueLinks(); }, }); await crawler.run(["https://crawlee.dev"]);
enqueueLinks には、様々なオプションがあります。
例えば、リンクを globs でフィルタリングしたり、anchor のセレクタを指定できたりします。
データは JSON で保存される
スクレイピングで手に入れたデータは、json で保存できます。
例えば、リクエストした URL を集めたいときは、次のようなコードです。
import { CheerioCrawler, Dataset } from "crawlee"; const crawler = new CheerioCrawler({ async requestHandler({ request }) { await Dataset.pushData({ url: request.url }); }, }); await crawler.run(["https://crawlee.dev"]);
保存先は、{PROJECT_FOLDER}/storage/datasets/default/
になります。
めちゃくちゃく簡単にデータが保存できます。
終わりに
Crawlee の SaaS として、Apify があります。これで気軽に試してみるのもありかもしれません。
turborepo-remote-cache でキャッシュサーバをセルフホストした
vercel 製の turborepo という ビルドシステムが爆速なモノレポツールがあります。 爆速にする機能の 1 つに、リモートキャッシュというものがあります。 この機能は vercel のキャッシュサーバを使うのですが、キャッシュサーバをセルフホストする方法もあります。 今回は、それを紹介します。
なぜ、セルフホストしたいのか
vercel のキャッシュサーバを使う場合、vercel のアカウントが必要です。
vercel の pricingを見ると、個人利用(Hobby)では無料ですが、会社(Pro)で使うとすると、$20 per user / month
という価格になります。費用対効果に見合うならそれで良いかもしれませんが、まだそれがわからない段階でコストをかけられない場面もあると思います。そこで、公式にも書いてあるとおり、キャッシュサーバをセルフホストする方法があります。
ローカルで、やってみた
実際に試してみました。ソースコードは、次のリンクにあります。
手元に Git clone して、README に従って動作確認できると思います。必要なソフトウェアは、Docker と Yarn です。
キャッシュサーバの準備
セルフホストする場合、キャッシュサーバを建てる必要があります。
キャッシュサーバは、https://github.com/fox1t/turborepo-remote-cache を使うと良いです。
Docker イメージが公開されているので、それを使っても良いですし、自前で docker build
しても良いです。
キャッシュサーバには、最低でも次の 2 つを環境変数を設定する必要があります。
- TURBO_TOKEN
- turborepo と api を繋げるための TOKEN
- STORAGE_PATH
- キャッシュオブジェクトを保存するパス
- STORAGE_PROVIDER が
s3
を指定する場合は、バケット名
簡単にするため、次の.env ファイルを用意しました。
# .env TURBO_TOKEN=mytoken STORAGE_PATH=/storage/
あとは、キャッシュサーバを起動するために、docker-compose を書きます。
# docker-compose.yml services: remote-cache: image: fox1t/turborepo-remote-cache:latest env_file: - .env ports: - "3000:3000"
次のコマンドで、キャッシュサーバを起動しましょう。
$ docker-compose up -d
これで、キャッシュサーバは PORT:3000 番 で起動します。
turbo build
では、実際に turborepo からつながるか、試してみます。
turborepo は、npx create-turbo@latest
で作成できます。
作成後、作成したフォルダで次のコマンドを実行します。
$ yarn $ yarn turbo run build --team="team_myteam" --token="mytoken" --api="http://localhost:3000"
turbo コマンドのオプションで、3 つ指定します。
実行すると次のログが表示されるはずです。
yarn run v1.22.19 turbo run build --team=team_myteam --token=mytoken --api=http://localhost:3000 • Packages in scope: docs, eslint-config-custom, tsconfig, ui, web • Running build in 5 packages • Remote computation caching enabled web:build: cache miss, executing 082bae5de9b1745f docs:build: cache miss, executing 5a55c6367c8caf01 ...
Remote computation caching enabled
で、リモートキャッシュが有効となりました。
初回の場合、cache miss となります。ハッシュ値は、web: 082bae5de9b1745f
と docs:5a55c6367c8caf01
になります。
キャッシュがローカルに保存されるため、削除します。
$ rm -rf node_modules/.cache/turbo
ではもう一度、turbo build してみましょう。
$ yarn turbo run build --team="team_myteam" --token="mytoken" --api="http://localhost:3000" yarn run v1.22.19 $ /Users/silverbirder/docker/node/turborepo-with-selfhost-remote-cache/node_modules/.bin/turbo run build --team=team_myteam --token=mytoken --api=http://localhost:3000 • Packages in scope: docs, eslint-config-custom, tsconfig, ui, web • Running build in 5 packages • Remote computation caching enabled docs:build: cache hit, replaying output 5a55c6367c8caf01 web:build: cache hit, replaying output 082bae5de9b1745f
どうでしょうか、cache hit
と表示されています。手元にキャッシュがないのにも関わらず、リモートのキャッシュサーバにキャッシュがあるため、cache hit
となります!
キャッシュオブジェクト
キャッシュのオブジェクトは、ハッシュ値名で、アウトプット(file やログ)のバイナリになります。 Docker コンテナ内で見ると、次のようなファイルが置かれています。
$ ls -hl storage/team_myteam/ total 5392 -rw-r--r-- 1 silverbirder staff 1.3M Sep 11 16:20 082bae5de9b1745f -rw-r--r-- 1 silverbirder staff 1.3M Sep 11 16:20 5a55c6367c8caf01
--team オプションで指定した名前で、フォルダが作成されています。 そのため、team 毎にキャッシュが作成されます。
キャッシュとは
turborepo のキャッシュについては、公式 を読むと良いでしょう。
ざっくりいうと、次の流れで cache miss,cache hit になります。
- turbo build を実行
- turbo.json の
build
タスクの inputs(ソースコードなど)や環境変数をハッシュ化 - キャッシュが既にローカルまたはリモートに存在していなければ、cache miss
- turbo.json の
build
タスクの outputs(dist フォルダ、標準出力など)をバイナリ化し、ハッシュ名で保存
3 の手順で、キャッシュが存在していれば、cache hit
となり、outputs が復元します。
クラウドで、やってみた
キャッシュサーバは、AWS や GCP などのクラウドベンダーにあるコンピューティングリソースへデプロイしましょう。 Docker イメージがあるので、AppRunner や CloudRun が楽にできそうです。
キャッシュストレージは、いまのところ AWS S3 のみ対応とのことです。 AWS S3 のクライアントは、S3Client を使っているため、GCS にも対応可能です。まあ README に従うなら、S3 に配置するのがベターでしょう。コンピューティングリソースを動かす IAM は、ストレージリソースへの READ/WRITE 権限を足しましょう。
おわりに
セルフホストして、リモートキャッシュが使えるようになりました。 まだ運用したことがないので、課題を実感していません。引き続き、利用してみようと思います。
Stable Diffusion API 開発
Stable Diffusion は、文章を渡すと画像を生成してくれる AI で OSS です。 これを自分の PC で動かそうとすると、GPU が必要になります。 (CPU で動かせるstable_diffusion.openvino というのもあります)
できれば、どの PC でも使えるように、かつ、Slack などサービスと連携できるよう API がほしいなと思いました。 そこで、Stable Diffusion の API を開発しました。
結論
beta.dreamstudio.aiの SDK、 stability-sdkを使いました。
成果物は、次のリポジトリに置いています。
ローカル環境でも、Docker コンテナでも、動きます。
動かすには、beta.dreamstudio.aiの API Key が必要になります。 Docker で動くので、Docker をデプロイできるサービスなら、どこでも動きます。(GPU は不要です)
私は、GCP が好きなので、CloudRun というサービスにデプロイしました。
API は、とりあえず、<url>/?prompt=<text>
というパラメータを受け取り、画像を返却します。
Slack で使ってみると、こんな感じになりました。
ひとまず、API で Stable Diffusion を動かせました。
GPU と設計
stability-sdkを使う前までは、自前で Stable Diffusion を動かす環境を用意しようと設計を考えました。設計の調査メモは、次のリンクにメモを残しています。
具体的に、次のようなパターンを考えました。
- Google Colaboratory の GPU を使って Stable Diffusion を動かし、簡易な API で公開する
- サーバー(GCE や CloudRun など) で GPU を使って Stable Diffusion を動かし、簡易な API で公開する
- バッチ(Cloud Batch)で GPU を使って Stable Diffusion を動かし、必要なときに動かす。(API からバッチ処理をキックする)
1 番目は、Google Colaboratory の利用は 12 時間制限というのがあり、そこを回避する何かが必要なります。ただし、本来の用途と外れていると思うので、却下しました。
2 番目は、金銭的に数万~数十万円以上のランニングコストが発生するので却下です。
3 番目は、一番最初の構想したものです。2 番目のような GPU のサーバを常時起動しているとめちゃくちゃもったいないので、 バッチ処理として 3 番目の案を考えていました。3 番目で実際に構築してみると、(何が原因か深く調べていないですが) 起動に 30 分以上かかってしまい、使い物にならなさそうでした。
で、悩んだ結果、stability-sdk がメンテナンスやランニングコストも不要で、シュッとできそうだったことに気づきました。
もちろん、デメリットはあります。
しかし、個人レベルで利用するという前提でしたので、デメリットよりもメリットの方が大きいと判断しました。
stability-sdk
beta.dreamstudio.ai は、Stable Diffusion を使っています。 API として、stability-sdk を公開しています。 使うには、Python で書く必要があります。 ソースコードを読むと、gRPC を使っているため、別言語で SDK を書くのは比較的簡単だと思います。 私は、Python でシュッと書けるので、flask と stability-sdk を使いました。
ひとまず、Prompt だけを受け付ける超絶シンプルな API を書きました。 stability-sdkは、様々パラメータがあるので、それも受け付けられるようにしようかなと思ったり、Midjourney の discord のボットのようなモノを書いても面白そうだなと思いました。
終わりに
マークダウンで、画像を読み込むときに、今回開発した API を指定すると、マークダウンを開いたタイミングで画像が毎回変わります。 prompt と seed を指定すれば固定できるんですけど、こういうのも面白いなと思っています。
ERNIE-ViLG を Google Colaboratory で動かしてみた
ERNIE-ViLG というのが、"二次元キャラ" に強いという記事を目にしました。
実際に使ってみようと、次のページで試したんですが、レスポンスがイマイチでした。
そこで、公式ページを参考にして、ERNIE-ViLG を Google Colaboratory を書こうと思いました。
Google Colaboratory で動かす
実際に作ったものは、次のモノです。
中身については、正直良くわかっていないですが(公式ページ 通りに試しただけ)、簡単に紹介しようと思います。
準備
次のコマンドを叩いて、ERNIE-ViLG の準備をします。(GPU 環境でないと動作しません)
$ pip install paddlepaddle-gpu -U $ pip install paddlehub==2.1.0
import paddlehub
paddlehub.server_check()
$ hub install ernie_vilg
ERNIE-ViLG を使う
使うのは、2 つのパターンがあります。
CLI の場合は、次のとおりです。
$ hub run ernie_vilg --text_prompts "宁静的小镇" --style "油画" --output_dir ernie_vilg_out
Python の場合は、次のとおりです。
import paddlehub as hub module = hub.Module(name="ernie_vilg") text_prompts = ["宁静的小镇"] images = module.generate_image(text_prompts=text_prompts, style='油画', output_dir='./ernie_vilg_out/')
オプションは、次の説明の通りです。
- text_prompts
- 生成したい画像の内容を記述した入力文
- style
- topk
- 生成する画像数(最大 6 枚)
- output_dir
- 保存先のディレクトリ (デフォルト:ernievilg_output)
text_prompts や style は、中国語で書く必要があります。
Google Colaboratory で 画像を簡単に見たい
ERNIE-ViLG を動かすと、出力ファイルが Google Colaboratory のフォルダに入ります。 画像を見るためには、画像をダウンロードして、開くという手間があります。
そこで、フォルダを Google Drive と同期するという機能があります。 これを使えば、保存先を Google Drive にしておけば、Google Drive の UI 上から画像を見ることができます。
めちゃくちゃ便利なので、ぜひ使ってみてください。
Midjourney, StableDiffusion で役立つPrompt フレーズ集
Midjourney や StableDiffusion を使っていると、どういうフレーズを使えばよいかわからなくなります。 そこで、フレーズ集を作って、Prompt で役立てたいなと思っています。
練習場
どこで Prompt の練習したら良いか悩むので、まとめておきました。 お勧めは、DreamStudio.ai です。
- Midjourney
- Discord
- 課金制(無料枠あり)
- Discord
- StableDiffusion
- OSS
- Google Colab
- WebApp
- DreamStudio.ai
- 課金制(無料枠あり)
- DreamStudio.ai
- Docker
Prompt で入力する文章構成
Prompt で入力する文章は、AI に理解しやすい構成である方が良いです。その方が欲しい画像を手に入れやすくなります。 文章の構成は、次のフォーマットです。
<全体のフォーマット> <主題> <主題の補足> <作者> <全体の補足> <フレーバー> 例: <全体フォーマット>Detailing oil painting of <主題>The great white castle on deep forest landscape <英霊>by CASPAR DAVID FRIEDRICH and CLAUDE LORRAIN, <全体の補足> perfect lighting, golden hour, <フレーバー> taken with Canon 5D Mk4
※ 魔術として理解するお絵描き AI 講座 より引用
各項目についてのフレーズを、まとめておきました。
全体のフォーマット
- Ancient of
- Beautiful concept art of
- Cartoon of
- Concept art of
- Detailed illustration of
- Detailed water painting of
- Detailing oil painting of
- Futuristic of
- Illustration of
- Image of nightmare of
- Logo about
- Pencil sketch of
- Photo of
- Pop art of
- Portrait of
- Scene of graphic novel that
- Scene of the movie that
- Screenshot of UE5 of
- Side profile of
- Sketch of
作者
- by <作者>
- in <作者> style
全体の補足
- 8k
- art
- beautiful shadow
- collection sheet
- comic
- golden hour
- grid
- kawaii
- manga
- perfect lighting
- pixiv
- realistic photo
- unreal engine
フレーバー
- 11mm
- by Canon EOS 5D Mark4 and SIGMA Art Lens 35mm F1.4 DG HSM, F1.4, ISO 200 Shutter Speed 2000
- EF11-24mm F4L USM
- no background
- taken with Canon 5D Mk4
- white background
終わりに
フレーズをまとめておくと、Prompt で文章入力するときに、参考にできて便利でした。 別件で、そもそも私には英語力がないため、Deepl を欠かせないことに気づきました...。 では、よい お絵かきライフを!