偷个懒,公号抠腚早报80%自动化——1.批量生成微信封面图

4,448 阅读12分钟

简述

2018年的三月份写过一篇:《小猪的Python学习之旅 —— 18.Python微信转发小宇宙早报》,从一开始 手动转发别人发的新闻早报,到编写脚本到自动转发。然后毕竟这个是别人整理的,并不能保证准时,很多 时候早报变成了午报,然后还有存档问题,每次想看之前的早报就,都要微信搜聊天记录。啧啧,我琢磨着, 要不自己来整理早报。于是偷偷开始手动去整理早报,然后发在公号「抠腚男孩」上:

毕竟自己整理的,不确定看官能不能接受,得找几个小火汁来验证下下~

然后,把我整理的日报,套模板,改下日期,丢到童鞋群里看看他们有木有发现,测试两天后~

行吧,我就发吧,接下来简单说下每天发早报的流程。

  • 1.制作当天早报的封面图
  • 2.浏览新华社,i黑马,第一财经周刊等新闻站点,采集有趣的新闻,复制下标题
  • 3.把复制的标题粘贴到文本编辑器中,凑够15条新闻
  • 4.登录微信公众平台,打开昨天的文章,复制样式,粘贴,然后把今天的内容填进去

接着检查下,没什么问题,就发布了。一开始,每天要耗费将近一个小时的时间来完成这件 事情,每天的摸鱼时间这么宝贵,花一个小时来做这件事情,显得有些得不偿失。作为一个 **勤(lan)奋(duo)**的开发仔,肯定要想办法来优化下,最好的结果就是:发早报完全自动化~

当然,也是想想而已,有读者可能好奇,为啥标题是80%,那么剩下的20%是什么?

答:15%是新闻的过滤,筛选有意思的新闻标题,这一步其实可以优化,最简单的就是,直接爬热搜 或者评论多的新闻标题,但是这样莫得灵魂,我更倾向于训练一个机器人,让他自动去筛选标题。 但是目前还不会这些东西,所以先搁一搁咯,还得手动去筛选~ 剩下的5%是把早报发表到公号上,其实也可以自动化,通过selenium写个脚本自动点点点 就好了,不过个人感觉意义不大,而且我习惯发出去之前还需要预览下,确认内容 无误后才发送,毕竟文章发布后只能改标题,不能修改内容。

说下我目前想达到的一个形态吧

  • 1.编写脚本批量生成微信的封面图。
  • 2.编写爬虫定时去爬取新闻,保存到本地数据库中。
  • 3.编写接口,包括获取当天采集到的新闻,加入到新闻筛选池等。
  • 4.编写用来筛选新闻的APP,利用上班坐地铁的时间快速筛选当天的新闻标题。
  • 5.编写一键生成统一样式的早报文章的脚本。
  • 6.编写一键生成当天新闻详情页面的脚本。
  • 7.复制粘贴生成的样式文章,填写标题,发布者,在阅读原文中添加当天新闻详情页面的url,完成发布。
  • 8.编写微信机器人,定时(暂定10点),拉取早报进行文本处理后,自动转发到相关的群。

好吧,大概的路线就这样,本节先从制作早报封面图开始**优(偷)化(懒)**吧。


1.封面图的制作过程

先来看看我每天的早报封面图吧,是介样的:

组成部分:背景图(900*383)+ 大标题(52px) + 二级标题(44px) 接着缩下我是制作这种封面图的流程:

  • 1.平时闲着没事逛下一些壁纸的APP或者站点,觉得好看的就保存下来。
  • 2.打开Pixelmator Pro新建一个900*383的模板,把图片拖进去,调节图片大小直到图片的宽度和模板的宽度相等。
  • 3.接着移动调整缩放后的图片,直到自己喜欢位置。
  • 4.依次添加大小标题,调整居中。
  • 5.合并图层,裁剪。
  • 6.导出成jpg文件。

为了让你们感受这个流程,我大概录了个Gif演示下,实际操作耗时远比这个久(7,8分钟的样子)。

每天如机器搬重复着这样的操作,多呆哦~然后,我竟然坚持了60+天(┬_┬); 着实需要一个脚本,把我从这种繁冗的工作中解脱出来。

读者可能对图源感兴趣,我一般喜欢直接保存壁纸APP里精选的靓图, 当然也可以自行爬取一些壁纸站点。另外,我发现,有些长图,其实 可以裁剪成几份来作为多期的封面,比如这样的图:

分割成两个,挺好看的。

感觉像像集卡一样,有点意思。


2.提取图片处理的流程

