マンガサイト

漫画サイト。 それは漫画がブラウザで読める web サイトのことである。

漫画サイトには大きく分けて二種類ある。一方が違法サイトで、もう一方が合法サイトである。 仕組みは表面上はどちらもよく似ている。内部的には違うが、目的が違うので手段が違うと言っていいだろうが、大まかには同じようなものだ。

ここでは、違法サイトについて調べてみたい。

まず、あるひとつの違法サイトでのコンテンツの総数を数えてみたい。 現段階で、数え上げたのは四万四千のタイトル数だった。これは全てがコミックの1巻分ではなくて、第何話や、週刊誌の第何号というものを含めての数で、プラスこの数の20%分くらいがまだ隠れ層になっていて表出していない。

これはどう云うことかというと、いずれ説明するが、今回とった手法が関係しているが、例えば 100 回ランダムに違法サイトのページアドレスを出して、データベースに記録するということを繰り返して、次回はデータベースに記録されているもの以外をデータベースに記録していくようにしたとして、100 回のうち 20 回ほどが、記録されてないアドレスになってきた場合、続けていくと100 回のうち 15 、10 、とだんだんと記録されていないアドレスがランダムでは出にくくなっていくことになる。 このことをここで、隠れ層と云った。 あくまでも、違法サイトのコンテンツページのアドレスを前もって知っているわけではなく、ページにあるマンガ画像が表示されない方法で、ダウンロードせずにコンテンツが埋め込まれている URL を収集して、漫画のタイトルや著者情報を分析していく。

マンガサイトを調査 実践。( Ruby )

有効な URL と 有効ではない URL を調査し、データベースに記録しよう。

Ruby 言語を使って https リクエストして調査してみましょう。 有効なURLとは漫画の画像があるページのことになります。すなわち、マンガのサイトにある全てのコンテンツの数を全部数えてみようということになります。 対象は漫画Bankです。

Python 言語の場合はこっちにあります。

kuroca.hatenablog.com

ポリシーについてはこちら

kuroca.hatenablog.com

  • レポート
    f:id:kuroca:20211106114710p:plain
    Cloudflare ドメイン
    この日本語の翻訳ニュースの配信の約1時間後、漫画bankはサイトを閉鎖。 本稿以降のプログラムは対象のサイトが消失。

curl -is "https://mangabank.org/watch/?tour=/vol" | grep location

ターミナルコマンドを実行してリダイレクトされるアドレスを表示するには上記のようになりま。

require 'net/http'
require 'uri'
require 'sqlite3'


SQL =<<EOS
create table url (
    id INTEGER PRIMARY KEY,
    url text
    );
EOS

## ----------------------------------
# _INVALID DB ===> DB1
db1 = SQLite3::Database.open("mb_urls_INVALID.db")
tb1 = db1.execute("SELECT COUNT(*) FROM sqlite_master WHERE TYPE='table' AND NAME='url';")
if tb1[0][0] == 0 then
    db1.execute(SQL)
    last_id1 = 0
else
    last1 = db1.execute("SELECT id FROM url ORDER BY id DESC LIMIT 1;")
    puts "invalid"
    pp last1[0][0]
    puts " ---------------------------------- "
    last_id1 = last1[0][0]
end

# ----------------------------------
# _VALID DB ===> DB
db = SQLite3::Database.open("mb_urls_VALID.db")
tb = db.execute("SELECT COUNT(*) FROM sqlite_master WHERE TYPE='table' AND NAME='url';")
if tb[0][0] == 0 then
    db.execute(SQL)
    last_id = 0
else
    last = db.execute("SELECT id FROM url ORDER BY id DESC LIMIT 1;")
    puts "valid"
    pp last[0][0]
    puts " ---------------------------------- "
    last_id = last[0][0]
end

count = last_id
count1 = last_id1

# ----------------------------------
target_url ="https://mangabank.org/watch/?tour=/vol/"

#uri = URI.parse(target_url)

# extract redirect URL
def get_redirect_url(uri)
    redirect = Net::HTTP.get_response(uri)['location']
    return redirect
end

# ----------------------------------

50000.times do |repeat|

    uri = URI.parse(target_url)
    redirect_url = get_redirect_url(uri)
    puts redirect_url
    address = redirect_url

    s_number = address.split('/')[-1]
    puts s_number
    number = s_number.to_i 
    
    def dbr(address,number,db1,count1,db,count,second)
        gate = false
        again = false
        11.times do |x|
            puts " ---------------------------------- "
            puts x
            ###### <1 check invalid db
            if x == 0 then
                puts " ---------------------------------- "
                flag1 = nil
                flag1 = db1.execute("SELECT id FROM url WHERE url=\"#{address}\" ;")
                if !flag1.empty? then
                    puts "flag 1:INVALID DB id=#{flag1[0][0]} #{address}"
                    db1.execute("UPDATE url SET url='null' WHERE id=#{flag1[0][0]} ;")
                    puts "is VALID / remove from INVALID DB"
                    gate = true
                end
                if second != true then
                    number = number - 10    
                    address = "https://mangabank.org/#{number}/"
                end
            end
            ###### 1>
            if x != 0 then
                number = number + 1
                address = "https://mangabank.org/#{number}/"
                puts " ---------------------------------- "
                puts address
                ###### <2
                flag2 = nil
                flag2 = db1.execute("SELECT id FROM url WHERE url=\"#{address}\" ;")

                if !flag2.empty? then
                    puts "flag 2:INVALID DB id=#{flag2[0][0]} #{address}"
                    puts "#{number} + INVALID"

                    if gate != true then
                        next
                    end
                end
                ###### 2>
                puts " ---------------------------------- "
                uri = URI.parse(address)
                res = Net::HTTP.get_response(uri)
                puts "#{address} responce code: #{res.code}"

                if res.code != '200' then
                    count1 += 1
                    puts "insert INVALID DB id: #{count1} -> #{address}"
                    db1.execute("INSERT INTO url (id,url) VALUES (?,?)",count1,address )
#                    db1.commit
                    
                    if second == true then
                        break
                    end

                    next

                elsif !flag2.empty? and res.code == '200' then
                    puts "flag 2:INVALID DB id=#{flag2[0][0]} + #{address}"
                    db1.execute("UPDATE url SET url='null' WHERE id=#{flag2[0][0]} ;")
                    puts "is VALID / remove from INVALID DB"
                end
            end

            flag = nil
            flag = db.execute("select id from url where url=\"#{address}\" ;")

            if !flag.empty? then
                puts "flag 0: id=#{flag[0][0]} #{address}"
                puts "already exsist in DB"
                puts
                break
            else
                if x == 0 then
                    uri = URI.parse(address)
                    res = Net::HTTP.get_response(uri)
                    puts "#{address} responce code: #{res.code}"
                end

                if res.code == '200' then
                    count += 1
                    puts "insert VALID DB id: #{count} -> #{address}"
                    db.execute("INSERT INTO url (id,url) VALUES (?,?)",count,address )
#                    db.commit
                    again = true
                    next
                elsif res.code != '200' then
                    count1 += 1
                    puts "insert NOT-VALID-DB id: #{count1} -> #{address}"
                    db1.execute("INSERT INTO url (id,url) VALUES (?,?)",count1,address )
#                    db1.commit
                end
            end
        end
        return count1,count,address,again
    end

    second = false
    count1,count,address,again = dbr(address,number,db1,count1,db,count,second)

    puts

    limit = 5
    while again == true do
        puts " ---------------------------------- "
        puts "      " + limit.to_s
        number = number + 1
        address = "https://mangabank.org/#{number}/"
        puts address
        uri = URI.parse(address)
        res = Net::HTTP.get_response(uri)
        puts "#{address} responce code: #{res.code}"
        
        if res.code == '200' then
            second = true
            count1,count,address,again = dbr(address,number,db1,count1,db,count,second)
            limit = 5
        else
            limit -= 1
            puts "   Retry #{limit}"
        ###### <3
            flag1 = nil 
            flag1 = db1.execute("SELECT id FROM url WHERE url=\"#{address}\" ;")

            if !flag1.empty? then
                puts "flag 3: id=#{flag1[0][0]} #{address}"
                puts "#{number} INVALID"

                if limit < 1 then
                    again = false
                end
                next
            end
        ###### 3>
            count1 += 1
            puts "insert INVALID DB id: #{count1} -> #{address}"
            db1.execute("INSERT INTO url (id,url) VALUES (?,?)",count1,address )
##            db1.commit
            if limit < 1 then
                again = false
            end
        end
    end
end

db.close
db1.close

2つのデータベースをマージするためのもの。Ruby

##---- Lua
##local sqlite3 = require("lsqlite3")
##
##--[[local db1 = sqlite3.open('lua_mb_urls_not.db') -- Lua
##local db2 = sqlite3.open('mb_urls_not.db') -- other
##
##local newdb = sqlite3.open('mb_url_notadditive.db') -- for working space
##local newdb1 = sqlite3.open('lua_mb_url_not1.db') -- additive & order by url
##]]
##---- Lua

####----> Ruby >
##db1 = SQLite3::Database.open "lua_mb_urls_not.db" # Lua
##db2 = SQLite3::Database.open "mb_urls_not.db" # made from python code
##newdb = SQLite3::Database.open "mb_url_notadditive.db" # additive work space
##newdb1 = SQLite3::Database.open "mb_url_not1.db" # additive & order by url
####----< Ruby <

##---- Lua
##local db1 = sqlite3.open('lua_mb_urls_new.db') -- Lua
##local db2 = sqlite3.open('mb_urls_new.db') -- other
##
##local newdb = sqlite3.open('mb_url_additive.db') -- for working space
##local newdb1 = sqlite3.open('lua_mb_url_new1.db')
##
##newdb:exec[[
##  CREATE TABLE IF NOT EXIST url (id INTEGER PRIMARY KEY, url text);
##]]
##
##newdb1:exec[[
##  CREATE TABLE IF NOT EXIST url (id INTEGER PRIMARY KEY, url text);
##]]
##---- Lua

##----> Ruby >
require 'sqlite3'

db1 = SQLite3::Database.open "lua_mb_urls_new.db" # Lua
db2 = SQLite3::Database.open "mb_urls_new.db" # made from python code
newdb = SQLite3::Database.open "mb_url_additive.db" # additive work space
newdb1 = SQLite3::Database.open "mb_url_new1.db" # additive & order by url

SQL =<<EOS
create table IF NOT EXISTS url(
    id INTEGER PRIMARY KEY,
    url text
    );
