使用python制作epub

news/2024/11/7 18:49:50/

使用python制作epub

  • 前期工作
  • 开始制作
    • 第一步、分析网站
    • 第二步、抓取数据并清洗
      • 获取文章内容
    • 第三步、保存到Epub中
  • 全部代码

前期工作

  1. ebookLib库
    1. 关于该库,如果pypi版本太低,需要去gitlab上clone,然后运行python setup.py install
  2. zhconv库,主要用来简繁转换
  3. 一个允许抓取数据的小说网站novel-backup
  4. 一点点时间

开始制作

第一步、分析网站

根据自己抓取的网站,获取所有章节的链接
https://novels.novel-backup.cf/index/1558018541.json

根据获得的内容,对内容进行分析
(已省略部分数据)

[{"name": "41.成套的葡萄酒杯","id": 7460},{"name": "42.烤肉午餐","id": 7550}
]

里面的id就是下面章节内容的链接xx/yy.json的yy

再获取章节内容,对其内容进行分析
https://novels.novel-backup.cf/novels/93065.json
(已省略部分数据)

{"code_id": 1558018541,"title": "第1卷插圖","create_date": "2020-10-07 20:51:33","content": "<p><img src=\"https://live.staticflickr.com/65535/50431755246_afecb655fc_o.png[/img][/url][url=https://flic.kr/p/2jQtVPu]魔導具師ダリヤはうつむかない 1-0[/url] by [url=https://www.flickr.com/photos/55799173@N00/]jameslam518[/url], on Flickr\"  class=\"fr-fic fr-dib\"></p><p><br></p>","author": "職業量地官","views": 2896
}

对于我们来说,有用的是title、content、author

第二步、抓取数据并清洗

ebookLib的章节顺序是按照add_item来排的,所以我们需要对抓取的章节进行排序。
首先新建一个py文件,然后新建一个类Espider

    def getJson(self,url):html:requests.Response= requests.get(url)return html.json()def getDictList(self,url):js:typing.List[dict]=self.getJson(url)return jsdef getFilter(self,li_list):maxx=0id_dicts=[]for li in li_list:idict=liidict['name']=convert(idict['name'],'zh-hans')ll=re.findall(r'([1-9]\d*.\d*|0\.\d*[1-9]\d*)',idict['name'])if(len(ll)>0):s:str=ll[0]num=int(s[:-1])idict['num']=nummaxx=max(maxx,num)else:ll=re.findall(r'第([1-9]\d*)话',idict['name'])if(len(ll)>0):s:str=ll[0]num=int(s)idict['num']=nummaxx=max(num,maxx)else:maxx+=1idict['num']=maxxid_dicts.append(idict)id_dicts.sort(key=lambda it:it['num'])tmp_list:typing.List[dict]=[]for i in range(len(id_dicts)):id_dicts[i]['i']=str(i)tmp_list.append(id_dicts[i])return tmp_list

首先是获取数据,然后将数据转换格式(getJson,getDictList)
getFilter长长的代码简单理解就是将章节的链接List中每个174. 疲勞與真心話第3話 商業公會前面的数字取出来,然后如果有不存在数字的章节,就让这个章节的id=maxx

获取文章内容

对于图片需要特殊处理,先保存到本地后再添加到epub文件里

    def getDict(self,url):js:dict=self.getJson(url)return jsdef saveImg(self,title,src):path='Images/{}'.format(title)if(os.path.exists(path)==False):os.mkdir(path)s=re.findall(r'65535/(.*?)\[/img\]',src)if(len(s)==0):s=re.findall(r'65535/(.*?.png)',src)[0]else:s=s[0]res:requests.Response=requests.get(src,stream=True)res.raise_for_status()with open("{}/{}".format(path,s),"wb") as f:f.write(res.content)self.img_list.append({'src':"{}/{}".format(path,s),'uid':s.split('.')[0]})return "{}/{}".format(path,s)def contentCheck(self,title,content:str):soup=BeautifulSoup(content,'lxml')for img in soup.findAll('img'):s=self.saveImg(title,img['src'])img['src']=sreturn str(soup.body)def getContent(self,id):url_s='https://novels.novel-backup.cf/novels/'url_e='.json'print(url_s+id+url_e)js=self.getDict(url_s+id+url_e)js['author']=convert(js['author'],'zh-hans')js['title']=convert(js['title'],'zh-hans')js['content']=convert(js['content'],'zh-hans')return '<p>搬运:'+js['author']+'</p>'+self.contentCheck(js['title'],js['content'])

getDict是获取数据,然后getContentjs取出属性,使用contentCheck对内容处理,之后将其保存到List中。

