欢迎关注我的专栏( つ•̀ω•́)つ【人工智能通识】
【专题】简书下载器:Python-Tkinter项目编程入门
继续前面的文章。
创建文件夹
存储之前先检查文件夹是否存在,如果不存在的话要先创建,否则会导致目录找不到的错误。
以下代码仅供示意,稍后将用于获取和保存图片文件:
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)
这里需要先导入os模块import os
,然后就可以利用os.path.exists(imgUrl)
来检查文件或文件夹是否存在了。这里是如果图片文件存在那么就不进行下载,因为简书的图片名称都是唯一的,不会重复,重新上传图片后文件名也会更改,所以也只要下载一次。
再看下面的示意代码,它可以创建文件夹。
dir = os.getcwd()+'/data/' + vol['name'] + '/'
fname = dir+art['title']
if not os.path.exists(dir):
os.makedirs(dir)
os.makedirs(dir+'/imgs/')
这里的os.getcwd()是获取当前运行项目的目录地址(绝对路径),我们可以利用它拼合得到要存储的文件夹路径/data/xxxx.png。
下面的os.makedirs
用于创建路径文件夹,可以创建多层文件夹,比如/project/dev/data/
三层。这里我们分别创建了存储数据的data文件夹以及下面的/data/imgs/文件夹。当然只要os.makedirs(dir+'/imgs/')
一句就可以完成,os.makedirs(dir)
这句是多余的,这里保留它只是为了说明情况。
写入文件
Python中写入文件可以参考下面的示意代码:
with open(dir+'imgs/'+os.path.basename(imgUrl), 'wb') as f:
f.write(img)
f.close()
使用with这种语法实际上并不需要说明关闭数据流,也就是说f.close()
其实是多余的。
另外需要注意的是关于写入文本信息时候的编码问题,如果遇到乱码可以参考这个文章:
0110编程-Python的中文编码
用正则表达式提取图片地址
我们知道简书markdown语法中的图片表述是![image.png](https://upload-images.jianshu.io/upload_images/xxxx.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
,这里的xxxx是独特唯一的字符。
由上可知,简书中的图片地址是感叹号开头,然后一对方括号,然后小括号里面就是我们需要的东西了。
一篇文章中可能有很多图片,我们如何把它们提取出来?
正则表达式re模块为我们提供了findall方法,可以根据规则把图片地址都提取出来。看一下这个写法(这里的cont即md文档文字内容):
imglist = re.findall(r"\!\[[^\]]*\]\((.+?)\)", cont, re.S)
因为感叹号在正则表达式中有特别意义,所以我们要使用它的时候就需要用斜杠转义\!
,同样的还有方括号和小括号,所以[图片上传失败...(image-ae7599-1559177451782)]
。
中间的(.+?)
表示我们要提取的部分,点代表任意字符,+表示至少一个字符(空字符就不要了),?表示有则提取没有则忽略。
注意中间[^\]]?
这个写法,这里没有被斜杠转义的方括号表示可选,就是说方括号里面的字符都可以出现,比如[a-z]
表示26个小写字母,[a-z]+
表示至少一个小写字母;^符号表示否定,这里[^\]]
就是后方括号]
以外的的字符都可选,言外之意就是截取到后面一个方括号就可以了,即对于[imga.png](...)
那么方括号中只能到.png]
就结束,不能一直向后无限延伸了。
整个imglist = re.findall("\!\[[^\]]?\]\((.+?)\)", cont, re.S)
这句就得到了一个列表[],包含很多https://upload-images....png?image...1240
这样的图片地址列表。
然后我们可以用?将地址分成两段,取前面一段即得到图片的最终地址了。
imgUrl = iu.split('?')[0]
图片地址替换
我们把markdown中的图片下载下来放在/data/imgs/文件夹,然后也要把相应的图片链接修改,指向新的/data/imgs/文件夹。
我们可以在最后写入.md文档的时候执行替换:
newcont = cont.replace('https://upload-images.jianshu.io/upload_images', 'imgs')
传递文件目录
我们需要把文章内容都存储在data/文集名/文章名.md
中,对应的图片存储在data/文集名/imgs/xxxx.png
。
所以我们只要在获取文章内容的函数getArticle(artId)
中进行读写就可以,但是只有文章ID(artId)是不能知道文集名的,所以我们需要从最初获取文集列表函数getVolums中,调用获取文章列表getArticlesList的时候就要传递文集名称过去,然后再传递到getArticle。
与其只传递文集名称,还不如直接把整个文集对象都传递过去,然后用vol['name']得到文集名。同理可以用art['title']得到文章名称。
示意代码:
def getVolums():
getArticlesList(d)
def getArticlesList(vol):
getArticle(d, vol)
def getArticle(art, vol):
dir = os.getcwd()+'/data/' + vol['name'] + '/'
fname = dir+art['title']
我们可以使用os.path.basename(imgUrl)
方法获得文章名称,如xxxx.png
。
另外一个需要注意的是,简书有两种主要的文章格式,art['note_type'] == 2
的是md文件,另外还有html格式的,所以我们要加以判断,示意代码如下:
if art['note_type'] == 2:
fname += '.md'
else:
fname += '.html'
if os.path.exists(fname):
os.remove(fname)
这里对重复文件进行了删除。
reqs.py的正式代码
上面解释了很多内容,但都不是最终代码。
以下是正式代码:
import time
from threading import Thread
import requests
import json
import os
import re
import options as opt
atotal = 32 # 总文章数量
afini = 0 # 已读取的文章数量
cookiestr = '' # cookie全局变量
def genInfoStr(): # 拼接信息字符串
global atotal
global afini
infoStr = '正在获取('+str(afini)+'/'+str(atotal)+'):'
per = int(atotal/15)
fi = int(afini/per)
for _ in range(fi):
infoStr += '■'
for _ in range(15-fi):
infoStr += '□'
return infoStr
def getAll(cookieStr): # 获取全部
t = Thread(target=getVolums) # 多线程,避免锁死界面
t.start()
return 'RUNNING!'
def getArticle(art, vol): # 获取文章内容
print('GETTING ARTICLE:',art['title'])
artId = art['id']
global afini
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]
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()
afini += 1
return 'getArticles OK!'
def getArticlesList(vol): # 获取文章列表
print('GETTING VOL:',vol['name'])
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)
for d in resdata:
time.sleep(1)
getArticle(d, vol)
return 'getArticlesList OK!'
def getVolums(): # 获取文集列表
print('GETTING VOLS:',opt.urlVolumns)
res = requests.get(opt.urlVolumns, headers=opt.headers)
resdata = json.loads(res.text)
for d in resdata[:1]:
time.sleep(1)
getArticlesList(d)
print("DOWNLOAD FINI!",atotal,afini)
return 'getVolums OK!'
getVolums()
这里仅拉取了第一个文集的全部文章和图片,运行后得到的文件结果如下:
其中imgs文件夹下包含了很多图片。
检查效果
在VSCode'中,我们可以下载Markdown preview插件,安装后,打开我们下载的.md文件,右击即可进行预览。
应该可以看到文章中的图片即使断网情况也可以正常显示。
到这里,对于会编程的朋友,其实已经可以用来下载自己的简书了,但是我们还没有把它和Tkinter界面连接起来,后面的文章我们会进行这方面的改进。
欢迎关注我的专栏( つ•̀ω•́)つ【人工智能通识】
每个人的智能新时代
如果您发现文章错误,请不吝留言指正;
如果您觉得有用,请点喜欢;
如果您觉得很有用,欢迎转载~
END