1. 程式人生 > >偷個懶,公號摳腚早報80%自動化——1.批量生成微信封面圖

偷個懶,公號摳腚早報80%自動化——1.批量生成微信封面圖

簡述

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 = "20190110"  # 繪製圖片的其起始日期


# 判斷資料夾是否存在,不存在則新建
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))
            if len(crop_path_list) == 0:
                print("裁剪資料夾中無圖片,請先生成裁剪圖片!")
            else:
                print("開始批量加字...")
                begin = datetime.now()
                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:公號目前只是堅持發早報,在慢慢完善,有點心虛,只敢貼個小圖,想看早報的可以關注下~