ページの中から lazy load の画像 URL を抽出する。
tags: Ruby Nokogiri lazyload
本記事は、「マンガサイトにつひての色々な事情 03 」 からのつづきになっています。
ここまでのこと
「マンガサイトにつひての色々な事情 02 」において設定した
目標
ゴール設定は、オンラインリーディング型のサイトであるマンガ Thank(仮称)にコンテンツとして使用されている漫画について、すべて権利者を割り出して、並べて見ること。
については、「マンガサイトにつひての色々な事情 03 」までで到達しました。
=====> ブクログ web本棚サービス 色々な事情 (色々な事情,マンガサイトにつひての)
この記事の内容
ここでは、画像ファイルがすべて cloudflare 管轄のドメインのものを使用されているのか確認したい・・・というステップになるでしょう。
あるマンガサイトに使われている特定のマンガコンテンツについては、全て網羅的な属性 ... 書誌情報を得ました。この記事では、さらにマンガコンテンツの画像のデータについてを扱います。
「マンガサイトにつひての色々な事情 02 」 の中の「違法と思われるマンガ Thank(仮称)の html の構造をよく確認する」で簡単に説明した箇所を引用します。
違法と思われるコンテンツである漫画のスキャンデータ、もしくは電子書籍マンガからのコピーである画像ファイルは、概ね cloudflare 社[1]のキャッシュのイメージファイルが <img> で直接指定されている。
また、lazy loading で読み込まれている。
仕組みとしてはページに別のサーバーにある WordPress で管理された画像群の画像ファイル(ここが cloudflare のキャッシュになっているということで、それは当該のファイルの URL を逆引きしてドメインからドメイン所有者をわりだすと cloudflare になっているということ。いずれ図説したい。)をページ内に lazy loading で読み込んで表示させていることが確認できる。
シンプルなので理解しやすいはずである。
画像ファイルの置いてあるドメインについては、こちらからどうぞ。
lazy load の箇所
<figure class="gallery-item">
<div class="gallery-icon portrait">
<img width="750" height="1181" src="src="https://ssl.クラウドフレアだよ.store/wp-content/plugins/a3-lazy-load/assets/images/lazy_placeholder.gif" data-lazy-type="image" data-src="https://ssl.クラウドフレアだよ.store/wp-content/uploads/2019/09/16/aaaaaa-750x1181.jpg" class="attachment-large size-large lazy-loaded" alt="" loading="lazy"><noscript><img width="750" height="1181" src="https://ssl.クラウドフレアだよ.store/wp-content/uploads/2019/09/16/aaaaaa-750x1181.jpg" class="attachment-large size-large" alt="" loading="lazy" /></noscript>
</div></figure>
<div class="gallery-icon portrait">
の中の <img>
をクローズアップする。
<img width="750" height="1181" src="https://ssl.クラウドフレアだよ.store/wp-content/plugins/a3-lazy-load/assets/images/lazy_placeholder.gif" data-lazy-type="image" data-src="https://ssl.クラウドフレアだよ.store/wp-content/uploads/2019/09/16/aaaaaa-750x1181.jpg" class="lazy-hidden attachment-large size-large" alt="" loading="lazy">
このような <img>
が、不特定多数 ( 50000*300 回程度頻出する可能性) あるような気がする・・・という気分で、ここからプリローダー画像ではない画像の URL ( attribute data-src
の値。)だけを抽出することを考える。
Rf. a modern vanilla JavaScript version of the original Lazy Load plugin
シンプルなサンプル
require 'nokogiri'
require 'open-uri'
require 'csv'
URL = 任意のアドレスまたは database から読み込む
doc = Nokogiri::HTML(URI.open(URL))
title = doc.css('title').inner_text
figure = doc.css('figure')
#puts figure
#puts figure.inner_html
#img_tag = figure.css("img[src$='.jpg']")
#arr = []
#img_tag.each do |line|
# arr.push(line.attr('src'))
#end
#arr.each do |data|
# CSV.open("list.csv","a+") do |f|
# f << [data]
# end
#end
figure.css('img').each do |tag|
if tag.attribute('data-src')
p tag.attribute('data-src').value
image = tag.attribute('data-src').value
CSV.open("image-list.csv","a+") do |f|
f << [URL,title,image]
end
end
end;nil
2 つのテーブルから読み込んだデータで lazy load の画像 URL を抽出する。
2つのデータベーステーブルは、こうなっていました。
mangathank.db
[tbl_manga]
id INTEGER PRIMARY KEY,
title text,
url text,
updated_datetime datetime,
author text,
book_title text
Rf.「ひとつめのデータベーステーブル [tbl_manga] 」
bookdata.db
[tbl_ bookdata]
id INTEGER PRIMARY KEY,
book_title text,
url text,
author text,
creatortranscription text,
volume text,
seriestitle text,
publisher text,
isbn text,
mangathank_title text,
ex_id integer
[tbl_manga] から [tbl_bookdata] へ
[tbl_bookdata] book_title
と author
でクエリを作り国会図書館サーチ API へ問い合わせ、[tbl_bookdata] のそれぞれのカラムへ書籍情報データを書き込む。
Rf.「ふたつめのデータベーステーブル [tbl_ bookdata] 」
2 つのテーブルから読み込んだデータで lazy load の画像 URL を抽出して csv ファイルに書き込む。
サンプル。よく読んで、ご注意ください。
require 'nokogiri'
#require 'open-uri'
require 'httparty'
require 'sqlite3'
require 'csv'
require 'time'
db1 = SQLite3::Database.open "mangathank.db"
db2 = SQLite3::Database.open "bookdata.db"
ex_id = 0
(1..51717).each do |count_up|
address = db1.execute("select url from tbl_manga where id = '#{count_up}'")
ex_title = db1.execute("select title from tbl_manga where id = '#{count_up}'")
isbn = db2.execute("select isbn from tbl_bookdata where ex_id = '#{count_up}'")
if isbn[0]
isbn = isbn[0].pop
else
isbn = ''
end
book = ex_title[0].pop
URL = address[0].pop
ex_id = count_up
puts ex_id ,puts
response = HTTParty.get(URL)
doc = Nokogiri::HTML(response.body)
#doc = Nokogiri::HTML(open(URL))
doc.remove_namespaces!
title = doc.css('title').inner_text
figure = doc.css('figure')
#puts figure
#puts figure.inner_html
figure.css('img').each do |tag|
if tag.attribute('data-src')
#p tag.attribute('data-src').inner_html
image = tag.attribute('data-src').value
CSV.open("image-list.csv","a+") do |f|
f << [book,ex_id,URL,title,isbn,image]
end
end
end
#sleep 0.01
end
db1.close
db2.close;nil
こういふリバースエンジニアリングのようなプログラミングは、ふいにどこかで止まる・・・ということを危惧して、ループが途中で止まってもカウンターの最後を確認できるようにしておく方が良い。
見たところ、やりたいことはわかるプログラムだが、51717 * 300 個の画像ファイル と仮定すると、かなりの数の行数になるので、きっと動かないだろう。
さらに、SQLite3 のテーブルに読み込むプログラムも込みだと数時間動いていたが、芳しくなかった。カラム数を考えてみた。数百万・・・たくさんになる(サイトで使用されているマンガの画像の数分あります)。
止まらないプログラムに修正
とてもたくさんの csv ファイルができることになる。
require 'nokogiri'
require 'open-uri'
#require 'httparty'
require 'sqlite3'
require 'csv'
require 'time'
db1 = SQLite3::Database.open("mangathank.db")
db2 = SQLite3::Database.open("bookdata.db")
ex_id = 0
num = 0
limit_num = 51717
1.step(limit_num,50) {|x|
(x..(x + 50)).each do |count_up|
if count_up > limit_num
break
end
if (count_up % 100) == 0
num += 1
end
address = db1.execute("select url from tbl_manga where id = '#{count_up}'")
ex_title = db1.execute("select title from tbl_manga where id = '#{count_up}'")
isbn = db2.execute("select isbn from tbl_bookdata where ex_id = '#{count_up}'")
author = db2.execute("select author from tbl_bookdata where ex_id = '#{count_up}'")
if isbn[0]
isbn = isbn[0].pop
else
isbn = ''
end
if author[0]
author = author[0].pop
else
author = ''
end
book = ex_title[0].pop
URL = address[0].pop
ex_id = count_up
puts
puts ex_id
puts author
puts book
puts
#response = HTTParty.get(URL)
#doc = Nokogiri::HTML(response.body)
doc = Nokogiri::HTML(open(URL))
doc.remove_namespaces!
title = doc.css('title').inner_text
figure = doc.css('figure')
#puts figure
#puts figure.inner_html
figure.css('img').each do |tag|
if tag.attribute('data-src')
#p tag.attribute('data-src').inner_html
image = tag.attribute('data-src').value
CSV.open("image-list_"+"#{num}"+".csv","a+") do |f|
f << [author,book,ex_id,URL,title,isbn,image]
end
end
end
sleep 0.01
end
}
db1.close
db2.close;nil
ドメイン
コンテンツページ内でローディングされる画像ファイルのあるドメインを調べるモジュール。
つまり、それぞれマンガ画像ファイルが、どこのドメインにあるか調べるため、画像ファイルまでのフルパスではなくて、ホスト名を抽出します。
たとえば、画像ファイルがこのように lazy load に指定されてあるとして、
<img width="750" height="1181" src="https://ssl.クラウドフレアだよ.store/wp-content/plugins/a3-lazy-load/assets/images/lazy_placeholder.gif" data-lazy-type="image" data-src="https://ssl.クラウドフレアだよ.store/wp-content/uploads/2019/09/16/aaaaaa-750x1181.jpg" class="lazy-hidden attachment-large size-large" alt="" loading="lazy">
画像までのフルパスは、data-src
の値ですから、
"https://ssl.クラウドフレアだよ.store/wp-content/uploads/2019/09/16/aaaaaa-750x1181.jpg"
です。
ここから、
"ssl.クラウドフレアだよ.store"
を抽出します。
temp_hostname = ''
figure.css('img').each do |tag|
if tag.attribute('data-src')
#p tag.attribute('data-src').inner_html
image = tag.attribute('data-src').value
###### ######
uri = URI.parse(image)
if temp_hostname != uri.host
temp_hostname = uri.host
CSV.open("imagefile-hostdomain.csv","a++") do |f|
f << [uri.host.to_s]
p uri.host
end
end
###### ######
CSV.open("image-list_"+"#{num}"+".csv","a+") do |f|
f << [author,book,ex_id,URL,title,isbn,image]
end
end
end
ひとつ前のプログラムコードに上記のホスト名を抽出するモジュールを追加して、さらに、存在する分だけデータベースimagefile_domain.db
に書き込むプログラムにしてみます。
すると、いったいいくつのホストドメインがあるのかわかります。
digging_imagefile-hostdomain.rb
code
require 'nokogiri'
require 'open-uri'
#require 'httparty'
require 'sqlite3'
require 'csv'
require 'time'
SQL =<<EOS
create table tbl_hostdomain (
id INTEGER PRIMARY KEY,
domain_name text
);
EOS
new_db = SQLite3::Database.open("imagefile_domain.db")
new_db.execute(SQL)
db1 = SQLite3::Database.open("mangathank.db")
db2 = SQLite3::Database.open("bookdata.db")
ex_id = 0
num = 0
limit_num = 51717
counter = 0
1.step(limit_num,50) {|x|
(x..(x + 50)).each do |count_up|
if count_up > limit_num
break
end
if (count_up % 100) == 0
num += 1
end
address = db1.execute("select url from tbl_manga where id = '#{count_up}'")
ex_title = db1.execute("select title from tbl_manga where id = '#{count_up}'")
isbn = db2.execute("select isbn from tbl_bookdata where ex_id = '#{count_up}'")
author = db2.execute("select author from tbl_bookdata where ex_id = '#{count_up}'")
if isbn[0]
isbn = isbn[0].pop
else
isbn = ''
end
if author[0]
author = author[0].pop
else
author = ''
end
book = ex_title[0].pop
URL = address[0].pop
ex_id = count_up
puts
puts ex_id
#puts author
#puts book
#puts
#response = HTTParty.get(URL)
#doc = Nokogiri::HTML(response.body)
doc = Nokogiri::HTML(URI.open(URL))
doc.remove_namespaces!
title = doc.css('title').inner_text
figure = doc.css('figure')
#puts figure
#puts figure.inner_html
temp_hostname = ''
figure.css('img').each do |tag|
if tag.attribute('data-src')
#p tag.attribute('data-src').inner_html
image = tag.attribute('data-src').value
###### ######
uri = URI.parse(image)
if temp_hostname != uri.host
temp_hostname = uri.host
if counter == 0 then
counter = 1
new_db.execute("insert into tbl_hostdomain (id, domain_name) values('#{counter}','#{uri.host}');")
end
search_flag = new_db.execute("select id from tbl_hostdomain where domain_name ='#{uri.host}' ;")
if search_flag.any? then
p counter,uri.host
next
else
counter += 1
new_db.execute("insert into tbl_hostdomain (id, domain_name) values('#{counter}','#{uri.host}');")
CSV.open("imagefile-hostdomain"+"#{num}"+".csv","a++") do |f|
f << [uri.host.to_s]
end
end
end
next
#この後はスキップされます
###### ######
CSV.open("image-list_"+"#{num}"+".csv","a+") do |f|
f << [author,book,ex_id,URL,title,isbn,image]
end
end
end
sleep 0.02
end
}
new_db.close
db1.close
db2.close;nil
csv から sqlite3 へ
require 'sqlite3'
require 'csv'
SQL =<<EOS
create table tbl_image_list (
id INTEGER PRIMARY KEY,
author text,
title text,
ex_id integer,
url text,
isbn text,
page_title text,
image_url text
);
EOS
count = 0
new_db = SQLite3::Database.open("image_list.db")
new_db.execute(SQL)
CSV.foreach('image-list_0.csv') do |line|
count += 1
author = line[0]
author.to_s.gsub!(/\'/,"\'\'")
title = line[1]
title.to_s.gsub!(/\'/,"\'\'")
ex_id = line[2]
url = line[3]
isbn = line[4]
page_title = line[5]
page_title.to_s.gsub!(/\'/,"\'\'")
image_url = line[6]
id = count
new_db.execute("insert into tbl_image_list (id, author, title, ex_id, url, isbn, page_title, image_url) values('#{id}','#{author}','#{title}','#{ex_id}','#{url}','#{isbn}','#{page_title}','#{image_url}');")
end
new_db.close
require 'nokogiri'
require 'open-uri'
require 'sqlite3'
#require 'csv'
require 'time'
db1 = SQLite3::Database.open("mangathank_new.db")
SQL =<<EOS
create table tbl_imgurl (
id INTEGER PRIMARY KEY,
ex_id integer,
imgurl text,
url text,
title text,
author text,
book_title text
);
EOS
last_id = 0
db1.execute("select id from tbl_manga order by id desc limit 1 ;") do |data|
last_id = data[0]
end
ex_id = 0
id = 0
total = 0
limit_num = last_id
current = 0
db2 = SQLite3::Database.open("imgurl"+"#{current}"+".db")
db2.execute(SQL)
#(1..last_id).each do |count_up|
1.step(limit_num,51) {|x|
(x..(x + 50)).each do |count_up|
if count_up > limit_num
break
end
if (count_up % 100) == 0
current += 1
db2.close
id = 0
db2 = SQLite3::Database.open("imgurl"+"#{current}"+".db")
db2.execute(SQL)
end
p count_up,current
address = db1.execute("select url from tbl_manga where id='#{count_up}' ;")
title = db1.execute("select title from tbl_manga where id='#{count_up}' ;")
book_title = db1.execute("select book_title from tbl_manga where id='#{count_up}' ;")
author = db1.execute("select author from tbl_manga where id='#{count_up}' ;")
pp title
if address[0]
address = address[0].pop
end
if title[0]
title = title[0].pop
end
if !book_title[0][0].empty?
book_title = book_title[0].pop
else
book_title = title.slice(/((?<=\]).+?$)/)
if book_title !=nil then
num = book_title.to_s.slice(/((?<=第)\d+(?=巻|卷$))/)
book_title.to_s.gsub!(/((?=第).*(巻|卷))/,'')
book_title.to_s.gsub!(/((?=第).*話)/,'')
book_title.to_s.gsub!(/(.(?<=\()文庫版(?=\)).)/,'')
book_title.to_s.gsub!(/(.(?<=\[)文庫版(?=\]).)/,'')
book_title.to_s.gsub!(/文庫版/,'')
book_title.to_s.gsub!(/(.(?<=\()完(?=\)).)/,'')
book_title.to_s.gsub!(/(.(?<=【).*(?=】).)/,'')
book_title.to_s.gsub!(/(.(?<=\[).+?(?=\]).)/,'')
book_title.to_s.gsub!(/\'/,"\'\'")
book_title.lstrip!
book_title.rstrip!
else
book_title = title
num = book_title.to_s.slice(/((?<=第)\d+(?=巻|卷$))/)
book_title.to_s.gsub!(/((?=第).*(巻|卷))/,'')
book_title.to_s.gsub!(/((?=第).*話)/,'')
book_title.to_s.gsub!(/(.(?<=\()文庫版(?=\)).)/,'')
book_title.to_s.gsub!(/(.(?<=\[)文庫版(?=\]).)/,'')
book_title.to_s.gsub!(/文庫版/,'')
book_title.to_s.gsub!(/(.(?<=\()完(?=\)).)/,'')
book_title.to_s.gsub!(/(.(?<=【).*(?=】).)/,'')
book_title.to_s.gsub!(/(.(?<=\[).+?(?=\]).)/,'')
book_title.to_s.gsub!(/\'/,"\'\'")
book_title.strip!
end
if num != nil then
num = num.to_i
book_title += ' ' + num.to_s
end
end
if !author[0][0].empty?
author = author[0].pop
else
author = title.slice(/(?<=\[).*?(?=\])/)
end
ex_id = count_up
# puts
# puts ex_id
# puts title
puts author
puts book_title
puts
url = address
doc = Nokogiri::HTML(URI.open(url))
doc.remove_namespaces!
page_title = doc.css('title').inner_text
#p page_title
figure = doc.css('figure')
#puts figure
#puts figure.inner_html
temp_amount = 0
figure.css('img').each do |tag|
if tag.attribute('data-src')
#p tag.attribute('data-src').inner_html
imgurl = tag.attribute('data-src').value
id += 1
total += 1
temp_amount += 1
# id # amount of total image file
db2.execute("insert into tbl_imgurl (id,ex_id,imgurl,url,title,author,book_title) values('#{id}','#{ex_id}','#{imgurl}','#{url}','#{title}','#{author}','#{book_title}') ;")
end
end
print 'amount of images:', temp_amount
puts
puts
#sleep 0.01
end
}
db2.close
db1.close
print 'amount of total images:',total
関連記事
-
空中分解…海賊版サイト対策検討会はなぜ迷走したか https://www.yomiuri.co.jp/fukayomi/20181017-OYT8T50059/2/ ↩︎
Discussion