EOS

newdb.execute(SQL)
newdb1.execute(SQL)

##----< Ruby <

##---- Lua
##local smt1 = "SELECT id FROM url ORDER BY id DESC LIMIT 1 ;" 
##
##local last1 = 0
##local last2 = 0
##
##for id in db1:urows(smt1) do
##    last1 = id
##end
##
##for id in db2:urows(smt1) do
##    last2 = id
##end
##
##print("db1 has "..last1.." URLs")
##print("db2 has "..last2.." URLs")
##---- Lua

##----> Ruby >
last1 = db1.execute("select id from url order by id desc limit 1")
last2 = db2.execute("select id from url order by id desc limit 1")

k = last1[0].pop # offset
l = last2[0].pop

puts "db1 has #{k} URLs"
puts "db2 has #{l} URLs"

i = 0

newdb_last = newdb.execute("select id from url order by id desc limit 1")
if !newdb_last.empty? then
    newcount = newdb_last[0].pop # offset
else
    newcount = 0
end
##----< Ruby <

##---- Lua
##local newcount = 0
##for last_id in newdb:urows(smt1) do
##    newcount = last_id
##end
##
##local i = 0
##
##for url_data in db2:urows("SELECT url FROM url ;") do
##--[[
##for url_data in db2:urows("SELECT url FROM url ORDER BY url ;") do
##]]
##    i = i + 1
##    print("copy from db2 ")
##    print(i)
##    print("total "..last2)
##
##    local existflag = 0
##
##    for exist in newdb:urows("SELECT id FROM url WHERE url=\""..url_data.."\" ;") do
##        existflag = exist
##        print(url_data.." is aleady exist in DB / skip")
##    end
##
##    if existflag == 0 then -- record
##        newcount = newcount + 1
##        print()
##        print("newdb : "..newcount.." "..url_data)
##        local stmt2 = newdb:prepare[[ INSERT INTO url VALUES (:id, :url) ]]
##        stmt2:bind_names{  id = newcount,  url = url_data    }
##        stmt2:step()
##        stmt2:reset()
##        stmt2:finalize()
##    end
##end
##
##db2:close()
##
##i = 0 -- reset
##
##for url_data in db1:urows("SELECT url FROM url ;") do
##--[[
##for url_data in db1:urows("SELECT url FROM url ORDER BY url ;") do
##]]
##    i = i + 1
##    print("copy from db1 ")
##    print(i)
##    print("total "..last1)
##
##    local existflag = 0
##    
##    for exist in newdb:urows("SELECT id FROM url WHERE url=\""..url_data.."\" ;") do
##        existflag = exist
##        print(url_data.." is aleady exist in DB / skip")
##    end
##
##    if existflag == 0 then -- record
##        newcount = newcount + 1
##        print()
##        print("newdb : "..newcount.." "..url_data)
##        local stmt2 = newdb:prepare[[ INSERT INTO url VALUES (:id, :url) ]]
##        stmt2:bind_names{  id = newcount,  url = url_data    }
##        stmt2:step()
##        stmt2:reset()
##        stmt2:finalize()
##    end
##end
##
##db1:close()
##---- Lua

##----> Ruby >
l.times do |index|
    i += 1
    res = db2.execute("select url from url where id='#{i}' ;")
    if res.empty? then
        puts "no data / skip"
        next
    end
    x = res[0][0] # url
    puts "copy from db2"
    p i,l

    res2 = newdb.execute("select url from url where url='#{x}' ;")
    if !res2.empty? then
        p  x
        puts 'already exist in db / skip'
      next
    end
    puts
    newcount += 1
    url = x
    #url.gsub!(/\'/,"\'\'")
    puts "#{newcount}  #{url}"
    newdb.execute("insert into url (id, url) values( '#{newcount}','#{url}') ;")
end

db2.close

i = 0
k.times do |index|
    i += 1
    res = db1.execute("select url from url where id='#{i}' ;")
    if res.empty? then
        puts "no data / skip"
        next
    end
    x = res[0][0] # url
    f = newdb.execute("select url from url where url='#{x}' ;")
    puts "copy from db1"
    p i,k

    if !f.empty? then
        p res[0]
        puts 'already exist in db / skip'
        next
    else
        newcount += 1
        url = x
        #url.gsub!(/\'/,"\'\'")
        puts "#{newcount}  #{url}"
        newdb.execute("insert into url (id, url ) values( '#{newcount}','#{url}') ;")
    end
end

db1.close
##----< Ruby <

##---- Lua
##print("new DB last id : "..newcount)
##---- Lua

##----> Ruby >
puts "new DB last id:#{newcount}"
##----< Ruby <

##---- Lua
##local newdb1_last = 0
##for last_id in newdb1:urows(smt1) do
##    newdb1_last = last_id
##end
##
##local counter = newdb1_last -- first time ==> 0
##---- Lua

##----> Ruby > 
newdb1_last = newdb1.execute("select id from url order by id desc limit 1")
if !newdb1_last.empty? then
    counter = newdb1_last[0].pop # offset
else
    counter = 0
end
##----< Ruby <

##---- Lua
##i = 0 -- reset
##---- Lua

##----> Ruby >
i = 0
##----< Ruby <

##---- Lua
##for url_data in newdb:urows("SELECT url FROM url ORDER BY url ;") do
##    i = i + 1
##    print("copy from newdb ")
##    print(i)
##    print("total "..newcount)
##
##    local existflag = 0
##    
##    for exist in newdb1:urows("SELECT id FROM url WHERE url=\""..url_data.."\" ;") do
##        existflag = exist
##        print(url_data.." is aleady exist in DB / skip")
##    end
##
##    if existflag == 0 then -- record
##        counter = counter + 1
##        print()
##        print("newdb1 : "..counter.." "..url_data)
##        local stmt2 = newdb1:prepare[[ INSERT INTO url VALUES (:id, :url) ]]
##        stmt2:bind_names{  id = counter,  url = url_data    }
##        stmt2:step()
##        stmt2:reset()
##        stmt2:finalize()
##    end
##end
##
##--[[
##newdb1:close() 
##newdb:close()
##]] -- end --
##---- Lua

##----> Ruby >
newdb.execute("select url from url order by url ;") do |row|
    i += 1
    url = row[0]
    #url.gsub!(/\'/,"\'\'")
    f = newdb1.execute("select url from url where url='#{url}' ;")
    puts "copy from newdb"
    p i,newcount

    if !f.empty? then
        p url
        puts 'already exist in db / skip'
        next
    else
        counter = counter + 1
        puts "#{counter}  #{url}"
        newdb1.execute("insert into url (id, url ) values( '#{counter}','#{url}') ;")
    end
end

newdb1.close
newdb.close
##----< Ruby <

漫画BANK マンガサイトを調査 実践。( python )

kuroca.hatenablog.com

kuroca.hatenablog.com

kuroca.hatenablog.com

Lisp 風だと

hy 1.0a3 using CPython(default) 3.9.7 on Linux
=> (import requests)
=> (setv res (requests.get "https://mangabank.org/watch/?tour=/vol/" :allow_redirects False))
=> (type res)                                                                                      
<class 'requests.models.Response'> 
=> (get res.headers "location")
pip install pycurl
import pycurl

c = pycurl.Curl()

#c.setopt(c.URL, 'https://mangabank.org/watch/?tour=/vol/')
## Follow redirect.
#c.setopt(c.FOLLOWLOCATION, 1) # True: 1 / False : 0
#for x in range(1):
#    c.perform()
#    #print(c.getinfo(c.REDIRECT_URL))
#    print(c.getinfo(c.EFFECTIVE_URL))
#
#c.close()

#import requests
import sqlite3


SQL = """
create table url(
    id int primary key,
    url text
    );
"""

## first time only
db = sqlite3.connect('mb_urls_not.db')
db.execute(SQL)
db.close()
db = sqlite3.connect('mb_urls_new.db')
db.execute(SQL)
db.close()


con1 = sqlite3.connect('mb_urls_not.db')
cur1 = con1.cursor()
con = sqlite3.connect('mb_urls_new.db')
cur = con.cursor()

last_id1 = cur1.execute("select id from url order by id desc limit 1;")

index_num1 = last_id1.fetchone()
if index_num1 != None:
    count1 = int(*index_num1)
else:
    count1 = 0

last_id = cur.execute("select id from url order by id desc limit 1;")

index_num = last_id.fetchone()
if index_num != None:
    count = int(*index_num)
else:
    count = 0

from urllib.parse import urlparse
import re

c.setopt(c.WRITEFUNCTION, lambda bytes: len(bytes))

for i in range(10000):
    # Follow redirect.
    c.setopt(c.FOLLOWLOCATION, 0) # True: 1 / False : 0
    c.setopt(c.URL, 'https://mangabank.org/watch/?tour=/vol/')
    c.perform()
    #res = c.getinfo(c.EFFECTIVE_URL)
    res = c.getinfo(c.REDIRECT_URL)
    address = res
    number = re.sub(r'\/','',urlparse(res).path)

    print()
    print("      " + number)
##    number = int(number) - 10
##    address = 'https://mangabank.org/'+str(number)+'/'

    def dbr(c,address,number,cur1,count1,cur,count,second):
        gate = False
        for x in range(11):
            print(x,end='  ')
            again = False 
            ###### <1 check invalid db 
            if x == 0:
                flag1 = None
                flag1 = cur1.execute("select id from url where url=? ;",[address])

                flagcheck = flag1.fetchone()
                if flagcheck != None:
                    print("flag 1: id=",*flagcheck,address)
                    cur1.execute("update url set url=?  where id=? ;",["null",*flagcheck])
                    #cur1.commit()
                    print(address,"is VALID / remove from NOT-VALID")
                    gate = True
                if second != True:
                    number = int(number) - 10
                    address = 'https://mangabank.org/'+str(number)+'/'
                    
            ###### 1>
            if x != 0:
                number = int(number) + 1
                address = 'https://mangabank.org/'+str(number)+'/'
