如何用 KindleEar 推送无 RSS 的网站内容(上篇)
之前书伴曾介绍过利用 Calibre 抓取网站内容制成电子书的方法,可以很方便地生成既美观又实用的期刊样式电子书。Calibre 功能的强大毋庸置疑,不过在实际使用时却有一个明显的不便之处:当你想要定时推送某个内容源时,就必须让运行 Calibre 的电脑一直保持开机状态。因此,很多小伙伴选择使用有着类似功能,并且能免费托管在 Google App Engine 服务器上的开源程序 KindleEar 来解决这个问题。
KindleEar 虽然支持直接订阅 RSS,但可惜很多 RSS 供稿可用性并不高,内置的订阅又无法满足个性化的需求,在这种情况下,就有必要学会自己编写可精准获取目标网站内容的订阅脚本。这里所说的“订阅脚本”也可以理解成为 KindleEar 添加内置订阅,内置的订阅其实也是由一个个定制的订阅脚本组成。
写这篇文章的动机是网友 Jone 发给书伴的一封长信,信中详细描述了自己想要解决的问题:如何在没有编程基础的前提下编写 KindleEar 订阅脚本,并能方便地重新部署到 Google App Engine 上。所以书伴花了几天时间研究了下 KindleEar 订阅脚本,并将经验分享给需要的小伙伴。为了顾及没有任何编程经验的小伙伴,本文会尽可能以普通用户的视角撰写,对涉及代码的部分尽可能做到形象化的说明。
目录
[ 上篇 ]
一、KindleEar 的订阅方式
二、KindleEar 的订阅脚本
三、KindleEar 的调试环境
1、安装 Python 2.7
2、安装 Google Cloud SDK
3、获取 KindleEar 源代码
4、在本地运行 KindleEar
[ 中篇 ]
一、新创建一个订阅脚本
二、订阅脚本的工作原理
三、从网站抽取文章 URL
四、分析 HTML 标签结构
1、分析文章列表的 HTML 标签结构
2、分析文章内容的 HTML 标签结构
五、测试订阅脚本的推送
[ 下篇 ]
一、文章列表的翻页和限定条目
二、文章内容的翻页和细节修改
三、上传到 Google App Engine
由于编写 KindleEar 订阅脚本牵涉到测试环境的配置,导致篇幅较长,所以本文分成了上、中、下三篇。上篇主要是订阅脚本的相关介绍和测试环境的配置步骤,中下篇则是编写订阅脚本的具体步骤。
一、KindleEar 的订阅方式
KindleEar 和 Calibre 一样,支持通过“RSS”或“网页(HTML)”两种方式抓取目标网站上的内容。
KindleEar 对 RSS 和 HTML 有着不同的处理方式。当目标站点提供 RSS 时,它就会用通用的 RSS 处理模块来提取供稿内容生成电子书,也就是前面提到的“自定义 RSS”。直接用 RSS 自然是最方便的,但现实世界的情况总比理想中的复杂,有很多种原因导致我们无法顺利获取 RSS,比如有些网站根本就不提供 RSS,或者提供了 RSS 却只有摘要信息,甚至提供的 RSS 存在格式上的错误无法正常读取等。
RSS 本质上只是一种简单的数据格式,其结构有着相对严格和固定的规范,所以只需要一个通用处理模块就可以应付几乎所有站点的 RSS 供稿。而 HTML 页面就没这么省心了,可以说不同网站的 HTML 标签结构存在着天壤之别,所以当目标站点不提供 RSS 时,就只能为其编写高度定制化的订阅脚本。
说点题外话:可能很多小伙伴会疑惑,为什么很多网站都不提供 RSS 呢?RSS 生来就是为了方便用户追踪网站更新的,从用户角度来看是相当方便——不用访问网站就能获取到网站内容更新,但是这却不可避免地影响到了内容提供者的商业利益。虽然十多年前很多人也为 RSS 做过一些商业化的尝试,但终以失败告终。随着 2013 年 Google 关闭 Google Reader,算是宣告了 RSS 大时代的终结。现在除了一些博客以及尚有情怀的网站外,大都不再提供 RSS 供稿了,即便提供也只是放点摘要信息,最终的目的还是把用户引导到自己的网站上。RSS 并没有消亡,只是因为它给了用户太多自由而不太被商业容忍。
二、KindleEar 的订阅脚本
在《Calibre 使用教程之抓取网站页面制成电子书》这篇文章中,书伴详细介绍了如何通过编写 Recipe 脚本的方式让 Calibre 抓取指定网站的内容,KindleEar 也提供了类似的功能。不过需要注意的是,虽然 KindleEar 的 MOBI 转换模块提取自 Calibre,但是订阅脚本却与 Calibre 的 Recipe 脚本并不通用,这是因为 KindleEar 并没有直接移植 Calibre 的 Recipe 处理模块,而是将其作为参考重新写了一个处理模块,这导致包括脚本后缀名(KindleEar 是 .py,Calibre 是 .recipe)、相关功能的实现等很多方面都有所不同。因此,你必须遵循 KindleEar 提供的相关功能函数为 KindleEar 编写专用的订阅脚本。
KindleEar 内置的抓取脚都存放在其项目目录下的 books 目录中,脚本的文件名均以英文命名并以 .py 为后缀。每个脚本都继承同目录下名为 base.py 的基类,该基类已对很多种订阅方式做了定义,比如 RSS、HTML 页面、漫画等。我们所创建的订阅脚本就是通过继承这个基类,再根据实际情况改写、定制其中的一些参数和函数,从而实现对目标网站内容的精准抓取。KindleEar 的作者在 base.py 做了大量注释,如果你有一定的编程经验,完全可以根据这些注释说明来理解其中的参数和函数都是如何工作的。
三、KindleEar 的调试环境
由于 KindleEar 的运行依赖于 Google App Engine 环境,无法像用 Calibre 测试 Recipe 脚本那样直接在本地运行,所以为了方便测试编写的 KindleEar 订阅脚本,我们需要在本地搭建可以为 KindleEar 虚拟运行环境的 Google Cloud SDK(Windows 还需要安装 Python 环境和相关的 Python 库)。
不要害怕任何技术性字眼,按照步骤一步步做一般不会有问题。注意不要忽略任何一段文字。
1、安装 Python 2.7
KindleEar 是 Python 程序,所以本地调试时会依赖 Python 环境。macOS 系统和 Linux 系统都预装了 Python,而对于 Windows 系统,如果没有安装 Python,就需要手动安装 2.7 版本的 Python:
* 提示:macOS 系统虽然内置了 Python 环境,但是并不推荐直接使用它,而是推荐使用 Homebrew 安装独立的 Python 环境。Homebrew 是一款包管理器,支持 macOS(或 Linux)系统,它能安装和管理独立于原生系统环境的工具包,可有效避免对原生系统环境产生影响,对于没有包管理器的 macOS 系统(或 Linux 系统中的非 root 用户)来说非常方便。
此外,KindleEar 的运行还依赖一些第三方 Python 库,这些库需要在命令行中用 pip 命令安装。
注意,本文之后的内容经常会用到命令行,所以应记住,当文中说到输入命令时,你需要打开“终端”(Windows 系统则打开“命令提示符”),把相关命令输入(或拷贝)进去,按回车执行。
macOS 和 Linux 用户可直接通过执行下面的命令安装这些第三方 Python 库:
pip install lxml pillow jinja2 pycrypto
* 提示:在 macOS 或 Linux 系统中,如果用的是原生系统的 Python 环境,并且是非 root 用户,则需要附加 sudo
命令。
Windows 用户需要先下载安装微软的 Microsoft Visual C++ Compiler for Python 2.7,因为安装第三方 Python 库的对其有依赖。安装完成后,通过以下命令安装 KindleEar 依赖的第三方 Python 库:
C:\Python\Scripts\pip install lxml pillow jinja2 pycrypto
* 提示:上面这条命令假设你的 Python 是默认安装在 C 盘的,如果指定了其它磁盘,请自行更改路径。
2、安装 Google Cloud SDK
Google Cloud SDK 的安装请参考《KindleEar 搭建教程:推送 RSS 订阅到 Kindle》这篇文章的第二部分“二、上传应用”中的“方法二:手动上传”中的第 1、2 小节提供的详细步骤。
如果你是使用 Homebrew 安装 Google Cloud SDK 的,请注意,默认情况下 dev_appserver.py
这个命令可能不会被添加到环境变量 $PATH 中,所以无法直接在“终端”或“命令提示符”中运行,因此,如果你也遇到了这个问题,可执行以下命令手动将这个命令的软链接添加到可执行程序目录中。
ln -s /usr/local/Caskroom/google-cloud-sdk/latest/google-cloud-sdk/bin/dev_appserver.py /usr/local/bin/dev_appserver.py
3、获取 KindleEar 源代码
首先需要获取一份 KindleEar 源代码到本地。你可以通过下面的链接下载 ZIP 包解压缩备用:
- KindleEar 源代码:https://github.com/cdhigh/KindleEar/archive/master.zip
如果你的电脑已经安装了 Git 工具,也可以通过 Git 的 Clone 命令将源代码拉取到本地:
git clone https://github.com/cdhigh/KindleEar.git
为了之后上传方便,建议去 GitHub 注册一个账户,然后把 KindleEar 项目 Fork 一份到自己的账户下,这样,在添加好编写的订阅脚本后,可以先将源代码 Push 到你 Fork 的项目中,再按照 KindleEar 搭建教程中的“自动上传”方式利用 Google App Engine 的云端 Shell 重新部署你 Fork 的 KindleEar 项目。
4、在本地运行 KindleEar
接下来就是让 KindleEar 在本机运行起来了。注意,在这里我们不使用带界面的 Google App Engine Launcher,而只使用它附带的命令行工具。默认情况下,Windows 系统可以直接在命令提示符中使用这些命令,而 macOS 系统需要先打开界面版的 Google App Engine Launcher,点击软件的菜单中的“Make Symlinks…”创建命令软链接才能使用命令。Linux 系统需要添加 PATH 变量才能使用命令。
打开终端(Windows 系统打开命令提示符)并定位到 KindleEar 的项目目录。假设项目在系统桌面上。
macOS 系统需要输入类似以下命令定位到 KindleEar 项目目录(注意替换 YOURNAME):
cd /Users/YOURNAME/Desktop/KindleEar
Windows 系统则需要输入类似以下命令定位到 KindleEar 项目目录(注意替换 YOURNAME):
cd C:\Users\YOURNAME\Desktop\KindleEar
定位到 KindleEar 项目目录后,输入以下命令让 KindleEar 运行起来(注意有两个空格):
dev_appserver.py ./app.yaml ./module-worker.yaml
在 Windows 系统下,有小伙伴反馈了两个问题。一个是输入 dev_appserver.py
无法成功执行命令,而是被记事本打开了,这是因为你强制设定了 .py 文件的打开方式,需要将其打开方式恢复成 python:随便找一个 .py 为后缀的文件,通过右键属性更改该文件的默认打开方式为你所安装的 python.exe。
另一个是在 Windows 7 系统下,输入 dev_appserver.py
提示 error: too few arguments
错误,这是由于某种原因 Python 无法读取命令后的参数导致的,这需要通过修改注册表来修复。在命令提示符中输入 regedit
打开注册表,依次展开 HKEY_CLASSES_ROOT\Applications\python.exe\shell\open\command
,如果发现 command
的值不是 "C:\Python27\python.exe" "%1" %*
就改成这个(通常是缺少了 %*
)。
当你看到终端(或命令提示符)上出现如下所示的输出,就说明 KindleEar 已经在本机正常运行了:
INFO 2019-05-11 13:51:41,358 sdk_update_checker.py:231] Checking for updates to the SDK.
INFO 2019-05-11 13:51:44,383 sdk_update_checker.py:247] Update check failed:
INFO 2019-05-11 13:51:44,613 api_server.py:275] Starting API server at: http://localhost:49342
INFO 2019-05-11 13:51:44,625 dispatcher.py:256] Starting module "default" running at: http://localhost:8080
INFO 2019-05-11 13:51:44,667 dispatcher.py:256] Starting module "worker" running at: http://localhost:8081
INFO 2019-05-11 13:51:44,672 admin_server.py:150] Starting admin server at: http://localhost:8000
INFO 2019-05-11 13:51:46,928 instance.py:294] Instance PID: 37115
打开浏览器(推荐用 Chrome),输入 http://localhost:8080 即可访问运行在本机上的 KindleEar 程序,输入默认的用户名和密码 admin 即可登入控制界面。至此,KindleEar 的调试环境便准备好了。
本文的下篇将会以 China Daily 为例,由浅入深详细说明如何编写 KindleEar 的订阅脚本。编写好的脚本可抓取指定板块下指定数量和日期的新闻条目,并将其整合到同一本电子书中,其中还包括对内容页、分页等细节的处理。最后,把测试成功的订阅脚本上传部署到 Google App Engine 的生产环境上。
▲ China Daily 网站抓取效果
为方便编写代码,建议先备好一款代码编辑器,推荐 Sublime Text 或 Visual Studio Code。如果你对 KindleEar 订阅脚本有什么疑问,或者发现本教程存在的谬误或不详尽之处,欢迎留言。
你可继续阅读:《如何用 KindleEar 推送无 RSS 的网站内容(中篇)》
© 「书伴」原创文章,转载请注明出处及原文链接:https://bookfere.com/post/750.html
“Kindle推送”相关阅读
- 解决 Calibre 推送“500 Error: bad syntax”错误
- 为什么推送到 Kindle 的 KF8 标准 MOBI 电子书不显示封面
- Calibre 使用教程之通过邮箱一键推送 Kindle 电子书
- 如何解决 Calibre 推送中文书到 Kindle 显示拼音书名问题
- BookDrop:用 Dropbox 自动同步电子书到 Kindle
- 如何用 KindleEar 推送无 RSS 的网站内容(下篇)
- Calibre 常用命令行工具详解之 calibre-smtp
- Kindle 怎么导入电子书(图解多种电子书导入方式)
- EpubPress:把打开的多个网页转成一本电子书
- Send to Kindle 微信推送教程:用 Kindle 读长文
- 使用 Kindle 推送时为何会收到“验证请求”邮件?
- 如何快速无损修复推送失败的 EPUB 格式电子书文件
- 如何单个或批量删除 Kindle 云端的电子书
- Kindle 推送教程:教你用电子邮箱推送电子书
- Kindle 个人文档服务 2022 年 8 月起不再支持推送 MOBI 格式
最后一步出现这个错误,请帮忙解决,非常感谢。
命令提示符是使用管理员身份运行的吗,如果不是的话,使用管理员身份运行,再运行命令试试看。
请问Google cloud sdk安装以后怎么给它设置代理呀?
请参考《KindleEar 搭建教程:推送 RSS 订阅到 Kindle》这篇文章的第二部分“二、上传应用”中的“方法二:手动上传”中的第 3 小节提供的详细步骤。
这个怎么办
你提供的日志没有错误信息,你遇到什么问题了吗?
您好我提示这个是为什么啊
Last login: Sun Mar 29 11:43:49 on ttys000
joii@huoguowa ~ % cd /Users/joii/Desktop/KindleEar
joii@huoguowa KindleEar % dev_appserver.py ./app.yaml ./module-worker.yaml
INFO 2020-03-29 03:46:04,778 sdk_update_checker.py:231] Checking for updates to the SDK.
INFO 2020-03-29 03:46:04,788 __init__.py:125] Connecting through tunnel to: appengine.google.com:443
Traceback (most recent call last):
File “/usr/local/bin/dev_appserver.py”, line 96, in
_run_file(__file__, globals())
File “/usr/local/bin/dev_appserver.py”, line 90, in _run_file
execfile(_PATHS.script_file(script_name), globals_)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/tools/devappserver2/devappserver2.py”, line 600, in
main()
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/tools/devappserver2/devappserver2.py”, line 588, in main
dev_server.start(options)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/tools/devappserver2/devappserver2.py”, line 355, in start
configuration.modules[0].application_root)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/tools/devappserver2/api_server.py”, line 654, in create_api_server
if options.support_datastore_emulator else None)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/tools/devappserver2/stub_util.py”, line 166, in setup_stubs
oauth_url=appidentity_oauth_url)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/app_identity/app_identity_stub.py”, line 199, in Create
dc = ai_stub.DefaultCredentialsBasedAppIdentityServiceStub()
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/app_identity/app_identity_defaultcredentialsbased_stub.py”, line 76, in __init__
client.GoogleCredentials.get_application_default())
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/oauth2client_devserver/oauth2client/client.py”, line 1036, in get_application_default
env_name = _get_environment()
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/oauth2client_devserver/oauth2client/client.py”, line 924, in _get_environment
response = urllib2_urlopen(‘http://metadata.google.internal’)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py”, line 154, in urlopen
return opener.open(url, data, timeout)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py”, line 429, in open
response = self._open(req, data)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py”, line 447, in _open
‘_open’, req)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py”, line 407, in _call_chain
result = func(*args)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py”, line 1228, in http_open
return self.do_open(httplib.HTTPConnection, req)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py”, line 1201, in do_open
r = h.getresponse(buffering=True)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py”, line 1121, in getresponse
response.begin()
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py”, line 438, in begin
version, status, reason = self._read_status()
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py”, line 402, in _read_status
raise BadStatusLine(“No status line received – the server has closed the connection”)
httplib.BadStatusLine: No status line received – the server has closed the connection
joii@huoguowa KindleEar % dev_appserver.py ./app.yaml ./module-worker.yaml
INFO 2020-03-29 03:55:29,839 sdk_update_checker.py:231] Checking for updates to the SDK.
INFO 2020-03-29 03:55:29,848 __init__.py:125] Connecting through tunnel to: appengine.google.com:443
Traceback (most recent call last):
File “/usr/local/bin/dev_appserver.py”, line 96, in
_run_file(__file__, globals())
File “/usr/local/bin/dev_appserver.py”, line 90, in _run_file
execfile(_PATHS.script_file(script_name), globals_)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/tools/devappserver2/devappserver2.py”, line 600, in
main()
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/tools/devappserver2/devappserver2.py”, line 588, in main
dev_server.start(options)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/tools/devappserver2/devappserver2.py”, line 355, in start
configuration.modules[0].application_root)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/tools/devappserver2/api_server.py”, line 654, in create_api_server
if options.support_datastore_emulator else None)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/tools/devappserver2/stub_util.py”, line 166, in setup_stubs
oauth_url=appidentity_oauth_url)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/app_identity/app_identity_stub.py”, line 199, in Create
dc = ai_stub.DefaultCredentialsBasedAppIdentityServiceStub()
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/api/app_identity/app_identity_defaultcredentialsbased_stub.py”, line 76, in __init__
client.GoogleCredentials.get_application_default())
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/oauth2client_devserver/oauth2client/client.py”, line 1036, in get_application_default
env_name = _get_environment()
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/lib/oauth2client_devserver/oauth2client/client.py”, line 924, in _get_environment
response = urllib2_urlopen(‘http://metadata.google.internal’)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py”, line 154, in urlopen
return opener.open(url, data, timeout)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py”, line 429, in open
response = self._open(req, data)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py”, line 447, in _open
‘_open’, req)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py”, line 407, in _call_chain
result = func(*args)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py”, line 1228, in http_open
return self.do_open(httplib.HTTPConnection, req)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/urllib2.py”, line 1201, in do_open
r = h.getresponse(buffering=True)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py”, line 1121, in getresponse
response.begin()
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py”, line 438, in begin
version, status, reason = self._read_status()
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/httplib.py”, line 402, in _read_status
raise BadStatusLine(“No status line received – the server has closed the connection”)
httplib.BadStatusLine: No status line received – the server has closed the connection
又提示了这个
Last login: Sun Mar 29 12:06:46 on ttys000
joii@huoguowa ~ % cd /Users/YOURNAME/Desktop/KindleEar
cd: no such file or directory: /Users/YOURNAME/Desktop/KindleEar
joii@huoguowa ~ % cd /Users/joii/Desktop/KindleEar
joii@huoguowa KindleEar % dev_appserver.py ./app.yaml ./module-worker.yaml
INFO 2020-03-29 04:07:29,018 sdk_update_checker.py:231] Checking for updates to the SDK.
INFO 2020-03-29 04:07:32,036 sdk_update_checker.py:247] Update check failed:
INFO 2020-03-29 04:07:32,271 api_server.py:275] Starting API server at: http://localhost:53415
INFO 2020-03-29 04:07:32,279 dispatcher.py:256] Starting module “default” running at: http://localhost:8080
INFO 2020-03-29 04:07:32,300 dispatcher.py:256] Starting module “worker” running at: http://localhost:8081
INFO 2020-03-29 04:07:32,302 admin_server.py:150] Starting admin server at: http://localhost:8000
INFO 2020-03-29 04:07:34,435 instance.py:294] Instance PID: 2131
WARNING 2020-03-29 04:08:10,560 __init__.py:40] Book ‘FTChinese’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,566 __init__.py:40] Book ‘Economist’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,583 __init__.py:40] Book ‘ZhihuDaily’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,587 __init__.py:40] Book ‘nfzm’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,592 __init__.py:40] Book ‘Aisixiang’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,598 __init__.py:40] Book ‘Dapenti’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,603 __init__.py:40] Book ‘Xueqiu’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,607 __init__.py:40] Book ‘Lifeweek’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,613 __init__.py:40] Book ‘TEDxBohaiBay’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,665 __init__.py:40] Book ‘cn3k5.WenDing’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,672 __init__.py:40] Book ‘biquge.NiLiu’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,677 __init__.py:40] Book ‘biquge.XiuZhen’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,681 __init__.py:40] Book ‘biquge.JianTu’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,689 __init__.py:40] Book ‘biquge.DaWang’ import failed : No module named lxml.etree
WARNING 2020-03-29 04:08:10,696 __init__.py:40] Book ‘biquge.JianLing’ import failed : No module named lxml.etree
ERROR 2020-03-29 04:08:10,772 wsgi.py:263]
Traceback (most recent call last):
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py”, line 240, in Handle
handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py”, line 299, in _LoadHandler
handler, path, err = LoadObject(self._handler)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py”, line 96, in LoadObject
__import__(cumulative_path)
File “/Users/joii/Desktop/KindleEar/apps/module_front.py”, line 47, in
from apps.View import *
File “/Users/joii/Desktop/KindleEar/apps/View/__init__.py”, line 14, in
module = loader.find_module(name).load_module(name)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pkgutil.py”, line 243, in load_module
mod = imp.load_module(fullname, self.file, self.filename, self.etc)
File “/Users/joii/Desktop/KindleEar/apps/View/Adv.py”, line 14, in
from PIL import Image
ImportError: No module named PIL
INFO 2020-03-29 04:08:10,783 module.py:861] default: “GET / HTTP/1.1” 500 –
INFO 2020-03-29 04:08:10,915 module.py:861] default: “GET /favicon.ico HTTP/1.1” 200 4286
ERROR 2020-03-29 04:08:30,639 wsgi.py:263]
Traceback (most recent call last):
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py”, line 240, in Handle
handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py”, line 299, in _LoadHandler
handler, path, err = LoadObject(self._handler)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py”, line 96, in LoadObject
__import__(cumulative_path)
File “/Users/joii/Desktop/KindleEar/apps/module_front.py”, line 47, in
from apps.View import *
File “/Users/joii/Desktop/KindleEar/apps/View/__init__.py”, line 14, in
module = loader.find_module(name).load_module(name)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pkgutil.py”, line 243, in load_module
mod = imp.load_module(fullname, self.file, self.filename, self.etc)
File “/Users/joii/Desktop/KindleEar/apps/View/Adv.py”, line 14, in
from PIL import Image
ImportError: No module named PIL
INFO 2020-03-29 04:08:30,649 module.py:861] default: “GET / HTTP/1.1” 500 –
还是进不去 8080
请仔细阅读本文内容,把 lxml pillow jinja2 pycrypto 这几个模块装上。
目前这套方案不能运行在python3上吗?
因为 KindleEar 是用 Python2 写的,所以需要 Python2 环境,除非你自己将项目改写成 Python3。
MacOS High Sierra运行pip install lxml pillow jinja2 pycrypto:
ERROR: Exception:
Traceback (most recent call last):
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/cli/base_command.py”, line 188, in main
status = self.run(options, args)
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/commands/install.py”, line 345, in run
resolver.resolve(requirement_set)
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/legacy_resolve.py”, line 196, in resolve
self._resolve_one(requirement_set, req)
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/legacy_resolve.py”, line 359, in _resolve_one
abstract_dist = self._get_abstract_dist_for(req_to_install)
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/legacy_resolve.py”, line 307, in _get_abstract_dist_for
self.require_hashes
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/operations/prepare.py”, line 199, in prepare_linked_requirement
progress_bar=self.progress_bar
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/download.py”, line 1064, in unpack_url
progress_bar=progress_bar
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/download.py”, line 924, in unpack_http_url
progress_bar)
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/download.py”, line 1152, in _download_http_url
_download_url(resp, link, content_file, hashes, progress_bar)
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/download.py”, line 861, in _download_url
hashes.check_against_chunks(downloaded_chunks)
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/utils/hashes.py”, line 75, in check_against_chunks
for chunk in chunks:
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/download.py”, line 829, in written_chunks
for chunk in chunks:
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/utils/ui.py”, line 156, in iter
for x in it:
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_internal/download.py”, line 818, in resp_read
decode_content=False):
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_vendor/urllib3/response.py”, line 531, in stream
data = self.read(amt=amt, decode_content=decode_content)
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_vendor/urllib3/response.py”, line 496, in read
raise IncompleteRead(self._fp_bytes_read, self.length_remaining)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/contextlib.py”, line 35, in __exit__
self.gen.throw(type, value, traceback)
File “/Library/Python/2.7/site-packages/pip-19.2.2-py2.7.egg/pip/_vendor/urllib3/response.py”, line 411, in _error_catcher
raise ReadTimeoutError(self._pool, None, ‘Read timed out.’)
ReadTimeoutError: HTTPSConnectionPool(host=’files.pythonhosted.org’, port=443): Read timed out.
系统自带Python,已安装Homebrew,请问如何安装独立Python?
运行localhost:8000正常,8080报以下错误:
ERROR 2020-01-15 09:18:12,906 wsgi.py:263]
Traceback (most recent call last):
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py”, line 240, in Handle
handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py”, line 299, in _LoadHandler
handler, path, err = LoadObject(self._handler)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py”, line 96, in LoadObject
__import__(cumulative_path)
File “/Users/liuweiming/Desktop/KindleEar/apps/module_front.py”, line 47, in
from apps.View import *
File “/Users/liuweiming/Desktop/KindleEar/apps/View/__init__.py”, line 14, in
module = loader.find_module(name).load_module(name)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pkgutil.py”, line 246, in load_module
mod = imp.load_module(fullname, self.file, self.filename, self.etc)
File “/Users/liuweiming/Desktop/KindleEar/apps/View/Adv.py”, line 14, in
from PIL import Image
ImportError: No module named PIL
INFO 2020-01-15 09:18:12,912 module.py:861] default: “GET / HTTP/1.1” 500 –
ERROR 2020-01-15 09:18:13,363 wsgi.py:263]
Traceback (most recent call last):
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py”, line 240, in Handle
handler = _config_handle.add_wsgi_middleware(self._LoadHandler())
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py”, line 299, in _LoadHandler
handler, path, err = LoadObject(self._handler)
File “/Applications/GoogleAppEngineLauncher.app/Contents/Resources/GoogleAppEngine-default.bundle/Contents/Resources/google_appengine/google/appengine/runtime/wsgi.py”, line 96, in LoadObject
__import__(cumulative_path)
File “/Users/liuweiming/Desktop/KindleEar/apps/module_front.py”, line 47, in
from apps.View import *
File “/Users/liuweiming/Desktop/KindleEar/apps/View/__init__.py”, line 14, in
module = loader.find_module(name).load_module(name)
File “/System/Library/Frameworks/Python.framework/Versions/2.7/lib/python2.7/pkgutil.py”, line 246, in load_module
mod = imp.load_module(fullname, self.file, self.filename, self.etc)
File “/Users/liuweiming/Desktop/KindleEar/apps/View/Adv.py”, line 14, in
from PIL import Image
ImportError: No module named PIL
INFO 2020-01-15 09:18:13,371 module.py:861] default: “GET / HTTP/1.1” 500 –
再次运行下载lmxl,提示:
ERROR: Could not find a version that satisfies the requirement lxml (from versions: none)
ERROR: No matching distribution found for lxml
安装 Python 2.7,直接运行 brew install python@2 就可以了。
没有成功安装相关依赖就会出现 ImportError 错误,也就是类似你提供的第二个错误提示信息。
你贴出的第一个错误提示信息中有 Read timed out 字样,大概是因为网络问题导致无法正常安装依赖包,建议你重试几次 pip 安装命令,还不行的话就只能挂代理了。
运行brew install python@2后,pip默认还是系统自带,请问怎么用?
安装完成后,退出并重新打开终端才会生效。
只是默认用户名是什么?
已解决。谢谢!
这是怎么了?
导致这个错误出现的具体命令是什么?
cd之后dev_appserver.py ./app.yaml ./module-worker.yaml
重启后又好了,弹出Python联网需求
您好,现在运行不了KindleEar,百度也找不到解决办法,请问一下该怎么办?
c:\KindleEar>dev_appserver.py ./app.yaml ./module-worker.yaml
INFO 2019-09-18 19:18:59,980 sdk_update_checker.py:231] Checking for updates to the SDK.
INFO 2019-09-18 19:19:06,515 sdk_update_checker.py:247] Update check failed:
WARNING 2019-09-18 19:19:06,516 devappserver2.py:291] The default encoding of your local Python interpreter is set to ‘gbk’ while App Engine’s production environment uses ‘ascii’; as a result your code may behave differently when deployed.
Traceback (most recent call last):
File “C:\Program Files (x86)\Google\google_appengine\dev_appserver.py”, line 96, in
_run_file(__file__, globals())
File “C:\Program Files (x86)\Google\google_appengine\dev_appserver.py”, line 90, in _run_file
execfile(_PATHS.script_file(script_name), globals_)
File “C:\Program Files (x86)\Google\google_appengine\google\appengine\tools\devappserver2\devappserver2.py”, line 600, in
main()
File “C:\Program Files (x86)\Google\google_appengine\google\appengine\tools\devappserver2\devappserver2.py”, line 588, in main
dev_server.start(options)
File “C:\Program Files (x86)\Google\google_appengine\google\appengine\tools\devappserver2\devappserver2.py”, line 355, in start
configuration.modules[0].application_root)
File “C:\Program Files (x86)\Google\google_appengine\google\appengine\tools\devappserver2\api_server.py”, line 654, in create_api_server
if options.support_datastore_emulator else None)
File “C:\Program Files (x86)\Google\google_appengine\google\appengine\tools\devappserver2\stub_util.py”, line 201, in setup_stubs
consistency_policy=datastore_consistency))
File “C:\Program Files (x86)\Google\google_appengine\google\appengine\datastore\datastore_sqlite_stub.py”, line 618, in __init__
factory=sql_conn)
sqlite3.OperationalError: unable to open database file
错误提示找不到 sqlite 数据库,可能因为某种原因(比如用什么软件清理过系统垃圾)删除了之前生成的数据库,导致运行环境找不到它。你可以尝试在运行
dev_appserver.py
命令的时候加上一个参数--clear_datastore
让本地运行环境重建包括数据库在内的所有数据。你好,非常感谢分享。
但是我到最后一步的时候卡住了。
dev_appserver.py ./app.yaml ./module-worker.yaml
输入后显示的是
‘dev_appserver.py’ 不是内部或外部命令,也不是可运行的程序
或批处理文件。
因为我没有什么编程基础,这个问题怎么处理呢?我看了一下下载目录里面确实找不到’dev_appserver.py’ 。
谢谢!
首先你需要确认,已经安装了 APP Engine SDK。然后还需要确认,已经按照本文第三部分的第 3 个步骤的说明,根据不同类型的系统,把 SDK 的可执行程序所在目录添加进了环境变量。
我发现我的除了跟你的PID数字显示不同,其他的都一样,
但是我的http://localhost:8000页面打开,跟你的不一样,
这是我的截屏,我的电脑是Mac,
![截屏](https://i.loli.net/2019/06/25/5d1206fea338971610.png)
大佬可以不用回复我了,我搞定了
截图界面是 worker 模块,但是你给出的日志 default 模块的端口确实是 8000。你可以点击截图页面中的 default 连接看看它到底是用的哪个端口。
运行之后只有8000端口能打开,其他端口打不开,一直转圈
出现这个WARNING 2019-05-30 09:15:24,132 simple_search_stub.py:1196] Could not read search indexes from c:\users\lzlyv\appdata\local\temp\appengine.kindleear\search_indexes
如果是 Windows 系统,可以尝试以管理员身份打开命令提示符,重新运行一次命令。
你只需要访问可浏览前端页面的 8000 端口,其它端口一般不需要用浏览器访问。
您好,有一个问题想问一下:
在执行代码:dev_appserver.py ./app.yaml ./module-worker.yaml
显示:
WARNING:Your python27 micro version is below 2.7.12, our current production version.
电脑系统是MacOS10.14.4,这个命令执行的时候应该是电脑自带的默认python2版本,
我应该怎么做,才能够没有这个警告⚠️,或者说,这个warning无伤大雅,可以直接忽略。
还有一个问题,我按照操作一步一步来,
输入代码:dev_appserver.py ./app.yaml ./module-worker.yaml
之后进入网址:http://localhost:8080/
居然显示:该网页无法正常运作localhost ,目前无法处理此请求,HTTP ERROR 500。
去查了一下:500 Internal Server Error 是表示服务器端错误的响应状态码,意味着所请求的服务器遇到意外的情况并阻止其执行请求。
有甚多不明白之处,还望包含。
只通过 HTTP 的 500 错误无法排查问题出在哪里,你需要看终端上给出的具体错误提示(一般是 Python 的 Traceback 错误提示)。
这个提示说明的是 SDK 要求最低 Python 版本为 2.7.12,而系统自带 Python 版本有点低。如果一切运行正常可以忽略这个提示,不过对于 macOS 系统还是建议按照文中的提示,安装一个 Homebrew,安装独立的 Python 环境。
嗯,好,我会试试
上传完打开 http://APPID.appspot.com 显示
Error: Server Error
The server encountered an error and could not complete your request.
Please try again in 30 seconds.
错误报告显示
UnicodeDecodeError: ‘ascii’ codec can’t decode byte 0xc3 in position 5:
请问如何解决啊?
本文内容没有涉及 KindleEar 的上传。你对源码做了什么修改?从代码错误看是字符串编码问题。
我是根据这里操作的【为了之后上传方便,建议去 GitHub 注册一个账户,然后把 KindleEar 项目 Fork 一份到自己的账户下,这样,在添加好编写的订阅脚本后,可以先将源代码 Push 到你 Fork 的项目中,再按照 KindleEar 搭建教程中的“自动上传”方式利用 Google App Engine 的云端 Shell 重新部署你 Fork 的 KindleEar 项目。】
我加了一个自定义书籍,里面有í符号。
后来我上传源码
[rm -f uploader.sh* && \
wget https://raw.githubusercontent.com/kindlefere/KindleEar-Uploader/master/uploader.sh && \
chmod +x uploader.sh && \
./uploader.sh],也出现这样的错误。
问题应该出在你写的脚本上。把你 GitHub 地址贴一下吧,得看看你写的代码。
https://github.com/kimfyy/KindleEar/blob/master/books/ELPAIS.py
好的,感谢!
我直接把你 Github 下的 KindleEar 项目上传到 GAE,可以正常访问,并没有出现你留言中说的编码错误。在订阅列表中也能看到《El País》。也就是说 ELPAIS.py 这个脚本没有编码问题。
对于自己修改的项目,建议还是用命令上传,不要用那个 Shell 脚本。上传命令也很简单,在云端 Shell 中把你 KindleEar 上的项目 Clone 下来(如果已 Clone 就用 Pull 命令更新),然后用 cd 命令定位到 KindleEar 项目目录用 appcfg.py 命令上传即可:
如果上传后还是出现错误,建议去 GAE 的日志中看一下错误的原因是什么。
另外,我看 ELPAIS.py 这个脚本中的 URL 都是 RSS 地址,为什么不直接用 KindleEar 的“自定义 RSS”功能呢?
【我直接把你 Github 下的 KindleEar 项目上传到 GAE,可以正常访问】请问要怎么上传?我根据原有书籍再次修改了ELPAIS.py文件,但更新之后已部署的文件并没有变化,ELPAIS.py还是原来错误的格式。可能是我的上传的方式不对,没有更新成功。我输入./uploader.sh,显示
【在云端 Shell 中用 cd 命令定位到 KindleEar 项目目录】这个 KindleEar 项目目录该在哪里找?请问该怎么操作啊?
是这个错误报告吗?我看不懂……😂
我想把这个西语报纸的rss集中在一个文件里,这样方便查词典以及整理标注,在自定义订阅里可以订阅零散的英语rss。
没变化就表示上传没成功。上传 KindleEar 简单说就两步,先获取到源码,再用 appcfg.py 命令上传到 GAE。
不过你得清楚你上传的思路是什么。是想要在本地直接把源码上传到 GAE(具体步骤参考《KindleEar 搭建教程》的手动上传部分),还是想要从云端 Shell 要把 Push 到 GitHub 的代码 Pull 下来上传到 GAE。因为本地上传步骤比较繁琐,所以才推荐利用云端 Shell 从 Github 拉取源代码上传。
KindleEar 的设置中有“多本书籍合并投递为一本”这个选项的,勾选并保存之后,所有自定义 RSS 都会合并成一本书推送。
您好,3.1节安装安装 App Engine SDK时,您只是提到下载安装 App Engine SDK包完成安装,但是我下载以后并没有发现有exe或者是bat文件,请问要怎么安装?
请问你使用的是什么操作系统?
win10 64位
很抱歉,Windows 版本的百度网盘链接贴错了,现已修复,你得重新下载了。
链接:https://pan.baidu.com/s/1SixHxay5IReSmo-SfNc2qA,提取码:u293
您好,我把所有项目完成以后,运行命令也出现了”info……PID”等一连串代码,但是在浏览器始终打不开http://localhost:8080 地址,提示为“localhost 拒绝了我们的连接请求”,具体截图已经发您邮箱,请您帮助,谢谢
你提供的截图显示,comic 中的 yuna.py 想要从 tencentbase.py 中导入 TencentBaseBook 这个类,却找不到 tencentbase 这个模块。但是我看了一下代码,KindleEar 自带的 yuna.py 并没有导入 TencentBaseBook 这个类。
你对代码做改动了吗?如果改动了代码最好说一下都改了什么。
谢谢您!已经搞定。
我fork源码以后把book文件夹中不需要的订阅脚本删除了,可能是误删导致了上面的问题。我直接用您提供的源码以后,目前已经成功登陆
删除不需要的订阅脚本需要注意清理相关的一些模块依赖。比如想要删除 comic 下 cartoonmadbase.py 模块,就要在 __init__.py 文件中移除对该模块的导入,同时还要把所有依赖此模块的其它 py 模块也删除。
不知道能不能抓取并推送存在网站上的*.mobi这样的文件?
不能。这个方法只是把网站上的内容转成电子书。
不知道有什么办法能实现自动抓取并推送存在网站上的*_001.mobi, *_002.mobi顺序文件?
如果下载链接有规律,可以直接用下载软件的批量下载功能实现。比如迅雷就支持批量任务,假设想要下载网站 http://example.com 上的 _001.mobi 到 _100.mobi 这 100 个文件,可以在新建的批量任务中设定含通配符的 URL:http://exsample.com/_(*).mobi,然后将把通配符匹配长度设为 3,就可以批量下载这些文件了。
第二篇啥时候出啊,手痒难耐了
正在撰写下篇,敬请期待。已完成。什么年代了,还装py2,不知道py2马上就要停止支持了嘛?而且py2编码暗坑无数,我不信你没被编码搞到脑袋爆炸过。
选择 Python2 主要是因为 KindleEar 依赖的 Calibre 处理模块不支持 Python3。这个问题在 KindleEar 的 Issue 中有讨论:考虑兼容python3?。
不过 Calibre 在 3.41.3 版本的更新日志中表示,正在对代码库做了大量更改,以便为支持 Python3 做好准备。如果到时候 KindleEar 替换成支持 Python3 的 Calibre 模块,就可以用 Python3 环境了。或者你自己更改目前的 Calibre 模块源代码使其支持 Python3 也可以。