今回は Nim 言語を使って、Wikipedia の新着ファイルページから画像を収集するシンプルなクローラーを作ってみたいと思います
作業環境
HTML を取得する
まずは URL にアクセスして、HTML を取得するところまでやってみます
標準ライブラリの httpclient
を使って HTTP リクエストを送りました
import httpclient const url = "https://ja.wikipedia.org/wiki/特別:新着ファイル" var client = newHttpClient() var response = client.get(url) echo response.body
HttpClient を作成し、指定した URL にリクエストを送ってレスポンスを取得しています
これを実行してみると次のようなエラーが出ます
Error: unhandled exception: SSL support is not available. Cannot connect over SSL. [HttpRequestError] Error: execution of an external program failed: '/path/to/hogehoge'
このエラーはリクエスト先の URL が https の場合に出るエラーのようで、コンパイルするときに -d:ssl
をつけるとうまくコンパイルできるようです
-d:ssl
をつけてコンパイル、実行してみます
$ nim c -d:ssl -r hogehoge.nim ...(略) </body> </html>
これで HTML が取得できるようになりました!
HTML から img タグを抽出し、画像 URL を取得する
取得した HTML から正規表現で img タグのみ抽出していきます
正規表現は re
という標準ライブラリがあったので、これを使って img タグを取得します
import httpclient, re # re ライブラリを追加 const url = "https://ja.wikipedia.org/wiki/特別:新着ファイル" var client = newHttpClient() var response = client.get(url) # HTML から img タグのみ取得 var elements = response.body.findAll(re"<img(.*?)>")
findAll()
を使うとパターンにマッチした文字列を順に配列に格納していくようです
配列にうまく入っているか for 文を回して 1 つずつ出力してみます
for element in elements: echo element
$ nim c -d:ssl -r hogehoge.nim ...(略) <img src="/static/images/poweredby_mediawiki_88x31.png" alt="Powered by MediaWiki" srcset="/static/images/poweredby_mediawiki_132x47.png 1.5x, /static/images/poweredby_mediawiki_176x62.png 2x" width="88" height="31"/>
これで img タグのみ取得することができました!
次にそれぞれの img タグからさらに src 属性の値(画像 URL)を取得します
ここでも正規表現で img タグの中から src="〜"
の部分を抽出していきます
for element in elements: # img タグの中で src="〜" の部分が img タグ全体の何文字目から何文字目までかをタプルで取得 var range = element.findBounds(re"""src=\"(.*?)\"""") # img タグから src="〜" の部分を取得 var attr = element[range.first..range.last] # 最初の src=" と最後の " は不要なので、それ以外の部分を再度取得 var image = attr[5..^2]
findBounds()
を使うとパターンにマッチした部分が何文字目から何文字目までかを start, last が key のタプルで取得します
(re"""src=\"(.*?)\""""
の部分は re"src=\"(.*?)\""
ではエラーになってしまったので、このようなかたちになってしまいました・・・)
^2
の部分は負の値を設定しているところで ..^2
と書くと末尾から2文字目までという意味になるみたいです
Wikipedia の投稿画像は //upload.wikimedia.org/〜
という形式で img タグに設定されているようなので、次は画像にアクセスするために URL を組み立てていきます
まずは //upload.wikimedia.org/〜
という形式か簡単にチェックします
for element in elements: # ... if image[0..7] == "//upload": # ...
そしてスキームを先頭につけます
for element in elements: # ... if image[0..7] == "//upload": image = "https:" & image
これで画像 URL は完成しましたが、画像をローカルに保存するときはファイル名はそのままにしたいので、画像 URL からファイル名を取得します
for element in elements: # ... if image[0..7] == "//upload": image = "https:" & image var splited = image.split(re"/") var fileName = splited[high(splited)]
かなり強引なんですが、画像 URL を /
区切りで配列にし、その配列の最後の要素をファイル名として取得しています
画像 URL と保存時のファイル名が定義できたので、画像をダウンロードします
for element in elements: # ... if image[0..7] == "//upload": # ... client.downloadFile(image, fileName)
これで実行すると実行ファイルが置いてあるディレクトリに画像が保存されていくのが確認できると思います
完成したコードはこんな感じです
import httpclient, re const url = "https://ja.wikipedia.org/wiki/特別:新着ファイル" var client = newHttpClient() var response = client.get(url) var elements = response.body.findAll(re"<img(.*?)>") for element in elements: var range = element.findBounds(re"""src=\"(.*?)\"""") var attr = element[range.first..range.last] var image = attr[5..^2] if image[0..7] == "//upload": image = "https:" & image var splited = image.split(re"/") var fileName = splited[high(splited)] client.downloadFile(image, fileName)
まとめ
チュートリアル的な記事であったり、参考になるサイトや情報が少なく、ほとんど本家のドキュメントを見ながらの作業になりました
たぶんもっと良い書き方がたくさんあると思うので、色々ご指摘いただけると幸いです
- 作者: Dominik Picheta
- 出版社/メーカー: Manning Pubns Co
- 発売日: 2017/08/24
- メディア: ペーパーバック
- この商品を含むブログを見る