#               print(address)
                ###### <2
                flag2 = None
                flag2 = cur1.execute("select id from url where url=? ;",[address])

                flagcheck = flag2.fetchone()
                if flagcheck != None:
                    print()
                    print("flag 2: id= ",*flagcheck,address)
                    print(number,"NOT-VALID")
                    if gate != True:
                        continue
                ###### 2>
                c.setopt(c.URL,address)
                c.setopt(c.FOLLOWLOCATION, 0) # True: 1 / False : 0
                c.perform()
                print(address,"responce code: ", c.getinfo(c.HTTP_CODE))
                if c.getinfo(c.HTTP_CODE) != 200:
                    count1 += 1
                    print("insert NOT-VALID-DB id: ",count1," -> ",address)
                    cur1.execute("insert into url (id,url) values(?,?) ;",[count1,address])
                    con1.commit()
                    if second == True:
                        break
                    continue
                elif c.getinfo(c.HTTP_CODE) == 200:
                    if flagcheck != None:
                        print()
                        print("flag 2-1: id=",*flagcheck,address)
                        cur1.execute("update url set url=?  where id=? ;",["null",*flagcheck])
                        #cur1.commit()
                        print(address,"is VALID / remove from NOT-VALID")



            flag = None
            flag = cur.execute("select id from url where url=? ;",[address])

            flagcheck = flag.fetchone()
            if flagcheck != None:
                print()
                print("flag 0: id= ",*flagcheck,address)
                print("already exsist in DB")
                print()
                break
            else:
                if x == 0 :
                    c.setopt(c.URL,address)
                    c.setopt(c.FOLLOWLOCATION, 0) # True: 1 / False : 0
                    c.perform()
                    print(address,"responce code: ", c.getinfo(c.HTTP_CODE))

                if c.getinfo(c.HTTP_CODE) == 200:
                    count += 1
                    print("insert id: ",count," -> ",address)
                    cur.execute("insert into url (id,url) values(?,?) ;",[count,address])
                    con.commit()
                    again = True
                    continue
                    #break 
                elif c.getinfo(c.HTTP_CODE) != 200:
                    count1 += 1
                    print("insert NOT-VALID-DB id: ",count1," -> ",address)
                    cur1.execute("insert into url (id,url) values(?,?) ;",[count1,address])
                    con1.commit()
        return count1,count,address,again

    second = False
    count1,count,address,again = dbr(c,address,number,cur1,count1,cur,count,second)
    
    print()
    limit = 5
    while again == True:
        print("      ", str(limit))
        number = re.sub(r'\/','',urlparse(address).path)
        number = int(number) + 1
        address = 'https://mangabank.org/'+str(number)+'/'
        c.setopt(c.URL,address)
        c.setopt(c.FOLLOWLOCATION, 0) # True: 1 / False : 0
        c.perform()
        print(address,"responce code: ", c.getinfo(c.HTTP_CODE))
        if c.getinfo(c.HTTP_CODE) == 200:
            second = True
            count1,count,address,again = dbr(c,address,number,cur1,count1,cur,count,second)
            limit = 5
        else:
            limit -= 1
        print("  retry ", str(limit))
        ###### <3
            flag1 = None
            flag1 = cur1.execute("select id from url where url=? ;",[address])

            flagcheck = flag1.fetchone()
            if flagcheck != None:
                print("flag 3: id=",*flagcheck,address)
                print( number,"NOT-VALID")
                
                if limit < 1:
                    again = False

                continue
        ###### 3>
            count1 += 1
            print("insert NOT-VALID-DB id: ",count1," -> ",address)
            cur1.execute("insert into url (id,url) values(?,?) ;",[count1,address])
            con1.commit()
            if limit < 1:
                again = False
c.close()
con1.close()
con.close()

漫画Bank マンガサイトを調査 実践。

有効な URL と 有効ではない URL を調査し、データベースに記録しよう。

Lua 言語で cURL のライブラリを使って https リクエストして調査してみましょう。

Python 言語の場合はこっちにあります。

kuroca.hatenablog.com

kuroca.hatenablog.com

kuroca.hatenablog.com

curl -is "https://mangabank.org/watch/?tour=/vol" | grep location

これは、上記のターミナルコマンドを実行してリダイレクトされるアドレスを記録していくプログラムです。 ただ、一度噛みつくと深く掘り返すようになっています。

local sqlite3 = require("lsqlite3")
local curl = require('cURL')
local db = sqlite3.open('lua_mb_urls_new.db')
local db1 = sqlite3.open('lua_mb_urls_not.db')


db:exec[[
  CREATE TABLE url (id INTEGER PRIMARY KEY, url);
]]
db1:exec[[
  CREATE TABLE url (id INTEGER PRIMARY KEY, url);
]]


--[[ lsqlite3 usage ]]-- 
--for row in db:nrows("SELECT * FROM url") do
--  print(row.id, row.url)
--end

--for row in db:rows("SELECT * FROM url") do
--  print(row[1], row[2])
--end

--for id,url in db:urows("SELECT * FROM url") do
--  print(id, url)
--end


local id_count = 0

for bigloop=1,1000 do

local easy = curl.easy{
    url            = "https://mangabank.org/watch/?tour=/vol",
  --followlocation = true,  -- true: 1
    followlocation = 0, -- false: 0
    maxredirs      = 2,
    [curl.OPT_VERBOSE] = 0,
}

local buffer = {}
easy:setopt_writefunction(table.insert, buffer)
-----------------------------------------------------------------
--[[ hettp request ]]--
easy:perform()

local res_code = easy:getinfo_response_code()
local res_location = easy:getinfo_redirect_url() -- 'URL'... redirect to 
print()
--print("code:",res_code) -- always 301 not 200
print("redirect URL:",res_location)

local address_code = res_location:match("%d+") -- "[0-9]+" string
local address_number = tonumber(address_code)

--[[ example: 'https://mangabank.org/2457593/' --]]

local smt1 = "SELECT id,url FROM url WHERE url=".."\""..res_location.."\""
--print(smt1)

local exist = false
---- VALID DB
for id,url in db:urows(smt1) do
-- print(id,url)
    exist = true
end

local gate = false
local remove = false
if exist == true then
    print(res_location.." is aleady exist in VALID-DB")
else-- exist == false
    gate = true

---- INVALID DB
    for id,url in db1:urows(smt1) do
        remove = true
        print(res_location.." is VALID.remove from INVALID-DB")
        local null_smt1 = "UPDATE url SET url='null' WHERE id="..id..";" 
        assert(db1:exec(null_smt1))
    end
    local id_count = 0
    for id in db:urows("SELECT id FROM url ORDER BY id DESC LIMIT 1;") do
        id_count = id
    end
    id_count = id_count + 1
    print(res_location.." record in VALID-DB id:"..tostring(id_count))
    local stmt2 = db:prepare[[ INSERT INTO url VALUES (:id, :url) ]]

    stmt2:bind_names{  id = id_count,  url = res_location    }
    stmt2:step()
    stmt2:reset()
    stmt2:finalize()
end

-----------------------------------------------------------------
if gate  == true then
    address_number = address_number - 11
    local record = false
    for i=1,20 do
        print("VALID DB DATA size :"..id_count)
        address_number = address_number + 1
        local next_url = "https://mangabank.org/"..tostring(address_number).."/"
        print()
        print("test:",next_url)
    
        smt1 = "SELECT id,url FROM url WHERE url=".."\""..next_url.."\""
        --print(smt1)
    
        local exist = false
        ---- VALID DB == db
        for id,url in db:urows(smt1) do
        -- print(id,url)
            exist = true
        end
        if exist == true then
            print(next_url.." is aleady exist in VALID-DB")
        else-- exist == false
            -- INVALID DB == db1
            for id,url in db1:urows(smt1) do
                exist = true
                if remove ~= true then
                    print(next_url.." is aleady exist in INVALID-DB")
                    goto continue -- go to next loop
                end
            end
            -- http request
            if nexe_url == res_location then
                res_code = 200
                goto recording -- skip http request
            end
            easy:setopt(curl.OPT_URL,next_url)
            easy:perform()
            res_code = easy:getinfo_response_code()
            
            ::recording::
            if res_code == 200 then
                record = true
                --local id_count = 0
                for id in db:urows("SELECT id FROM url ORDER BY id DESC LIMIT 1;") do
                    id_count = id
                end
                id_count = id_count + 1
                print(res_code,next_url.." record in VALID-DB id:"..tostring(id_count))
                print()
                local stmt2 = db:prepare[[ INSERT INTO url VALUES (:id, :url) ]]
            
                stmt2:bind_names{  id = id_count,  url = next_url    }
                stmt2:step()
                stmt2:reset()
                stmt2:finalize()
            else
                print(res_code,next_url.." is INVALID")
                print()
                record = false
    
                ---- INVALID-DB
                if remove == true then
                    for id,url in db1:urows(smt1) do
                        print(next_url.." is aleady exist in INVALID-DB")
                        goto continue -- go to next loop
                    end
                end
                local id_count1 = 0
                for id in db1:urows("SELECT id FROM url ORDER BY id DESC LIMIT 1;") do
                    id_count1 = id
                end
                id_count1 = id_count1 + 1
                print(res_code,next_url.." record in INVALID-DB id:"..tostring(id_count1))
                print()
                local stmt2 = db1:prepare[[ INSERT INTO url VALUES (:id, :url) ]]
            
                stmt2:bind_names{  id = id_count1,  url = next_url    }
                stmt2:step()
                stmt2:reset()
                stmt2:finalize()

            end
        end
        ::continue::
    end

    while record == true do
        for x = 1,6 do

            address_number = address_number + 1
            local next_url = "https://mangabank.org/"..tostring(address_number).."/"
            print()
            print("test:",next_url)
        
            smt1 = "SELECT id,url FROM url WHERE url=".."\""..next_url.."\""
            --print(smt1)
        
            local exist = false
            ---- VALID DB == db
            for id,url in db:urows(smt1) do
            -- print(id,url)
                exist = true
            end
            if exist == true then
                print(next_url.." is aleady exist in VALID-DB")
            else-- exist == false
                -- INVALID DB == db1
                for id,url in db1:urows(smt1) do
                    exist = true
                    print(next_url.." is aleady exist in INVALID-DB")
                    goto continue2 -- go to next loop
                end
                -- http request
                easy:setopt(curl.OPT_URL,next_url)
                easy:perform()
                res_code = easy:getinfo_response_code()
                
                if res_code == 200 then
                    record = true
                    --local id_count = 0
                    for id in db:urows("SELECT id FROM url ORDER BY id DESC LIMIT 1;") do
                        id_count = id
                    end
                    id_count = id_count + 1
                    print(res_code,next_url.." record in VALID-DB id:"..tostring(id_count))
                    print()
                    local stmt2 = db:prepare[[ INSERT INTO url VALUES (:id, :url) ]]
                
                    stmt2:bind_names{  id = id_count,  url = next_url    }
                    stmt2:step()
                    stmt2:reset()
                    stmt2:finalize()
                    
                    ---- INVALID-DB
                    if remove == true then
                        for id,url in db1:urows(smt1) do
                            print(next_url.." is exist in INVALID-DB but now VAILD.")
                            local null_smt1 = "UPDATE url SET url='null' WHERE id="..id..";" 
                            assert(db1:exec(null_smt1))
                            goto continue2 -- go to next loop
                        end
                    end
                else
                    print(res_code,next_url.." is INVALID")
                    print()
                    record = false
        
                    ---- INVALID-DB
                    if remove == true then
                        for id,url in db1:urows(smt1) do
                            print(next_url.." is aleady exist in INVALID-DB")
                            goto continue2 -- go to next loop
                        end
                    end
                    --local id_count1 = 0
                    for id in db1:urows("SELECT id FROM url ORDER BY id DESC LIMIT 1;") do
                        id_count1 = id
                    end
                    id_count1 = id_count1 + 1
                    print(res_code,next_url.." record in INVALID-DB id:"..tostring(id_count1))
                    print()
                    local stmt2 = db1:prepare[[ INSERT INTO url VALUES (:id, :url) ]]
                
                    stmt2:bind_names{  id = id_count1,  url = next_url    }
                    stmt2:step()
                    stmt2:reset()
                    stmt2:finalize()

                end
            end
            ::continue2::
        end
    end
