Fr:
https://rentry.co/hzpue/
Eng:
https://rentry.co/vh3py
Spanish:
https://rentry.co/agr95
Japanese:
https://crieit.net/posts/Bank-6249a7b59dfd9
漫画バンク、だったものについて。
漫画バンクは消えた。
新たに13dl.meという違法サイトになったが、これまでのオンラインリーディング型のスタイルを捨てて、ダウンロードリーチ型になったので、この記事もボードで更新していたのを、新たに節目としてこのようにしてみた。
過去のボード
mangaBank が消えるまで
https://crieit.net/boards/manga-B
さて、Javascript ってどうやって書くんだろうと前々から気になっていたので、この新たな違法サイトについて、どういうものか見てみるのに Javascript で node.js を使ってプログラムを書いてみた。
Promise についてよくわからなかったので、ずいぶん時間がかかったが、Nim よりは Javascript について書かれたドキュメントは数多くあるので、プログラムが一応動くというところまでは早かった。
node.js をインストールして、npm パッケージマネージャで axios, cheerio をインストールする。
https://nodejs.org/ja/
https://github.com/axios/axios#installing
https://cheerio.js.org/
axios が httpsリクエストするモジュールで、Promise 型の返り値で、リクエストが成功するとresponse を処理できる。cheerio は、html テキストをオブジェクトにして css セレクターとして使う。Ruby での nokogiri に類する、pythonでの bueatifulsoup4 のような、go での goquery のような、Nim での nimquery のような扱いで使用した。こういう便利セレクターはそれぞれに使い方が似ていて違うので、よく調べないとちゃんと使えないということに慣れていない場合、使わなくても正規表現でもなんとかなるので、その場合は言語が違っても、同じ標準的な正規表現スタイルの場合は同じように書ける。go にも標準ライブラリではないけど perl 様式の正規表現モジュールはある。
RE2 is a fast, safe, thread-friendly alternative to backtracking regular expression engines like those used in PCRE, Perl, and Python. It is a C++ library.
License
 BSD-3-Clause license
https://github.com/google/re2/
1. Base
|  | const cheerio = require('cheerio'),
    axios = require('axios');
//  url = `https://13dl.me/`;
var url = `https://13dl.me/list/popular/`;
var counter = 0;
function recursive(url){
    let temp_url = url;
    axios.get(url)
    .then((response) => {
        if (response.status == 200){
            let $ = cheerio.load(response.data);
            $('a').each(function (i, e) {
                let title = $(e).attr('title');      
                let link = $(e).attr('href');      
                if (title !== undefined){
                    let h = /^Home/.test(title),
                        po = /^Popular\s/.test(title),
                        p = /^Prev/.test(title),
                        pa = /^Page/.test(title),
                        n = /^Next/.test(title);
                    if (h || po || p || pa || n || title === ``){
                        //unless
                    } else {
                        counter++;
                        console.log(counter + `{` + title +`{` + link);
//                      console.log(counter,title);
//                      console.log(`____________________`);
                    }
                }
                if (title === `Next`){
                    url = link;
//                    recursive(url);
                }
            })
            if (url !== temp_url){
                    recursive(url);
            }
        }
    }).catch(function (e) {
        console.log(e);
        recursive(url);
    });
}
recursive(url);
 | 
 
