本帖最后由 侃遍天下无二人 于 2023-4-6 21:12 编辑
这段时间因为各种需要和电子书相关的处理工具打交道比较多,恰好最近也更新了微信文章下载器,但下载器下下来的文章并不适合直接用pandoc转为电子书,因为里面有许多多余的层级结构,可能会导致排版出问题。我先前的做法是把需要制成电子书的文章复制粘贴到 typora 中,这样它就被规范成md格式了,然后再用pandoc将md转为epub。
但现在想来,这样搞存在大量需要手工处理的点,效率太低,于是这两天我基于这段时间积累的经验,稍微研究了一下要如何处理下载下来的微信文章,使之能被pandoc很好地从html直接转成epub格式,并尽量减少后续的处理步骤。研究成果与大家分享一下:
首先,我们不妨以吾爱破解的公众号为例(主要是放其他公众号有推广嫌疑),下载其中推送的 10 篇原创文章:
然后用pandoc将其中一篇直接转为epub,发现阅读体验非常差,甚至根本就读不到东西:
显然我们应该尽量简化写入html的样式结构,才能提升转换的质量。
我们打开调试工具,可以定位到一个恰好覆盖了整篇文章的节点,节点的id为 js_content :
另外,对于已经保存到本地的留言,它总是被包裹在[HTML] 纯文本查看 复制代码 <!--cmt_start--> 和[HTML] 纯文本查看 复制代码 <!--cmt_end--> 之间,可以直接用正则表达式提取,此外,留言的样式也很简单,只有如下几条,可以将其写入一个css文件让pandoc使用:
[CSS] 纯文本查看 复制代码 .avatar{width:32px;height:32px;margin-right:12px;border-radius:3px;margin-top:4px}.nickname_wrp{margin-top:5px;margin-bottom:5px;color:rgba(0,0,0,.5)}.reply_result.js_reply_item{border-left:5px solid #aaa;padding-left:15px}
这样,就可以将文章重组成较为精简的样式,从而提升转换质量。
具体要如何转换呢?下面就以golang为例,进行说明:
首先,我们需要将html文件全部读入内存,并转为字符串形式方便后续处理(代码略)。
接下来,我们要对字符串进行解析,这样才能执行dom操作,解析会用到 "golang.org/x/net/html" 包,代码如下:
[Golang] 纯文本查看 复制代码 doc, _ := html.Parse(strings.NewReader(content))
解析的目的是便于取出文章节点,并将其子节点全部转为字符串,我们编写一个findElementById函数实现:
[Golang] 纯文本查看 复制代码 func findElementById(n *html.Node, id string) *html.Node {
if n.Type == html.ElementNode && n.Attr != nil {
for _, attr := range n.Attr {
if attr.Key == "id" && attr.Val == id {
return n
}
}
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
if result := findElementById(c, id); result != nil {
return result
}
}
return nil
}
虽然函数看上去有点啰嗦,但调用起来很简单:
[Golang] 纯文本查看 复制代码 imgContent := findElementById(doc, "js_content")
取得这个节点后,我们要将其子节点全部转为字符串,以便后续重新写入文件:
[Golang] 纯文本查看 复制代码 innerHtml := extractInnerHTML(imgContent)
函数实现如下:
[Golang] 纯文本查看 复制代码 // 提取节点的innerHTML
func extractInnerHTML(n *html.Node) string {
var h string
for c := n.FirstChild; c != nil; c = c.NextSibling {
h += renderNode(c)
}
return h
}
// 将节点渲染为HTML字符串
func renderNode(n *html.Node) string {
var h string
switch n.Type {
case html.ErrorNode:
h = fmt.Sprintf("<!--%s-->", n.Data)
case html.TextNode:
h = n.Data
case html.DocumentNode, html.ElementNode:
h = fmt.Sprintf("<%s", n.Data)
for _, a := range n.Attr {
h += fmt.Sprintf(` %s="%s"`, a.Key, a.Val)
}
if n.Type == html.ElementNode {
h += ">"
}
for c := n.FirstChild; c != nil; c = c.NextSibling {
h += renderNode(c)
}
if n.Type == html.ElementNode {
h += fmt.Sprintf("</%s>", n.Data)
}
case html.CommentNode:
h = fmt.Sprintf("<!--%s-->", n.Data)
}
return h
}
当然,既然是innerHTML,取得的内容是不包含节点本身的,我们用字符串拼接给它补上:
[Golang] 纯文本查看 复制代码 innerHtml = "<div class=\"rich_media_content js_underline_content autoTypeSetting24psection\" id=\"js_content\" style=\"visibility: visible;\" deep=\"5\">" + innerHtml + "</div>"
文章标题也需要统一包上h2样式,方便pandoc处理,这个可以从文件名或者html的元数据中直接获取:
[Golang] 纯文本查看 复制代码 title = "<h2 class=\"rich_media_title \" id=\"activity-name\">" + title + "</h2>"
最后就是评论区的内容,这个直接用正则表达式找出来就好了
[Golang] 纯文本查看 复制代码 var REG_CMT = regexp.MustCompile("(?s)<!--cmt_start-->.*<!--cmt_end-->")
做完这些,我们就可以直接把处理结果返回了
[Golang] 纯文本查看 复制代码 submatch := REG_CMT.FindAllStringSubmatch(content, 1)
if len(submatch) == 1 {
return title + innerHtml + submatch[0][0]
} else {
return title + innerHtml
} [/mw_shl_code]
如下图,这就是处理后的html直接转为epub的效果,虽然浏览器上的阅读体验变差了,但转格式后的阅读体验有所提升:
当有多篇文章同时存在时,可以先将这些文章用二进制合并拼接成一个文件,再用pandoc转格式,最后用calibre编辑一下,并根据标题重新生成目录,就能得到一本效果还算不错的电子书了
转换并简单处理后的成品已上传,可以看看效果,祝大家玩得开心!
顺便说一下,这个功能后续会加入下载器中,期待的话请多多为我投票吧~
52pojie.epub - 蓝奏云
https://wwpv.lanzoue.com/iGIjw0s9nn5e |