end

easy:close()
end --[[big loop end]]--

db1:close()
db:close()

Lua 言語で軽いのと、わりと速いのですが数万件の https リクエストを処理するには時間がかかります。

複数のプロセスでリクエストして、あとから結果をマージすると速く多くのデータができます。 例えば、同じようなプログラムを他の言語(でなくてもいいですが)で書いて、同じような別のデータベースに記録するようにして、2つの結果のデータベースの中身を比べながら、どちらにも存在するデータ、どちらかにしかないデータをまた別のデータベースに記録していくと重複なくデータが抽出できます。 Ruby , python , Lua はよくにている言語なので、書き比べてみてはどうでしょうか。 マンガサイトを調査 実践。( python ) - 黒猫クックブック ちょっと毛色の違う Go 言語で書いてみると、ずいぶんと速かったので、http リクエストの実験として色々とやってみるのもいいかもしれません。

これは、釣り( fishing )です。 さびき釣りのようなイメージです。

ja.m.wikipedia.org

リクエストのリトライまでの間隔が短ければ、より速いので cURL を使い、さらに、リダイレクト先までフォローせずに、リクエストの戻ってきたヘッダーの中の location を読み取りリダイレクト先の URL を得ます。なるべくリクエストにかかる時間のオーバーヘッドを減らすということで速く釣りあげます。

2つのデータベースをマージするためのもの。Lua ( / Ruby )

local sqlite3 = require("lsqlite3")

--[[local db1 = sqlite3.open('lua_mb_urls_not.db') -- Lua
local db2 = sqlite3.open('mb_urls_not.db') -- other

local newdb = sqlite3.open('mb_url_notadditive.db') -- for working space
local newdb1 = sqlite3.open('lua_mb_url_not1.db') -- additive & order by url
]]

----> Ruby >
--db1 = SQLite3::Database.open "lua_mb_urls_not.db" # Lua
--db2 = SQLite3::Database.open "mb_urls_not.db" # made from python code
--newdb = SQLite3::Database.open "mb_url_notadditive.db" # additive work space
--newdb1 = SQLite3::Database.open "mb_url_not1.db" # additive & order by url
----< Ruby <

local db1 = sqlite3.open('lua_mb_urls_new.db') -- Lua
local db2 = sqlite3.open('mb_urls_new.db') -- other

local newdb = sqlite3.open('mb_url_additive.db') -- for working space
local newdb1 = sqlite3.open('lua_mb_url_new1.db')

newdb:exec[[
  CREATE TABLE IF NOT EXIST url (id INTEGER PRIMARY KEY, url text);
]]

newdb1:exec[[
  CREATE TABLE IF NOT EXIST url (id INTEGER PRIMARY KEY, url text);
]]

----> Ruby >
--require 'sqlite3'

--db1 = SQLite3::Database.open "lua_mb_urls_new.db" # Lua
--db2 = SQLite3::Database.open "mb_urls_new.db" # made from python code
--newdb = SQLite3::Database.open "mb_url_additive.db" # additive work space
--newdb1 = SQLite3::Database.open "mb_url_new1.db" # additive & order by url
--
--SQL =<<EOS
--create table IF NOT EXISTS url(
--    id INTEGER PRIMARY KEY,
--    url text
--    );
--EOS
--
--newdb.execute(SQL)
--newdb1.execute(SQL)
--
----< Ruby <

local smt1 = "SELECT id FROM url ORDER BY id DESC LIMIT 1 ;" 

local last1 = 0
local last2 = 0

for id in db1:urows(smt1) do
    last1 = id
end

for id in db2:urows(smt1) do
    last2 = id
end

print("db1 has "..last1.." URLs")
print("db2 has "..last2.." URLs")

----> Ruby >
--last1 = db1.execute("select id from url order by id desc limit 1")
--last2 = db2.execute("select id from url order by id desc limit 1")
--
--k = last1[0].pop # offset
--l = last2[0].pop
--
--puts "db1 has #{k} URLs"
--puts "db2 has #{l} URLs"
--
--i = 0
--
--newdb_last = newdb.execute("select id from url order by id desc limit 1")
--if !newdb_last.empty? then
--    newcount = newdb_last[0].pop # offset
--else
--    newcount = 0
--end
----< Ruby <

local newcount = 0
for last_id in newdb:urows(smt1) do
    newcount = last_id
end

local i = 0

for url_data in db2:urows("SELECT url FROM url ;") do
--[[
for url_data in db2:urows("SELECT url FROM url ORDER BY url ;") do
]]
    i = i + 1
    print("copy from db2 ")
    print(i)
    print("total "..last2)

    local existflag = 0

    for exist in newdb:urows("SELECT id FROM url WHERE url=\""..url_data.."\" ;") do
        existflag = exist
        print(url_data.." is aleady exist in DB / skip")
    end

    if existflag == 0 then -- record
        newcount = newcount + 1
        print()
        print("newdb : "..newcount.." "..url_data)
        local stmt2 = newdb:prepare[[ INSERT INTO url VALUES (:id, :url) ]]
        stmt2:bind_names{  id = newcount,  url = url_data    }
        stmt2:step()
        stmt2:reset()
        stmt2:finalize()
    end
end

db2:close()

i = 0 -- reset

for url_data in db1:urows("SELECT url FROM url ;") do
--[[
for url_data in db1:urows("SELECT url FROM url ORDER BY url ;") do
]]
    i = i + 1
    print("copy from db1 ")
    print(i)
    print("total "..last1)

    local existflag = 0
    
    for exist in newdb:urows("SELECT id FROM url WHERE url=\""..url_data.."\" ;") do
        existflag = exist
        print(url_data.." is aleady exist in DB / skip")
    end

    if existflag == 0 then -- record
        newcount = newcount + 1
        print()
        print("newdb : "..newcount.." "..url_data)
        local stmt2 = newdb:prepare[[ INSERT INTO url VALUES (:id, :url) ]]
        stmt2:bind_names{  id = newcount,  url = url_data    }
        stmt2:step()
        stmt2:reset()
        stmt2:finalize()
    end
end

db1:close()

----> Ruby >
--l.times do |index|
--    i += 1
--    res = db2.execute("select url from url where id='#{i}' ;")
--    if res.empty? then
--        puts "no data / skip"
--        next
--    end
--    x = res[0][0] # url
--    puts "copy from db2"
--    p i,l
--
--    res2 = newdb.execute("select url from url where url='#{x}' ;")
--    if !res2.empty? then
--        p  x
--        puts 'already exist in db / skip'
--      next
--    end
--    puts
--    newcount += 1
--    url = x
--    #url.gsub!(/\'/,"\'\'")
--    puts "#{newcount}  #{url}"
--    newdb.execute("insert into url (id, url) values( '#{newcount}','#{url}') ;")
--end
--
--db2.close
--
--i = 0
--k.times do |index|
--    i += 1
--    res = db1.execute("select url from url where id='#{i}' ;")
--    if res.empty? then
--        puts "no data / skip"
--        next
--    end
--    x = res[0][0] # url
--    f = newdb.execute("select url from url where url='#{x}' ;")
--    puts "copy from db1"
--    p i,k
--
--    if !f.empty? then
--        p res[0]
--        puts 'already exist in db / skip'
--        next
--    else
--        newcount += 1
--        url = x
--        #url.gsub!(/\'/,"\'\'")
--        puts "#{newcount}  #{url}"
--        newdb.execute("insert into url (id, url ) values( '#{newcount}','#{url}') ;")
--    end
--end
--
--db1.close
----< Ruby <

print("new DB last id : "..newcount)

----> Ruby >
--puts "new DB last id:#{newcount}"
----< Ruby <

local newdb1_last = 0
for last_id in newdb1:urows(smt1) do
    newdb1_last = last_id
end

local counter = newdb1_last -- first time ==> 0

----> Ruby > 
--newdb1_last = newdb1.execute("select id from url order by id desc limit 1")
--if !newdb1_last.empty? then
--    counter = newdb1_last[0].pop # offset
--else
--    counter = 0
--end
----< Ruby <

i = 0 -- reset

----> Ruby >
--i = 0
----< Ruby <

for url_data in newdb:urows("SELECT url FROM url ORDER BY url ;") do
    i = i + 1
    print("copy from newdb ")
    print(i)
    print("total "..newcount)

    local existflag = 0
    
    for exist in newdb1:urows("SELECT id FROM url WHERE url=\""..url_data.."\" ;") do
        existflag = exist
        print(url_data.." is aleady exist in DB / skip")
    end

    if existflag == 0 then -- record
        counter = counter + 1
        print()
        print("newdb1 : "..counter.." "..url_data)
        local stmt2 = newdb1:prepare[[ INSERT INTO url VALUES (:id, :url) ]]
        stmt2:bind_names{  id = counter,  url = url_data    }
        stmt2:step()
        stmt2:reset()
        stmt2:finalize()
    end
end

--[[
newdb1:close() 
newdb:close()
]] -- end --