第三步、保存到Epub中

新建一个ebook文件,将eooklib和Espider引入


toc = []
spine = ['nav']
book = epub.EpubBook()chp_list = []def init(title, author):# set metadatabook.set_identifier('id123456')book.set_title(title)book.set_language('cn')book.add_author(author)book.add_author('Anonymous', file_as='Anonymous',role='ill', uid='coauthor')# add default NCX and Nav filebook.add_item(epub.EpubNcx())book.add_item(epub.EpubNav())# define CSS stylestyle = 'pre{white-space:pre-wrap;background:#f7f9fa;padding:10px 15px;color:#263238;line-height:1.6;font-size:13px;border-radius:3px margin-top: 0;margin-bottom:1em;overflow:auto}b,strong{font-weight:bolder}#title{font-size:16px;color:#212121;font-weight:600;margin-bottom:10px}hr{height:10px;border:0;box-shadow:0 10px 10px -10px #8c8b8b inset}'nav_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style)# add CSS filebook.add_item(nav_css)def saveChapter():c1=getChapter('前言', '<p>使用python ebooklib整合,数据来源https://novel-backup.cf/,仅供参考请勿商用</p>', '000')book.add_item(c1)toc.append(epub.Link(c1.file_name,c1.title,c1.title))spine.append(c1)for it in chp_list:# For each chapter add chapter to the book, TOC and spinebook.add_item(it['chapter'])toc.append(epub.Link(it['chapter'].file_name,it['chapter'].title, it['chapter'].title))spine.append(it['chapter'])def saveImage(img_list:typing.List[dict]):for img in img_list:image_content = open(img['src'], 'rb').read()img = epub.EpubImage(uid=img['uid'], file_name=img['src'],media_type='image/png', content=image_content)book.add_item(img)def saveEpub(file_name):# define Table Of Contentsbook.toc = tuple(toc)# basic spinebook.spine = spine# write to the fileepub.write_epub('epub/'+file_name, book, {})def getChapter(title, content, id):c1 = epub.EpubHtml(title=title,file_name='chap_'+id+'.xhtml', lang='hr')c1.content = '<h1>'+title+'<h1>'+contentreturn c1def poChapter(it, llen):i = int(it['i'])+1c = getChapter(it['name'], es.getContent(str(it['id'])), str(i).zfill(llen))chp_list.append({'chapter': c,'id': i})if __name__ == '__main__':init('魔導具師妲莉雅不會低頭 ~從今天開始自由的職人生活~', '自动叉积·整合')es = Espider()li_url = 'https://novels.novel-backup.cf/index/1558018541.json'li_list = es.getDictList(li_url)id_dicts = es.getFilter(li_list)llen = len(str(len(id_dicts)))# poChapter(id_dicts[0],llen)# 创建线程index = [i for i in range(0, len(id_dicts), 4)]threads = []for i in index:for j in range(0, 4):threads.append(threading.Thread(target=poChapter, args=(id_dicts[i+j], llen)))for t in threads:t.start()for t in threads:t.join()print('Main thread has ended!')chp_list.sort(key=lambda it: it['id'])saveChapter()saveImage(es.img_list)saveEpub('《魔導具師妲莉雅不會低頭 ~從今天開始自由的職人生活~》.epub')

init来自ebookLib官方文档给出的函数,str(i).zfill(llen)是对数字进行数位补齐,如’chap_002.xhtml’
引入threading是为了在爬虫的时候进行多线程,提高效率。

全部代码

