読者です 読者をやめる 読者になる 読者になる

試される大地から

furaiboが送る技術ブログ。プログラミングのTipsなど書いていきます。

E-Hentaiギャラリー画像一括ダウンロードスクリプト

皆様へのお知らせ

2015/11/14

Ver 2.0が完成しました。
Windows用の実行ファイルも作成しましたので、こちらの記事をご覧ください。

geektrainee.hatenablog.jp


エロゲなどの画像を見ることが出来るサイトとして、E-Hentai.orgというサイトがあります。

http://e-hentai.org/




例えば、「ToHeart2 Another Days」の画像は以下のようなギャラリーページから閲覧することができます。

http://g.e-hentai.org/g/23157/a83c989f28/




ここではエロゲの画像を閲覧することが出来るのですが、会員登録&課金しない限りは一括でダウンロード出来ないのです。そこで、会員登録・ログイン・課金をしなくても目当ての画像がおいてあるE-HentaiギャラリーのURLを入力するだけで画像をダウンロードしてくれるPythonスクリプトを作成しました。



入力できるURLはE-Hentaiドメインのページのみとなっています。もしURLが最初のギャラリーページや画像ページでなくてもOKです。つまり、以下の様なページを入力しても最初の画像ページへとジャンプしてから最初の画像から全てをダウンロードしてくれます。


http://g.e-hentai.org/s/3685a68f43/23157-95

http://g.e-hentai.org/g/23157/a83c989f28/?p=4



こうしたページは「e-hentai」と同人誌やエロゲのタイトルをキーワードとしてGoogle検索することで見つけることができます。



※注意点

スクリプトLinuxMacでしか正しく動作しません。Windowsでも画像はダウンロード可能ですが、画像ファイル名が文字化けしてしまいます。


また、このスクリプトは途中ライブラリのエラーで実行がストップした場合にURLの取得が最初からやりなおしになってしまう欠点もありますが、現時点でも十分利用することは可能ですので、公開しておきます。


もちろんスクリプトの実行時にはpythonのインストールが必須です。また、root権限で実行して下さい。スクリプトソースコードは以下に掲載します。



・e-hentai.py

#!/usr/bin/python
#-*- coding:utf-8 -*-

#
# e-hentaiのページURLをもとにギャラリー画像を取得するスクリプト
# root権限で実行すること
#


# パッケージのインポート
import os
import sys
import re
import urllib2
import codecs
import StringIO
import gzip
from datetime import datetime
import os.path
from os.path import join, getsize


# 関数定義
# 指定したURLのHTMLページの内容をダウンロードする関数
def html_download(html_url):

	# 例外処理
	# HTMLページを取得しリストに格納
	# 失敗時にはエラーメッセージのみ返す
	try:
		# HTTPリクエスト
		req = urllib2.Request(html_url, headers={'User-Agent' : "Magic Browser"})
		con = urllib2.urlopen(req)
		html = con.read()
		
		# body部分がgzip化されている場合の処理
		if con.info().get('Content-Encoding') == 'gzip':
			data = StringIO.StringIO(html)
			gzipper = gzip.GzipFile(fileobj=data)
			html = gzipper.read()

	except urllib2.URLError, e:
		sys.exit("Error! : URLエラーにより画像を取得できません")
	except urllib2.HTTPError, e:
		sys.exit("Error! : HTTPエラーにより画像を取得できません")

	# 戻り値
	return html

	

# URLを元に画像をダウンロードする関数
def image_download(img_url, output):

	# HTTPリクエスト
	opener = urllib2.build_opener()
	req = urllib2.Request(img_url, headers={'User-Agent' : "Magic Browser"})

	# もし同名の画像ファイルが存在するならば、上書きは行わない
	if not os.path.exists(output):
		img_file = open(output, 'wb')
		img_file.write(opener.open(req).read())
		img_file.close()