----> Ruby >
--newdb.execute("select url from url order by url ;") do |row|
--    i += 1
--    url = row[0]
--    #url.gsub!(/\'/,"\'\'")
--    f = newdb1.execute("select url from url where url='#{url}' ;")
--    puts "copy from newdb"
--    p i,newcount
--
--    if !f.empty? then
--        p url
--        puts 'already exist in db / skip'
--        next
--    else
--        counter = counter + 1
--        puts "#{counter}  #{url}"
--        newdb1.execute("insert into url (id, url ) values( '#{counter}','#{url}') ;")
--    end
--end
--
--newdb1.close
--newdb.close
----< Ruby <

漫画サイトのたのしみ方

つかれたる牛のよだれはたらたらと千万年も尽きざるごとし

漫画サイト。 それは漫画がブラウザで読める web サイトのことである。

漫画サイトには大きく分けて二種類ある。一方が違法サイトで、もう一方が合法サイトである。 仕組みは表面上はどちらもよく似ている。内部的には違うが、目的が違うので手段が違うと言っていいだろうが、大まかには同じようなものだ。

ここでは、違法サイトについて調べてみたい。

まず、あるひとつの違法サイトでのコンテンツの総数を数えてみたい。 現段階で、数え上げたのは 51717 のタイトル数だった。これは全てがコミックの 1 巻分ではなくて、第何話や、週刊誌の第何号というものを含めての数で、プラスこの数の 20% 分くらいがまだ隠れ層になっていて表出していない。そしてコンテンツは日々、運営者によって更新されるので、時間とともに数は増えていく。

隠れ層 . . . これはどう云うことかというと、今回とった手法が関係しているが、例えば 100 回ランダムに違法サイトのページアドレスを出して、データベースに記録するということを繰り返して、次回はデータベースに記録されているもの以外をデータベースに記録していくようにしたとして、100 回のうち 20 回ほどが、記録されてないアドレスになってきた場合、続けていくと100 回のうち 15 、10 、5 とだんだんと記録されていないアドレスがランダムでは出にくくなっていくことになる。 このことをここで、隠れ層と云った。隠れ層とは云わないのかもしれない、全部ではないということ。隠れ層を見つけ出すという目的ではなく1、ある時点の表出しているコンテンツのメタ情報について考察していく。

kuroca.hatenablog.com

前提

あくまでも、違法サイトのコンテンツページのアドレスを前もって知っているわけではなく、ページにあるマンガ画像が表示されない方法で、ダウンロードせずにコンテンツが埋め込まれている URL を収集して、漫画のタイトルや著者情報を分析していくというパズルであり、漫画読みたいということではない。 スタンスとしては漫画家個人に寄りそう必要なデータを明瞭にオープンに出したい。このスタンスにおいては漫画家に不利益になることには言及しない。出版社、および漫画家の権利保守のための作業軽減に役立つことも提供できる可能性はあり。

漫画読みたい気持ちもある場合は、まずは合法マンガサイトを掘り進んでコードと構造に潜り込んでみるといい。 どの漫画家が作品を何点公開しているのかなど並べて見るプログラムを考えて書いてみるなどすると、いろいろサイトの事情も見えてくる。

code ⌨
合法マンガサイトでは、漫画家が作品を何点公開しているのか?
require 'open-uri'
require 'nokogiri'
require 'time'

address = 'https://www.mangaz.com/authors/list'
html = URI.open(address)
doc = Nokogiri::HTML(html)

mangaka ={}
count = 1

(16..794).each do |num|
    a_tag = doc.xpath('//a')[num]
    if a_tag.inner_text == 'ページの上部に戻る'
        next
    end

    count += 1
    author_profile_page = 'https://mangaz.com' + a_tag[:href].to_s
    author_name = a_tag.inner_text
    mangaka.store(author_name,author_profile_page)
end

total_titles = 0
manga = []
count = 0

mangaka.each do |key,value|
    html = URI.open(value)
    doc = Nokogiri::HTML(html)
    h4 = doc.css('h4')
    total_titles += h4.size
    a = doc.xpath('//h4/a')
    a.each do |atag|
        item_name = atag.inner_text
        item_url = atag['href']
        manga[count] = {'title' => item_name,
                        'book_profile_page' => item_url,
                        'author' => key,
                        'author_profile_page' => value
                        }
        count += 1

    end
    sleep 1
end

temp = ''
r18 =''
count = 0

manga.each_with_index do |data,index|
    if temp == data['author'] then
        count += 1
    else
        puts
        count = 1
    end
    if data['book_profile_page'].match("\/r18.")
        r18 = ' -R18- '
    else
        r18 =''
    end

    print index + 1, " #{r18} ",data['author'],"  #{count}  " ,data['title']
    puts
    temp = data['author']
    sleep 0.2
end
--- author, title, tag
temp = ''
r18 =''
count = 0

manga.each_with_index do |data,index|
    if temp == data['author'] then
        count += 1
    else
        puts
        count = 1
    end
    if data['book_profile_page'].match("\/r18.")
        r18 = ' -R18- '
    else
        r18 =''
    end

    print index + 1, " #{r18} ",data['author'],"  #{count}  " ,data['title']
    puts

    value = data['book_profile_page']
    html = URI.open(value)
    doc = Nokogiri::HTML(html)
    atag = doc.css('.tag > a')
    puts
    atag.each do |tag|
        print '   -tag-   ', tag.inner_text
        puts
    end
    puts

    temp = data['author']
    sleep 0.2
end
--- csv
require 'csv'
require 'httparty'
#require 'open-uri'
require 'nokogiri'
require 'time'

manga.each do |hash|
    CSV.open('mangaz-list.csv','a+') do |data|
        
        if hash['book_profile_page'].match("\/r18.")
            r18 = '-R18-'
        else
            r18 ='nonrestrictive'
        end

        value = hash['book_profile_page']
        #html = URI.open(value)
        html = HTTParty.get(value)
        doc = Nokogiri::HTML(html.body)
        doc.remove_namespaces!
        atag = doc.css('.tag > a')
        #doc = nil
        tags = ''
        atag.each do |tag|
            tags += tag.inner_text + ';'
        end
        
        data << [hash['author'].to_s,hash['author_profile_page'].to_s,
        hash['title'].to_s,hash['book_profile_page'].to_s,r18,tags]
        puts hash['title']
    end
    doc = nil
end
--- sqlite3
require 'sqlite3'
require 'csv'

SQL=<<EOS
create table tbl_manga_z (
    id integer,
    author text,
    author_profile_page text,
    title text,
    book_profile_page text,
    r18 text,
    tags text
);
EOS

db = SQLite3::Database.new("mangaz_list.db")
db.execute(SQL)