先来提取下图片处理的流程:

  • 图片缩放:保持长宽比例不变进行缩放,直到宽为900px为止。
  • 图片裁剪:先计算图片可以裁剪成多少份,以图片中间为基准裁剪,计算Y轴偏移,每个图片的坐标。
  • 图片加字:对裁剪后的图片依次添加大小标题。

3.材料准备

行吧,处理流程说了,说下用到的Python库,直接通过pip命令安装即可: (主要使用opencv来进行图片处理,pillow即PIL库)

pip install numpy
pip install opencv-python
pip install pillow

4.图片缩放

保持长宽比,设置为900px,我们通过opencv提供的imread()方法来获取一个图片对象,然后进行 相关操作。先获取一波高和宽度

import cv2

img = cv2.imread('1.jpg')
(h, w) = img.shape[:2]
print(h, w)

# 输出结果:956 1080

如果你想把图片显示出来,可以直接调用imshow()方法:

cv2.imshow('image', img)   # 参数依次为:窗口名称(窗口不能重名),读入的图片。

上述的代码,运行后会发现窗口一闪而过,可以调用waitKey()让窗口不关闭

cv2.waitkey()   # 想窗口一直不关闭,可以不填参数或填0;也可以指定一个等待时间(单位毫秒)
                # 在一个时间段内,等待用户按键触发关闭,如果一直不按键,到了时间会自动关闭。

但是,这里其实隐藏着一个小坑:如果你的图片是中文文件名或文件路径包含中文,调用imread会报错,比如:

img = cv2.imread('测试.jpg')
cv2.imshow('img', img)
cv2.waitKey()

运行结果

同样调用imwrite()方法也是无法生成带有中文路径的图片的,可以自行编写两个函数来解决:

def cv_imread(file_path):
    cv_img = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)
    return cv_img
    
def cv_imwrite(img, file_path):
    cv2.imencode('.jpg', img)[1].tofile(file_path)

行吧,能获取到宽高了,接着调用opencv提供的resize()方法调整图片的尺寸,参数依次为: 图片宽高元组,还有一个可选参数:interpolation插值方法,默认使用INTER_LINEAR 双线性插值,其他的还有:INTER_NEARESTINTER_AREAINTER_CUBICINTER_LANCZOS4

import cv2
import numpy as np


def cv_imread(file_path):
    cv_img = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)
    return cv_img


def cv_imwrite(img, file_path):
    cv2.imencode('.jpg', img)[1].tofile(file_path)


img = cv_imread('测试.jpg')
(h, w) = img.shape[:2]
print("缩放前的尺寸:", img.shape[:2])
res = cv2.resize(img, (900, round(h * (900 / w))))
print("裁剪后的尺寸:", res.shape[:2])

运行结果

缩放前的尺寸: (956, 1080)
缩放后的尺寸: (797, 900)

5.图片裁剪

缩放完,接着就到裁剪了,先是计算图片能裁剪成几张:

crop_pic_count = int(ch / 383)
print("图片可以裁剪为:%d张" % crop_pic_count)

# 输出结果:图片可以裁剪为:2张

接着是裁剪图片,可以通过:图片对象[y轴起始坐标:y轴终点坐标, x轴起始坐标:x轴终点坐标],来裁剪。 所以,我们要计算每个裁剪区域的对应的坐标方位。另外,这里还要考虑一个偏移,以中间位置为基准进行 裁剪,这样感觉会好一点。给个加偏移和不加偏移裁剪后的对比图吧:

so,我还是倾向于加偏移,计算偏移也很简单,直接拿高对383进行求余,然后除以2。

start_y = int(ch % 383 / 2)

接着根据能切成的图片张数,计算怎么裁剪

for i in range(0, crop_pic_count):
    crop_img = res[383 * i + start_y: 383 * (i + 1) + start_y, 0:900]
    cv_imwrite(crop_img, '剪切图%d.jpg' % (i+1))

裁剪后的图片

6.图片加字

行吧,图片也裁剪好了,接着就是图片加字了,可以通过opencv提供的putText()添加文字, 参数依次为: 图像文字内容坐标字体大小颜色字体厚度

img = cv_imread('剪切图0.jpg')
cv2.putText(img, 'Test', (50, 300), cv2.FONT_HERSHEY_SIMPLEX, 1.2, (255, 255, 255), 2)
cv2.imshow('image', img)
cv2.waitKey()

运行结果如下

加字成功,挺简单的,是吧?但是,如果你添加的文字不是字母或数字,而是中文的话,那么恭喜,黑人问号~