# HTMLタイトルを取得する関数
def get_page_title(html):

	# 変数
	title = ""						# Webページのタイトル

	# 正規表現
	pat_title = re.compile('<title>(.*?)</title>')		# ページタイトルを抜き出す

	# ページタイトルを取得
	m = pat_title.search(html)
	
	# タイトルが取得できなかった場合は日時をタイトルとする
	if not m is None:
		title = m.group(1)
	else:
		d = datetime.now()
		title = d.strftime('%Y%m%d%H%M%S')

	# ページタイトル内のスラッシュを変換する
	title = title.replace("/", "_")

	# 戻り値
	return title



# E-Hentaiギャラリーの最初の画像ページURLを取得する関数
def get_first_img_page_url(html_url):

	# 変数
	gallary_top_url    = ""		# ギャラリートップのURL
	first_img_page_url = ""		# 最初の画像ページURL

	# 正規表現
	pat_gallery         = re.compile('^(http://g.e-hentai.org/g/.*/).*$')							# ギャラリーのURLであるか / URLからギャラリーを抜き出す
	pat_img_page        = re.compile('^http://g.e-hentai.org/s/.+/.*$')							# 画像ページのURLであるか
	pat_gallary_top     = re.compile('<a href="(http[s]*?://g.e-hentai.org/g/.*)".*?>')					# HTMLからギャラリートップのURLを抜き出す	
	pat_first_img_page1 = re.compile('<div style=".*?"><a href="(.*?)"><img alt="[0]*1".*?/><br />[0]*1</a>')	# ギャラリーのトップページのHTMLから最初の画像ページURLを抜き出す
	pat_first_img_page2 = re.compile('<div class="sn"><a onclick=".*?" href="(.*?)".*?>')				# 画像ページのHTMLから最初の画像ページURLを抜き出す

	# 入力したE-Hentaiのページの判定
	m1 = pat_gallery.search(html_url)
	m2 = pat_img_page.search(html_url)

	# 入力URLがギャラリーページの場合
	if not m1 is None:
		# ギャラリーのトップページURLを取得
		gallery_top_url = m1.group(1)

		# 最初の画像ページURLを取得
		m3 = pat_first_img_page1.search(html_download(gallery_top_url))
		first_img_page_url = m3.group(1)

	# 入力URLが画像ページの場合
	elif not m2 is None:
		# 最初の画像のURLを取得
		m4 = pat_first_img_page2.search(html_download(html_url))
		first_img_page_url = m4.group(1)

	# どれにもあてはまらない場合
	else:
		sys.exit("これはE-Hentaiギャラリーのギャラリーページあるいは画像ページではありません。")

	# 戻り値
	return first_img_page_url



# E-Hentaiギャラリーの最初の画像ページURLから全画像のURLのリストを取得する関数
def get_img_url_list(first_img_page_url, count_limit):
	
	# 変数
	count = 1					# リスト要素の番号
	current_page_url = first_img_page_url		# 現在のページのURL
	img_url = []					# 画像ファイルのURLを格納するリスト

	# 正規表現
	pat_img  = re.compile('<img id="img" src="(.*?)".*?>')			# 画像URLを抜き出す
	pat_next = re.compile('<a id="next" onclick=".*?" href="(.*?)".*?>')	# 次ページURLを抜き出す

	# ループで繰り返しURLを取得
	while True:
		# 現在のページのHTMLの内容
		html = html_download(current_page_url)

		# マッチング
		# 現在のHTMLページ内から画像のURLを抜き出す
		m1 = pat_img.search(html)
		if not m1 is None:
			# リストに要素を追加
			img_url.append(m1.group(1))

			### 出力テスト ###
			print "%d : %s"%(count, m1.group(1))
			count += 1
			#################


		# 現在のHTMLページ内から次ページへのリンクを抜き出す
		m2 = pat_next.search(html)
		if not m2 is None:
			# URLを取得
			# 同じURLが現れた場合には終了
			if not current_page_url == m2.group(1):
				current_page_url = m2.group(1)
			else:
				break
		else:
			# リンクが無い場合は終了
			break


		# countの値が制限を越えた場合は終了
		if count > count_limit:
			break


		# ダウンロード途中の状況を一時ファイルに書き出す
		#write_tmp_image_urls()


	# 戻り値
	return img_url



