美文网首页人工智能通识程序员程序园
【编程】批量下载简书文章和图片

【编程】批量下载简书文章和图片

作者: 张老师Klog | 来源:发表于2019-05-30 08:51 被阅读6次

欢迎关注我的专栏( つ•̀ω•́)つ【人工智能通识】
【专题】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("\!\[[^\]]?\]\((.+?)\)", 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("\!\[[^\]]?\]\((.+?)\)", 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

相关文章

网友评论

    本文标题:【编程】批量下载简书文章和图片

    本文链接:https://www.haomeiwen.com/subject/niiptctx.html