偷個懶,公號摳腚早報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_NEAREST,INTER_AREA,INTER_CUBIC,INTER_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:公號目前只是堅持發早報,在慢慢完善,有點心虛,只敢貼個小圖,想看早報的可以關注下~