# 画像ファイル名の命名
def get_img_file_name(dl_path, name, ext, count, name_mode):

	# 変数
	output = ""

	# ファイルの命名モードによってファイル名を決定
	if name_mode == "number":
		output = "%s/%03d.%s"%(dl_path, count, ext)
	elif name_mode == "filename":
		output = "%s/%s.%s"%(dl_path, name, ext)
	elif name_mode == "date":
		d = datetime.now()
		output = d.strftime('%Y%m%d%H%M%S')

	# ファイル名のスラッシュを変換する
	output = output.replace("/", "_")

	# 戻り値
	return output



# 画像ファイルがダウンロードを許可された画像フォーマットかどうか判定する関数
def validate_img_format(file_ext, img_format):

	# 変数
	result = False		# 許可されているかどうかのフラグ

	# 画像フォーマットの判定
	for i in img_format:
		# 比較
		if i == file_ext:
			result = True
			break

	# 戻り値
	return result



# ダウンロード先URL一覧を一時ファイルに書き出す関数
'''
def write_tmp_image_urls(tmp_path):
	# 出力テスト
	print "write_tmp_image_urls"
'''


# ダウンロード状況を一時ファイルに書き出す関数
'''
def write_tmp_download_status(html_url, img_path, count_limit, name_mode, img_format, tmp_path):
	# 出力テスト
	print "write_tmp_download_status"
'''


# HTMLページの取得・タグの抜き出しの処理後、画像収集を行う
def start_collect_image(html_url, img_path, tmp_path, start_pos, finish_pos, count_limit, name_mode, img_format):
	
	# 変数
	count              = 0				# ファイル番号
	title              = ""				# Webページのタイトル
	first_img_page_url = ""				# 最初の画像ページのURL
	tmp_buffer         = ""				# ダウンロード情報を格納するバッファ
	img_url            = []				# 画像URLのリスト(初期値は空リスト)

	# 正規表現
	pat_img_name = re.compile('.+/(.+)\.(.*)')	# 画像ファイル名と拡張子を取得する


	# 画像ダウンロードの開始地点の値が1よりも小さければ1にする
	if start_pos < 1:
		start_pos = 1

	# URLが空かどうかの確認
	if html_url == "":
		sys.exit("Error! : URLが入力されていません")
	# URLが有効であるかどうかの確認
	elif re.match('^http[s]*://', html_url) is None:
		sys.exit("Error! : 有効なURLではありません")
	# e-hentaiのURLであるかどうかの確認
	elif re.match('^http[s]*://g.e-hentai.org/', html_url) is None:
		sys.exit("Error! : E-hentai GallaryのURLではありません")

	# E-hentaiギャラリーの最初の画像ページURLを取得する
	first_img_page_url = get_first_img_page_url(html_url)

	# ページタイトルを取得
	title = get_page_title(html_download(first_img_page_url))

	# ページタイトルと同名のディレクトリを作成する
	dl_path = "%s/%s"%(img_path, title)		# 画像をダウンロードするディレクトリ名
	if not os.path.exists(dl_path):
		os.makedirs(dl_path)
	os.chmod(dl_path, 0777)				# ディレクトリ権限の変更

	#  画像URLのリストを取得する
	img_url = get_img_url_list(first_img_page_url, count_limit)

	# 画像URLのリストから実際の画像をダウンロードする
	for i in img_url:
		# 変数countの値のインクリメント
		count += 1

		# 画像がダウンロード範囲内かどうか判定
		if count < start_pos:
			continue
		elif count > finish_pos:
			break

		# 画像ファイル名を決定
		m = pat_img_name.search(i)
		if not m is None:
			# 画像ファイル名と拡張子を取得する
			name = m.group(1)
			ext  = m.group(2)

			# ファイルの命名モードによってファイル名を決定
			output = get_img_file_name(dl_path, name, ext, count, name_mode)

			# すでに画像ファイルが存在する場合は次へ
			if os.path.exists(output):
				continue

			# 許可されていないファイル形式ならば次へ
			if not validate_img_format(ext, img_format):
				continue

			# 画像のダウンロード
			# 例外処理
			try:
				image_download(i, output)		# ダウンロードした画像ファイルを指定パスにダウンロード
				os.chmod(output, 0777)			# 画像ファイルの権限を変更
			except Exception:
				sys.exit("Error! : urllib2でエラーが発生したため、実行を停止します")
			
		else:
			# ファイル名が見つからない場合は次へ
			continue


		# ダウンロード状況を一時ファイルに書き出す
		'''
		if count % 5 == 0:
			write_tmp_download_status()
		'''
		
	
	# 成功のメッセージを出力
	print "Success! : 画像の取得が完了しました"
	return



