ruby+selenium webdriverで使うCSVについていろいろ
別にseleniumじゃなくてもrubyで使うcsvライブラリ
rubyでCSVファイルを扱うための csvライブラリ 。
CSVファイルを読み込んだり、CSVファイルを出力したりといろいろできます。
別にseleniumを使うために用意されたライブラリでは決してないのですが、
私が携わったselenium webdriverでの案件では本当に多用してきました。
テストでの利用シーンでは、CSVでデータをダウンロードできる機能でファイルの中身が正しいかどうかの検証。
ブラウザ自動操作では、テストアカウントの読み込みや入力するデータをCSVファイルに記載して、それを読み込んでの実行。
スクレイピングでは取得したデータをCSVファイルで出力するという機能。
selenium webdriverを使っていて欠かせない技術がCSVファイルの扱いになります。
今回は私が使って来たcsvライブラリ(クラス)の機能をいくつかご紹介したいと思います。
CSVファイルを読み込む
CSVファイルを読み込むにはまずcsvライブラリを読み込む必要があります。
csvライブラリはrubyに標準で付いているので特にインストールは要りません。
以下をコードに記載してライブラリを読み込みましょう。
1 |
require 'csv' |
例としてCSVファイルを用意してみます。
1 2 3 4 |
# input.csv katsuya,saitama,36 emi,tokyo,24 taro,osaka,20 |
では、このファイルをinput.csvとして保存して読み込んでみましょう。
私が使うのはforeachとreadです(他にもあるのでしょうか)。
foreachの場合は、CSVファイルを読み込むために先に配列を宣言する必要があります。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
require 'csv' # 配列input_foreachを宣言 input_foreach = [] # input.csvの中身を行毎にinput_foreachの行に追加していく CSV.foreach("input.csv") do |row| input_foreach << row end # input_forachの中身をプリント p input_foreach # => [["katsuya", "saitama", "36"], ["emi", "tokyo", "24"], ["taro", "osaka", "20"]] |
このように2次元配列で格納されているのがわかります。
わかっていると思いますが2行目の3列目を取得したい場合は
1 |
input_foreach[1][2] |
で取得できます。
readの場合は配列を宣言する必要がありません。
1 2 3 4 5 6 |
# input_readに2次元配列として格納される input_read = CSV.read("input.csv") p input_read # => [["katsuya", "saitama", "36"], ["emi", "tokyo", "24"], ["taro", "osaka", "20"]] |
出力結果は同じですね。
行毎に何か加工したい場合はforeach、そのまま読み込みたい場合はreadを使えば良いのでしょうか?(そうなのかな)
ヘッダー有りのCSVファイルを読み込む
CSVファイルにわかりやすくヘッダーをつけてみましょう。
1 2 3 4 5 6 |
# input.csv name,location,age katsuya,saitama,36 emi,tokyo,24 taro,osaka,20 |
これを先程のまま読み込むと1行目にname,location,ageが入るだけですが、
headers:オプションをつけることによって、ヘッダー部分をハッシュのようにキーとして扱うことができます。
headers:オプションを付けた場合、一行目はカウントされないので注意です。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
require 'csv' input_read = CSV.read("input.csv", headers: true) input_foreach = [] CSV.foreach("input.csv", headers: true) do |row| input_foreach << row end p input_read[0]["name"] # => "katsuya" p input_foreach[1]["age"] # => "24" |
ヘッダーがついているCSVファイルを扱う時には可読性が高くなって便利です。
CSVファイルは列が100行とかにもなるのでheaders:オプションを使うのはオススメです。
なお、headers: falseとするとヘッダー行を読み込まずに2行目以降を読み込んでくれます。
エンコードオプションを付けて読み込み
文字コードはデフォルトではutf-8で読み込む(と思う)のですが、
SJISで書き込まれたファイルを読み込む場合は以下でOKです。
1 2 3 4 5 6 |
input_read = CSV.read("input.csv", encoding: "SJIS") input_foreach = [] CSV.foreach("input.csv", encoding: "SJIS") do |row| input_foreach << row end |
他のエンコードの場合はそれを指定すれば良いのはないのでしょうか(そんなことあるのかな)。
CSVファイルを出力
今までは読み込みをしてきたのですが、次はCSVファイルを出力してみましょう。
CSVファイルを作成、上書き保存
まずは1行のみのcsvファイルを作成してみましょう。
1 2 3 4 5 |
require 'csv' CSV.open("output.csv","w") do |file| file << ["name", "location", "age"] end |
ファイル名の後の引数 “w” がwriteを意味しています(たぶん)。
このコードを実装するとrbファイルと同じディレクトリに output.csv が作成されているのがわかります。
保存ディレクトリを変えたければ
1 2 3 |
CSV.open("./result/output.csv","w") do |file| file << ["name", "location", "age"] end |
などの相対パスにすればOKです。
では、次のように実装した場合はどうなるでしょう。
1 2 3 4 5 6 7 8 9 |
require 'csv CSV.open("output.csv","w") do |file| file << ["name", "location", "age"] end CSV.open("output.csv","w") do |file| file << ["katsuya", "saitama", "36"] end |
このコードを実行すると以下のような output.csv が出力されてます。
1 |
"katsuya", "saitama", "36" |
つまり2度目の書き込みで上書き保存してしまっています。
ではCSVファイルに行を追加していく方法を紹介しましょう。
CSVファイルへの行追加
“w”ではファイルの上書きになってしまいましたが、
“a”にすることで、行の追加を行うことができます(”a”はaddの略でしょうね)。
1 2 3 4 5 6 7 8 9 |
require 'csv CSV.open("output.csv","w") do |file| file << ["name", "location", "age"] end CSV.open("output.csv","a") do |file| file << ["katsuya", "saitama", "36"] end |
このコードを実行するとちゃんと2行の output.csv が出力されているのがわかります。
CSVファイルの読み込みのコードと組み合わせると。
1 2 3 4 5 6 7 8 9 |
require 'csv' input_read = CSV.read("input.csv") input_read.each do |row| CSV.open("output.csv", "a") do |file| file << row end end |
input.csv のデータがそのまま output.csv に書き込まれましたね。
CSVのテーブル構造をオブジェクトとして取り込む
CSVファイルをオブジェクトとして取り込むこともできます。
私の利用シーンとしては、CSVファイルを入力し、そのデータを加工して出力する際などに使います。
読み込むにはこれだけ。
1 2 3 4 5 6 |
require 'csv' table = CSV.table(input.csv, headers: true) p table.class # => CSV::Table |
tableがCSV::tableクラスになっていることがわかりますね。
input.csv の中身が table にオブジェクトとして格納しています。
tableの中身は2次元配列で参照することができます。
では、今まで使ってきた input.csv の”taro”を”jiro”に変えて output.csv に出力しましょう。
1 2 3 4 5 6 7 8 9 10 |
require "csv" table = CSV.table("input.csv", headers: true) table[2][:name] = "jiro" CSV.open("output.csv", "w") do |file| table.each do |line| file << line end end |
nameをhashで取り出していることに注意してください。
このコードを実行するとtaroがjiroになっていることがわかります。
書き込み先を input.csv にすれば input.csv を上書きできます。
おまけ:BOM付きCSVファイルの出力
スクレイピングなどでファイルを出力した時に、エクセルで開くと文字化けしてしまっているということがよくありました。
そのファイルをテキストエディタで開いてUTF-8のBOM付きで保存し直してから開いてくださいなんて頼むのは野暮です。
BOM付きのCSVファイルを作成することができます。
1 2 3 4 5 6 7 8 9 10 |
require 'csv' bom = %w(EF BB BF).map { |e| e.hex.chr }.join csv_file = CSV.generate(bom) do |row| row << ["name", "location", "age"] end File.open("bom.csv","w") do |file| file.write(csv_file) end |
CSV.generate でCSVのBOM付きのオブジェクトを作ってから、
File.openの”w”でBOM付きファイルを作成しています。
このファイルを”a”で上書きしていけば、BOM付きのままなので、エクセルで開いた時も文字化けせずに表示されます。
今回はselenium webdriverでよく使うCSVを紹介したのですが、
他にもCSVを扱うのに便利な方法があれば是非コメントしてください。