# spider.py
import requests
from bs4 import BeautifulSoup
import typing
import re
import os
from zhconv import convertclass Espider:
# https://novels.novel-backup.cf/index/1558018541.json# https://novels.novel-backup.cf/novels/7460.jsonimg_list=[]def getJson(self,url):html:requests.Response= requests.get(url)# soup = BeautifulSoup(html.json())return html.json()def getDict(self,url):js:dict=self.getJson(url)# print(js)return jsdef getDictList(self,url):js:typing.List[dict]=self.getJson(url)# print(js)return jsdef saveImg(self,title,src):path='Images/{}'.format(title)if(os.path.exists(path)==False):os.mkdir(path)# print(src)s=re.findall(r'65535/(.*?)\[/img\]',src)# print(s)if(len(s)==0):s=re.findall(r'65535/(.*?.png)',src)[0]else:s=s[0]# print(s)res:requests.Response=requests.get(src,stream=True)res.raise_for_status()with open("{}/{}".format(path,s),"wb") as f:f.write(res.content)self.img_list.append({'src':"{}/{}".format(path,s),'uid':s.split('.')[0]})return "{}/{}".format(path,s)def contentCheck(self,title,content:str):soup=BeautifulSoup(content,'lxml')# print(soup)for img in soup.findAll('img'):s=self.saveImg(title,img['src'])img['src']=s# ''.join(str(it) for it in soup.find_all('p'))return str(soup.body)def getContent(self,id):url_s='https://novels.novel-backup.cf/novels/'url_e='.json'print(url_s+id+url_e)js=self.getDict(url_s+id+url_e)js['author']=convert(js['author'],'zh-hans')js['title']=convert(js['title'],'zh-hans')js['content']=convert(js['content'],'zh-hans')# print(js['author'],js['title'],js['content'])return '<p>搬运:'+js['author']+'</p>'+self.contentCheck(js['title'],js['content'])def getFilter(self,li_list):maxx=0id_dicts=[]for li in li_list:idict=liidict['name']=convert(idict['name'],'zh-hans')ll=re.findall(r'([1-9]\d*.\d*|0\.\d*[1-9]\d*)',idict['name'])if(len(ll)>0):s:str=ll[0]num=int(s[:-1])idict['num']=nummaxx=max(maxx,num)else:ll=re.findall(r'第([1-9]\d*)话',idict['name'])if(len(ll)>0):s:str=ll[0]num=int(s)idict['num']=nummaxx=max(num,maxx)else:maxx+=1idict['num']=maxxid_dicts.append(idict)id_dicts.sort(key=lambda it:it['num'])tmp_list:typing.List[dict]=[]for i in range(len(id_dicts)):id_dicts[i]['i']=str(i)tmp_list.append(id_dicts[i])return tmp_listdef getIdList(self,li_list):id_list:typing.List[str]=[str(it['id']) for it in li_list]return id_listif __name__=="__main__":print("爬取开始")# po=pool.Pool(5)# li_url='https://novels.novel-backup.cf/index/1558018541.json'es=Espider()# li_list=es.getDictList(li_url);# # print(li_list)# id_dicts=es.getFilter(li_list)# print(id_dicts)print(es.getContent('112353'))print(es.getContent('16733'))# print(es.img_list)print('爬取结束')# ebook.py
import threading
import typing
from ebooklib import epub
from spider import Espidertoc = []
spine = ['nav']
book = epub.EpubBook()chp_list = []def init(title, author):# set metadatabook.set_identifier('id123456')book.set_title(title)book.set_language('cn')book.add_author(author)book.add_author('Anonymous', file_as='Anonymous',role='ill', uid='coauthor')# add default NCX and Nav filebook.add_item(epub.EpubNcx())book.add_item(epub.EpubNav())# define CSS stylestyle = 'pre{white-space:pre-wrap;background:#f7f9fa;padding:10px 15px;color:#263238;line-height:1.6;font-size:13px;border-radius:3px margin-top: 0;margin-bottom:1em;overflow:auto}b,strong{font-weight:bolder}#title{font-size:16px;color:#212121;font-weight:600;margin-bottom:10px}hr{height:10px;border:0;box-shadow:0 10px 10px -10px #8c8b8b inset}'nav_css = epub.EpubItem(uid="style_nav", file_name="style/nav.css", media_type="text/css", content=style)# add CSS filebook.add_item(nav_css)def saveChapter():c1=getChapter('前言', '<p>使用python ebooklib整合,数据来源https://novel-backup.cf/,仅供参考请勿商用</p>', '000')book.add_item(c1)toc.append(epub.Link(c1.file_name,c1.title,c1.title))spine.append(c1)for it in chp_list:# For each chapter add chapter to the book, TOC and spinebook.add_item(it['chapter'])toc.append(epub.Link(it['chapter'].file_name,it['chapter'].title, it['chapter'].title))spine.append(it['chapter'])# print('save c', chapter.file_name)def saveImage(img_list:typing.List[dict]):for img in img_list:image_content = open(img['src'], 'rb').read()img = epub.EpubImage(uid=img['uid'], file_name=img['src'],media_type='image/png', content=image_content)book.add_item(img)def saveEpub(file_name):# define Table Of Contentsbook.toc = tuple(toc)# basic spinebook.spine = spine# write to the fileepub.write_epub('epub/'+file_name, book, {})def getChapter(title, content, id):c1 = epub.EpubHtml(title=title,file_name='chap_'+id+'.xhtml', lang='hr')c1.content = '<h1>'+title+'<h1>'+contentprint("g", c1.file_name, c1.title, id)return c1def poChapter(it, llen):# print("开始进程", it['i'])i = int(it['i'])+1c = getChapter(it['name'], es.getContent(str(it['id'])), str(i).zfill(llen))chp_list.append({'chapter': c,'id': i})# saveChapter(c, it['i'])if __name__ == '__main__':init('魔導具師妲莉雅不會低頭 ~從今天開始自由的職人生活~', '自动叉积·整合')es = Espider()li_url = 'https://novels.novel-backup.cf/index/1558018541.json'li_list = es.getDictList(li_url)id_dicts = es.getFilter(li_list)llen = len(str(len(id_dicts)))# poChapter(id_dicts[0],llen)# 创建线程index = [i for i in range(0, len(id_dicts), 4)]threads = []for i in index:for j in range(0, 4):threads.append(threading.Thread(target=poChapter, args=(id_dicts[i+j], llen)))for t in threads:t.start()for t in threads:t.join()print('Main thread has ended!')chp_list.sort(key=lambda it: it['id'])saveChapter()# es.img_list.append('Images/第6卷插圖/51154283631_826ee93727_o.png')saveImage(es.img_list)saveEpub('《魔導具師妲莉雅不會低頭 ~從今天開始自由的職人生活~》.epub')