count = 0
CSV.foreach('mangaz-list.csv') do |line|
    count += 1
    author = line[0]
    author.gsub!(/\'/,"\'\'")
    author_profile_page = line[1]
    author_profile_page.gsub!(/\'/,"\'\'")
    title = line[2]
    title.gsub!(/\'/,"\'\'")
    book_profile_page = line[3]
    book_profile_page.gsub!(/\'/,"\'\'")
    r18 = line[4]
    #r18.gsub!(/\'/,"\'\'")
    tags = line[5]
    tags.gsub!(/\'/,"\'\'")
    id = count
    db.execute("insert into tbl_manga_z (id,author,author_profile_page,title,book_profile_page,r18,tags) values('#{id}', '#{author}','#{author_profile_page}','#{title}','#{book_profile_page}','#{r18}','#{tags}')")
end

db.close

マンガサイト観測 1

違法と思われる漫画サイトの中でシンプルな構造のオンラインリーディング型のサイト . . . マンガ Thank をターゲットに観測す。(注)マンガ Thank というサイトは存在せず、こころの中に存在すべし。

違法と思われるマンガ Thank(仮称)の HTML の構造をよく確認する

違法と思われるコンテンツである漫画のスキャンデータ、もしくは電子書籍マンガからのコピーである画像ファイルは、概ね cloudflare 社2のキャッシュのイメージファイルが <img> で直接指定されている。 また、lazy loading3 で読み込まれている。 仕組みとしてはページに別のサーバーにある WordPress で管理された画像群の画像ファイル(ここが cloudflare のキャッシュになっているということで、それは当該のファイルの URL を逆引きしてドメインからドメイン所有者をわりだすと cloudflare になっているということ。いずれ図説したい。)をページ内に lazy loading で読み込んで表示させていることが確認できる。

https://urlscan.io/

Cloudflare ドメイン

Cloudflare, Inc.(クラウドフレア)とは https://ja.wikipedia.org/wiki/Cloudflare

シンプルなので理解しやすいはずである。 つまり、オンラインリーディング型のサイト、マンガ Thank(仮称)のメインのコンテンツは画像だけなので、その画像がページ内にたとえば 100 点収まっているだけというシンプルな構造で、付加的にアップデート日時がタグで囲まれて配置されている。これはマンガ Thank(仮称)のシステム管理者用にあるのだと考えられる。 アップデート日時の情報は、マンガ Thank(仮称)のマンガコンテンツを表示するページのソースコード表示で確認できるが、ブラウザでレンダリングされた表示では見えないようにはなっている。

ひとつめのデータベーステーブル [tbl_manga]

このマンガ Thank(仮称)ページの構造から考えて、以下のようなデータベースを作ることにする。

[tbl_manga]

    id INTEGER PRIMARY KEY,
    title text,
    url text,
    updated_datetime datetime,
    author text,
    book_title text

カラム title の値は、ページにあるタイトルを抽出したもの。 カラム url の値は、コンテンツの埋め込まれたページの URL で、ページに埋め込まれた画像ファイルの URL ではない。画像の URL はこのステップでは収集しない。 画像ファイルがすべて cloudflare 管轄のドメインのものを使用されているのか確認したい段階で URL を収集することになるのかもしれないが、それ自体は非常に簡単にできる。ここでは割愛。 仕組み上、この url の値はユニークである。つまり重複はない。title が重複することがあっても url は重複しない。なぜかは考えればすぐにわかるはずだ。 updated_datetime は、ページソースコードにあるアップデートした日時を収める。ただし、これが正確なのかは不明なので、後にプロファイルするための参考にするために抽出する。 その他は、ターゲットのページから抽出するのではなく、抽出されたデータをもとに生成する。

Scraping data from the site 🍄

Play with webdriver and headless chromium browser sample:
urls = generated

require 'sqlite3'
require 'csv'
require 'time'
require 'date'

SQL =<<EOS
create table tbl_manga (
    id INTEGER PRIMARY KEY,
    title text,
    url text,
    updated_datetime datetime,
    author text,
    book_title text
);
EOS

db = SQLite3::Database.open "mangathank_new.db"
db.execute(SQL)
db.close

100.times do |loop|

    db = SQLite3::Database.open "mangathank_new.db"

    4.times do |loop|
        driver.get(urls)
        #sleep 1
        flag = []
        URL = driver.current_url
        flag = db.execute("select id from tbl_manga where url='#{URL}'")
        #p flag
 
        if flag.any? then
            next
        else
            CSV.open('mangathank_TEMP.csv','a+') do |data|
                title = driver.find_element(:class_name, 'entry-title')
                updated = driver.find_element(:class_name,'updated')
                updated_datetime = updated.attribute('datetime')
                title.text.to_s.rstrip!

                pp title.text

                data << [URL,title.text,updated_datetime]
            end
        end
    end
    
    if File.exist?("mangathank_TEMP.csv") then
        CSV.foreach('mangathank_TEMP.csv') do |line|
            count += 1
            url = line[0]
            url.to_s.gsub!(/\'/,"\'\'")
            title = line[1]
            author_name = title.slice(/(?<=\[).*?(?=\])/)
            updated_datetime = line[2]
            updated_datetime.to_s.gsub!(/\'/,"\'\'")
            id = count
            title.to_s.gsub!(/\'/,"\'\'")

            #puts title
            #puts author_name 
            
            db.execute("insert into tbl_manga (id, title, url, updated_datetime ) values('#{id}', '#{title}','#{url}','#{updated_datetime}')")
            
            if author_name != nil then #
                author_name.gsub!(/\'/,"\'\'")
                
                pp author_name

            end

            book_title = title.slice(/((?<=\]).+?$)/)
            if book_title then
                book_title.gsub!(/((?=).*(|))/,'')
                book_title.gsub!(/((?=).*)/,'')
                book_title.gsub!(/(.(?<=\()文庫版(?=\)).)/,'')
                book_title.gsub!(/(.(?<=\[)文庫版(?=\]).)/,'')
                book_title.gsub!(/文庫版/,'')
                book_title.gsub!(/(.(?<=\()(?=\)).)/,'')
                book_title.gsub!(/(.(?<=).*(?=).)/,'')
                book_title.gsub!(/(.(?<=\[).+?(?=\]).)/,'')
                book_title.gsub!(/\'/,"\'\'")
                book_title.lstrip!
                book_title.rstrip!
            else
                book_title = title
            end
 
            pp book_title

            db.execute("update tbl_manga set author = '#{author_name.to_s}', book_title = '#{book_title.to_s}' where id = '#{id}' ;")
        end

        db.close
        system("rm -r mangathank_TEMP.csv")
    else 
        next
    end
    db.close
end

目標 ゴール設定は、オンラインリーディング型のサイトであるマンガ Thank(仮称)にコンテンツとして使用されている漫画について、すべて権利者を割り出して、並べて見ること。

たとえば、コミックスであれば、たいていのもは出版社が発行しているので、その出版社名、書籍データを、違法にアップロードされたものからできるだけ正確に逆引きして表示するまでがひとまずの目標とする。曖昧なメタデータから、正確なメタデータへ変換するということ。

書籍データは ISBN4 をともなったものが多いが、全てではないのと、電子書籍は書籍という枠には入れられていないために ISBN での管理を外れていても例外とは云えない。 おおかたの書籍検索システムは ISBN コードを主軸としているので、ISBN を使って書籍データを問い合わせる仕組みになっているが、これでは要件に適していないので、本のタイトルから書籍データを問い合わせることが可能なシステムの API を使い、これを実現する

マンガサイト観測 2

マンガサイトにある 51717 タイトルの出版社情報を並べてみる。 マンガ 51717 タイトル list pdf マンガ 51717 タイトル list pdf

違法と思われるオンラインリーディング型サイトのマンガ Thank (仮称) から現時点で抽出された 51717 のタイトル情報を国会図書館 NDL (National Diet Library) サーチに問い合わせて書籍データを得る。

Rf. API仕様の概要 « 国立国会図書館サーチについて https://iss.ndl.go.jp/information/api/riyou/

マンガサイト観測 1 で解説したように ISBN ではなく、サイト内で独自に割り振られたタイトルと著者名らしき文字列の情報から、タイトル名と著者名を抜き出して、NDL ( National Diet Library ) サーチの API を使いデータ照会できるように、仕様に合わせたクエリをつくり HTTPS でリクエストする。

つまり、 ISBN はわからないので、タイトルから ISBN をわりだすということが可能な API を使うということになる。 このような場合、国会図書館サーチか amazonAPI かの選択になるが、今回は制限のほぼなさそうな国会図書館サーチを選ぶ。国立国会図書館サーチの使用法は仕様公開ページをよく読んでもどこか説明が足りないので、実際に使えるサンプルを探して試行錯誤する方がよいと思う。

マンガ Thank (仮称) のコンテンツのタイトルは独自につけられている為、というより何者かによってコンテンツ(スキャンされたマンガの画像のこと)がアップロードされた時点でメタ情報が入力されているので、そのメタ情報入力の際に明らかにタイトルの英単語のつづりを間違っているなどの場合がある。

これを間違ったそのままでクエリにして、 NDL サーチから正しい情報が引き出せないケースがあるが、現時点では、それは修正せずに間違っていようがタイトルからタイトルらしきものを文字列抽出し、著書名らしきものを文字列抽出してクエリに組み込むプログラムを作った。つまり照会結果が無い場合、なんらかのメタデータにパターンや平均的でない特徴が見られることが期待できる。

NDL search

この照会結果を新たなデータベースに書き込んでいくが、データベースのテーブルは以下のようになっている。

なぜデータベースを使うのか? コンテンツの数量が多いからである。 50000 を超えて、さらにあと 10% 前後はまだデータ未取得で、さらには日々増えているので、一気に全データを取得 . . . とは考えずに継続的に改良を加えながらデータをとっていく。

ふたつめのデータベーステーブル [tbl_ bookdata]

[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

マンガサイト観察 1 で用意した [tbl_manga] とは別に分けている。これは、 [tbl_manga] から読みだしたデータを使って、 NDL サーチにクエリをリクエストして得た情報を [tbl_bookdata] に書き込むということになる。

このデータベースのテーブル [tbl_bookdata] に書き込まれたものから、 id, seriestitle, publisher, url を抽出したものはこうなる。

id, seriestitle, publisher, url

 "1","null","null","null"
 "2","null","null","null"
 "3","null","null","null"
 "4","null","null","null"
 "5","null","Sony Music Labels","https://iss.ndl.go.jp/books/R100000002-I027014370-00"
 "49738","null","Sony Music Labels","https://iss.ndl.go.jp/books/R100000002-I027014370-00"
 "6","null","Sony Music Labels","https://iss.ndl.go.jp/books/R100000002-I027014370-00"
 "7","null","null","null"
 "8","null","null","null"
 "9","null","null","null"
 "10","null","null","null"
 "11","null","null","null"
 "12","null","アスキー・メディアワークス,KADOKAWA","https://iss.ndl.go.jp/books/R100000002-I024687562-00"
 "13","null","アスキー・メディアワークス,KADOKAWA","https://iss.ndl.go.jp/books/R100000002-I024687572-00"
 "14","null","null","null"
 "15","null","null","null"
 "16","null","null","null"
 "17","null","null","null"
 "18","null","null","null"
 "19","null","null","null"
 "20","null","null","null"
 "21","null","null","null"
 "22","null","平凡社","https://iss.ndl.go.jp/books/R100000002-I027189887-00"
 "23","null","平凡社","https://iss.ndl.go.jp/books/R100000002-I028029779-00"
 "24","null","平凡社","https://iss.ndl.go.jp/books/R100000002-I000011141069-00"
 "25","null","平凡社","https://iss.ndl.go.jp/books/R100000002-I023371158-00"
 "26","null","平凡社","https://iss.ndl.go.jp/books/R100000002-I024193406-00"
 "27","null","平凡社","https://iss.ndl.go.jp/books/R100000002-I025336987-00"
 "28","null","null","null"
 "29","角川コミックス・エース ; KCA500-1","KADOKAWA","https://iss.ndl.go.jp/books/R100000002-I026685661-00"
 "30","角川コミックス・エース ; KCA500-2","KADOKAWA","https://iss.ndl.go.jp/books/R100000002-I027116764-00"
 "31","YOUNG ANIMAL COMICS","白泉社","https://iss.ndl.go.jp/books/R100000002-I030414985-00"
 "32","YOUNG ANIMAL COMICS","白泉社","https://iss.ndl.go.jp/books/R100000002-I030704315-00"
 "33","YOUNG ANIMAL COMICS","白泉社","https://iss.ndl.go.jp/books/R100000002-I031233553-00"
 .
 .
 .
 .
 .
 .

コンテンツのタイトルのみで著者の情報がミッシングしている場合は、国会図書館 NDL サーチでは必ずしも正しい照会結果になるとは限らない。 例としては、上の囲みで"Sony Music Labels"となっている 3 行は、明らかに正しくない結果だが、照会結果がゼロではなく、見当違いのものにクエリがマッチしたということになる。

上の囲みので id, seriestitle, publisher, url という並びで 1 行になっている。 id はカウントアップされいく整数で、このデータベースでは 51717 行あるので 1~51717 まである。データベーステーブル [tbl_bookdata] で ex_id という整数のコラムを用意したが、ここへは [tbl_manga] の id の値が入る。ex_id も 1~51717 まである。ということは、[tbl_bookdata] と [tbl_manga] を内部結合に使うこともできる。

seriestitle, publisher については NDL サーチの結果のデータとして用意されているものだ。 seriestitle が連載誌名で publisher がその出版社名に当てはまる。これらはない場合もあるので、その場合は値は空になる。 [tbl_bookdata] においての url は、国会図書館サーチの結果の web ページの URL が値として入る。 [tbl_manga] においての url とは異なっていて、[tbl_manga] の urlマンガ Thank (仮称) のそれぞれのコンテンツの URL が収まっているので関連はあるが別のものを指している。 [tbl_bookdata] の urlマンガ Thank (仮称) のコンテンツはこの書誌であるという補足になっている関係になる。

また、国会図書館 NDL サーチでは、ことば(キーワード)の揺れにたいして特に寛容というわけでもない(が、アクセスの回数や頻度については明確な制限が提示されていないけれども、たいへん寛容である5)ので、ことばがマッチせずに探し出せないケースが多々ある。 独自に付けられた識別のことばが含まれたまま NDL サーチにクエリが送られた場合、マッチせずに結果が無い状態になり null で置き換えられる。 なるべくキーワードが NDL のデータベース上のものと一致するように、予め NDL 内での書籍データを確認して(何度かテストして失敗したもののクエリ内容と、手動で検索して発見される書籍データをよく見比べて)、違法漫画サイトで付与されているコンテンツタイトルを正規表現で NDL 内でのデータの収まり方に寄せて照会のリクエストのクエリを組むようにする。

例) コンテンツのタイトルのパターン

[田河水泡] のらくろ 漫画集 文庫版 第01巻

コンテンツのタイトルから正規表現を使って、必要のない文字列を除去し、タイトルと著者に分け、 NDL サーチにリクエストするクエリに組み込まれる。 "のらくろ 漫画集 1" "田河水泡"

のらくろ漫画集 (講談社): 1975|書誌詳細|国立国会図書館サーチ


例) コンテンツのタイトルのパターン

[長屋憲 × 佐藤秀峰] ブラックジャックによろしく 第01巻

コンテンツのタイトルから正規表現を使って、著者の候補を分けて、NDL サーチにリクエストするクエリをつくる。 "ブラックジャックによろしく 1" "佐藤秀峰" "長屋憲"

ブラックジャックによろしく 1 佐藤秀峰 - 国立国会図書館サーチ

ブラックジャックによろしく 1 長屋憲 - 国立国会図書館サーチ


詳細はこちら [NDL search (Ruby)]

出版社データ

以上のことを踏まえて、必ずしも正確ではない、コンテンツにたいして著作権を保持している可能性のある出版社を列挙す。

select distinct(publisher) from tbl_bookdata group by mangathank_title ;

出版社データ 🍞

 "null"
 "Sony Music Labels"
 "アスキー・メディアワークス,KADOKAWA"
 "平凡社"
 "KADOKAWA"
 "白泉社"
 "徳間書店"
 "Cygames,講談社"
 "集英社"
 "マッグガーデン"
 "角川書店,角川グループパブリッシング"
 "講談社"
 "少年画報社"
 "角川書店"
 "ヒーローズ,小学館クリエイティブ"
 "アスキー・メディアワークス,角川グループパブリッシング"
 "光文社"
 "メディアワークス,角川書店"
 "スクウェア・エニックス"
 "角川書店(発売),バンダイビジュアル (販売)"
 "EGMONT MANGA & ANIME"
 "エイベックス・エンタテインメント,エイベックス・マーケティング"
 "新書館"
 "バンダイビジュアル"
 "オーバーラップ"
 "秋田書店"
 "一迅社"
 "ホビージャパン"
 "キルタイムコミュニケーション"
 "マーベラスAQL,ポニーキャニオン"
 "一迅社,講談社"
 "アース・スターエンターテイメント,泰文堂"
 "アース・スターエンターテイメント"
 "Kadokawa"
 "KADOKAWA"
 "角川書店,KADOKAWA"
 "ハーレクイン"
 "日本文芸社"
 "星海社,講談社"
 "リイド社"
 "集英社クリエイティブ,集英社"
 "芳文社"
 "双葉社"
 "小学館"
 "バンダイナムコアーツ"
 "幻冬舎コミックス,幻冬舎(発売)"
 "TBS,ポニーキャニオン"
 "秋水社,大都社"
 "ジェネオン・ユニバーサル・エンターテイメント"
 "角川書店,角川グループホールディングス"
 "ブシロード,KADOKAWA"
 "富士見書房,角川グループパブリッシング"
 "久保書店"
 "マイクロマガジン社"
 "朝日ソノラマ"
 "朝日新聞社,朝日新聞出版"
 "朝日新聞出版"
 "創美社,集英社"
 "幻冬舎コミックス,幻冬舎"
 "アスキー・メディアワークス,Kadokawa"
 "TYPE-MOON,Kadokawa"
 "TYPE-MOON,KADOKAWA"
 "アイプロダクション,祥伝社"
 "ぶんか社"
 "TOブックス"
 "メディアファクトリー"
 "SBクリエイティブ"
 "角川グループパブリッシング"
 "アニプレックス"
 "PHP研究所"
 "イースト・プレス"
 "竹書房"
 "一二三書房"
 "コミックス,講談社 (共同刊行・発売)"
 "宙出版"
 "アスキー・メディアワークス,角川グループパブリッシング(発売)"
 "アスキー・メディアワークス,角川グループパブリッシング (発売)"
 "新潮社"
 "アルファポリス,星雲社"
 "エイベックス・ピクチャーズ"
 "小学館,ジェネオン・ユニバーサル・エンターテイメント"
 "Tonkam"
 "ジーオーティー"
 "Jパブリッシング"
 "ラポート"
 "中央公論社"
 "祥伝社"
 "ワニブックス"
 "アスキー・メディアワークス,角川グループホールディングス"
 "キングレコード"
 "ノース・スターズ・ピクチャーズ,徳間書店"
 "リブレ"
 "スーパー・ビジョン,ポリドール映像販売"
 "ノース・スターズ・ピクチャーズ,竹書房"
 "マガジンハウス"
 "フジテレビ映像企画部,ポニーキャニオン"
 "ジェネオンエンタテインメント"
 "主婦の友社"
 "NBCユニバーサル・エンターテイメント"
 "サード・ライン・ネクスト,星雲社 (発売)"
 "ぺんぎん書房"
 "宝島社"
 "マーベラスエンターテイメント,ポニーキャニオン"
 "みなみ出版,星雲社"
 "ホーム社"
 "青磁ビブロス"
 "ジャイブ"
 "学習研究社"
 "コロムビアミュージックエンタテインメント"
 "ビブロス"
 "ハーパーコリンズ・ジャパン"
 "アルファポリス,星雲社 (発売)"
 "SG企画"
 "ワーナー・ブラザース・ホームエンターテイメント"
 "ハピネット"
 "主婦と生活社"
 "ホーム社,集英社(発売)"
 "ホーム社,集英社"
 "学研プラス"
 "講談社,コミックス"
 "虫プロ商事"
 "TBS,日本コロムビア"
 "フォーラムエイトパブリッシング,フォーラムエイト (発売)"
 "文禄堂"
 "愛媛県教育会"
 "労働教育センター"
 "NHN comico,双葉社"
 "スターツ出版"
 "エンターブレイン,角川グループパブリッシング"
 "ラジオ大阪"
 "ポニーキャニオン"
 "GRINP"
 "Kodansha,ポニーキャニオン"
 "実業之日本社"
 "南海出版公司"
 "秋田書店,白泉社"
 "白泉社,集英社 (発売)"
 "あおば出版"
 "中央公論新社"
 "フロンティアワークス"
 "小池書院"
 "大都社"
 "小学館,メディアファクトリー"
 "東映ビデオ"
 "太田出版"
 "東宝"
 "フロンティアワークス,KADOKAWA"
 "ジュリアンパブリッシング"
 "星海社,講談社 (発売)"
 "ブライト出版"
 "オークラ出版"
 "誠文堂新光社"
 "角川書店,角川グループパブリッシング (発売)"
 "富士見書房,角川グループホールディングス"
 "フレックスコミックス,ソフトバンククリエイティブ"
 "SBクリエイティブ"
 "バップ"
 "G-NOVELS,誠文堂新光社"
 "NHN Comico,双葉社"
 "LINE,日販アイ・ピー・エス"
 "LINE Digital Frontier,日販アイ・ピー・エス"
 "サンリオ"
 ""
 "ネクストF,ジャイブ"
 "三交社"
 "自称清純派"
 "フレックスコミックス"
 "ポッポ焼き屋"
 "彗星社,星雲社"
 "HSU出版会,幸福の科学出版"
 "松竹"
 "フジテレビ,東宝"
 "エンターブレイン,角川グループホールディングス"
 "リブレ出版"
 "フレックスコミックス,ほるぷ出版"
 "飛鳥新社"
 "LDH pictures,バップ"
 "Avex Pictures"
 "ソフトバンククリエイティブ"
 "「インベスターZ」製作委員会,バップ"
 "大和書房"
 "湖南美术出版社"
 "冬水社"
 "エンターブレイン,KADOKAWA"
 "インデックス・コミュニケーションズ"
 "モール・オブ・ティーヴィー"
 "幻冬舎"
 "テレビ東京,ポニーキャニオン"
 "新紀元社"
 "コアマガジン"
 "サンタスティック・エンタテイメント"
 "NBCユニバーサル・エンターテイメント,エイベックス・ピクチャーズ"
 "Viz Media"
 "フジテレビジョン,ポニーキャニオン"
 "ワニマガジン社"
 "朝日新聞社"
 "オレンジページ"
 "文藝春秋"
 "コミックス,講談社"
 "富士見書房,KADOKAWA"
 "M'sワールド,GPミュージアムソフト"
 "山と溪谷社"
 "「嬢王3~Special Edition~」製作委員会,東宝"
 "エンターブレイン"
 "林檎プロモーション"
 "[八木戸マト]"
 "「Claymore」製作委員会,エイベックス・マーケティング"
 "トゥーマックス,エイベックス"
 "トゥーマックス,エイベックス・ディストリビューション"
 "トゥーマックス,avex distribution"
 "エンターブレイン,角川グループパブリッシング (発売)"
 "ソニー・マガジンズ"
 "ロングランドジェイ,ジーウォーク"
 "ハーレクイン・エンタープライズ日本支社"
 "早川書房"
 "スタジオDNA"
 "エニックス"
 "KADOKAWAメディアファクトリー"
 "エイベックス・マーケティング"
 "河出書房新社"
 "ワンツーマガジン社"
 "コアミックス"
 "小学館クリエイティブ,小学館"
 "メディアワークス,主婦の友社"
 "メディアワークス"
 "松竹映像商品部"
 "東映ビデオ,東映"
 "ベストフィールド"
 "日本評論社"
 "Ariola Japan"
 "リンダパブリッシャーズ,徳間書店"
 "ひばり書房"
 "ワーナー・ブラザースホームエンターテイメント"
 "OKAWA-Verlag"
 "OKAWA-VERLAGS GMBH"
 "สยามอินเตอร์คอมิกส์"
 "東芝エンタテインメント,ジェネオンエンタテインメント"
 "偕成社"
 "マーベラスエンターテインメント,松竹ビデオ事業室"
 "Nozomi entertainment : Right Stuf"
 "云南人民出版社"
 "민음사"
 "Gantz Partners,松竹ビデオ事業室"
 "日本放送出版協会"
 "スタジオ・シップ"
 "近代映画社"
 "小学館,コロムビアミュージックエンタテインメント"
 "宝塚クリエイティブアーツ"
 "AKS"
 "台灣東販"
 "ヒーローズ,小学館クリエイティブ (発売)"
 "創美社"
 "スコラ"
 "テレビ朝日,ポニーキャニオン"
 "マーベラスエンターテイメント,メディアファクトリー"
 "ネクストF,ジャイブ (発売)"
 "ギャガ"
 "フリュー,エイベックス・ピクチャーズ"
 "ABCライツビジネス,ポニーキャニオン"
 "[集英社]"
 "Bbmfマガジン"
 "青泉社"
 "潮出版社"
 "白泉社,集英社"
 "筑摩書房"
 "フジテレビジョン"
 "幻冬舎コミックス,幻冬舎 (発売)"
 "ビズコミュニケーションズジャパン"
 "メディアワークス,角川グループパブリッシング"
 "一賽舎"
 "フロンティアワークス,NBCユニバーサル・エンターテイメント"
 "一賽舎,スタジオDNA"
 "講談社コミッククリエイト,講談社"
 "ブッキング"
 "ブシロードメディア,KADOKAWA"
 "Carlsen"
 "茜新社"
 "メディアワークス,角川書店,角川グループパブリッシング"
 "ラクセント,フロンティアワークス"
 "小学館クリエイティブ,小学館 (発売)"
 "[斎創@さいそう。]"
 "マーベラスエンターテイメント,エイベックス・マーケティング・コミュニケーションズ"
 "ビクターエンタテインメント"
 "大陸書房"
 "モーターマガジン社"
 "扶桑社"
 "時鐘舎,北國新聞社"
 "Gzブレイン,KADOKAWA"
 "KADOKAWA Game Linkage,KADOKAWA"
 "KADOKAWA Game Linkage,KADOKAWA (発売)"
 "JICC出版局"
 "エイベックス,Avex Distribution"
 "バードランドミュージックエンタテインメント (発売),アドニス・スクウェア (販売)"
 "フロンティアワークス,KADOKAWAメディアファクトリー"
 "富士見書房"
 "「新米姉妹のふたりごはん」製作委員会,ポニーキャニオン"
 "「怨み屋本舗reboot」製作委員会,東宝"
 "デジタルサイト,ジェネオンエンタテインメント"
 "ハーレクイン,洋販"
 "東芝エンタテインメント,ポニーキャニオン"
 "小学館,ジェネオンエンタテインメント"
 "ユニバーサル・ピクチャーズ・ジャパン,ジェネオンエンタテインメント"
 "ミューズ・プランニング,エイベックス・マーケティング"
 "集英社クリエイティブ"
 "関西テレビ放送,ポニーキャニオン"
 "ひかりのくに"
 "東映アニメーション,ジェネオン・エンタテインメント"
 "ネルケプランニング"
 "東映アニメーション,東映"
 "日本コロムビア"
 "二見書房"
 "講談社 : 講談社コミッククリエイト"
 "学習研究社,少年画報社"
 "Tokyopop"
 "ベストセラーズ"
 "岩崎書店"
 "小学館,エイベックス・マーケティング・コミュニケーションズ"
 "小学館,エイベックス・マーケティング"
 "笠倉出版社"
 "外道高校野球部,東宝"
 "マーベラスエンターテイメント,キングレコード"
 "ハーヴェスト出版,星雲社"
 "プランタン出版,フランス書院"
 "宙出版,主婦と生活社"
 "エスピーオー"
 "白泉社,ジェネオンエンタテインメント"
 "Glénat"
 "Planet Manga : Panini Comics"
 "Pika édition"
 "小学館,ポニーキャニオン"
 "フジテレビジョン,よしもとミュージック"
 "青林堂"
 "富士見書房,角川書店"
 "ジェネオン・エンタテインメント"
 "GDH"
 "NHKソフトウェア,ジェネオンエンタテインメント"
 "若木書房"
 "エンジェル出版"
 "松文館"
 "テレビ東京,バップ"
 "comico,双葉社"
 "日本映像,フルメディア"
 "VERTICAL"
 "宙出版,主婦の友社"
 "サード・ライン・ネクスト,星雲社"
 "インテルフィン"
 "ポプラ社"
 "秋水社,双葉社"
 "シンエイ動画,バンダイビジュアル"
 "楽楽出版"
 "メディエイション,廣済堂出版"
 "金の星社"
 "ランティス,キングレコード"
 "メイド様!プロジェクト,ジェネオン・ユニバーサル・エンターテイメント"
 "藤子不二雄ファンサークルネオ・ユートピア"
 "講談社,バンダイビジュアル"
 "「新宿セブン」製作委員会,東宝"
 "大垣書店"
 "DREAMUSIC PUBLISHING,KING RECORDS"
 "講談社インターナショナル"
 "Funimation Entertainmment"
 "アスキー,アスペクト"
 "アスキー"
 "テレビ朝日,ジェネオンエンタテインメント"
 "テレビ東京 (製作),創通映像 (製作),シンエイ動画 (製作),バンダイビジュアル (発売)"
 "クロスメディア・パブリッシング,インプレス"
 "主婦の友インフォス,主婦の友社"
 "ミリオン出版,大洋図書"
 "SQUARE ENIX"
 "姉妹社"
 "ランティス,バンダイビジュアル"
 "VIZ Media,LLC"
 "長春出版社"
 "ゴマブックス"
 "三栄書房"
 "Bbmfマガジン,グリーンアロー出版社"
 "テレビ東京,エイベックス・ピクチャーズ"
 "東京漫画社"
 "シンエイ動画"
 "コスミック出版"
 "キングレコード,ポニーキャニオン"
 "小学館,エイベックス・ディストリビューション"
 "GDH,ビクターエンタテインメント"
 "国書刊行会"
 "NHKエンタープライズ"
 "小学館クリエイティブ"
 "フェアベル"

"青林堂"が含まれている。青林工藝舎は含まれていない。

著者データ

マンガ家の方のチェック用CVSAuthor (著者)Publisher (出版社)のデータのみ確認できるファイル。

author_publisher.csv

https://we.tl/t-HbrbAUUZFB?src=dnlwe.tl

https://we.tl/t-TYe3kJsh6qwe.tl

これはつまり、ここに名前があれば、あなたの著作が、おそらく無断で掲載されているので、あなたは当事者ですからテイクダウンする対策をこうじてくださいね、ということで候。

マンガサイト観測 3

マンガ Thank(仮称)のなかにあるテキストデータで、コンテンツの内容を表現したタイトル( title )をいくつか抽出して、その文字列で google 検索してみると、マンガ Thank(仮称)以外のページがヒットする場合があることに気がつくだろう。

当初考えていたのは、マンガ Thank(仮称)というサイトで、表示されているコンテンツ(つまりマンガのスキャンデータ)は、サイトの運営者がスキャンして、それをアップロードしているものと考えていた。

NDL サーチにヒットしない文字列のケースを観察して、正規表現のパズルを解き続けていると、なぜ、入力するメタデータに一定の命名規則がきちんと適用されないのかという疑問がわいた。その理由はいくつか考えられる。クオリティに明らかなばらつきが見られることから、少なくともメタデータを入力している作業者は複数で、最低限の文字処理の知識、データベースで運用する前提の知識のコモンセンスは徹底されてないことは確かである。

だが、マンガ Thank (仮称)で使っているタイトルの文字列のまま、他のサイトでも使われているということ、また、そのサイト mangaΠ (仮称) というのが、オンラインリーディング型ではなくて、ダウンロード型・・・つまり、マンガの画像データを zip, rar など圧縮してまとめてダウンロードさせる配布サイトで、それもどうやらマンガ Thank(仮称)よりも保有コンテンツ数が多いような雰囲気(印象であって不確かなもの)があるので、これはもしかして、ダウンロードサイトからコンテンツをダウンロードし、そのファイルを解凍し、それをマンガ Thank(仮称)のコンテンツとして使用しているのではないか?という予測に至った。

これはコンテンツのアップロードの日時を比較し、ファイルの内容を比較すれば、流れのつじつまはひとつ確認できるのであろうけれども、未確認である。

この仮説が示すのは、エコサイクルが形成されているということ。 つまり、マンガ Thank(仮称)の運営側には、マンガをスキャンしてアップロードする作業スタッフを抱えていないのかもしれないということだ。

そのステップは、他にあって、オンラインリーディング型のサイトマンガ Thank (仮称)とダウンロード型サイト mangaΠ (仮称) は、双方の運営は、まったくの無関係でいながら結果的に分業している場合もありうる。それは、わからないことだが。

マンガ Thank(仮称)に限ったことで、問題は、このコンテンツである画像ファイルは cloudflare のドメインにあることだ。

cloudflare のドメインにある画像ファイルを直接読み込むようにしているため、cloudflare にコンテンツ配信のキャッシュファイルを個別に配信停止するように求めなければ、じつはこれは、画像ファイルの URL さえ記述すればどのウェブサイトであろうと、コンテンツを公開できてしまうということに候。

仮にマンガ Thank(仮称)がドメインごと消えた場合でも、再度画像をアップロードすることなしに新たに同じようにコンテンツが配信される可能性はあるし、全く新たに似たようなサイトが始動することもありえる。

つづく


  1. それもパズルなり。https://kuroca.hatenablog.com/entry/20211009/1633774217

  2. 空中分解…海賊版サイト対策検討会はなぜ迷走したか https://www.yomiuri.co.jp/fukayomi/20181017-OYT8T50059/2/

  3. A lightweight but powerful delayed content, image and background lazy-loading plugin for jQuery & Zepto http://jquery.eisbehr.de/lazy/example_basic-usage

  4. 日本図書コード管理センター https://isbn.jpo.or.jp/index.php/fixabout/fixabout_3/

  5. ※アクセス数の上限につきましては、サービスへの影響等を含めて総合的に判断されるため、具体的な数値の目安をお示しすることができません。恐れ入りますが、APIをご利用いただく際は、多重アクセスが生じないようご対応をお願いいたします。https://iss.ndl.go.jp/information/api/