参考这篇文章

最近沉迷看网络小说,苦于部分网站用手机浏览器看很不方便、不能同步阅读进度,更重要的是在电梯车库等地方手机都没信号,因此准备用爬虫爬下来看.

其实TXT版本的电子书比较好找,但是没有目录什么的,很难受.因此选择epub格式的电子书,对于kindle用户可以用calibre转换成mobi或直接使用官方转换推送一条龙服务.

前面的参考博客里推荐的是使用pandoc软件实现有格式的TXT文件转换至epub,挺简单,但是没有首行缩进很不舒服.

研究了很久pandoc的模板和css,也没实现首行缩进,最终决定换个方式.

pandoc会把正文放进code标签内,找不到实现缩进的css代码,如果有办法实现,也可以通过自定义css的方式.

搜索许久找到一个介绍还算比较清楚的库mkepub,记录一下踩到的坑.

从网站爬内容,在参考博客里写的很清楚,不再多说,根据网站实际情况清洗就好了.

爬到正文以后,先将NBSP替换成空格,之后根据实际情况拆分成列表.
爬取和清洗的代码如下:

    def _get_web(self,url):res=S.get(url) # S是requests.session()res.encoding='gbk' # 该网站编码是'ISO-8859-1',会有乱码soup=BeautifulSoup(res.text, "html.parser")return soupdef _anl_web(self,this_url:str):url=self.url_basic+this_urlsoup=self._get_web(url)title=soup.select('#content > div > div.h1title > h1')[0].getText() # select 参数根据实际情况确定content = soup.select('#htmlContent')[0].getText().split('\r\n')[1].replace(' ',' ').strip().split('    ') # replace第一个参数实际上是 NBSPnext=soup.select('#pager_next')[0]next_url = next.get('href')next_title = next.getText() # 该网站末页的标题是特殊固定的,用于判断结束return [title,content,next_url,next_title]

需要注意的是,bs4的select返回值是一个列表,需要先取值.因为网站格式统一,一般不会出现空列表的情况,所以就没做判断.

还需要获取书的meta数据,根据网站不同,获取方式也不相同,不过大体类似

到这一步后,参考博客里是将爬到的数据写进了txt文件,我这里是写进了xhtml文件.

XHTML_SMP="""<section id="{title}" class="level1" data-number="{no}">
<h1 data-number="{no}">{title}</h1>
{body}
</section>
"""

这是一个xhtml文件的模板,大概就是解包了一个epub,从里面抄的,需要注意的是,mkepub会自己加上html,head,body等内容及标签,因此只需要保留body内的部分就好.

模板里用花括号包裹的内容,是利用python的format字符串替换的内容.替换方式见后.

写入文件部分代码如下:

no=0
xhtml_list=[]
while self.flag:no+=1xhtml_path:Path=self.dirpath/('ch'+f'{no}'.rjust(5,'0')+'.xhtml') # 这里用到了path库title,content,url,next_title=self._anl_web(url)print(title) # 打印标题,表示没卡死if '书末页' in next_title:self.flag=Falsebody=''for line in content:body+=f'<p>{line}</p>\n' # 每行用<p>标签包裹,方便用css定义首行缩进.加个换行符是为了源码好看,对最终实现效果没影响xhtml_path.write_text(XHTML_SMP.format(**locals())) # 替换模版里的变量xhtml_list.append([xhtml_path,title]) # 返回xhtml文件路径以及章标题,mkepub要用
return xhtml_list										

XHTML_SMP.format(**locals())的作用是用本地变量格式化字符串.需要注意的是变量名和作用域.
下面是错误示范:

def some():c=1def some2():self.a=1b=2s='{a}{self.b}{c}'.format(**locals())

a和b是因为变量名不同,c是因为作用域不同,都会报错

之后是用mkepub制作epub文件.

import json
import mkepub
from path import Path
from com.Smp import CSS_PATH
class make_epub:def __init__(self,xhtml_list:[Path,str],dir_path:Path):self.dir_path = dir_pathself.xhtml_list = xhtml_listdef to_do(self):self._get_mate()book = mkepub.Book(title=self.book_name, author=self.author) # 定义书名和作者 book.set_stylesheet(CSS_PATH.read_text(encoding='utf8')) # 传入的是css文件内容,CSS_PATH类型是path.Pathfor xhtml_path,title in self.xhtml_list:book.add_page(title=title,content=xhtml_path.read_text(encoding='UTF-8'))# 传入的是文件内容,book.save(self.dir_path.parent/(self.book_name+'.epub'))def _get_mate(self):# 之前将mate信息写进了mate.json文件内mate_path:Path=self.dir_path/'meta.json'mate=json.loads(mate_path.read_text(encoding='utf8'))self.book_name=mate['book_name']self.author = mate['author']

css文件是抄的pandoc的默认css,加了点内容,文件内容如下:

/* This defines styles and classes used in the book */
body { margin: 5%; text-align: justify; font-size: medium; }
code { font-family: monospace; }
h1 { text-align: left; }
h2 { text-align: left; }
h3 { text-align: left; }
h4 { text-align: left; }
h5 { text-align: left; }
h6 { text-align: left; }
/* For title, author, and date on the cover page */
h1.title { }
p.author { }
p.date { }
p{text-indent: 2em;} /* 加入的内容,首行缩进两个字符 */
nav#toc ol,
nav#landmarks ol { padding: 0; margin-left: 1em; }
nav#toc ol li,
nav#landmarks ol li { list-style-type: none; margin: 0; padding: 0; }
a.footnote-ref { vertical-align: super; }
em, em em em, em em em em em { font-style: italic;}
em em, em em em em { font-style: normal; }
code{ white-space: pre-wrap; }
span.smallcaps{ font-variant: small-caps; }
span.underline{ text-decoration: underline; }
q { quotes: "“" "”" "‘" "’"; }
div.column{ display: inline-block; vertical-align: top; width: 50%; }
div.hanging-indent{margin-left: 1.5em; text-indent: -1.5em;}
@media screen { /* Workaround for iBooks issue; see #6242 */.sourceCode {overflow: visible !important;white-space: pre-wrap !important;}
}

至此全部代码完成,不过实际测试的时候发现生成的epub文件有问题,提示编码错误.检查mkepub源码修改了点内容,如下:

# mkepub.pydef _write(self, template, path, **data):with open(str(self.path / path), 'w',encoding='UTF-8') as file: # 加入了encoding参数file.write(env.get_template(template).render(**data))

之后可以正常使用.

我将整个程序分成了四部分,分别是:

  1. 入口流程main模块,用来解析url,并分配不同的rule类
  2. rule模块,对于不同的网站实际处理方式不同(现在就只完成了一个网站)
  3. make_epub模块,把xhtml文件打包成epub文件
  4. 一些辅助的小模块,例如前面用到的S的定义