cms网站内容如何自定义(如何用KindleEar推送无RSS的网站内容(上篇))
优采云 发布时间: 2021-10-26 10:22cms网站内容如何自定义(如何用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'),
]
这段代码做了三件事:在base.py中导入基类BaseFeedBook,继承参数和函数;为最终生成的电子书设置书名、作者、介绍、语言等元数据信息;指定了两个收录 文章 列表的主题页面 URL。
现在我们为 KindleEar 添加了一个新的内置订阅。在网页浏览器中访问:8080并登录您的帐户,点击导航中的“我的订阅”进入订阅管理页面,您可以在“未订阅”列表中看到新添加的订阅。
如上图所示,点击新订阅项后面的【订阅】按钮,将其添加到“已订阅”列表中。如下图所示,点击导航中的“高级设置”,进入“立即发送”页面,勾选新订阅,点击【推送】按钮,手动执行新添加的订阅脚本。只是脚本目前没有实际功能,所以只会生成一个状态为nonews的空日志。
点击【推送】按钮执行订阅脚本后,可以看到终端(或命令提示符)输出如下两条信息:
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。为避免测试时频繁点击【推送】按钮,建议在浏览器中直接访问刷新该网址,而不是点击推送按钮。注意,与访问 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),
]
提示:文章 元组中的所有参数都必须指定,但“Summary”除外。即使没有填写“Summary”的内容,也要把值设置为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订阅脚本抓取网站内容的大致操作流程,下面来试试我们的技巧吧。
三、从网站中提取文章 URL
让我改进我之前写的代码,添加一些必要的参数,并添加函数 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获取响应内容,后者用于解析响应内容以提取文章Content数据。
我们还添加了一些参数。其中,coverfile用于设置电子书的“封面图片”,mastheadfile用于设置期刊式电子书独有的“标题图片”。制作这两张图片时,它们的大小和格式可以参考KindleEar项目images目录下已有的图片,你制作的图片也保存在这个目录下。注意参数值需要图片的文件名,不需要指定路径,因为KindleEar默认图片在images目录下。本例中使用了如下两张图片,您也可以将其保存以备使用。
▲ 封面图:cv_chinadaily.jpg
▲ 刊头图片:mh_chinadaily.gif
然后有两个参数,page_encoding 和 fulltext_by_readability。前者的作用是设置要获取的页面的编码类型。一般现代WEB页面使用“UTF-8”,但有些网站使用其他编码。具体可以在页面源码中的标签中找到charset的值。后者是前面提到的,就是使用 Beautiful Soup 启用手动清理内容。
还有一个keep_only_tags参数,告诉内容清理函数需要保留文章页面中的哪些内容元素,排除其他不需要的元素。这个参数的值是一个字典容器dict(),一般可以设置两种key值,一种是元素的标签名,即代码中的名字,一种是前者的选择器,也就是代码中的class_。(或*敏*感*词*)。这种参数其实是Beautiful Soup的find_all()或者find()方法用来解析内容的(具体参考)。
最后,添加了这个新订阅脚本的核心函数 ParseFeedUrls()。让我们详细解释一下它在做什么。
四、分析HTML标签结构
在解释函数ParseFeedUrls()之前,我们先来分析一下“文章List”和“文章Content”的HTML标签结构。
1、解析文章列表的HTML标签结构
首先是文章列表的标签结构。使用Chrome访问中国日报社社版块,可以看到如下图所示的常规文章列表。请注意,前几个方块只是在顶部文章,它们实际上是从列表中选择的,所以不要担心它们。
▲ 文章 列表显示效果
页面右击,点击菜单上的“检查”,调出开发者工具,可以方便的查看文章列表的代码结构。
▲ 文章 列表标签结构
在这个代码结构中,我们可以看到我们需要的文章数据存储在重复的span.tw3_01_2_t标签中,文章标题在它的子标签a中,文章链接是this a标签的href属性值,文章日期在子标签b标签中。如下所示:
▲ 文章 列表结构说明
2、解析文章内容的HTML标签结构
就像查看文章列表的标签结构一样,我们也可以用同样的方法在文章内容页上查找超出我们需要的数据:文章信息存储在类中名为.info_l的span标签中,文章的内容存放在id为Content的div标签中。
▲ 文章 内容展示效果
▲ 文章内容标签结构
▲ 文章内容结构说明
在分析例子网站中国日报网站时,你可能已经发现所有主题页面的文章列表和文章标签结构是一样的,这也是为什么我们可以在feeds列表中添加多个主题页面链接,统一处理。
一旦了解了文章列表和文章内容的标签结构,就可以轻松解析它们。回顾函数 ParseFeedUrls() 做了什么。它首先循环处理提要列表中每个主题页面的 URL,然后使用新导入的函数 URLOpener() 请求当前处理的 URL。成功获取响应后,将响应HTML代码转换为Beautiful Soup对象进行解析。
然后使用find_all()方法从Beautiful Soup对象中找到所有的文章条目,并循环处理这些条目,将每个文章的“标题”和“链接”变成一个元组,然后将生成的元组附加到预定义的 url 列表中。
运行完所有的循环后,可以得到一个收录所有文章信息的完整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 邮箱或任何普通邮箱(注意上面使用的邮箱),然后刷新测试链接(或进入“高级设置”页面) KindleEar,点击“立即发布”上的【推送】按钮运行订阅脚本。如果不出意外,您将在终端中看到以下输出:
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订阅脚本正常运行了,不完善的细节我们会在下一篇文章中处理。
如果您对本教程有任何疑问,或发现内容不准确或不完整,请留言。
您可以继续阅读:《如何使用 KindleEar 在没有 RSS 的情况下推送 网站 内容(第 2 部分)》