ISEE小语
昔日立誓远离烟,如今烟雾伴身边。狂言不变的你,怎能敌得过生活的变迁呢?
看书的时候,突然有一个奇葩有意思的想法,将电子书解析到数据库中,前端搜索曾经看到过的金句,就可以找到出自哪本书、哪个章节,甚至是定位到准备的位置。
挺有意思的,可以试试看!
环境:
Pycharm
Python 3.9.16
安装:
pip install 以下所有的包
alembic==1.13.1
beautifulsoup4==4.12.2
blinker==1.7.0
EbookLib==0.18
Flask==2.3.1
Flask-Migrate==4.0.4
Flask-SQLAlchemy==3.0.5
greenlet==3.0.3
itsdangerous==2.1.2
lxml==5.1.0
Mako==1.3.0
six==1.16.0
soupsieve==2.5
SQLAlchemy==2.0.25
typing_extensions==4.9.0
Werkzeug==3.0.1
实现原理:
使用Flask+SQLAlchemy,主要特点是方便、简单、快速,最重要的一点就是前面文章分享过基本使用,不需要使用多的时间去研究了!
主要包括三大块:
一、解析电子书并保存到数据库
二、服务端查询和返回逻辑实现
三、前端页面查询入口及返回显示
项目结构:
新创建一个Flask项目,具体结构如下:
```结构
book_read_flask
├─instance
│ └─database.db
├─templates
│ └─index.html
├─static
├─app.py
├─config.py
├─models.py
├─requirements.txt
└─一句顶一万句.epub
```
解析电子书
解析电子书,用到了EbookLib和beautifulsoup4这两个三方库。
EbookLib是一个用于处理电子书文件的Python第三方库。它提供了一组简单易用的API,可以用于读取、解析和操作常见的电子书格式,如EPUB、MOBI和AZW3等。
BeautifulSoup是用于从HTML或XML文档中提取数据的工具。EbookLib解析出来的源文会带有html标签,需要再次进行数据处理。
本次解析的是【刘震云】老师的《一句顶一万句》,epub格式。
# -*- coding: utf-8 -*-
import ebooklib
from bs4 import BeautifulSoup
from ebooklib import epub
book_name = '一句顶一万句.epub'
author = '刘震云'
introduction = '作家'
# 读取EPUB文件
book_content = epub.read_epub(book_name)
# 遍历电子书中的所有项目
for item in book_content.get_items():
if item.get_type() == ebooklib.ITEM_DOCUMENT:
# 使用BeautifulSoup解析HTML内容
soup = BeautifulSoup(item.get_content(), 'html.parser')
chapter_tag = soup.find('h1')
chapter_text = chapter_tag.get_text(separator=' ', strip=True) if chapter_tag is not None else ''
if chapter_text != '':
print("章节:", chapter_text)
print(soup.get_text())
(左右滑动查看完整代码)
创建Flask项目
数据库选择的是Sqlite。
框架是Flask+SQLAlchemy。
首先,创建一个Flask项目,设计两个数据表books和books_detail,在项目根目录下新建models.py
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.orm import relationship
from datetime import datetime
db = SQLAlchemy()
class Books(db.Model):
__tablename__ = 'books'
id = db.Column(db.Integer, primary_key=True)
name = db.Column(db.String(50))
author = db.Column(db.String(20))
introduction = db.Column(db.Text)
add_time = db.Column(db.String(255), default=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
details = relationship('BookDetail', back_populates='book')
class BookDetail(db.Model):
__tablename__ = 'books_detail'
id = db.Column(db.Integer, primary_key=True)
book_id = db.Column(db.Integer, db.ForeignKey('books.id'))
chapter = db.Column(db.String(255))
content = db.Column(db.Text)
add_time = db.Column(db.String(255), default=datetime.now().strftime('%Y-%m-%d %H:%M:%S'))
book = relationship('Books', back_populates='details')
(左右滑动查看完整代码)
然后,配置数据库链接,在项目根目录下新建config.py
# -*- coding: utf-8 -*-
# 连接数据库
SQLALCHEMY_DATABASE_URI = 'sqlite:///database.db'
SQLALCHEMY_TRACK_MODIFICATIONS = False
(左右滑动查看完整代码)
最后,在应用app.py中创建init_app初化数据库,以及创建Migrate
from flask import Flask
from flask_migrate import Migrate
from models import db
import config
app = Flask(__name__)
app.config.from_object(config)
db.init_app(app)
with app.app_context():
db.create_all()
migrate = Migrate(app, db)
def hello():
return 'hello world!'
if __name__ == '__main__':
app.run(host='0.0.0.0')
(左右滑动查看完整代码)
运行app.py,启动项目,在项目根目录下将自动创建instance文件夹,其中database.db被初始化成功创建。
在终端中进入项目目录,并执行命令初始化数据库迁移
flask db init
执行命令生成数据库迁移脚本
flask db migrate -m "Initial migration"
执行命令应用数据库迁移:
flask db upgrade
最后重启Flask应用后,就可以看到Models的变更已经反映在数据库表结构中
电子书写入DB
解析电子书完成后,我们按章节存到数据库中。
在app.py中构造一个read_epud函数
def read_epud():
book_name = '一句顶一万句.epub'
author = '刘震云'
introduction = '作家'
# 检查书籍是否存在
book_exist = db.session.query(Books.query.filter_by(name=book_name).exists()).scalar()
if not book_exist:
book_ = Books(name=book_name, author=author, introduction=introduction)
db.session.add(book_)
db.session.commit()
# 读取EPUB文件
book_content = epub.read_epub(book_name)
book = Books.query.filter_by(name=book_name).first()
# 检查书箱详细内容是否存在
book_detail_exist = db.session.query(BookDetail.query.filter_by(book_id=book.id).exists()).scalar()
if not book_detail_exist:
# 遍历电子书中的所有项目
for item in book_content.get_items():
if item.get_type() == ebooklib.ITEM_DOCUMENT:
# 使用BeautifulSoup解析HTML内容
soup = BeautifulSoup(item.get_content(), 'html.parser')
chapter_tag = soup.find('h1')
chapter_text = chapter_tag.get_text(separator=' ', strip=True) if chapter_tag is not None else ''
if chapter_text != '':
book_detail = BookDetail(chapter=chapter_text, content=soup.get_text(), book_id=book.id)
# 将新的书籍详情对象添加到会话中
db.session.add(book_detail)
# 提交会话,将新的书籍详情对象插入到数据库中
db.session.commit()
return '电子书解析入库成功!'
(左右滑动查看完整代码)
启动服务,打开浏览器,运行网址:
http://127.0.0.1:5000/read_epud
说明调用电子书解析入库已经成功!
注:这个地方的电子书没有做传参,如果换其他电子书写入,需要在源码中修改,
book_name = '一句顶一万句.epub'
author = '刘震云'
introduction = '作家'
后期抽时间再做优化!
指定内容搜索
电子书存到数据库后,接下来我们就进行指定的内容搜索。
在app.py中构造一个sentence_search函数
@app.route('/sentence_search', methods=['GET'])
def sentence_search():
sentence = request.args.get('sentence', '')
if not sentence:
return jsonify([{'sentence': '请输入搜索的内容!', 'book_name': '', 'book_author': '', 'book_chapter': '',
'chapter_content': ''}])
# 查询包含特定内容的书籍信息
book_details = BookDetail.query.join(Books).filter(BookDetail.content.contains(sentence)).all()
# 创建book_list返回结果
book_list = [
{
'sentence': sentence,
'book_name': book_detail.book.name,
'book_author': book_detail.book.author,
'book_chapter': book_detail.chapter,
'chapter_content': book_detail.content
} for book_detail in book_details
]
return jsonify(book_list)
(左右滑动查看完整代码)
启动服务,打开浏览器,运行网址:
http://127.0.0.1:5000/sentence_search?sentence=
说明调用电子书指定内容搜索接口是成功的。
设计前端搜索页面
设计一个前端页面,指定的内容搜索框及返回内容显示。
在templates文件夹中新建一个index.html页面,页面主体如下(样式太多就不全贴了,有兴趣的看源码):
<body>
<div class="Search_content">
<div class="Search_input__1">
<i class="fas fa-search"></i>
<input type="search" id="search_id" value="" placeholder="请输入内容">
</div>
<button class="Search_btn" id="go_search">去找寻</button>
</div>
<div class="Output_box">
<div class="label_class">出自:</div>
<div id="output"></div>
</div>
</body>
(左右滑动查看完整代码)
服务返回内容处理及显示:
<script>
document.getElementById('go_search').addEventListener('click', function() {
const searchInput = document.getElementById('search_id').value;
// 发送请求到后端
const xhr = new XMLHttpRequest();
xhr.open('GET', '/sentence_search?sentence=' + searchInput, true);
xhr.onreadystatechange = function() {
if (xhr.readyState === XMLHttpRequest.DONE && xhr.status === 200) {
const responses = JSON.parse(xhr.responseText);
// 清空之前的内容
document.getElementById('output').innerHTML = '';
// 遍历每个返回的 JSON 数据对象
responses.forEach(function(response) {
// 创建显示的元素
const element = document.createElement('div');
element.innerHTML = `
<div class="sentence">搜索:${response.sentence}</div>
<div class="book_name">出自:${response.book_name}</div>
<div class="author">作者:${response.book_author}</div>
<div class="chapter">章节:${response.book_chapter}</div>
章节内容:<div class="chapter_content">${highlightSentence(response.chapter_content)}</div>
<div>-----------------------------</div>
`;
// 添加到输出框中
document.getElementById('output').appendChild(element);
});
}
};
xhr.send();
});
function highlightSentence(content) {
const searchInput = document.getElementById('search_id').value;
const regex = new RegExp(searchInput, 'gi');
const replacedContent = content.replace(regex, '<span class="highlight">$&</span>');
const replacedWithBreaks = replacedContent.replace(/\n/g, '<br>');
return replacedWithBreaks;
}
</script>
(左右滑动查看完整代码)
服务添加路由,调用index.html页面:
@app.route('/')
def index():
return render_template('index.html', chart_html='')
启动服务,打开浏览器,运行网址:
http://127.0.0.1:5000
成功!
接下来输入搜索内容,看看实际效果:
总结
这个小项目前端页面有点单调,在处理上也可能会有些Bug,但不会有大的影响,后续再丰富吧。
本次除了解析电子书《一句顶一万句》,还解析了《影响力》,解析、入库、搜索都正常。
这两本电子书也会一同分享出来,有兴趣的也可以看看书,这两本书其实还挺不错的!
点个“赞”和“在看”,是对小栈最大的支持!
后台回复“book_read”即可获取源码和电子书!
文章就分享到这儿,喜欢就点个赞吧!