如何用KindleEar推送无RSS的网站内容转换成电子书
优采云 发布时间: 2021-05-25 21:08如何用KindleEar推送无RSS的网站内容转换成电子书
本文详细介绍了KindleEar订阅脚本的工作原理,并以新闻网站中国日报为例。它详细说明了如何为此网站编写自定义的订阅脚本。将指定主题页面的文章内容转换为电子书。
内容
[第1部分]
[第2部分]
[下一步]
在开始以下步骤之前,请确保已在本地成功运行KindleEar程序,否则,请参考上一篇文章文章“如何使用KindleEar推送网站内容而不使用RSS(第1部分) ”中提供的步骤,设置调试环境以运行KindleEar。
一、创建新的订阅脚本
首先,我们需要向KindleEar添加新的内置订阅,即创建一个新的订阅脚本。具体步骤是:打开代码编辑器,创建一个新的空文档,如下所示输入(或复制)代码,然后将其保存到KindleEar项目的books目录中。请注意,文件名的名称是任意的,但必须是英文字符,后缀名必须是.py,例如chinadaily.py。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from base import BaseFeedBook # 继承基类BaseFeedBook
# 返回此脚本定义的类名
def getBook():
return ChinaDaily
# 继承基类BaseFeedBook
class ChinaDaily(BaseFeedBook):
# 设定生成电子书的元数据
title = u'China Daily' # 设定标题
__author__ = u'China Daily' # 设定作者
description = u'Chinadaily.com.cn is the largest English portal in China. ' # 设定简介
language = 'en' # 设定语言
# 指定要提取的包含文章列表的主题页面链接
# 每个主题是包含主题名和主题页面链接的元组
feeds = [
(u'National affairs', 'http://www.chinadaily.com.cn/china/governmentandpolicy'),
(u'Society', 'http://www.chinadaily.com.cn/china/society'),
]
此代码执行3件事:在base.py中导入基类BaseFeedBook以继承参数和函数;为最终生成的电子书数据信息设置标题,作者,简介,语言和其他元素;指定了两个收录文章列表的主题页面URL。
现在,我们为KindleEar添加了新的内置订阅。在网络浏览器中访问:8080并登录到您的帐户,在导航上单击“我的订阅”以进入订阅管理页面,然后您可以在“未订阅”列表中看到新添加的订阅。
如上图所示,单击新订阅项目后面的[Subscribe]按钮,将其添加到“ Subscribed”列表中。如下图所示,单击导航上的“高级设置”并进入“立即交付”页面,保持选中新订阅,然后单击[Push]按钮以手动执行新添加的订阅脚本。但是,该脚本当前没有实际功能,因此只会生成状态为nonews的空日志。
单击[Push]按钮执行订阅脚本后,您可以看到终端(或命令提示符)输出以下两条信息:
INFO 2019-05-12 13:13:37,408 Worker.py:235] No new feeds.
INFO 2019-05-12 13:13:37,425 module.py:861] worker: "GET /worker?u=admin&id=4876402788663296 HTTP/1.1" 200 13
提示:测试脚本中可能出现的错误提示将显示在终端(或命令提示符)上,我们需要根据此信息调试代码。
URL是在单击[Push]按钮后请求执行脚本的URL。为了避免在测试过程中频繁单击[Push]按钮,建议直接在浏览器中访问和刷新此URL,而不是单击按钮。请注意,与访问KindleEar的端口8080不同,用于此URL的端口是8081,并且ID值是脚本的唯一标识符,无论该字符串出现在您自己的命令行中是什么:
http://localhost:8081/worker?u=admin&id=6192449487634432
到目前为止,我们已经创建了一个可以正常运行的订阅脚本(尽管我们还没有抓取任何内容),并且我们还知道如何更轻松地测试该脚本。接下来,让我们看一下订阅脚本的工作原理以及使用它来获取网站内容的想法。
二、订阅脚本如何工作
之前,我们已经从模块base.py中为新的订阅脚本导入了一个名为BaseFeedBook的基类,以便新脚本继承了该基类提供的各种参数和功能,只要我们根据实际情况下,我们可以在新脚本中进行一些自定义和重写,以便KindleEar可以将目标网站上的文章内容抓取并根据我们的意愿将其转换为电子书。
提醒:实际上,模块base.py中存在WebpageBook,BaseUrlBook和BaseComicBook三类,它们也继承了BaseFeedBook,但针对不同的内容类型进行了自定义。但是在本文中,为了更好地控制内容的提取,只选择了基本类BaseFeedBook。
在基类BaseFeedBook中,除了之前已定义的某些参数(例如书名等)和稍后将定义的某些参数外,还可以调用一些函数功能或重写。最重要的功能是Item(),它负责将文章的捕获内容传递到转换模块以生成电子书。捕获文章内容的Item()函数需要另一个函数ParseFeedUrls()提供的URL,该函数需要返回收录文章 URL的列表。我们的主要工作是重写ParseFeedUrls()函数。通过分析目标网站 文章列表的HTML标记结构,在此函数中编写一些逻辑以完成文章 URL的提取。
ParseFeedUrls()函数返回的列表结构如下所示。该列表收录一些元组,每个元组收录文章的“主题”,“标题”,“链接”和“抽象”。 KindleEar会在生成电子书时根据这些主题对文章进行分类。
[
('主题A','标题1', 'http://www.sample.com/post-1', None),
('主题A','标题2', 'http://www.sample.com/post-2', None),
('主题B','标题3', 'http://www.sample.com/post-3', None),
('主题B','标题4', 'http://www.sample.com/post-4', None),
('主题C','标题5', 'http://www.sample.com/post-5', None),
('主题C','标题6', 'http://www.sample.com/post-6', None),
...
('主题Z','标题n', 'http://www.sample.com/post-n', None),
]
提示:文章元组中的所有参数都必须指定,但“摘要”除外。即使未填写“摘要”,该值也应设置为“无”,否则会发生错误。本文中的示例未设置摘要,因为一旦设置了摘要,Item()函数将直接使用摘要作为文章的内容,这显然不是我们想要的。
当Item()函数提取文章的内容时,默认情况下它将自动调用函数readability()以清除文章的内容,以优化读取效果。此函数使用第三方Python库readability-lxml,该库可自动处理页面的内容,通常可获得良好的效果。但是为了更准确地处理页面内容,本文使用了另一个功能readability_by_soup()来通过Beautiful Soup手动处理页面内容。请注意,为了使Item()在默认情况下调用readability_by_soup()函数,您需要将订阅脚本中的参数fulltext_by_readability的值设置为False,这将在后面提到。
此外,KindleEar还向清洗内容功能中插入了两个功能:preprocess()和soupprocessex()。前者可以在处理页面内容之前对页面内容的原创HTML代码进行一些预处理(处理后的内容需要在处理后返回),而后者可以对处理后页面内容的Beautiful Soup对象进行一些后处理(仅负责处理该过程不需要返回内容。
现在我们知道了KindleEar订阅脚本用于获取网站内容的一般操作流程,让我们尝试一下我们的技能。
三、从网站中提取文章网址
让我改进之前编写的代码,添加一些必要的参数,并添加函数ParseFeedUrls()。以下是已编写的完整代码,每行都有详细的注释。稍后,我将解释这些新添加的代码的作用。
#!/usr/bin/env python
# -*- coding:utf-8 -*-
from base import BaseFeedBook # 继承基类BaseFeedBook
from lib.urlopener import URLOpener # 导入请求URL获取页面内容的模块
from bs4 import BeautifulSoup # 导入BeautifulSoup处理模块
# 返回此脚本定义的类名
def getBook():
return ChinaDaily
# 继承基类BaseFeedBook
class ChinaDaily(BaseFeedBook):
# 设定生成电子书的元数据
title = u'China Daily' # 设定标题
__author__ = u'China Daily' # 设定作者
description = u'Chinadaily.com.cn is the largest English portal in China. ' # 设定简介
language = 'en' # 设定语言
coverfile = 'cv_chinadaily.jpg' # 设定封面图片
mastheadfile = 'mh_chinadaily.gif' # 设定标头图片
# 指定要提取的包含文章列表的主题页面链接
# 每个主题是包含主题名和主题页面链接的元组
feeds = [
(u'National affairs', 'http://www.chinadaily.com.cn/china/governmentandpolicy'),
(u'Society', 'http://www.chinadaily.com.cn/china/society'),
]
page_encoding = 'utf-8' # 设定待抓取页面的页面编码
fulltext_by_readability = False # 设定手动解析网页
# 设定内容页需要保留的标签
keep_only_tags = [
dict(name='span', class_='info_l'),
dict(name='div', id='Content'),
]
# 提取每个主题页面下所有文章URL
def ParseFeedUrls(self):
urls = [] # 定义一个空的列表用来存放文章元组
# 循环处理fees中两个主题页面
for feed in self.feeds:
# 分别获取元组中主题的名称和链接
topic, url = feed[0], feed[1]
# 请求主题链接并获取相应内容
opener = URLOpener(self.host, timeout=self.timeout)
result = opener.open(url)
# 如果请求成功,并且页面内容不为空
if result.status_code == 200 and result.content:
# 将页面内容转换成BeatifulSoup对象
soup = BeautifulSoup(result.content, 'lxml')
# 找出当前页面文章列表中所有文章条目
items = soup.find_all(name='span', class_='tw3_01_2_t')
# 循环处理每个文章条目
for item in items:
title = item.a.string # 获取文章标题
link = item.a.get('href') # 获取文章链接
link = BaseFeedBook.urljoin(url, link) # 合成文章链接
urls.append((topic, title, link, None)) # 把文章元组加入列表
# 如果请求失败通知到日志输出中
else:
self.log.warn('Fetch article failed(%s):%s' % \
(URLOpener.CodeMap(result.status_code), url))
# 返回提取到的所有文章列表
return urls
基于先前创建的订阅脚本,我们将两个模块URLOpener和BeautifulSoup新导入到代码头中。前者用于请求页面URL以获得响应内容,后者用于解析响应内容以提取文章内容数据。
我们还添加了一些参数。其中,coverfile用于设置电子书的“封面图片”,mastheadfile用于设置日记样式电子书独有的“标题图片”。制作这两张图片时,它们的大小和格式可以参考KindleEar项目的images目录中已经存在的图片,并且您制作的图片也保存在该目录中。请注意,该参数值需要图像的文件名,并且不需要其他路径,因为KindleEar的默认图像位于图像目录中。在此示例中,使用了两张图片,如下所示,您也可以保存它们以供使用。
▲封面图片:cv_chinadaily.jpg
▲标题图片:mh_chinadaily.gif
然后有两个参数,page_encoding和fulltext_by_readability。前者的功能是设置要获取的页面的编码类型。通常,现代WEB页面使用“ UTF-8”,但是某些网站使用其他编码。您可以在页面源代码的标记中找到charset的值。后者已在前面提到,这是为了使用Beautiful Soup手动清除内容。
还有一个keep_only_tags参数,该参数告诉内容清除功能需要保留文章页面中的哪些内容元素,以排除其他不必要的元素。此参数的值是字典容器dict(),通常可以设置两种类型的键值,一种是元素的标记名,这是代码中的名称,另一种是前者的选择器,这是代码中的class_。 (或ID)。这种参数实际上是Beautiful Soup的find_all()或find()方法用来解析内容的(有关详细信息,请参考
)。
最后,添加了此新订阅脚本的核心功能ParseFeedUrls()。让我们详细解释它在做什么。
四、 HTML标记结构分析
在解释函数ParseFeedUrls()之前,让我们分析“ 文章 List”和“ 文章 Content”的HTML标记结构。
1、分析文章列表的HTML标记结构
首先,是文章列表的标签结构。使用Chrome浏览《中国日报》的“社会”部分,您可以看到常规的文章列表,如下图所示。请注意,最上面的几个正方形正好位于顶部文章上,它们实际上是从列表中选择的,因此不必担心。
▲文章列表显示效果
右键单击页面,然后在菜单上单击“检查”以调用开发人员工具,您可以轻松查看文章列表的代码结构。
▲文章列表标记结构
在此代码结构中,我们可以看到我们所需的文章数据存储在重复的span.tw3_01_2_t标签中,文章标题位于其子标签a中,文章链接为以下内容的href a a标签属性值文章日期在子标签b标签中。如下图所示:
▲文章列表结构说明
2、分析文章内容的HTML标记结构
与查看文章列表的标签结构相同,我们还可以使用相同的方法查找超出文章内容页面上所需数据的数据:文章信息与类名.info_l,文章内容存储在id为Content的div标签中。
▲文章内容显示效果
▲文章内容标签结构
▲文章内容结构说明
在分析示例网站中国日报网站时,您可能发现文章列表的标签结构和所有主题页面的文章内容都是相同的,这也是我们可以做到的提要中的列表的原因添加多个主题页面链接并对其进行统一处理的原因。
了解文章列表的标签结构和文章的内容之后,您可以轻松地解析它们。回顾一下函数ParseFeedUrls()的功能。它首先循环处理提要列表中每个主题页面的URL,然后使用新导入的函数URLOpener()请求当前处理的URL。成功获取响应后,它将响应HTML代码转换为可以解析的Beautiful Soup对象。
然后使用find_all()方法从Beautiful Soup对象中找到所有文章条目,并循环处理这些条目,并将每个文章的“标题”和“链接”转换为元组,然后将生成的元组附加到预定义的网址列表中。
运行完所有循环后,将获得收录所有文章信息的url的完整列表,最后,将使用关键字return将其返回给函数Item()。到目前为止,函数ParseFeedUrls()已完成其工作,并且我们的脚本可以正常使用。
五、测试订阅脚本的推送
最后,我们需要测试此订阅脚本的推送。测试之前,您需要准备一个可用的SMTP服务器。这里以163个邮箱为例。准备就绪后,请在终端(或命令提示符)中按Ctrl + C退出Google App Engine(如果它仍在运行)。然后,按原样添加以下参数,并用您自己的电子邮件帐户信息替换中文部分:
dev_appserver.py \
--smtp_host=smtp.163.com \
--smtp_port=25 \
--smtp_user=邮箱用户名@163.com \
--smtp_password=邮箱授权码 \
--smtp_allow_tls=False \
./app.yaml ./module-worker.yaml
请注意,Windows命令提示符不支持使用反斜杠来包装命令,因此您需要在同一行上编写命令:
dev_appserver.py --smtp_host=smtp.163.com --smtp_port=25 --smtp_user=邮箱用户名@163.com --smtp_password=邮箱授权码 --smtp_allow_tls=False ./app.yaml ./module-worker.yaml
还修改KindleEar项目中的config.py文件,并将SRC_EMAIL参数值临时更改为上面使用的电子邮件地址。
现在,转到KindleEar的“设置”页面,将“ Kindle邮箱”设置为您的Kindle邮箱或任何普通邮箱(请注意上面使用的邮箱),然后刷新测试链接(或输入“高级设置” ,点击“立即交付”上的[Push]按钮以运行订阅脚本。如果没有其他操作,您将在终端中看到以下输出:
INFO 2019-05-14 15:15:31,133 resources.py:49] Serializing resources...
INFO 2019-05-14 15:15:31,144 mobioutput.py:149] Creating MOBI 6 output
INFO 2019-05-14 15:15:31,932 manglecase.py:34] Applying case-transforming CSS...
INFO 2019-05-14 15:15:31,944 parse_utils.py:302] Forcing toc.html into XHTML namespace
INFO 2019-05-14 15:15:33,267 mail_stub.py:170] MailService.Send
From: YOUREMAILNAME@163.com
To: YOUREMAILNAME@kindle.cn
Subject: KindleEar 2019-05-14_23-15
Body:
Content-type: text/plain
Data length: 22
Attachment:
File name: China Daily(2019-05-14_23-15).mobi
Data length: 110878
INFO 2019-05-14 15:15:34,306 module.py:861] worker: "GET /worker?u=admin&id=6192449487634432 HTTP/1.1" 200 40
稍后,您将能够接收由您在填写的Kindle邮箱(或普通邮箱)中编写的脚本生成的电子书。如下图所示:
▲订阅脚本推送效果
但是,到目前为止,我们生成的电子书并不完美。例如,文章的内容收录重复的网站名称,文章的数目始终为20,并且未按时间过滤,翻页时未处理列表,并且[如果有分页,则不会处理文章 ...
最初,合作伙伴估计可以完成两篇文章文章,但我发现在我撰写本文时篇幅超出了我们的预期,因此我只能将这篇文章分为第一篇,第二篇和下一篇三篇文章。在本文中,KindleEar订阅脚本已正常运行。在下一篇文章中,我们将讨论不完美的细节。
如果对本教程有任何疑问,或者发现内容不正确或不完整,请留言。
您可以继续阅读:“如何在不使用RSS的情况下使用KindleEar推送网站内容(第2部分)”