http://www.ppmy.cn/news/456176.html

相关文章

74160分频_74160引脚图及功能

3、用两片74160组成24/12进制递增计数器?时钟部分? 由两片74160组成的能实现12和24进制转换的同步递增计数器 如图? ?小时部分电路? 图中个位与十位计数器均为...... (时钟部分) 由两片 74160 组成的能实现 12 和 24 进制转换的同步递... 基于74160计数器的电子时钟设计_物…

74LS系列芯片简记——00-09

由于兴趣与实际需求特学习对应的数字芯片&#xff0c;以此笔记简略记录用途等&#xff0c;如有不足欢迎批评指正。欢迎大家踊跃交流。前期门芯片讲述较为简单。 74LS00(四组2输入与非门) 逻辑为&#xff1a;可用于进行与非判断&#xff0c;基本RS触发器&#xff0c;与非门方波发…

互联网晚报 | 06月21日 星期二 | iPhone14或提高全系售价;腾讯控股出售新东方在线7460万股;​乐高8月涨价...

iPhone14或提高全系售价 据上游供应链最新爆料&#xff0c;目前全球通胀&#xff0c;对应的各种原材料上涨&#xff0c;同时还有人力成本的提高&#xff0c;这倒逼苹果不得不提高iPhone 14系列的售价&#xff0c;当然苹果也会拉大同系列产品间的配置&#xff0c;来引导用户购买…

0002-TIPS-2020-hxp-kernel-rop : bypass-smep-with-rop

使用的开始上一节中的pwn题 SMEP (Supervisor Mode Execution Prevention) SMEP安全机制&#xff1a;禁止在执行内核空间代码时突然执行用户空间代码。类似 NX。 在没有开启SMEP的情况下&#xff0c;攻击者将rip或者函数指针指向了用户空间&#xff0c;可在用户空间布局shell…

SpringMvc详解

SpringMvc用来代替展示层Servlet&#xff0c;均属于Web层开发技术 Servlet是如何工作的 1、导入Servlet依赖坐标 2、创建一个Servlet接口实现类&#xff0c;重写其中的所有方法 3、在Servlet实现类上加上WebServlet注解&#xff0c;用来配置Servlet访问路径 4、启动Tomca…

Mysql索引、事务以及存储引擎

目录 一、索引 1.概述 2.作用 3.索引的缺点 4.创建索引的原则依据 5.索引分类和创建 5.1普通索引 5.2唯一索引 5.3主键索引 5.4组合索引&#xff08;单列索引与多列索引&#xff09; 5.5全文索引&#xff08;FULLTEXT&#xff09; 6.查看索引 7.删除索引 二、事务…

oppo服务器是网络运营商的吗,三大运营商慌了!OPPO宣布“无网络通信技术”,可绕开基站控制?...

原标题&#xff1a;三大运营商慌了&#xff01;OPPO宣布“无网络通信技术”&#xff0c;可绕开基站控制&#xff1f; 在通信领域&#xff0c;国内一直以移动、联通、电信三大运营商最为权威。让我们轻松实现了通话、上网和发短信等功能。 尽管现在的网络范围已经足够广&#xf…

oppo服务器暂时不可用,oppo手机网络连接不可用是怎么回事

大家好&#xff0c;我是时间财富网智能客服时间君&#xff0c;上述问题将由我为大家进行解答。 oppo手机网络连接不可用的原因如下&#xff1a; 1、手机欠费了。联系运营商确认手机SIM卡开通了上网功能或是否欠费。 2、确保数据网络开关打开&#xff0c;重启手机后尝试是否可以…