原因是:opencv自带的putText函数无法输出utf8类型的字符,因此无法将中文打印到图片上。 两个解决方法:

  • 方法一:利用另一个freetype库,将字符解码转码,不过有点繁琐。
  • 方法二:利用pillow库里ImageDraw类的text函数绘制中文,先从成cv2转PIL格式,加完中文再转回cv2格式输出。

这里采用的是方法二,text函数的参数:起始坐标元组,文字内容,字体,颜色。 问题来了,怎么确定绘制文字的起始坐标?

答:如果你要程序算,挺麻烦的,文字宽度怎么获取,既然图片尺寸固定,文字长度不变,为何不取巧一下呢?

直接在Pixelmator Pro上把文字拖好,然后复制下坐标,不就好了~

另外,这里笔者用的字体是 苹果-简,常规体,可以自行下载,记得把字体文件名改成英文文件名, 不然,会读取不到字体。好的,撸代码试试:

img = cv_imread('剪切图0.jpg')
# 将图片从OpenCv格式转为PIL格式
img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
# 加载字体(字体文件名,字体大小)
title_font = ImageFont.truetype('apple-simple.ttf', 52)
date_font = ImageFont.truetype('apple-simple.ttf', 44)
# 绘制文字的位置
title_pos = (236, 110)
date_pos = (338, 192)
# 绘制内容
title_content = u"『抠腚早报速读』"
date_content = u"第190111期"
# 绘制
draw = ImageDraw.Draw(img_pil)
draw.text(title_pos, title_content, font=title_font, fill=(255, 255, 255))
draw.text(date_pos, date_content, font=date_font, fill=(255, 255, 255))
img_open_cv = cv2.cvtColor(np.asarray(img_pil), cv2.COLOR_RGB2BGR)
cv_imwrite(img_open_cv, "加字后.jpg")

接着看下输出的图片

啧啧啧,完美,到此自动裁剪生成一个早报封面的脚本就完成啦,接下来我们来补全和完善下我们的程序。

7.代码补全完善

就是加了循环,一些小逻辑,比较简单,注释也比较清晰,就不叨逼叨了,直接上完整代吧:

# -*- coding: utf-8 -*-
import os
import cv2
import numpy as np
import time
from PIL import Image, ImageDraw, ImageFont
from datetime import datetime, timedelta
import shutil

pic_source_dir = os.path.join(os.getcwd(), "news_pic_source\\")  # 原图路径
pic_crop_dir = os.path.join(os.getcwd(), "news_pic_crop\\")  # 裁剪后的图片路径
pic_font_dir = os.path.join(os.getcwd(), "news_pic_font\\")  # 加字后的图片路径
start_date = "20190112"  # 绘制图片的其起始日期


# 判断文件夹是否存在,不存在则新建
def is_dir_existed(path, mkdir=True):
    if mkdir:
        if not os.path.exists(path):
            os.makedirs(path)
    else:
        return os.path.exists(path)


# opencv读取中文路径名会乱码
def cv_imread(file_path):
    cv_img = cv2.imdecode(np.fromfile(file_path, dtype=np.uint8), -1)
    return cv_img


# opencv写入中文路径名会乱码
def cv_imwrite(img, file_path):
    cv2.imencode('.jpg', img)[1].tofile(file_path)


# 把原图裁剪为多个小图(900*383)
def crop_little_pic(pic_path):
    img = cv_imread(pic_path)
    (sh, sw) = img.shape[:2]
    # 将图片的宽设置为900,高则按比例缩放
    res = cv2.resize(img, (900, round(sh * (900 / sw))))
    # 获取缩放后的高和宽,判断图片可裁剪的张数
    (ch, cw) = res.shape[:2]
    crop_pic_count = int(ch / 383)
    # 计算Y轴偏移
    start_y = int(ch % 383 / 2)
    # 根据图片的张数来决定怎么裁剪
    for i in range(0, crop_pic_count):
        crop_img = res[383 * i + start_y: 383 * (i + 1) + start_y, 0:900]
        cv_imwrite(crop_img, os.path.join(pic_crop_dir, str(int(round(time.time() * 1000))) + '.jpg'))