# エラー等で中断した画像ファイル収集を一時情報をもとに再開する
'''
def resume_collect_image(html_url, img_path, count_limit, name_mode, img_format):
	# 出力テスト
	print "resume_image_collect"
'''


# 画像のダウンロード処理
def collect_image(html_url, img_path, tmp_path, start_pos, finish_pos, count_limit, name_mode, img_format):

	# ダウンロード処理
	start_collect_image(html_url, img_path, tmp_path, start_pos, finish_pos, count_limit, name_mode, img_format)

	# 一時ファイルがある場合とない場合で処理を分ける
	'''
	if os.path.exists(tmp_path):
		#resume_image_collect()		# 画像ダウンロードを再開
		#os.remove(tmp_path)		# 一時ファイルを消去する
	else:
		start_collect_image(html_url, img_path, tmp_path, start_pos, finish_pos, count_limit, name_mode, img_format)
	'''



# main関数
if __name__ == '__main__':

	# 変数
	html_url      = ""				# E-Hentai GalleryのURL
	img_path      = "./downloads"			# 画像のダウンロード先ディレクトリ
	tmp_path      = "./tmp.txt"			# ダウンロードの一時情報ファイル名
	count_limit   = 1000				# ダウンロードする画像の数の上限
	start_pos     = 1				# 画像ダウンロードの開始点
	finish_pos    = 1000				# 画像ダウンロードの終了点
	img_format    = ["jpg", "png", "gif", "bmp"]	# ダウンロードを許可する画像ファイル形式(拡張子)

	# 画像ファイルの命名モード
	# "number"   => ダウンロード番号を画像ファイル名とする
	# "filename" => ダウンロード時URLの画像ファイル名をそのまま流用して画像ファイル名とする
	# "date"     => ダウンロード時の時刻を画像ファイル名とする
	name_mode     = "number"			


	# URLの入力を受け付ける
	print u"E-Hentaiのギャラリー、または画像のあるURLを入力してください。"
	html_url = raw_input("URL : ")			# ユーザの入力を受け付ける

	# ダウンロード処理
	# 一時ファイルがある場合とない場合で処理を分ける
	collect_image(html_url, img_path, tmp_path, start_pos, finish_pos, count_limit, name_mode, img_format)



実行は以下のようにしてください。

[furaibo@localhost e-hentai]$ sudo python e-hentai.py
パスワード : [あなたのパスワードを入力]

E-Hentaiのギャラリー、または画像のあるURLを入力してください。
URL : [E-HentaiのURLをコピペかキーボードで入力] 


すると、スクリプトが置かれているディレクトリ内にDownloadsディレクトリと作品名のディレクトリが作成され、以下のようにダウンロードした画像が置かれます。

f:id:incodethx3932:20140101214256p:plain




まだまだ上記のスクリプトにはエラーやバグがありますが、現在Windowsに対応できていない問題の修正および、画像ダウンロード中にエラーが発生した場合でもエラー発生前の状況に戻ってダウンロードを再開する機能を付加したスクリプトを鋭意製作中です。これらの問題を解決したバージョンの実装が完了したらまた当ブログ上でお知らせしたいと思います。