どういうプログラムかというと、サイトにあるコンテンツを列挙していくものだ。
関数を再帰的に呼ぶのだけど、Javascript でこういう書き方するのかは、しらないまま書いたら、一応動いたので、列挙していくコンテンツを SQLite データベースに記録しよう。awk と併用すればこの base プログラムでも SQLite3 のデータベースファイルは作れますが。区切り文字 { で csv にして、SQLite3 で csv を読み込んで保存すればデータベースファイルになる。
https://rentry.co/i542t
Ruby
Ruby で似せて書くと
|  | require 'nokogiri'
#require 'oga' # or Nokogiri
require 'open-uri'
url = 'https://13dl.me/list/popular/'
counter = 0
threads = []
def recursive(counter,url,threads) 
  html = URI.open(url).read
  doc = Nokogiri::HTML.parse(html)
  #doc = Oga.parse_html(html)
  html = nil
  doc.css('a').each do |x|
    xx = x.attr('title')
    pa = /^Page/.match?("#{xx}")
    p = /^Prev/.match?("#{xx}")
    if xx && xx !='' &&  xx !='Home' && xx !='Popular Manga' then
      unless pa | p then
        n = /^Next/.match?("#{xx}")
        link = ''
        if n then
          link = x.attr('href')
          threads << Thread.new do
            recursive(counter,link,threads)
          end
        else
          counter += 1
          puts "#{counter} #{xx}"
        end
      end
    end
  end
  doc = nil
end
recursive(counter,url,threads)
threads.each(&:join)
 | 
 
python
|  | !python -m pip install requests beautifulsoup4
import requests
from bs4 import BeautifulSoup
url = 'https://13dl.me/list/popular/'
counter = 0
def scrape(url, counter):
    response = requests.get(url)
    soup = BeautifulSoup(response.content)
    response.close()
    xx = soup.find_all("a",title=True)
    next_url = ""
    for x in xx:
        if (x['title'] != '' and x['title'] != "Home" and x['title'] != "Popular Manga"):
            pa_tf = bool(re.search("^Page",x['title']))
            p_tf = bool(re.search("^Prev",x['title']))
            if (pa_tf == False and p_tf == False):
                if (x['title'] != "Next"):
                    counter += 1
                    print(counter,x['title'])
                else:
                    next_url = x['href']
    del xx,soup
    return next_url, counter
while(url != ""):
    url,counter = scrape(url, counter)
 | 
 
recursive
https://rentry.co/5ibqk
while
https://rentry.co/ir4b3
Nim
while
https://rentry.co/856s2
threadpool ... Work in Progress
https://rentry.co/ze8f4
perl5
https://rentry.co/perl5_manga
Go
recursive
https://rentry.co/f55r8
while(for)
https://rentry.co/75gch
&sync.wait.Group{} ... Work in Progress
https://rentry.co/wikwa
contents 数とpage 数 の関係 | channel , sync.Mutex
https://rentry.co/o8fp4
Elixir
https://rentry.co/nzo6g
2. SQLite3
|  | const cheerio = require('cheerio'), 
    axios = require('axios');
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('13dlme0.db',(err) => {
    if(err) {
        return console.error(err.message);
    }
    console.log('Connect to SQLite database.');
});
db.serialize(() => {
    db.run(`CREATE TABLE manga(id interger,title string,link string)`)
});
var url = `https://13dl.me/list/popular/`;
var counter = 0;
function recursive(url){
    let temp_url = url;
    axios.get(url)
    .then((response) => {
        if (response.status == 200){
            let $ = cheerio.load(response.data);
            $('a').each(function (i, e) {
                let title = $(e).attr('title');      
                let link = $(e).attr('href');      
                if (title !== undefined){
                    let h = /^Home/.test(title),
                        po = /^Popular\s/.test(title),
                        p = /^Prev/.test(title),
                        pa = /^Page/.test(title),
                        n = /^Next/.test(title);
                    if (h || po || p || pa || n || title === ``){
                        //unless
                    } else {
                        counter++;
//                        console.log(counter + ` ** ` + title + ` ** ` + link);
                        db.run(`insert into manga(id,title) VALUES(?,?)`,[counter,title]);
                        console.log(counter,title);
                        console.log(`____________________`);
                    }
                }
                if (title === `Next`){
                    url = link;
                }
            })
            if (temp_url != url){
                recursive(url);
            };
        }
    }).catch(function (e) {
        console.log(e);
        recursive(url);
    });
}
recursive(url);
 | 
 
db.close()
してない。
どうやって db.close() すべきだろうか ?
3. SQLite3 + db.close()
|  | const cheerio = require('cheerio'), 
    axios = require('axios');
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('13dlme_test1-1.db',(err) => {
    if(err) {
        return console.error(err.message);
    }
    console.log('Connect to SQLite database.');
});
db.serialize(() => {
    db.run(`CREATE TABLE manga(id interger,title string,link string)`)
});
var url = `https://13dl.me/list/popular/`;
var counter = 0;
function recursive(url){
    const temp_url = url;
    const promise1 = axios.get(url)
    const promiseData1 = promise1.then((response) => {
        if (response.status == 200){
            let $ = cheerio.load(response.data);
            $('a').each(function (i, e) {
                const title = $(e).attr('title');      
                const link = $(e).attr('href');      
                if (title !== undefined){
                    const h = /^Home/.test(title),
                        po = /^Popular\s/.test(title),
                        p = /^Prev/.test(title),
                        pa = /^Page/.test(title),
                        n = /^Next/.test(title);
                    if (h || po || p || pa || n || title === ``){
                        //unless
                    } else {
                        counter++;
//                        console.log(counter + ` ** ` + title + ` ** ` + link);
                        db.serialize(() => {
                            db.run(`insert into manga(id,title) VALUES(?,?)`,[counter,title]);
                        });
                        console.log(counter,title);
                        console.log(`____________________`);
                    }
                }
                if ((title === `Next`) && (link != undefined)){
                    url = link;
                }
            })
        } else {
            console.log(response);
        }
        if (temp_url !== url){
            return recursive(url);
//          `:keep going:`;
        } else {
            console.log('Close SQLite database.');
            return `:stop:`;
        }
    }).catch(function (e) {
        console.log(e);
    });
    Promise.all([promiseData1]).then((value) => {
        if (value[0] === `:stop:`){
            db.close((err) => {
            if(err) {
                console.error(err.message);
                }
            });
        }
    });
}
recursive(url);
 | 
 
db.close()できた?
4. async/await
|  | const cheerio = require('cheerio'), 
    axios = require('axios');
const sqlite3 = require('sqlite3').verbose();
const db = new sqlite3.Database('13dlme_test2.db',(err) => {
    if(err) {
        return console.error(err.message);
    }
    console.log('Connect to SQLite database.');
});
db.serialize(() => {
    db.run(`CREATE TABLE manga(id interger,title string,link string)`)
});
var url = `https://13dl.me/list/popular/`;
var counter = 0;
const recursive = async (url) => {
    console.log(url);
    const temp_url = url;
    try {
        const {data} = await axios.get(url);
        const $ = cheerio.load(data)
        $('a').each(function (i, e) {
            const title = $(e).attr('title');      
            const link = $(e).attr('href');      
            if (title !== undefined){
                const h = /^Home/.test(title),
                    po = /^Popular\s/.test(title),
                    p = /^Prev/.test(title),
                    pa = /^Page/.test(title),
                    n = /^Next/.test(title);
                if (h || po || p || pa || n || title === ``){
                    //unless
                } else {
                    counter++;
//                    console.log(counter + ` ** ` + title + ` ** ` + link);
                    db.run(`insert into manga(id,title) VALUES(?,?)`,[counter,title]);
                    console.log(counter,title);
                    console.log(`____________________`);
                }
            }
            if ((title === `Next`) && (link != undefined)){
                url = link;
            }
        });
        if (temp_url !== url) {
        recursive(url);
        //`:keep going:`;
    }else{
            console.log('Close SQLite database.');
            db.close();
        //`:stop:`;
    }
    } catch(error) {
        throw error;
    }
}
recursive(url);
 | 
 
ただ、2 ... の場合でも、クローズしてなくともデータベースには書き込めているようなので、comit できているよう。
ハードディスクにツクツク書き込む古い PC の場合、SQLite のファイルに書き込むのに、1つづつコミットしたり、オープンクローズを繰り返すとスピードが犠牲になり遅くなるが、メモリ上にテーブル作って、データを書き込んで、できたものを最後に .db ファイルに書き込むとコミットは最後だけで済むのでいいかもしれない。 3 , 4 のプログラムコードはそれぞれ、すべてのデータが書き込まれた場合、データベースをクローズするように書いたつもりではあるが、これではそうならないケースがあるはずだが、きれいにクローズされた。おかしいが、ノンブロッキング。追求の余地があるが、これで結果がエラーにならないから難しい。
解説すべきことは特にない。
Ruby, SQLite3
|  | require 'nokogiri'
require 'open-uri'
require 'sqlite3'
url = 'https://13dl.me/list/popular/'
counter = 0
threads = []
SQL =<<EOS                                                                     
create table manga(
    id INTEGER PRIMARY KEY,
    title text
    );
EOS
db = SQLite3::Database.open("13dlme.db")
db.execute(SQL)
def recursive(counter,url,threads,db) 
  html = URI.open(url).read
  doc = Nokogiri::HTML.parse(html)
  html = nil
  doc.css('a').each do |x|
    xx = x.attr('title')
    pa = /^Page/.match?("#{xx}")
    p = /^Prev/.match?("#{xx}")
    if xx && xx !='' &&  xx !='Home' && xx !='Popular Manga' then
      unless pa | p then
        n = /^Next/.match?("#{xx}")
        link = ''
        if n then
          link = x.attr('href')
          threads << Thread.new do
            recursive(counter,link,threads,db)
          end
        else
          counter += 1
          puts "#{counter} #{xx}"
          db.execute("insert into manga(id,title) values('#{counter}','#{xx}') ;")
        end
      end
    end
  end
  doc = nil
end
recursive(counter,url,threads,db)
threads.each(&:join)
db.close
 | 
 
プログラムとしては、スマートフォン等でアプリの termux 、あるいは userland が動作するのであれば root 化されていなくても node.js がパッケージでインストール可能なので動作します。
https://debimate.jp/2019/03/16/androidにlinux環境を構築するuserlandがソースリーディング環/
termux の場合は、node.js をインストール、npm パッケージマネージャーで axios , cheerio, sqlite3 をインストールすれば上記のプログラムを書けるわけですが、userland は、まず OS を選ぶところからですが、OS によって apt だったり apk だったりの違いがありますが node.js をインストールできれば、あとは termux とほぼ同じ行程です。
iOS の場合 iSh というターミナルエミュレーターのアプリがありますが、iOS ではテストしていません。mac を持っていないのと、iOS の iphone のお古をもらったけれども、apple という会社に生理的嫌悪があるのでなかなか触れない。
Ruby や python だとライブラリのインストールまでに OS によって色々と違いがありますが、node.js の場合、そう違いが発生しないのはメリットかもしれないですね。javascript がいいのかどうかはさておいて、ブラウザだけで動くようにも書き換えれるはずなので。
著作権管理している人としては、リストの中に管理してるタイトルがあれば、テイクダウンの手続きを。
漫画 Bank であったときは、cloudflare のプロクシー上の画像データは IP アドレスによって日本国外からのアクセスは受け付けないようになってましたが、今回はそうなっていないようですし、タイトルもアルファベットで日本語タイトルと対応させていることから海外向けの需要を意識しているのかもしれません。