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 アドレスによって日本国外からのアクセスは受け付けないようになってましたが、今回はそうなっていないようですし、タイトルもアルファベットで日本語タイトルと対応させていることから海外向けの需要を意識しているのかもしれません。