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

欢迎关注我的专栏( つ•̀ω•́)つ【人工智能通识】
【专题】简书下载器: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

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,590评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 86,808评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,151评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,779评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,773评论 5 367
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,656评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,022评论 3 398
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,678评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,038评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,659评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,756评论 1 330
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,411评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,005评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,973评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,203评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,053评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,495评论 2 343

推荐阅读更多精彩内容