# 绘制文字
def draw_text(pic_path, date):
    img = cv_imread(pic_path)
    # 将图片从OpenCv格式转为PIL格式
    img_pil = Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB))
    # 加载字体(字体文件名,字体大小)
    title_font = ImageFont.truetype('apple-simple.ttf', 52)
    date_font = ImageFont.truetype('apple-simple.ttf', 44)
    # 绘制文字的位置
    title_pos = (236, 110)
    date_pos = (316, 192)
    # 绘制内容
    title_content = u"『抠腚早报速读』"
    date_content = u"第%s期" % date[2:]
    # 绘制
    draw = ImageDraw.Draw(img_pil)
    draw.text(title_pos, title_content, font=title_font, fill=(255, 255, 255))
    draw.text(date_pos, date_content, font=date_font, fill=(255, 255, 255))
    img_open_cv = cv2.cvtColor(np.asarray(img_pil), cv2.COLOR_RGB2BGR)
    cv_imwrite(img_open_cv, os.path.join(pic_font_dir, date[2:] + '.jpg'))


# 遍历获得某类文件路径列表
def fetch_file_path(path, file_type):
    file_list = []
    f = os.listdir(path)
    for i in f:
        if i.endswith(file_type):
            file_list.append(os.path.join(path, i))
    return file_list


# 构造生成日期列表
def init_date_list(begin_date, count):
    d_list = []
    begin_date = datetime.strptime(begin_date, "%Y%m%d")
    end_date = datetime.strptime((datetime.now() + timedelta(days=count)).strftime("%Y%m%d"), "%Y%m%d")
    while begin_date <= end_date:
        date_str = begin_date.strftime("%Y%m%d")
        d_list.append(date_str)
        begin_date += timedelta(days=1)
    return d_list


if __name__ == '__main__':
    is_dir_existed(pic_source_dir)
    while True:
        choice = input(
            "%s\n请输入你想进行的操作\n1.进行图片裁剪\n2.图片加字\n3.清空裁剪文件夹\n4.清空加字文件夹\n5.退出程序\n%s\n" % ('=' * 32, '=' * 32))
        if choice == '1':
            is_dir_existed(pic_crop_dir)
            pic_path_list = fetch_file_path(pic_source_dir, ".jpg")
            if len(pic_path_list) == 0:
                print("原图文件夹中无图片,请先添加图片!")
            else:
                print("开始批量裁剪...")
                begin = datetime.now()
                for pic in pic_path_list:
                    crop_little_pic(pic)
                end = datetime.now()
                print("批量裁剪完毕,生成图片:%d张,耗时:%s秒" % (len(fetch_file_path(pic_crop_dir, ".jpg")), (end - begin).seconds))
        elif choice == '2':
            is_dir_existed(pic_font_dir)
            crop_path_list = fetch_file_path(pic_crop_dir, ".jpg")
            date_list = init_date_list(start_date, len(crop_path_list) + 1)
            if len(crop_path_list) == 0:
                print("裁剪文件夹中无图片,请先生成裁剪图片!")
            else:
                print("开始批量加字...")
                begin = datetime.now()
                print(len(crop_path_list), len(date_list))
                for i in range(len(crop_path_list)):
                    draw_text(crop_path_list[i], date_list[i])
                end = datetime.now()
                print("批量加字完毕,处理图片:%d张,耗时:%s秒" % (len(fetch_file_path(pic_font_dir, ".jpg")), (end - begin).seconds))
        elif choice == '3':
            if is_dir_existed(pic_crop_dir, False):
                shutil.rmtree(pic_crop_dir)
                print("文件夹删除成功!")
            else:
                print("文件夹不存在,删除失败~")
        elif choice == '4':
            if is_dir_existed(pic_font_dir, False):
                shutil.rmtree(pic_font_dir)
                print("文件夹删除成功!")
            else:
                print("文件夹不存在,删除失败~")
        elif choice == '5':
            exit("退出程序~")
        else:
            print("错误序号,请确认后重新输入!!!")

执行前,先准备一波图片原图,这里准备了:

接着运行一波代码,运行后依次键入1,2进行裁剪和加字:

啧啧,94张原图生成了243张封面图,而且,只花了十几秒,打开生成的文件夹看一波:

都生成到9月份了,2333,真人生苦短,我用Python,此处应该有掌声~

另外前几天写了个脚本是采集一堆视频第一帧然后进行处理的,赶脚有同学会需要,把核心代码也贴下把~

# 截取视频的第一帧
def fetch_video_first_frame(path_list):
    for mp4 in path_list:
        cap = cv2.VideoCapture(mp4)
        if cap.isOpened():
            ret, im = cap.read()
            cv2.imencode('.jpg', im)[1].tofile(
                os.path.join(pic_source_output_dir, mp4.split("\\")[-1]).replace("mp4", "jpg"))
        cap.release()

行吧,本节内容就这么多,有疑问的欢迎在评论区留言~


Tips:公号目前只是坚持发早报,在慢慢完善,有点心虚,只敢贴个小图,想看早报的可以关注下~