欢迎关注我的专栏( つ•̀ω•́)つ【人工智能通识】
【专题】简书下载器:Python-Tkinter项目编程入门
继续本专题前面的文章。
梳理Tkinter界面
梳理后运行结果如下:
对应的main.py代码如下:
from tkinter import *
from tkinter import ttk
import time
import random
import modules.reqs as reqs # 导入reqs请求函数
import modules.options as opts
from tkinter.scrolledtext import ScrolledText
# 创建窗体
root = Tk()
root.title('简书文章下载器')
root.resizable(width=False, height=False)
root.config(background='#EEE')
def run(): # 启动获取动作
reqs.getAll(opts, [0], [4])
def refreshInfo(): # 信息的自刷新函数
text = reqs.genInfoStr()
text += '\nState:'+reqs.state
text += '\nCurVol:'+reqs.curVol
text += '\nCurArt:'+reqs.curArt
text += '\nCurImg:'+reqs.curImg
info.config(text=text)
info.after(500, refreshInfo)
# 创建界面
rown = 0 # 占位符
ttk.Frame(root, height=10).grid()
rown += 1 # header输入框
iptHeader = ScrolledText(root, height=1, width=50)
iptHeader.grid(row=rown, padx=10, pady=0, sticky=W)
rown += 1 # header输入框说明
label1 = ttk.Label(root, text='粘贴header,不要包含:打头的部分,去除if-none-match行')
label1.grid(row=rown, pady=0, padx=10, sticky=W)
rown += 1 # 占位符
ttk.Frame(root, height=20).grid(row=rown)
rown += 1 # 文集序号输入框
iptVol = Text(root,height=1, width=50)
iptVol.grid(row=rown, padx=10, pady=0, sticky=W)
rown += 1 # vol输入框说明
label2 = ttk.Label(root, text='文集列表,请用逗号分隔,如0,1,2')
label2.grid(row=rown, pady=0, padx=10, sticky=W)
rown += 1 # 占位符
ttk.Frame(root, height=20).grid(row=rown)
rown += 1 # 文集序号输入框
iptArt = Text(root,height=1, width=50)
iptArt.grid(row=rown, padx=10, pady=0, sticky=W)
rown += 1 # vol输入框说明
label3 = ttk.Label(root, text='文章列表,请用逗号分隔,如0,1,2')
label3.grid(row=rown, pady=0, padx=10, sticky=W)
rown += 1 # 占位符
ttk.Frame(root, height=30).grid(row=rown)
# 运行按钮
rown += 1
bt = ttk.Button(root, text='开始下载', width=30,command=run)
bt.grid(row=rown, padx=10,ipady=10 ,pady=0, sticky='WE')
# 信息标签
rown += 1
info = ttk.Label(root, text='?/?')
info.grid(row=rown, padx=10, ipady=10, ipadx=10, sticky=W)
info.after(500, refreshInfo) # 自动循环更新
root.mainloop()
获取用户输入
在Tkinter中可以使用xxx.get('1.0',END)
来获取输入内容,这里的1.0表示从头获取,END表示获取全部。
我们修改run函数:
def run(): # 启动获取动作
hdrs = iptHeader.get("1.0", END)
vols = iptVol.get("1.0", END)
arts = iptArt.get("1.0", END)
opts.headers = opts.str2obj(hdrs, '\n', ': ')
volnarr = []
vols=vols.replace('\n', '')
volsarr = vols.split(',')
volnarr = map(lambda x: int(x), volsarr)
volnarr = list(set(volnarr))
artnarr = []
artsarr = arts.split(',')
map(lambda x: int(x), artsarr)
artnarr = list(set(artnarr))
writeHeaders() # 保存设置
reqs.getAll(opts, volnarr, artnarr)
然后运行main.py,从浏览器复制粘贴你的headers,然后设定文集列表和文章列表,点击按钮就能开始下载文章了。
自动保存和读取headers
每次都手工复制headers比较麻烦,我们为软件增加自动保存和读取headers的功能。
每次打开的时候自动尝试读取config.txt文件,填充到iptHeaders,然后每次运行都自动将输入框的文字保存到config.txt文件。
在最底部添加代码:
def writeHeaders(): # 将header写入到临时文件
hdrs = iptHeader.get("1.0", END)
with open(os.getcwd()+'/config.txt', 'a') as f:
f.write(hdrs)
def readHeaders(): #读取设置文件并填充到界面
fpath=os.getcwd()+'/config.txt'
if os.path.exists(fpath):
f=open(fpath,'r')
hdrs=f.read()
iptHeader.insert(INSERT,hdrs)
readHeaders()
root.mainloop()
注意root.mainloop()要放在最后。
最后汇总
如果要打包成独立运行的软件(不安装python也可以运行),那么请参考这个文章:
整个项目代码已经同步到我的github中,你也可以从dist文件夹下找到mac版本的打包软件直接下载使用。地址是:
整个项目暂时告一段落,全部文章已整理在:
最终代码
最新修订请参照 Github项目:JianshuDownloader
main.py
from tkinter import *
from tkinter import ttk
import time
import random
import modules.reqs as reqs # 导入reqs请求函数
import modules.options as opts
from tkinter.scrolledtext import ScrolledText
import os
# 创建窗体
root = Tk()
root.title('简书文章下载器')
root.resizable(width=False, height=False)
root.config(background='#EEE')
def run(): # 启动获取动作
hdrs = iptHeader.get("1.0", END)
vols = iptVol.get("1.0", END)
arts = iptArt.get("1.0", END)
opts.headers = opts.str2obj(hdrs, '\n', ': ')
volnarr = []
vols=vols.replace('\n', '')
volsarr = vols.split(',')
volnarr = map(lambda x: int(x), volsarr)
volnarr = list(set(volnarr))
artnarr = []
artsarr = arts.split(',')
map(lambda x: int(x), artsarr)
artnarr = list(set(artnarr))
writeHeaders() # 保存设置
reqs.getAll(opts, volnarr, artnarr)
def refreshInfo(): # 信息的自刷新函数
text = reqs.genInfoStr()
text += '\nState:'+reqs.state
text += '\nCurVol:'+reqs.curVol
text += '\nCurArt:'+reqs.curArt
text += '\nCurImg:'+reqs.curImg
info.config(text=text)
info.after(500, refreshInfo)
# 创建界面
rown = 0 # 占位符
ttk.Frame(root, height=10).grid()
rown += 1 # header输入框
iptHeader = ScrolledText(root, height=1, width=50)
iptHeader.grid(row=rown, padx=10, pady=0, sticky=W)
rown += 1 # header输入框说明
text1 = '''
请从浏览器右击检查打开控制台
切换到Network部分
从XHR类型中找到notebooks请求
复制它的Request Headers部分
注意不要包含:打头的部分,并去除if-none-match行'
'''
label1 = ttk.Label(root, text=text1)
label1.grid(row=rown, pady=0, padx=10, sticky=W)
rown += 1 # 占位符
ttk.Frame(root, height=20).grid(row=rown)
rown += 1 # 文集序号输入框
iptVol = Text(root, height=1, width=50)
iptVol.grid(row=rown, padx=10, pady=0, sticky=W)
rown += 1 # vol输入框说明
label2 = ttk.Label(root, text='文集列表,请用英文逗号分隔,如0,1,2')
label2.grid(row=rown, pady=0, padx=10, sticky=W)
rown += 1 # 占位符
ttk.Frame(root, height=20).grid(row=rown)
rown += 1 # 文集序号输入框
iptArt = Text(root, height=1, width=50)
iptArt.grid(row=rown, padx=10, pady=0, sticky=W)
rown += 1 # vol输入框说明
label3 = ttk.Label(root, text='文章列表,请用英文逗号分隔,如0,1,2')
label3.grid(row=rown, pady=0, padx=10, sticky=W)
rown += 1 # 占位符
ttk.Frame(root, height=30).grid(row=rown)
# 运行按钮
rown += 1
bt = ttk.Button(root, text='开始下载', width=30, command=run)
bt.grid(row=rown, padx=10, ipady=10, pady=0, sticky='WE')
# 信息标签
rown += 1
info = ttk.Label(root, text='?/?')
info.grid(row=rown, padx=10, ipady=10, ipadx=10, sticky=W)
info.after(500, refreshInfo) # 自动循环更新
def writeHeaders(): # 将header写入到临时文件
hdrs = iptHeader.get("1.0", END)
with open(os.getcwd()+'/config.txt', 'a') as f:
f.write(hdrs)
def readHeaders(): # 读取设置文件并填充到界面
fpath = os.getcwd()+'/config.txt'
if os.path.exists(fpath):
f = open(fpath, 'r')
hdrs = f.read()
iptHeader.insert(INSERT, hdrs)
readHeaders()
root.mainloop()
modules/reqs.py
import time
from threading import Thread
import requests
import json
import os
import re
import math
atotal = 1 # 总文章数量
afini = 0 # 已读取的文章数量
cookiestr = '' # cookie全局变量
curVol = '...' # 正在获取的文集名
curArt = '...' # 正在获取的文章名
curImg = '...' # 正在获取的图片名
state = '等待中' # 当前状态,等待中,获取中,已完成。
def genInfoStr(): # 拼接信息字符串
global atotal
global afini
infoStr = '正在获取('+str(afini)+'/'+str(atotal)+'):'
per = atotal/15
fi = math.ceil(afini/per)
for _ in range(fi):
infoStr += '■'
for _ in range(15-fi):
infoStr += '□'
return infoStr
# volnarr文集列表,artnarr文章列表,例如volnarr=[0,3,1]表示只获取三个文集,默认为全部
def getAll(opt, volnarr, artnarr): # 启动线程
t = Thread(target=getVolums, args=(opt, volnarr, artnarr)) # 多线程,避免锁死界面
t.start()
global state
state = '获取中'
print(state)
return 'RUNNING!'
def getArticle(art, vol, opt): # 获取文章内容
artId = art['id']
urlArt = 'https://www.jianshu.com/author/notes/'+str(artId)+'/content'
res = requests.get(urlArt, headers=opt.headers)
resdata = json.loads(res.text)
cont = resdata['content']
# 文件路径
dir = os.getcwd()+'/data/' + vol['name'] + '/'
fname = dir+art['title']
if not os.path.exists(dir):
os.makedirs(dir)
os.makedirs(dir+'/imgs/')
# 获取图片地址
imglist = re.findall(r"[^`]\!\[[^\]]*\]\((.+?)\)", cont, re.S)
for iu in imglist:
imgUrl = iu.split('?')[0]
global curImg
curImg = os.path.basename(imgUrl)
print('GETTING IMAGE:', curImg)
if not os.path.exists(imgUrl):
res = requests.get(imgUrl)
img = res.content
with open(dir+'imgs/'+os.path.basename(imgUrl), 'wb') as f:
f.write(img)
time.sleep(1)
# 保存md文件
if art['note_type'] == 2:
fname += '.md'
else:
fname += '.html'
if os.path.exists(fname):
os.remove(fname)
with open(fname, 'a') as f:
newcont = cont.replace(
'https://upload-images.jianshu.io/upload_images', 'imgs')
f.write(newcont)
f.close()
return 'getArticles OK!'
def getArticlesList(vol, opt, artnarr): # 获取文章列表
volId = vol['id']
urlVol = 'https://www.jianshu.com/author/notebooks/'+str(volId)+'/notes'
res = requests.get(urlVol, headers=opt.headers)
resdata = json.loads(res.text)
getlist = [] # 指定获取列表
if len(artnarr) == 0:
getlist = resdata
else:
for i in artnarr:
if i < len(resdata):
getlist.append(resdata[i])
# 每个文集计算文章数量
global atotal
global afini
atotal = len(getlist)
afini = 0
print("ATOTAL:", atotal, afini)
n = 0
for d in getlist:
global curArt
curArt = str(n)+':' + d['title']
n += 1
print('GETTING ARTICLE:', curArt)
time.sleep(1)
getArticle(d, vol, opt)
afini += 1 # 计数加1
return 'getArticlesList OK!'
def getVolums(opt, volnarr, artnarr): # 获取文集列表
res = requests.get(opt.urlVolumns, headers=opt.headers)
resdata = json.loads(res.text)
getlist = [] # 指定获取列表
if len(volnarr) == 0:
getlist = resdata
else:
for i in volnarr:
if i < len(resdata):
getlist.append(resdata[i])
n = 0 # 序号
for d in getlist:
global curVol
curVol = str(n)+':'+d['name']
n += 1
print('GETTING VOLUMN:', curVol)
time.sleep(1)
getArticlesList(d, opt, artnarr)
print("DOWNLOAD FINI!", atotal, afini)
global state
state = '已完成'
print(state)
return 'getVolums OK!'
modules/options.py
savePath='../data'
urlVolumns = 'https://www.jianshu.com/author/notebooks'
params = {'order_by': 'shared_at', 'page': '1'}
headers = '''
请复制粘贴您浏览器的headers信息
'''
def str2obj(s, s1=';', s2='='):
li = s.split(s1)
res = {}
for kv in li:
li2 = kv.split(s2)
if len(li2) > 1:
res[li2[0]] = li2[1]
return res
headers = str2obj(headers, '\n', ': ')
欢迎关注我的专栏( つ•̀ω•́)つ【人工智能通识】
每个人的智能新时代
如果您发现文章错误,请不吝留言指正;
如果您觉得有用,请点喜欢;
如果您觉得很有用,欢迎转载~
END