- 概览
- 总体架构
- 项目组成
- 模块简介
- tools
- Config
- Debug
- Path
- Http
- Match
- Type
- DB
- Controler
- HtmlCreator
- worker
- parser
- epub
- tools
知乎助手的核心其实就是:抓取数据&生成网页。架构其实就是尽可能地解耦这两大工作单元,将整个工作流程拆分成独立的模块,在1.7.3版中,我所理解的助手工作流程如下:
- 初始化环境(建立数据库,载入设置项等)
- 分析ReadList
- 根据ReadList内容,生成待下载的网址列表,以及抽取数据的SQL语句(此处实现网页下载与数据抽取的分离)
- 根据下载网址列表,下载网址并进行分析,将分析结果存入数据库内
- 第3步完成后,根据SQL语句,从数据库中抽取相应数据,并进行处理,使之可以生成html网页
- 将抽取出的数据填充至预先编写好的html模板中
- 在磁盘上写入html网页,下载图片并压缩成epub电子书
大致如此。
根据架构思路中的工作流程,可以很自然的将项目分成如下几个模块
- 环境模块
- 该模块负责初始化助手的运行环境,包括如下几个部分
- guide.py
- 负责提供交互界面中用到的提示语句,引导用户进行设置
- login.py
- 负责进行登陆操作,以获取cookie,方便后期的抓取
- main.py
- 主程序,负责控制整个助手的流程(类似于MVC结构中的Control)
- 由于历史原因该模块还顺手完成了检查更新,初始化数据库,载入设置项等杂项操作
- 主程序,负责控制整个助手的流程(类似于MVC结构中的Control)
- tools/
- config.py
- 负责保存全局设置(利用Python中导入模块时只导入一次的特性实现(或者说,利用共享的类变量实现))
- path.py
- 负责保存路径信息(原理同config.py),提供文件系统接口(例如copy,cd,rm操作等)
- db.py
- 负责提供数据库操作
- debug.py
- 负责提供打印日志,打印变量等debug操作
- match.py
- 负责进行文字匹配,以及文字整理(html除错)。
- controler.py
- 提供一个线程池,用于实现并行化下载
- template_config.py
- 模板配置项,主要用于记录html模板的位置
- html_creator.py
- 用于将从数据库中抽取出的数据填充成html
- type.py
- 用于记录类别名称
- http.py
- 用于进行网络操作
- extra_tools.py
- 负责提供杂项工具
- config.py
- 容器模块
- 用于容纳各种数据
- container
- book
- image
- page
- task
- ReadList分析模块
- 用于将ReadList的内容分析成task数据,以便worker进行下载以及RawBooks生成电子书
- read_list_parser.py
- 下载模块
- 主要负责根据task内容生成待抓取网页列表并下载网页,下载下来之后将内容交由Parser进行分析并存入数据库中
- work.py
- 数据分析模块
- 用于对网页数据进行分析
- lib/parser
- 内容处理
- 用于根据task的设定从数据库中取出相应数据,进行相应处理后交由Epub模块生成电子书
- RawBook
- 电子书生成
- 用于将html网页压制成epub电子书
- lib/epub
- tools模块内为通用类库
- Debug
- 负责打印日志
- Path
- 负责进行常见文件系统操作
- 保存常用路径地址
- 路径地址最后一律没有'/'
- Debug
0. 快速迭代,先只做将指定用户的回答导出为epub的功能
1. 通过API直接抓取数据,存入数据库中
2. 从数据库里读取文件,渲染到模版里
3. 生成epub
知乎助手本质上是一段命令式代码脚本,面向对象、框架、GUI等特点对于功能本身仅仅只是作为锦上添花的功能而存在
这一点在进行编码过程中必须谨记
切勿为了炫技而影响到功能的实现
Keep It Simple & Stupid
1. 确认抓取类型
2. 抓取数据至数据库中
3. 在数据库中抽取数据,生成网页
1. 初始化
1. 载入 & 初始化数据库及表结构,由DB类掌控数据库连接
2. 载入 & 初始化配置项。
可配置内容有:
1. 用户名
2. 密码
3. 是否记住密码(记住密码后下次直接使用该账号密码进行登录)
4. 图片质量:无图/标清/高清
5. 每卷大小:
当电子书体积过大时,应当分卷展示
由本项对每卷电子书中包含的问题数/回答数/每卷大小进行配置
6. 排序规则
按照什么对内容进行排序:创建时间/赞同数
排序规则:正序/倒序
7. 下载图片时的超时等待时间
8. 是否是debug模式,日志显示级别
2. 功能引导
1. 展示欢迎语
2. 引导设置配置项
3. 初始化zhihu-oauth类
1. 若配置项中勾选了自动登录
1. 尝试使用帐号密码登录
1. 若数据库中保存了token,且时间在三天以内
1. 直接使用使用token
1. 使用完成后调取接口进行测试,
1. 登录成功自动返回,
2. 登录失败继续登录流程
2. 使用帐号密码进行登录
1. 使用完成后调取接口进行测试,
1. 登录成功自动返回,
2. 登录失败继续登录流程
3. 执行至此说明账号密码不正确,提示账号密码不正确,引导用户重新输入账号密码
1. 回到开头,重新执行登录流程
3. 功能实现
1. 分析指令
1. 逐行读取ReadList.txt文件
2. 匹配出要抓取的类别
1. 是否多卷合一(一本电子书中包含多种内容)
1. true
2. false
2. 任务类别
1. 作者
1. author_id
2. 话题
1. topic_id
3. 收藏夹
1. collection_id
4. 指定问题
1. question_id
5. 指定回答
1. question_id
2. answer_id
6. 专栏
1. column_id
7. 文章
1. column_id
2. article_id
3. 执行抓取任务
抓取任务主要依靠zhihu-oauth库实现。感谢[@七秒不觉梦](https://github.com/7sDream)同学的努力,依靠这个库,可以直接调取知乎接口获取内容,再也不用担心由于知乎网页结构有变导致的答案抓取失败了
这里需要考虑一个问题:我们在谈知乎内容的时候,谈的是什么
答案很简单,是 问题 => 答案 对
对于知乎问题来说,一个问题对应多个答案; 对于专栏文章来说,一个标题(问题)对应一个答案(文章内容)
而专栏、作者、话题、收藏夹、etc,其实都只是一层外壳而已,输出内容的时候特殊处理下即可,不用太在意
所以,抓取的主要目的应该是:
1. 获取信息层数据(作者/话题/专栏/etc)
2. 获取问题答案对信息,将问题、答案分别存储,同时记录下获取顺序
具体执行流程如下:
1. 根据待抓取类型,实例化ZhihuClient
2. 获取信息,存入数据库中(信息层数据获取完成)
3. 遍历答案,存入数据库中(这一步同时抓取问题和答案)
遍历答案的同时记录下Index值,也存入数据库中,方便后期生成对应结果
(数据部分获取完成)
4. 生成页面
1. 判断类别,获取数据
1. 作者
1. 相关SQL
1. 作者信息
select * from Author where author_id = '{{author_id}}'
获取作者的hash_id(数据库内为id)
2. 问题与答案
select *
from Answer
left join Question on Answer.question_id = Question.id
where Answer.author_id = '{{author_info[id]}}'
2. 生成作者信息页面, 返回html代码(合并输出为一张html时直接抽取body标签内的数据即可)
2. 话题
1. 相关SQL
1. 话题信息,话题index信息作为字段直接存在Topic表中,使用逗号分隔(方便在Topic表中进行查找)
select * from Topic where id = '{{topic_id}}'
2. 问题与答案
select *
from Answer
left join Question on Answer.question_id = Question.id
where Answer.id in ({{topic_info[best_answer_id_list]}})
2. 话题信息储存起来,由书籍容器负责将之渲染成html
3. 收藏夹
1. 相关SQL
1. 话题信息,话题index信息作为字段直接存在Topic表中,使用逗号分隔(方便在Topic表中进行查找)
select * from COLLECTION where id = '{{collection_id}}'
2. 问题与答案
select *
from Answer
left join Question on Answer.question_id = Question.id
where Answer.id in ({{collection_info[collected_answer_id_list]}})
2. 将收藏夹信息储存起来, 由书籍容器负责将之渲染成html
4. 问题
5. 答案
6. 专栏
7. 文章
2. 组合问题、答案对
将问题答案对存入容器中,
文章可以视为只有一个回答的问题
对此,可以构建出一个抽象的容器,用于储存内容信息(见附注)
在获取到内容后, 执行下载图片任务
所有图片下载完成后,更新容器内答案大小记录
附注:
下载图片逻辑:
1. 依次遍历所有答案、问题,获取其中的图片链接地址
2. 将链接地址替换为设置里的电子书图片地址
3. 将图片文件名存于答案容器中
4. 下载图片,存储于设置中的图片池目录中
容器结构
0. 数据容器
1. 基础属性
1. 容器类别
1. 作者
2. 话题
3. 收藏夹
4. 问题
5. 答案
6. 专栏
7. 文章
2. 容器信息数据
在容器层面上,要将数据结构包裹起来,使后面的电子书生成部分可以独立操作
同样,对于不同容器,也需要提供一套共有的接口,方便电子书生成部分生成书籍信息
2. 方法
1. 添加问题
2. 对容器内问题按照以下条件进行排序,默认排序方式由配置项指定
1. 问题下答案总数
2. 问题下答案赞同数总和
3. 问题下答案字数总数
3. 获取体积在m之内的前n个问题
1. 不对问题排序,返回问题时按当前排序
防止由于问题内答案被剔除导致顺序紊乱
1. 返回后将返回的问题在容器内删除(避免重复返回)
2. 如果当前已返回问题体积和小于m,再多返回一个问题则体积大于m,则会多返回一个问题(导致最后问题集总体积和大于m)
3. (根据2可知,该接口)至少返回一个问题(问题内至少有一个答案)
4. 可能导致问题被拆分,但不影响问题顺序
4. 获取容器内问题总体积
5. 更新容器内项目体积数据(问题体积/答案体积),方便排序
6. 获取容器信息(即容器信息数据,Topic/Collection的info信息)
7. 获取容器标准信息
title
1. 问题
1. 基础属性
1. 问题题图(对应文章题图)
2. 问题标题(对应文章标题/问题)
3. 问题描述
2. 扩展属性
1. 答案数
2. 其下答案总赞同数
3. 其下答案总字数
4. 其下答案总大小
5. 问题本身大小(问题描述中本身也有可能有图片)
3. 方法
1. 排序
1. 对答案按照以下条件进行排序,默认排序方式由配置项指定
1. 答案赞同数
2. 答案更新时间
3. 答案字数
4. 答案收藏数
2. 更新问题的扩展属性
3. 添加答案
4. 按顺序获取尽可能多的答案,答案体积和在m以内
0. 首先对容器内答案进行排序操作
1. 返回后将返回的答案在容器内删除(避免重复返回)
2. 如果当前已返回答案体积和小于m,再多返回一个答案则体积大于m,则会多返回一个答案(导致最后答案总体积和大于m)
3. (根据2可知,该接口)至少返回一个答案
5. 更新下载问题描述中的图片
6. 下载图片
6. 获取问题元数据(基础属性和扩展属性)
方便将问题拆分成两块,以按体积控制电子书
7. 按顺序获取前n个答案(方便按答案数控制电子书分卷数)
2. 答案
1. 答主信息(对应文章作者信息)
1. 作者头像
2. 作者hash_id
3. 作者签名
2. 答案详情
1. 答案内容
2. 答案评论数
3. 答案更新时间
4. 答案收藏数
5. 答案赞同数
3. 扩展信息
1. 图片名列表
存储所有图片的文件名,方便统计大小,生成电子书
2. 答案大小
用于记录答案的大小,方便分页
统计规则为:计算答案中图片的大小并加总,以KB为单位进行计算
4. 方法
1. 获取待下载图片地址列表
1. 替换答案中出现的图片路径
2. 更新答案大小字段
3. 获取图片名列表
3. 生成电子书包裹
在这一步将已有数据处理成电子书包裹,包裹中具有create_epub方法,可以直接输出epub
在包裹中存放了各个容器,因此,包裹需要一个方法,将自身安装配置项里的要求,按大小/答案数/问题数拆分成符合要求的数个包裹,并返回一个列表,以便批量生成epub
为了成功分卷,包裹需要一个方法用于获取容器内的大小,并按需求拆分容器
包裹需要按照容器类别生成
各个容器的信息页
根据容器类别选择模版,然后将模版数据套入模版中即可
为问题/答案渲染选择模版
同信息页生成方法
目录
总目录和分卷目录
书名
总长不得超过200个字符,超过部分用『...等n个』知乎回答集锦代替
具体名字为:
作者-{{作者名}}
话题-{{话题名}}
收藏夹-{{收藏夹名}}
专栏-{{专栏名}}
问题-{{问题名}}
答案-{{问题名}}
文章-{{文章名}}
不同标题使用#进行区隔
其中,标题顺序按书中出现的顺序进行排列
分卷后在最后标记卷号
只有一卷的图书不标记
书名示例:
作者-Yao#话题-ZhihuHelp#问题-知乎助手今天更新了吗#...等8个知乎回答集锦【卷3】.epub
书籍封面
其内有书籍中所包含种类的详细介绍
如果分为多部的话,在容器名后添加所在卷的位置
书籍目录
书籍内部的目录
按容器类别进行分开,只显示在书记内部的文章名列表
当电子书体积过大时,会导致无法打开对应的epub文件,因此需要对包裹进行分组
因此有书籍容器
附录:
书籍容器
1. 基础属性
1. 书籍名
2. 数据券号
2. 扩展属性
3. 方法
1. 生成epub
2. 调取容器接口,处理图片
3. 调取容器接口,更新容器大小
4. 生成书籍名
5. 生成书籍封面
5. 记录书籍目录
6. 生成书籍目录
@todo
- 如果需要获取单个答案的话,直接获取即可,不要获取整个问题
- 添加下载话题图片的功能