update
This commit is contained in:
parent
8f55a51140
commit
00f77967a3
1
.gitignore
vendored
1
.gitignore
vendored
@ -1,3 +1,4 @@
|
|||||||
.scrapy/*
|
.scrapy/*
|
||||||
images/*
|
images/*
|
||||||
|
json/*
|
||||||
/**/__pycache__
|
/**/__pycache__
|
||||||
BIN
CBZ/私宅女主人/第1话-提供顶级服务的随行秘书.CBZ
Normal file
BIN
CBZ/私宅女主人/第1话-提供顶级服务的随行秘书.CBZ
Normal file
Binary file not shown.
@ -8,6 +8,8 @@ from Comics.items import ComicItem
|
|||||||
from Comics.settings import COMIC_INFO_XML_STORE
|
from Comics.settings import COMIC_INFO_XML_STORE
|
||||||
from Comics.utils.Constant import ComicPath
|
from Comics.utils.Constant import ComicPath
|
||||||
from scrapy.utils.python import is_listlike, to_bytes, to_unicode
|
from scrapy.utils.python import is_listlike, to_bytes, to_unicode
|
||||||
|
from itemadapter import ItemAdapter
|
||||||
|
|
||||||
|
|
||||||
class ItemExporter(PythonItemExporter):
|
class ItemExporter(PythonItemExporter):
|
||||||
def convert(self, data):
|
def convert(self, data):
|
||||||
@ -46,7 +48,12 @@ class ComicInfoXmlItemExporter(XmlItemExporter):
|
|||||||
|
|
||||||
def comic_to_info_item(self, comic_item):
|
def comic_to_info_item(self, comic_item):
|
||||||
comic_info = {}
|
comic_info = {}
|
||||||
comic_info_dict = getattr(ComicItem, "data", 0)
|
info_item = ItemAdapter(ComicInfoItem())
|
||||||
|
comic_info_dict = {}
|
||||||
|
for field in info_item.field_names():
|
||||||
|
meta_info = info_item.get_field_meta(field).get('info')
|
||||||
|
if meta_info is not None:
|
||||||
|
comic_info_dict[meta_info] = field
|
||||||
for key, value in ComicItem(comic_item).items():
|
for key, value in ComicItem(comic_item).items():
|
||||||
new_key = comic_info_dict.get(key)
|
new_key = comic_info_dict.get(key)
|
||||||
if new_key is not None:
|
if new_key is not None:
|
||||||
@ -63,7 +70,7 @@ class ComicInfoXmlItemExporter(XmlItemExporter):
|
|||||||
value = str(value).split(',')
|
value = str(value).split(',')
|
||||||
if value is not None or value != "":
|
if value is not None or value != "":
|
||||||
self._export_xml_field(name, value, depth=2, child_element=child_element)
|
self._export_xml_field(name, value, depth=2, child_element=child_element)
|
||||||
self._beautify_indent(depth=1)
|
#self._beautify_indent(depth=1)
|
||||||
return comic_info
|
return comic_info
|
||||||
|
|
||||||
def _export_xml_field(self, name, serialized_value, depth, child_element="value"):
|
def _export_xml_field(self, name, serialized_value, depth, child_element="value"):
|
||||||
@ -71,8 +78,8 @@ class ComicInfoXmlItemExporter(XmlItemExporter):
|
|||||||
self.xg.startElement(name, {})
|
self.xg.startElement(name, {})
|
||||||
if hasattr(serialized_value, "items"):
|
if hasattr(serialized_value, "items"):
|
||||||
self._beautify_newline()
|
self._beautify_newline()
|
||||||
for subname, value in serialized_value.items():
|
for sub_name, value in serialized_value.items():
|
||||||
self._export_xml_field(subname, value, depth=depth + 1)
|
self._export_xml_field(sub_name, value, depth=depth + 1)
|
||||||
self._beautify_indent(depth=depth)
|
self._beautify_indent(depth=depth)
|
||||||
elif is_listlike(serialized_value):
|
elif is_listlike(serialized_value):
|
||||||
self._beautify_newline()
|
self._beautify_newline()
|
||||||
|
|||||||
@ -4,18 +4,8 @@
|
|||||||
# https://docs.org/en/latest/topics/items.html
|
# https://docs.org/en/latest/topics/items.html
|
||||||
from scrapy.item import Item, Field
|
from scrapy.item import Item, Field
|
||||||
from Comics.utils.Constant import ComicPath
|
from Comics.utils.Constant import ComicPath
|
||||||
from dataclasses import dataclass
|
|
||||||
from scrapy.loader.processors import TakeFirst, MapCompose, Join
|
from scrapy.loader.processors import TakeFirst, MapCompose, Join
|
||||||
|
|
||||||
|
|
||||||
data = {}
|
|
||||||
def setinfo(**kwds):
|
|
||||||
def decorate(f):
|
|
||||||
for k in kwds: data[k] = kwds[k]
|
|
||||||
setattr(f, "data", data)
|
|
||||||
return f
|
|
||||||
return decorate
|
|
||||||
|
|
||||||
def serialize_to_chinese(value):
|
def serialize_to_chinese(value):
|
||||||
return ComicPath.chinese_convert(value)
|
return ComicPath.chinese_convert(value)
|
||||||
|
|
||||||
@ -27,34 +17,29 @@ class ComicOItem(Item):
|
|||||||
name = Field()
|
name = Field()
|
||||||
chapterItem = Field()
|
chapterItem = Field()
|
||||||
|
|
||||||
@setinfo(name="Series", chapter="Title",
|
|
||||||
author="Writer", tags="Tags",
|
|
||||||
dep="Summary", genre="Genre",
|
|
||||||
index="Number", images_name="Pages",
|
|
||||||
age_rating="AgeRating")
|
|
||||||
class ComicItem(Item):
|
class ComicItem(Item):
|
||||||
# 编号
|
# 编号
|
||||||
index = Field()
|
index = Field(output_processor=TakeFirst())
|
||||||
# 漫画名
|
# 漫画名
|
||||||
name = Field(serializer=serialize_to_fix_file, output_processor=TakeFirst())
|
name = Field(serializer=serialize_to_fix_file, output_processor=TakeFirst())
|
||||||
# 章节名
|
# 章节名
|
||||||
chapter = Field(serializer=serialize_to_fix_file)
|
chapter = Field(serializer=serialize_to_fix_file, output_processor=TakeFirst())
|
||||||
# 图片链接
|
# 图片链接
|
||||||
list_img = Field()
|
list_img = Field()
|
||||||
# 作者
|
# 作者
|
||||||
author = Field(serialize_to_chinese=serialize_to_chinese, output_processor=TakeFirst())
|
author = Field(serialize_to_chinese=serialize_to_chinese, output_processor=TakeFirst())
|
||||||
# 封面链接
|
# 封面链接
|
||||||
icon = Field()
|
icon = Field(output_processor=TakeFirst())
|
||||||
# 标签
|
# 标签
|
||||||
tags = Field(serializer=serialize_to_chinese)
|
tags = Field(serializer=serialize_to_chinese, output_processor=TakeFirst())
|
||||||
# 概述
|
# 概述
|
||||||
dep = Field(serializer=serialize_to_chinese)
|
dep = Field(serializer=serialize_to_chinese, output_processor=TakeFirst())
|
||||||
# 时间
|
# 时间
|
||||||
date = Field()
|
date = Field(output_processor=TakeFirst())
|
||||||
# 流派
|
# 流派
|
||||||
genre = Field()
|
genre = Field(output_processor=TakeFirst())
|
||||||
# 年龄分级
|
# 年龄分级
|
||||||
age_rating = Field()
|
age_rating = Field(output_processor=TakeFirst())
|
||||||
|
|
||||||
images = Field()
|
images = Field()
|
||||||
images_name = Field()
|
images_name = Field()
|
||||||
@ -72,22 +57,22 @@ def serializer_info_writer(value):
|
|||||||
return ",".join(list_value)
|
return ",".join(list_value)
|
||||||
|
|
||||||
class ComicInfoItem(Item):
|
class ComicInfoItem(Item):
|
||||||
Title = Field()#"章节名",True]
|
Title = Field(info='chapter')#"章节名",True]
|
||||||
Series = Field()# ","漫画名",True]
|
Series = Field(info='name')# ","漫画名",True]
|
||||||
Number = Field()# ","编号",True]
|
Number = Field(info='index')# ","编号",True]
|
||||||
SeriesGroup = Field()# ","别名",False]
|
SeriesGroup = Field()# ","别名",False]
|
||||||
Summary = Field()# ","概述",True]
|
Summary = Field(info='dep')# ","概述",True]
|
||||||
Year = Field()# ","年",False]
|
Year = Field()# ","年",False]
|
||||||
Month = Field()# ","月",False]
|
Month = Field()# ","月",False]
|
||||||
Day = Field()# ","日",False]
|
Day = Field()# ","日",False]
|
||||||
Writer = Field(serializer=serializer_info_writer)# "作者",True]
|
Writer = Field(info='author',serializer=serializer_info_writer)# "作者",True]
|
||||||
Publisher = Field()# ","出版社",False]
|
Publisher = Field()# ","出版社",False]
|
||||||
Genre = Field()# ","流派",True]
|
Genre = Field(info='genre')# ","流派",True]
|
||||||
Tags = Field()# ","标签",True]
|
Tags = Field(info='tags')# ","标签",True]
|
||||||
Web = Field()# ","主页",False]
|
Web = Field()# ","主页",False]
|
||||||
PageCount = Field()# ","总页数",True]
|
PageCount = Field()# ","总页数",True]
|
||||||
LanguageISO = Field()#","语言",True]
|
LanguageISO = Field()#","语言",True]
|
||||||
AgeRating = Field()#","年龄分级",False]
|
AgeRating = Field(info='age_rating')#","年龄分级",False]
|
||||||
Pages = Field()#","页码",True]
|
Pages = Field(info='images_name')#","页码",True]
|
||||||
Page = Field()
|
|
||||||
# ComicInfo.xml and ComicChapter.json end
|
# ComicInfo.xml and ComicChapter.json end
|
||||||
|
|
||||||
|
|||||||
44
Comics/loader.py
Normal file
44
Comics/loader.py
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
import json
|
||||||
|
from scrapy.loader import ItemLoader
|
||||||
|
class ComicLoader(ItemLoader):
|
||||||
|
def parseExec(cls,data,exec):
|
||||||
|
if data !=None and exec != None:
|
||||||
|
dots = str(exec).split(".")
|
||||||
|
if not isinstance(data,dict): data = json.loads(data)
|
||||||
|
for dot in dots:
|
||||||
|
data = data.get(dot)
|
||||||
|
return data
|
||||||
|
|
||||||
|
def add_xpath(self, field_name, xpath, *processors, index=None, exec=None, re=None, **kw):
|
||||||
|
"""
|
||||||
|
Similar to :meth:`ItemLoader.add_value` but receives an XPath instead of a
|
||||||
|
value, which is used to extract a list of strings from the
|
||||||
|
selector associated with this :class:`ItemLoader`.
|
||||||
|
|
||||||
|
See :meth:`get_xpath` for ``kwargs``.
|
||||||
|
|
||||||
|
:param xpath: the XPath to extract data from
|
||||||
|
:type xpath: str
|
||||||
|
|
||||||
|
Examples::
|
||||||
|
|
||||||
|
# HTML snippet: <p class="product-name">Color TV</p>
|
||||||
|
loader.add_xpath('name', '//p[@class="product-name"]')
|
||||||
|
# HTML snippet: <p id="price">the price is $1200</p>
|
||||||
|
loader.add_xpath('price', '//p[@id="price"]', re='the price is (.*)')
|
||||||
|
|
||||||
|
"""
|
||||||
|
values = self._get_xpathvalues(xpath, **kw)
|
||||||
|
if exec is not None:
|
||||||
|
values = self.parseExec(values, exec)
|
||||||
|
if index is not None:
|
||||||
|
values = values[index]
|
||||||
|
self.add_value(field_name, values, *processors, re=re, **kw)
|
||||||
|
|
||||||
|
def add_exec(self, field_name, value, str_exec=None, *processors, re=None, **kw):
|
||||||
|
if str_exec is not None:
|
||||||
|
value = self.parseExec(value, str_exec)
|
||||||
|
self.add_value(field_name, value, *processors, re=re, **kw)
|
||||||
|
|
||||||
|
def get_exec(self, value, str_exec):
|
||||||
|
return self.parseExec(value, str_exec)
|
||||||
@ -4,13 +4,16 @@
|
|||||||
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html
|
# https://docs.scrapy.org/en/latest/topics/spider-middleware.html
|
||||||
|
|
||||||
from scrapy import signals
|
from scrapy import signals
|
||||||
import random
|
import random,logging
|
||||||
|
from pathlib import Path
|
||||||
from Comics.settings import PROXY_LIST
|
from Comics.settings import PROXY_LIST
|
||||||
# useful for handling different item types with a single interface
|
# useful for handling different item types with a single interface
|
||||||
from itemadapter import is_item, ItemAdapter
|
|
||||||
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
class ProxyMiddleware(object):
|
class ProxyMiddleware(object):
|
||||||
def process_request(self, request, spider):
|
def process_request(self, request, spider):
|
||||||
|
if len(PROXY_LIST) != 0:
|
||||||
request.meta["proxy"] = random.choice(PROXY_LIST)
|
request.meta["proxy"] = random.choice(PROXY_LIST)
|
||||||
|
|
||||||
class ComicsSpiderMiddleware:
|
class ComicsSpiderMiddleware:
|
||||||
|
|||||||
@ -5,7 +5,7 @@
|
|||||||
|
|
||||||
|
|
||||||
# useful for handling different item types with a single interface
|
# useful for handling different item types with a single interface
|
||||||
import os,requests,re,scrapy,logging
|
import os, scrapy
|
||||||
from Comics import settings
|
from Comics import settings
|
||||||
from Comics.utils.FileUtils import imageUtils
|
from Comics.utils.FileUtils import imageUtils
|
||||||
from Comics.utils.FileUtils import fileUtils
|
from Comics.utils.FileUtils import fileUtils
|
||||||
@ -15,7 +15,7 @@ from Comics.items import ImageItem
|
|||||||
from scrapy.pipelines.images import ImagesPipeline
|
from scrapy.pipelines.images import ImagesPipeline
|
||||||
from Comics.exporters import ComicInfoXmlItemExporter
|
from Comics.exporters import ComicInfoXmlItemExporter
|
||||||
from Comics.exporters import ItemExporter
|
from Comics.exporters import ItemExporter
|
||||||
from Comics.utils.CBZUtils import CBZUtils
|
from Comics.utils.FileUtils import CBZUtils
|
||||||
|
|
||||||
class ComicsPipeline:
|
class ComicsPipeline:
|
||||||
def open_spider(self, spider):
|
def open_spider(self, spider):
|
||||||
|
|||||||
@ -34,7 +34,7 @@ DOWNLOAD_DELAY = 20
|
|||||||
RETRY_ENABLED = True
|
RETRY_ENABLED = True
|
||||||
RETRY_TIMES = 10 # 想重试几次就写几
|
RETRY_TIMES = 10 # 想重试几次就写几
|
||||||
# 下面这行可要可不要
|
# 下面这行可要可不要
|
||||||
#RETRY_HTTP_CODES = [500, 502, 503, 504, 408]
|
RETRY_HTTP_CODES = [500, 502, 503, 504, 408, 401]
|
||||||
# The download delay setting will honor only one of:
|
# The download delay setting will honor only one of:
|
||||||
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
|
#CONCURRENT_REQUESTS_PER_DOMAIN = 16
|
||||||
#CONCURRENT_REQUESTS_PER_IP = 16
|
#CONCURRENT_REQUESTS_PER_IP = 16
|
||||||
@ -102,7 +102,8 @@ AUTOTHROTTLE_DEBUG = False
|
|||||||
HTTPCACHE_ENABLED = True
|
HTTPCACHE_ENABLED = True
|
||||||
HTTPCACHE_EXPIRATION_SECS = 0
|
HTTPCACHE_EXPIRATION_SECS = 0
|
||||||
HTTPCACHE_DIR = 'httpcache'
|
HTTPCACHE_DIR = 'httpcache'
|
||||||
HTTPCACHE_IGNORE_HTTP_CODES = [500, 502, 404, 403]
|
HTTPCACHE_IGNORE_HTTP_CODES = [500, 502, 404, 403, 401]
|
||||||
|
#HTTPCACHE_STORAGE = 'Comics.middlewares.MyFilesystemCacheStorage'
|
||||||
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
|
HTTPCACHE_STORAGE = 'scrapy.extensions.httpcache.FilesystemCacheStorage'
|
||||||
|
|
||||||
CBZ_EXPORT_PATH = "CBZ"
|
CBZ_EXPORT_PATH = "CBZ"
|
||||||
|
|||||||
@ -1,9 +1,8 @@
|
|||||||
import urllib.parse
|
import scrapy
|
||||||
|
|
||||||
import scrapy,json,requests
|
|
||||||
from Comics.items import ComicItem
|
from Comics.items import ComicItem
|
||||||
from Comics.utils.FileUtils import CommonUtils
|
from Comics.loader import ComicLoader
|
||||||
from scrapy.loader import ItemLoader
|
from itemadapter import ItemAdapter
|
||||||
|
from Comics.items import ComicInfoItem
|
||||||
|
|
||||||
class RmComicSpider(scrapy.Spider):
|
class RmComicSpider(scrapy.Spider):
|
||||||
name = 'rm_comic'
|
name = 'rm_comic'
|
||||||
@ -15,48 +14,49 @@ class RmComicSpider(scrapy.Spider):
|
|||||||
yield scrapy.Request('https://rm01.xyz'
|
yield scrapy.Request('https://rm01.xyz'
|
||||||
'/books/306ec1e2-f701-4fda-bb78-041ad6ec4020', callback=self.parse_comic)
|
'/books/306ec1e2-f701-4fda-bb78-041ad6ec4020', callback=self.parse_comic)
|
||||||
|
|
||||||
|
# 获取某个漫画的相关数据
|
||||||
|
# 获取到多个章节链接后进入下个流程
|
||||||
def parse_comic(self, response):
|
def parse_comic(self, response):
|
||||||
comic = ComicItem()
|
comic_item = ComicLoader(item=ComicItem(), response=response)
|
||||||
# comic_item = ItemLoader(item=ComicItem(), response=response)
|
comic_item.add_xpath('name', '//div[@class="col"]/h5/text()')
|
||||||
comic['name'] = response.xpath('//div[@class="col"]/h5/text()').extract_first()
|
comic_item.add_xpath('icon', '//img[@class="img-thumbnail"]/@src')
|
||||||
comic['icon'] = response.xpath('//img[@class="img-thumbnail"]/@src').extract_first()
|
comic_item.add_xpath('author', '//div[contains(@class,"bookid_bookInfo")]/p[1]/text()', index=1)
|
||||||
comic['author'] = response.xpath('//div[contains(@class,"bookid_bookInfo")]/p[1]/text()').extract()[1]
|
comic_item.add_xpath('tags', '//div[contains(@class,"bookid_bookInfo")]/p[3]/b/text()')
|
||||||
comic['tags'] = response.xpath('//div[contains(@class,"bookid_bookInfo")]/p[3]/b/text()').extract_first()
|
comic_item.add_xpath('dep', '//div[contains(@class,"bookid_bookInfo")]/p[4]/text()', index=1)
|
||||||
comic['dep'] = response.xpath('//div[contains(@class,"bookid_bookInfo")]/p[4]/text()').extract()[1]
|
comic_item.add_xpath('date', '//div[contains(@class,"bookid_bookInfo")]/p[5]/small/text()', index=1)
|
||||||
comic['date'] = response.xpath('//div[contains(@class,"bookid_bookInfo")]/p[5]/small/text()').extract()[1]
|
comic_item.add_value('genre', "韩漫")
|
||||||
comic['genre'] = "韩漫"
|
comic_item.add_value('age_rating', "R18+")
|
||||||
comic['age_rating'] = "R18+"
|
chapter_href = comic_item.get_xpath('//div[contains(@class,"bookid_chapterBox")]'
|
||||||
chapters = response.xpath('//div[contains(@class,"bookid_chapterBox")]'
|
'//div[contains(@class,"bookid_chapter")]/a/@href')
|
||||||
'//div[contains(@class,"bookid_chapter")]/a/text()').extract()
|
#chapters = response.xpath('//div[contains(@class,"bookid_chapterBox")]'
|
||||||
chapter_href = response.xpath('//div[contains(@class,"bookid_chapterBox")]'
|
# '//div[contains(@class,"bookid_chapter")]/a/text()').extract()
|
||||||
'//div[contains(@class,"bookid_chapter")]/a/@href').extract()
|
|
||||||
#for chapter, link in zip(chapters, chapter_href):
|
#for chapter, link in zip(chapters, chapter_href):
|
||||||
for i, link in enumerate(chapter_href, start=1):
|
for i, link in enumerate(chapter_href, start=1):
|
||||||
yield scrapy.Request(self.main_url+link, meta={'item' : comic, 'number': i}, callback=self.parse_chapter)
|
yield scrapy.Request(self.main_url+link, meta={'item': comic_item.load_item(), 'num': i}, callback=self.parse_chapter)
|
||||||
|
|
||||||
|
# 读取某章节下的所有图片
|
||||||
def parse_chapter(self, response):
|
def parse_chapter(self, response):
|
||||||
item = response.meta['item']
|
comic_item = ComicLoader(item=response.meta['item'], response=response)
|
||||||
number = response.meta['number']
|
data = comic_item.get_xpath('//script[@id="__NEXT_DATA__"]/text()')[0]
|
||||||
data = response.xpath('//script[@id="__NEXT_DATA__"]/text()').extract_first()
|
|
||||||
str_exec = "props.pageProps."
|
str_exec = "props.pageProps."
|
||||||
comic_name = CommonUtils.parseExec(data, str_exec+"bookName")
|
#comic_item.add_exec('name', data, str_exec=str_exec+"bookName")
|
||||||
chapterName = CommonUtils.parseExec(data, str_exec+"chapterName")
|
#comic_item.add_exec('dep', data, str_exec=str_exec+"description")
|
||||||
description = CommonUtils.parseExec(data, str_exec+"description")
|
comic_item.add_value('index', response.meta['num'])
|
||||||
images = CommonUtils.parseExec(data, str_exec+"images")
|
comic_item.add_exec('chapter', data, str_exec=str_exec + "chapterName")
|
||||||
chapter_api_url = CommonUtils.parseExec(data, str_exec+"chapterAPIPath")
|
comic_item.add_exec('list_img', data, str_exec+"images")
|
||||||
item['chapter'] = chapterName
|
comic = comic_item.load_item()
|
||||||
item['list_img'] = images
|
chapter_api_url = comic_item.get_exec(data, str_exec+"chapterAPIPath")
|
||||||
item['index'] = number
|
if chapter_api_url is not None:
|
||||||
if chapter_api_url != None:
|
yield scrapy.Request(self.main_url + chapter_api_url, meta={'item': comic}, callback=self.parse_chapter_api)
|
||||||
yield scrapy.Request(self.main_url+ chapter_api_url,meta={'item' : item}, callback= self.parse_chapter_api)
|
|
||||||
else:
|
else:
|
||||||
yield item
|
yield comic
|
||||||
|
|
||||||
|
# 加密数据API处理
|
||||||
def parse_chapter_api(self, response):
|
def parse_chapter_api(self, response):
|
||||||
item = response.meta['item']
|
comic_item = ComicLoader(item=response.meta['item'], response=response)
|
||||||
item['chapter'] = CommonUtils.parseExec(response.text, "chapter.name")
|
comic_item.add_exec('chapter', response.text, str_exec='chapter.name')
|
||||||
item['list_img'] = CommonUtils.parseExec(response.text, "chapter.images")
|
comic_item.add_exec('list_img', response.text, str_exec='chapter.images')
|
||||||
yield item
|
yield comic_item.load_item()
|
||||||
|
|
||||||
def parse(self, response):
|
def parse(self, response):
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
@ -1,105 +0,0 @@
|
|||||||
import os, shutil, time, logging
|
|
||||||
from datetime import datetime
|
|
||||||
from pathlib import Path
|
|
||||||
from zipfile import ZipFile
|
|
||||||
from Comics.settings import COMIC_INFO_XML_FILE,CBZ_EXPORT_PATH,IMAGES_STORE
|
|
||||||
|
|
||||||
class CBZUtils:
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def readDirsOrFiles(cls,dir,type):
|
|
||||||
data = []
|
|
||||||
files = os.listdir(dir)
|
|
||||||
for file in files:
|
|
||||||
path = os.path.join(dir,file)
|
|
||||||
if type == "files" and os.path.isfile(path):
|
|
||||||
data.append(path)
|
|
||||||
if type == "dirs" and os.path.isdir(path):
|
|
||||||
data.append(path)
|
|
||||||
return data
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def zip_compression(cls, source_dir=None, target_file=None, remove=True):
|
|
||||||
target_dir = os.path.dirname(target_file)
|
|
||||||
if not os.path.exists(target_dir):
|
|
||||||
os.makedirs(target_dir)
|
|
||||||
if not os.path.exists(target_file) and source_dir != None:
|
|
||||||
with ZipFile(target_file, mode='w') as zf:
|
|
||||||
for path, dir_names, filenames in os.walk(source_dir):
|
|
||||||
path = Path(path)
|
|
||||||
arc_dir = path.relative_to(source_dir)
|
|
||||||
y = 0
|
|
||||||
for filename in filenames:
|
|
||||||
y = y + 1
|
|
||||||
print("打包中:" + str(y) + "/" + str(len(filenames)), os.path.join(source_dir, filename))
|
|
||||||
zf.write(path.joinpath(filename), arc_dir.joinpath(filename))
|
|
||||||
zf.close()
|
|
||||||
logging.info(f"打包完成:{target_file}")
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def packComicChapterCBZ(cls, comic, chapter, remove=True):
|
|
||||||
images_chapter_path = os.path.join(IMAGES_STORE, comic, chapter)
|
|
||||||
cbz_chapter_path = os.path.join(CBZ_EXPORT_PATH, comic, chapter)+".CBZ"
|
|
||||||
if os.path.exists(images_chapter_path):
|
|
||||||
dirs = os.listdir(images_chapter_path)
|
|
||||||
for file in dirs:
|
|
||||||
if file.startswith("scramble="):
|
|
||||||
try:
|
|
||||||
os.remove(file)
|
|
||||||
except:
|
|
||||||
print(f"删除 {file} 发生错误,已跳过")
|
|
||||||
return False
|
|
||||||
cls.zip_compression(images_chapter_path, cbz_chapter_path)
|
|
||||||
time.sleep(0.1)
|
|
||||||
if remove: shutil.rmtree(images_chapter_path)
|
|
||||||
return True
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def replaceZip(cls,filepath,unpack_dir=None):
|
|
||||||
if not cls.compareFileDate(filepath): return None
|
|
||||||
if unpack_dir == None:
|
|
||||||
unpack_dir = str(filepath).split(".")[0]
|
|
||||||
fz = ZipFile(filepath, 'r')
|
|
||||||
for file in fz.namelist():
|
|
||||||
if file.endswith(".jpg"):
|
|
||||||
data = fz.read(file)
|
|
||||||
if len(data) < 500 and os.path.exists(filepath):
|
|
||||||
os.remove(filepath)
|
|
||||||
print(f"数据不完整,已删除:{filepath}")
|
|
||||||
if cls.compareFileDate(filepath):
|
|
||||||
os.utime(filepath)
|
|
||||||
print(f"已更新文件时间 {filepath}")
|
|
||||||
if os.path.exists(unpack_dir):
|
|
||||||
shutil.rmtree(unpack_dir)
|
|
||||||
# 删除删除main.ftl文件
|
|
||||||
#delete_filename = ''
|
|
||||||
#if os.path.exists(delete_filename):
|
|
||||||
# os.remove(delete_filename)
|
|
||||||
# time.sleep(60)
|
|
||||||
# shutil.copy(文件的路径,另一个目录);拷贝main.ftl到准备压缩的目录下
|
|
||||||
#cls.zip_compression()
|
|
||||||
#小于则运行
|
|
||||||
@classmethod
|
|
||||||
def compareFileDate(cls,filepath):
|
|
||||||
if os.path.exists(filepath):
|
|
||||||
ctime = os.path.getmtime(filepath)
|
|
||||||
str_ctime = datetime.fromtimestamp(int(ctime))
|
|
||||||
file_ctime = str(str_ctime.year)+"{:0>2d}".format(str_ctime.month)+"{:0>2d}".format(str_ctime.day)+"{:0>2d}".format(str_ctime.hour)
|
|
||||||
c_ctime = 2023011603
|
|
||||||
else:
|
|
||||||
return False
|
|
||||||
if int(file_ctime) < c_ctime:
|
|
||||||
return True
|
|
||||||
return False
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def zip_info(cls, path, filter=True):
|
|
||||||
result = None
|
|
||||||
try:
|
|
||||||
with ZipFile(path, "r") as zip_file:
|
|
||||||
result = zip_file.namelist()
|
|
||||||
if filter:
|
|
||||||
result.remove(COMIC_INFO_XML_FILE)
|
|
||||||
except Exception as e:
|
|
||||||
print(e)
|
|
||||||
return result
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
import json,os
|
|
||||||
import logging
|
|
||||||
from xml.dom.minidom import Document
|
|
||||||
from Comics.utils.Constant import ComicPath
|
|
||||||
from itemadapter import is_item, ItemAdapter
|
|
||||||
|
|
||||||
class ComicInfo:
|
|
||||||
IS_NEW_ICON = False
|
|
||||||
document = Document()
|
|
||||||
path_comic_info = None
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def setNodeAndValue(cls,node,value):
|
|
||||||
if value != None:
|
|
||||||
if isinstance(value,str):
|
|
||||||
c_node = cls.document.createElement(node)
|
|
||||||
child_node = cls.document.createTextNode(value)
|
|
||||||
c_node.appendChild(child_node)
|
|
||||||
return c_node
|
|
||||||
else: return value
|
|
||||||
return None
|
|
||||||
|
|
||||||
#页数
|
|
||||||
@classmethod
|
|
||||||
def setPages(cls,values=None):
|
|
||||||
#if values == None: values = Comic.getChapterFilesName()
|
|
||||||
if values != None and isinstance(values,list):
|
|
||||||
suffix = "."+str(values[0]).split(".")[-1]
|
|
||||||
join_list=",".join(values).replace(suffix,"")
|
|
||||||
values = join_list.split(",")
|
|
||||||
#Comic.setPageCount(len(values)+1 if cls.IS_NEW_ICON else len(values))
|
|
||||||
root_node = cls.document.createElement("Pages")
|
|
||||||
if cls.IS_NEW_ICON:
|
|
||||||
#添加封面
|
|
||||||
icon_node = cls.document.createElement("Page")
|
|
||||||
icon_node.setAttribute("Image",ComicPath.COMIC_ICON_NAME)
|
|
||||||
icon_node.setAttribute("Type","FrontCover")
|
|
||||||
root_node.appendChild(icon_node)
|
|
||||||
for page in values:
|
|
||||||
c_node = cls.document.createElement("Page")
|
|
||||||
page = page.split("_")[-1]
|
|
||||||
c_node.setAttribute("Image",page)
|
|
||||||
root_node.appendChild(c_node)
|
|
||||||
#Comic.dict_pages = Comic.setField(Comic.dict_pages,root_node,convert=False)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def getBaseUrl(cls,url=None):
|
|
||||||
#if url == None:
|
|
||||||
# url = Comic.getHomePage()
|
|
||||||
(num,index) = [3,0]
|
|
||||||
for x in range(0, num):
|
|
||||||
index = str(url).find("/",index)+1
|
|
||||||
return url[0:index-1]
|
|
||||||
|
|
||||||
#XML根文档
|
|
||||||
@classmethod
|
|
||||||
def root_node(cls,root_value): return cls.document.createElement(root_value)
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def add_nodes(cls,root,item):
|
|
||||||
item = ItemAdapter(item)
|
|
||||||
keys = item.keys()
|
|
||||||
files = item.field_names()
|
|
||||||
values = item.values()
|
|
||||||
print("test")
|
|
||||||
#if len(list_value) == 0: return list_value
|
|
||||||
#for value in list_value:
|
|
||||||
# #Comic.chapter
|
|
||||||
# if value[0] == None and value[4]:
|
|
||||||
# #数据为空 value[0], 但不允许为空value[4] = False
|
|
||||||
# msg = f"#数据为空 key={value[3]} value[0]={value[0]}, 但不允许为空value[4]={value[4]}"
|
|
||||||
# logging.error(msg)
|
|
||||||
# exit()
|
|
||||||
# if value[0] != None: root.appendChild(cls.setNodeAndValue(value[2],value[0]))
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def initComicInfoXML(cls):
|
|
||||||
cls.setPages()
|
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def writeComicInfoXML(cls,item,overlay=False):
|
|
||||||
#save_path = ComicPath.getPathComicInfoXML()
|
|
||||||
save_path = "ComicInfo.xml"
|
|
||||||
if os.path.exists(save_path):
|
|
||||||
if overlay:
|
|
||||||
os.remove(save_path)
|
|
||||||
logging.info(f"已存在文件,已删除... {save_path}")
|
|
||||||
else:
|
|
||||||
logging.info(f"已存在文件,跳过中... {save_path}")
|
|
||||||
return True
|
|
||||||
cls.initComicInfoXML()
|
|
||||||
root = cls.root_node("ComicInfo")
|
|
||||||
new_document = Document()
|
|
||||||
new_document.appendChild(root)
|
|
||||||
cls.add_nodes(root, item)
|
|
||||||
with open(save_path, "w", encoding="utf-8") as fo:
|
|
||||||
new_document.writexml(fo, indent='', addindent='\t', newl='\n', encoding="utf-8")
|
|
||||||
fo.close()
|
|
||||||
logging.info(f"已生成文件... {save_path}")
|
|
||||||
@ -1,14 +1,15 @@
|
|||||||
import os.path
|
import os.path
|
||||||
import re
|
import re
|
||||||
from opencc import OpenCC
|
from opencc import OpenCC
|
||||||
from Comics.settings import IMAGES_STORE
|
|
||||||
class ComicPath:
|
class ComicPath:
|
||||||
|
PREFIX_SCRAMBLE = "scramble="
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getDirComicChapter(cls):
|
def getDirComicChapter(cls):
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getFileScrambleImageName(cls,count,block,suffix=".jpg"): return "scramble="+str(block)+"_"+str(count)+suffix
|
def getFileScrambleImageName(cls,count,block,suffix=".jpg"): return cls.PREFIX_SCRAMBLE+str(block)+"_"+str(count)+suffix
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def getFileScrambleImageSave(cls,file): return str(file).split("_")[-1]
|
def getFileScrambleImageSave(cls,file): return str(file).split("_")[-1]
|
||||||
|
|||||||
@ -1,8 +1,10 @@
|
|||||||
import base64,hashlib,os,shutil
|
import base64,hashlib,os,shutil
|
||||||
import math,time,json,datetime,logging
|
import math,time,json,datetime,logging
|
||||||
from PIL import Image
|
from PIL import Image
|
||||||
from tinydb import TinyDB, Query
|
|
||||||
from Comics.utils.Constant import ComicPath
|
from Comics.utils.Constant import ComicPath
|
||||||
|
from pathlib import Path
|
||||||
|
from zipfile import ZipFile
|
||||||
|
from Comics.settings import COMIC_INFO_XML_FILE,CBZ_EXPORT_PATH,IMAGES_STORE
|
||||||
|
|
||||||
class fileUtils:
|
class fileUtils:
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -32,15 +34,15 @@ class imageUtils:
|
|||||||
if os.path.exists(chapter_dir): #获取章节图片路径
|
if os.path.exists(chapter_dir): #获取章节图片路径
|
||||||
dirs = os.listdir(chapter_dir)
|
dirs = os.listdir(chapter_dir)
|
||||||
for img in dirs:
|
for img in dirs:
|
||||||
if img.startswith("scramble="):
|
if img.startswith(ComicPath.PREFIX_SCRAMBLE):
|
||||||
imageUtils.encode_scramble_image(os.path.join(chapter_dir,img))
|
imageUtils.encode_scramble_image(os.path.join(chapter_dir,img))
|
||||||
scramble_count += 1
|
scramble_count += 1
|
||||||
logging.debug(f"scramble= {scramble_count}")
|
logging.debug(f"{ComicPath.PREFIX_SCRAMBLE} {scramble_count}")
|
||||||
return scramble_count
|
return scramble_count
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def deScrambleImagesByPath(cls, img_path, img_save=None):
|
def deScrambleImagesByPath(cls, img_path, img_save=None):
|
||||||
if os.path.basename(img_path).startswith("scramble="):
|
if os.path.basename(img_path).startswith(ComicPath.PREFIX_SCRAMBLE):
|
||||||
img_path = imageUtils.encode_scramble_image(img_path, img_save)
|
img_path = imageUtils.encode_scramble_image(img_path, img_save)
|
||||||
return img_path
|
return img_path
|
||||||
|
|
||||||
@ -233,3 +235,106 @@ class imageUtils:
|
|||||||
os.remove(imgpath)
|
os.remove(imgpath)
|
||||||
print("remove=",imgpath)
|
print("remove=",imgpath)
|
||||||
return save_path
|
return save_path
|
||||||
|
|
||||||
|
|
||||||
|
class CBZUtils:
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def readDirsOrFiles(cls, dir, type):
|
||||||
|
data = []
|
||||||
|
files = os.listdir(dir)
|
||||||
|
for file in files:
|
||||||
|
path = os.path.join(dir, file)
|
||||||
|
if type == "files" and os.path.isfile(path):
|
||||||
|
data.append(path)
|
||||||
|
if type == "dirs" and os.path.isdir(path):
|
||||||
|
data.append(path)
|
||||||
|
return data
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def zip_compression(cls, source_dir=None, target_file=None, remove=True):
|
||||||
|
target_dir = os.path.dirname(target_file)
|
||||||
|
if not os.path.exists(target_dir):
|
||||||
|
os.makedirs(target_dir)
|
||||||
|
if not os.path.exists(target_file) and source_dir is not None:
|
||||||
|
with ZipFile(target_file, mode='w') as zf:
|
||||||
|
for path, dir_names, filenames in os.walk(source_dir):
|
||||||
|
path = Path(path)
|
||||||
|
arc_dir = path.relative_to(source_dir)
|
||||||
|
y = 0
|
||||||
|
for filename in filenames:
|
||||||
|
y = y + 1
|
||||||
|
print("打包中:" + str(y) + "/" + str(len(filenames)), os.path.join(source_dir, filename))
|
||||||
|
zf.write(path.joinpath(filename), arc_dir.joinpath(filename))
|
||||||
|
zf.close()
|
||||||
|
logging.info(f"打包完成:{target_file}")
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def packComicChapterCBZ(cls, comic, chapter, remove=True):
|
||||||
|
images_chapter_path = os.path.join(IMAGES_STORE, comic, chapter)
|
||||||
|
cbz_chapter_path = os.path.join(CBZ_EXPORT_PATH, comic, chapter) + ".CBZ"
|
||||||
|
if os.path.exists(images_chapter_path):
|
||||||
|
dirs = os.listdir(images_chapter_path)
|
||||||
|
for file in dirs:
|
||||||
|
if file.startswith(ComicPath.PREFIX_SCRAMBLE):
|
||||||
|
try:
|
||||||
|
os.remove(file)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"删除 {file} 发生错误 {e},已跳过")
|
||||||
|
return False
|
||||||
|
cls.zip_compression(images_chapter_path, cbz_chapter_path)
|
||||||
|
time.sleep(0.1)
|
||||||
|
if remove: shutil.rmtree(images_chapter_path)
|
||||||
|
return True
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def replaceZip(cls, filepath, unpack_dir=None):
|
||||||
|
if not cls.compareFileDate(filepath): return None
|
||||||
|
if unpack_dir == None:
|
||||||
|
unpack_dir = str(filepath).split(".")[0]
|
||||||
|
fz = ZipFile(filepath, 'r')
|
||||||
|
for file in fz.namelist():
|
||||||
|
if file.endswith(".jpg"):
|
||||||
|
data = fz.read(file)
|
||||||
|
if len(data) < 500 and os.path.exists(filepath):
|
||||||
|
os.remove(filepath)
|
||||||
|
print(f"数据不完整,已删除:{filepath}")
|
||||||
|
if cls.compareFileDate(filepath):
|
||||||
|
os.utime(filepath)
|
||||||
|
print(f"已更新文件时间 {filepath}")
|
||||||
|
if os.path.exists(unpack_dir):
|
||||||
|
shutil.rmtree(unpack_dir)
|
||||||
|
# 删除删除main.ftl文件
|
||||||
|
# delete_filename = ''
|
||||||
|
# if os.path.exists(delete_filename):
|
||||||
|
# os.remove(delete_filename)
|
||||||
|
# time.sleep(60)
|
||||||
|
# shutil.copy(文件的路径,另一个目录);拷贝main.ftl到准备压缩的目录下
|
||||||
|
# cls.zip_compression()
|
||||||
|
# 小于则运行
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def compareFileDate(cls, filepath):
|
||||||
|
if os.path.exists(filepath):
|
||||||
|
ctime = os.path.getmtime(filepath)
|
||||||
|
str_ctime = datetime.fromtimestamp(int(ctime))
|
||||||
|
file_ctime = str(str_ctime.year) + "{:0>2d}".format(str_ctime.month) + "{:0>2d}".format(
|
||||||
|
str_ctime.day) + "{:0>2d}".format(str_ctime.hour)
|
||||||
|
c_ctime = 2023011603
|
||||||
|
else:
|
||||||
|
return False
|
||||||
|
if int(file_ctime) < c_ctime:
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def zip_info(cls, path, filter=True):
|
||||||
|
result = None
|
||||||
|
try:
|
||||||
|
with ZipFile(path, "r") as zip_file:
|
||||||
|
result = zip_file.namelist()
|
||||||
|
if filter:
|
||||||
|
result.remove(COMIC_INFO_XML_FILE)
|
||||||
|
except Exception as e:
|
||||||
|
print(e)
|
||||||
|
return result
|
||||||
Loading…
Reference in New Issue
Block a user