** python把數(shù)據(jù)轉(zhuǎn)為圖片 / python推送圖片到釘釘群 **
需求:通過python訪問mysql數(shù)據(jù)庫,統(tǒng)計業(yè)務(wù)相關(guān)數(shù)據(jù)。把統(tǒng)計的結(jié)果數(shù)據(jù)生成表格形式的圖片并發(fā)送到釘釘群里。
一:CentOS安裝wkhtmltoimage 、wkhtmltopdf
wkhtmltopdf 和 wkhtmltoimage 是一個開源的命令行工具,可以將 HTML 轉(zhuǎn)換為 pdf 文檔和圖片。
1.1:安裝字體等依賴
yum install -y xorg-x11-fonts-75dpi
yum install -y xorg-x11-fonts-Type1
yum install -y fontconfig
yum install -y libX11
yum install -y libXext
yum install -y libXrender
1.2:下載安裝文件
官網(wǎng)下載地址:https:///downloads.html
查看自己CentOS版本信息:
[root@centos src]# cat /etc/redhat-release
CentOS Linux release 7.7.1908 (Core)
選擇自己對應(yīng)的系統(tǒng)下載文件即可:
wget https://github.com/wkhtmltopdf/packaging/releases/download/0.12.6-1/wkhtmltox-0.12.6-1.centos7.x86_64.rpm
下載完成的文件:wkhtmltox-0.12.6-1.centos7.x86_64.rpm
1.2:安裝
[root@centos src]# rpm -Uvh wkhtmltox-0.12.6-1.centos7.x86_64.rpm
error: Failed dependencies:
fontconfig is needed by wkhtmltox-1:0.12.6-1.centos7.x86_64
libX11 is needed by wkhtmltox-1:0.12.6-1.centos7.x86_64
libXext is needed by wkhtmltox-1:0.12.6-1.centos7.x86_64
libXrender is needed by wkhtmltox-1:0.12.6-1.centos7.x86_64
xorg-x11-fonts-75dpi is needed by wkhtmltox-1:0.12.6-1.centos7.x86_64
xorg-x11-fonts-Type1 is needed by wkhtmltox-1:0.12.6-1.centos7.x86_64
# 安裝對應(yīng)的依賴包后重新安裝
重新安裝
[root@centos src]# rpm -Uvh wkhtmltox-0.12.6-1.centos7.x86_64.rpm
Preparing... ################################# [100%]
Updating / installing...
1:wkhtmltox-1:0.12.6-1.centos7 ################################# [100%]
[root@centos src]# whereis wkhtmltopdf
wkhtmltopdf: /usr/local/bin/wkhtmltopdf
[root@centos src]# whereis wkhtmltoimage
wkhtmltoimage: /usr/local/bin/wkhtmltoimage
[root@centos src]# /usr/local/bin/wkhtmltoimage --version
wkhtmltoimage 0.12.6 (with patched qt)
[root@centos src]# /usr/local/bin/wkhtmltopdf --version
wkhtmltopdf 0.12.6 (with patched qt)
1.3:問題匯總
1:轉(zhuǎn)換過程中文不顯示
查看 Linux 是否有中文字體,無任何輸出說明沒有中文字體。
fc-list :lang=zh
安裝中文字體 simsun.ttc
字體下載 http://www./fontlist/fontlist_1_1.html
將下載后的字體放到文件夾 /usr/share/fonts 下
二:使用python統(tǒng)計mysql數(shù)據(jù)并返回結(jié)果
關(guān)鍵py代碼:
class DatabaseMonitor(object):
"""數(shù)據(jù)庫操作對象"""
def __init__(self, server, port, username, password, databaseName):
self.server = server
self.port = port
self.username = username
self.password = password
self.databaseName = databaseName
def mysql_server_connect(self):
try:
self.connect = pymysql.connect(host=self.server, port=self.port, user=self.username, password=self.password,database=self.databaseName, connect_timeout=5) # 服務(wù)器名,賬戶,密碼,數(shù)據(jù)庫名
cur = self.connect.cursor()
if cur:
print(f"mysql 連接成功! ")
else:
print(f"mysql連接失敗2!")
return cur
except Exception as e:
print(f"mysql連接失敗3! 報錯信息:{e}")
raise (NameError, "數(shù)據(jù)庫連接異常")
def mysql_server_monitor(self):
try:
cur = self.mysql_server_connect()
# 具體的查詢統(tǒng)計SQL
sql1 = (f""" SELECT * FROM table_name WHERE xxx ; """)
### 執(zhí)行sql獲取返回結(jié)果集
cur.execute(sql1)
result1Query = cur.fetchall()
return result1Query
# # 關(guān)閉游標(biāo)
cur.close()
# 關(guān)閉連接
self.connect.commit()
self.connect.close()
except Exception as e:
print(f"mysql執(zhí)行存儲或語句失敗!,\n報錯信息:{e}")
三:使用python把獲得的數(shù)據(jù)生成圖片
安裝依賴
pip3 install html-table
pip3 install imgkit
關(guān)鍵py代碼:
## imgTitle標(biāo)題內(nèi)容
## jpgPath生成圖片保存的路徑+文件名稱,比如/usr/local/aaa.jpg
## headTuple數(shù)據(jù)行標(biāo)題 以元組形式傳入, 比如(('列名1','列名2','列名3',...),)
## dataTuple數(shù)據(jù)行 以元組形式傳入,比如(('第一行列1數(shù)據(jù)','第一行列2數(shù)據(jù)','第一行列3數(shù)據(jù)',...),('第二行列1數(shù)據(jù)','第二行列2數(shù)據(jù)','第二行列3數(shù)據(jù)',...),...)
def createImage(imgTitle,jpgPath,headTuple,dataTuple):
""" 根據(jù)查詢的結(jié)果,生成圖片"""
# 標(biāo)題
table = HTMLTable(caption=imgTitle)
# 表頭行
table.append_header_rows(headTuple)
# 數(shù)據(jù)行
table.append_data_rows(dataTuple)
# 標(biāo)題樣式
table.caption.set_style({
'font-size': '24px',
})
# 表格樣式,即<table>標(biāo)簽樣式
table.set_style({
'border-collapse': 'collapse',
'word-break': 'keep-all',
'white-space': 'nowrap',
'font-size': '14px',
})
# 統(tǒng)一設(shè)置所有單元格樣式,<td>或<th>
table.set_cell_style({
'width': "250px",
'border-color': '#000',
'border-width': '1px',
'border-style': 'solid',
'padding': '5px',
})
# 表頭樣式
table.set_header_row_style({
'color': '#fff',
'background-color': '#48a6fb',
'font-size': '18px',
})
# 覆蓋表頭單元格字體樣式
table.set_header_cell_style({
'padding': '15px',
})
# # 調(diào)小次表頭字體大小
# table[1].set_cell_style({
# 'padding': '8px',
# 'font-size': '15px',
# })
# # 遍歷數(shù)據(jù)行,如果增長量為負,標(biāo)紅背景顏色
# for row in table.iter_data_rows():
# if row[0].value == '所有渠道':
# row.set_style({
# 'background-color': '#ffdddd',
# })
body = table.to_html()
# html的charset='UTF-8'必須加上,否則中文會亂碼
html = "<!DOCTYPE html><html><head><meta charset='UTF-8'></head><body>{0}</body></html>".format(body)
# 生成圖片
imgkit.from_string(html,jpgPath)
四:把生成本地圖片上傳到阿里云OSS并獲取圖片url
4.1:在OSS上創(chuàng)建保存圖片的目錄
4.2:創(chuàng)建阿里云RAM賬號
阿里云主賬號AccessKey擁有所有API的訪問權(quán)限,風(fēng)險很高。強烈建議您創(chuàng)建并使用RAM賬號進行API訪問或日常運維。
請登錄 https://ram.console.aliyun.com 創(chuàng)建RAM賬號。
創(chuàng)建成功后把對應(yīng)的AccessKey ID和AccessKey Secret記錄下來,后面要用到。
然后在用戶列表的【添加權(quán)限】中添加剛剛創(chuàng)建賬號對oss的訪問操作權(quán)限。
4.3:用python把本地圖片上傳到阿里云并獲取訪問鏈接
官方幫助文檔:https://help.aliyun.com/document_detail/32026.html
yum install python-devel
pip3 install oss2
如果在安裝oss2的時候出現(xiàn)如下錯誤:ModuleNotFoundError: No module named 'setuptools_rust'
需要升級一下setuptools : pip3 install -U pip setuptools
驗證是否安裝成功:出現(xiàn)ImportError: No module named _crcfunext說明crcmod沒有安裝成功。
具體原因參考官方描述:https://help.aliyun.com/document_detail/85288.html
如果crcmod未安裝成功,執(zhí)行以下操作。
# 先卸載
pip3 uninstall crcmod
# 重新安裝crcmod
pip3 install crcmod
# 我重裝了,依舊報錯ModuleNotFoundError: No module named 'crcmod._crcfunext'
# 先不管了,安裝失敗還是照樣能用的,按官方說法就是上傳性能不是很好。這個以后有空再看。
測試操作:把百度首頁內(nèi)容轉(zhuǎn)換成pdf或jpg圖片。
wkhtmltoimage https://www.baidu.com baidu.jpg
wkhtmltopdf https://www.baidu.com/ baidu.pdf
生成圖片的py代碼如下:
#!/usr/bin/python3
# -*- coding: utf-8 -*-
# @CreateDate : 2021/12/14 13:59
# @Author : dbartist
# @Email : dbartist@163.com
# @ScriptFile : picDemo.py
# @Project : PyScripts
# @Describe :
import imgkit
from HTMLTable import HTMLTable
## 下面的標(biāo)題,表頭行和數(shù)據(jù)行可以根據(jù)自己的需求自行調(diào)整。
## 最后的標(biāo)紅背景顏色可以根據(jù)需求調(diào)整下,其它可以不用動。
# 標(biāo)題
table = HTMLTable(caption='果園收成表')
# 表頭行
table.append_header_rows((
('名稱', '產(chǎn)量 (噸)','增長量 (噸)','增長率 (%)'),
))
# 數(shù)據(jù)行
table.append_data_rows((
('荔枝', 11, 1, 10),
('芒果', 9, -1, -10),
('香蕉', 6, 1, 20),
))
# 標(biāo)題樣式
table.caption.set_style({
'font-size': '15px',
})
# 表格樣式,即<table>標(biāo)簽樣式
table.set_style({
'border-collapse': 'collapse',
'word-break': 'keep-all',
'white-space': 'nowrap',
'font-size': '14px',
})
# 統(tǒng)一設(shè)置所有單元格樣式,<td>或<th>
table.set_cell_style({
'width': "250px",
'border-color': '#000',
'border-width': '1px',
'border-style': 'solid',
'padding': '5px',
})
# 表頭樣式
table.set_header_row_style({
'color': '#fff',
'background-color': '#48a6fb',
'font-size': '18px',
})
# 覆蓋表頭單元格字體樣式
table.set_header_cell_style({
'padding': '15px',
})
# 調(diào)小次表頭字體大小
table[1].set_cell_style({
'padding': '8px',
'font-size': '15px',
})
# 遍歷數(shù)據(jù)行,如果增長量為負,標(biāo)紅背景顏色
for row in table.iter_data_rows():
if row[2].value < 0:
row.set_style({
'background-color': '#ffdddd',
})
body = table.to_html()
# html的charset='UTF-8'必須加上,否則中文會亂碼
html = "<!DOCTYPE html><html><head><meta charset='UTF-8'></head><body>{0}</body></html>".format(body)
# 生成圖片
imgkit.from_string(html, 'out.jpg')
上傳阿里云oss的關(guān)鍵代碼:
# 連接oss
def oss_parser(oss_dirpath,local_dirpath):
endpoint = 'http://oss-cn-hangzhou.' # 在哪個城市就選那個城市的oss-cn
access_key_id = 'LTA****vBS'
access_key_secret = 'yPR****muI'
bucket_name = '******'
# 指定Bucket實例,所有文件相關(guān)的方法都需要通過Bucket實例來調(diào)用。
bucket = oss2.Bucket(oss2.Auth(access_key_id, access_key_secret), endpoint, bucket_name)
# result = bucket.put_object(f'{dirpath}/{imageName}', img.getvalue())
result = bucket.put_object_from_file(f'{oss_dirpath}', f'{local_dirpath}')
print('jpg upload oss success!')
return result.status
def test():
## 生成圖片 ######################################################
imgTitle = f'【統(tǒng)計內(nèi)容】XXX . . . 統(tǒng)計時間:{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
jpgName = f'jrdx{datetime.now().strftime("%Y%m%d%H%M%S")}.jpg'
jpgPath = f'/herajob/scripts_jpg/{jpgName}'
createImage(imgTitle, resultDic, jpgPath)
## 把生成的圖片上傳到阿里云oss
oss_dirpath = f'smddz/{jpgName}'
local_dirpath =f'/herajob/scripts_jpg/{jpgName}'
oss_parser(oss_dirpath,local_dirpath)
if __name__ == '__main__':
test()
4.4:異常問題
手動執(zhí)行py腳本生成jpg文件是正常的,但是我放在調(diào)度系統(tǒng)(我用的是hera)上執(zhí)行py腳本就會報錯,報錯如下:
which: no wkhtmltoimage in (/sbin:/bin:/usr/sbin:/usr/bin) No wkhtmltoimage executable found: "command not found"
which: no wkhtmltoimage in (/sbin:/bin:/usr/sbin:/usr/bin)
Traceback (most recent call last):
File "hera_smddz_rihuo_daily_image.py", line 430, in <module>
test()
File "hera_smddz_rihuo_daily_image.py", line 415, in test
createImage(imgTitle, resultDic, jpgPath)
File "hera_smddz_rihuo_daily_image.py", line 291, in createImage
imgkit.from_string(html,jpgPath)
File "/usr/local/lib/python3.6/site-packages/imgkit/api.py", line 106, in from_string
cover_first=cover_first,
File "/usr/local/lib/python3.6/site-packages/imgkit/imgkit.py", line 36, in __init__
self.wkhtmltoimage = self.config.get_wkhtmltoimage()
File "/usr/local/lib/python3.6/site-packages/imgkit/config.py", line 57, in get_wkhtmltoimage
raise OSError(wkhtmltoimage_error)
OSError:
No wkhtmltoimage executable found: "command not found"
If this file exists please check that this process can read it.
Otherwise please install wkhtmltopdf - http://
報錯原因:明明已經(jīng)成功安裝了,還是提示找不到wkhtmltoimage。是因為他從/sbin:/bin:/usr/sbin:/usr/bin目錄去which查找。
但是安裝wkhtmltoimage默認(rèn)是在/usr/local/bin目錄。所以找不到wkhtmltoimage。
解決方法:可以/usr/bin目錄添加軟鏈接指向到/usr/local/bin目錄的wkhtmltoimage。
[hdfs@centos ]$ which wkhtmltoimage
/usr/local/bin/wkhtmltoimage
sudo ln -s /usr/local/bin/wkhtmltopdf /usr/bin/wkhtmltopdf
sudo ln -s /usr/local/bin/wkhtmltoimage /usr/bin/wkhtmltoimage
參考文章:https://blog.csdn.net/weixin_44818729/article/details/110181938
五:發(fā)送圖片到釘釘群
采用markdown格式通過釘釘機器人推送到釘釘群。
關(guān)鍵py代碼:
def send_dingding_image(msg):
msgbody = {"msgtype": "markdown", "markdown": {"title":"推送標(biāo)題", "text": msg}}
try:
requests.post(
"https://oapi.dingtalk.com/robot/send?access_token=xxxxxx",
json=msgbody)
print(f"send ok")
except Exception as e:
print(f'報錯信息:{e}')
參考官方API文檔:https://open.dingtalk.com/document/group/custom-robot-access
六:完整python腳本代碼
# !/usr/bin/python3
# -*- coding: utf-8 -*-
# @CreateDate : 2021/12/14 11:04
# @Author : dbartist
# @Email : dbartist@163.com
# @ScriptFile : pydata2image.py
# @Project : PyScripts
# @Describe : 每日定時統(tǒng)計XXX數(shù)據(jù)轉(zhuǎn)成圖片并推送到釘釘群
import warnings
warnings.simplefilter('ignore', DeprecationWarning)
import sys
import importlib
importlib.reload(sys)
# sys.setdefaultencoding('utf8')
import pymysql
import smtplib
from email.mime.text import MIMEText
from datetime import datetime, date, timedelta
import time
import base64
import requests
from decimal import *
import imgkit
from HTMLTable import HTMLTable
import oss2
import uuid
import io
# mysql 連接信息
mysql_dbhost = "192.168.0.101"
mysql_dbport = 3306
mysql_dbname = "testdb"
mysql_dbuser = "db_user"
mysql_dbpasswd = "db_passwd"
class DatabaseOpt(object):
"""數(shù)據(jù)庫操作對象"""
def __init__(self, server, port, username, password, databaseName):
self.server = server
self.port = port
self.username = username
self.password = password
self.databaseName = databaseName
def mysql_server_connect(self):
try:
self.connect = pymysql.connect(host=self.server, port=self.port, user=self.username, password=self.password,database=self.databaseName, connect_timeout=5) # 服務(wù)器名,賬戶,密碼,數(shù)據(jù)庫名
cur = self.connect.cursor()
if cur:
print(f"mysql連接成功! ")
else:
print(f"mysql連接失敗2!")
return cur
except Exception as e:
print(f"mysql連接失敗3! 報錯信息:{e}")
raise (NameError, "數(shù)據(jù)庫連接失敗")
def mysql_server_monitor(self):
try:
cur = self.mysql_server_connect()
# 具體查詢統(tǒng)計的SQL
sql1 = (f""" SELECT * from table_name where xxxx ;""")
# 執(zhí)行SQL并返回查詢結(jié)果集
cur.execute(sql1)
result1Query = cur.fetchall()
return result1Query
# # 關(guān)閉游標(biāo)
cur.close()
# 關(guān)閉連接
self.connect.commit()
self.connect.close()
except Exception as e:
print(f"mysql執(zhí)行存儲或語句失敗!,\n報錯信息:{e}")
def send_dingding_image(msg):
"""給釘釘群發(fā)送消息"""
msgbody = {"msgtype": "markdown", "markdown": {"title":"DBA推送標(biāo)題", "text": msg}}
try:
# 釘釘群機器人webhook地址,換成自己的。
requests.post(
"https://oapi.dingtalk.com/robot/send?*****",
json=msgbody)
print(f"send ok")
except Exception as e:
print(f'報錯信息:{e}')
def monitorLogAdd(str):
"""添加日志信息"""
str = time.strftime('%Y-%m-%d %H:%M:%S', time.localtime(time.time())) + ':' + str
with open('/tmp/pyscript.log', 'a+') as f:
f.write(str)
f.write('\n')
def createImage(imgTitle,jpgPath,headTuple,dataTuple):
""" 根據(jù)查詢的結(jié)果,生成圖片"""
# 標(biāo)題
table = HTMLTable(caption=imgTitle)
# 表頭行
table.append_header_rows(headTuple)
# 數(shù)據(jù)行
table.append_data_rows(dataTuple)
# 標(biāo)題樣式
table.caption.set_style({
'font-size': '24px',
})
# 表格樣式,即<table>標(biāo)簽樣式
table.set_style({
'border-collapse': 'collapse',
'word-break': 'keep-all',
'white-space': 'nowrap',
'font-size': '14px',
})
# 統(tǒng)一設(shè)置所有單元格樣式,<td>或<th>
table.set_cell_style({
'width': "250px",
'border-color': '#000',
'border-width': '1px',
'border-style': 'solid',
'padding': '5px',
'text-align':'center',
})
# 表頭樣式
table.set_header_row_style({
'color': '#fff',
'background-color': '#48a6fb',
'font-size': '18px',
})
# 覆蓋表頭單元格字體樣式
table.set_header_cell_style({
'padding': '15px',
})
# # 調(diào)小次表頭字體大小
# table[1].set_cell_style({
# 'padding': '8px',
# 'font-size': '15px',
# })
# # 遍歷數(shù)據(jù)行,如果增長量為負,標(biāo)紅背景顏色
# for row in table.iter_data_rows():
# if row[0].value == '所有渠道':
# row.set_style({
# 'background-color': '#ffdddd',
# })
body = table.to_html()
# html的charset='UTF-8'必須加上,否則中文會亂碼
html = "<!DOCTYPE html><html><head><meta charset='UTF-8'></head><body>{0}</body></html>".format(body)
# 生成圖片
imgkit.from_string(html,jpgPath)
# 連接阿里云oss
def oss_parser(oss_dirpath,local_dirpath):
endpoint = 'http://oss-cn-hangzhou.' # 一般是在哪個城市就選那個城市的oss-cn,換成自己的
access_key_id = 'LTAI5***HoxvBS' # 換成自己的
access_key_secret = 'yPRMJ***YzmuI' # 換成自己的
bucket_name = '***' # 換成自己的
# 指定Bucket實例,所有文件相關(guān)的方法都需要通過Bucket實例來調(diào)用。
bucket = oss2.Bucket(oss2.Auth(access_key_id, access_key_secret), endpoint, bucket_name)
result = bucket.put_object_from_file(f'{oss_dirpath}', f'{local_dirpath}')
print('jpg upload oss success!')
return result.status
def test():
# 連接mysql,獲取統(tǒng)計結(jié)果數(shù)據(jù)
test = DatabaseOpt(mysql_dbhost, mysql_dbport, mysql_dbuser, mysql_dbpasswd, mysql_dbname)
returnQueryResult = test.mysql_server_monitor()
# 定義表格標(biāo)題元祖和數(shù)據(jù)元組
headTuple = (('統(tǒng)計內(nèi)容', '0H', '1H', '2H', '3H', '4H', '5H', '6H', '7H', '8H', '9H', '10H', '11H', '12H',
'13H', '14H', '15H', '16H', '17H', '18H', '19H', '20H', '21H', '22H', '23H', '合計'),)
dataTuple = returnQueryResult
## 生成圖片 圖片格式: today20211215235959.jpg
imgTitle = f'【今日標(biāo)題】今日XXX各小時段新增次留統(tǒng)計 . . . 統(tǒng)計時間:{datetime.now().strftime("%Y-%m-%d %H:%M:%S")}'
jpgName = f'today{datetime.now().strftime("%Y%m%d%H%M%S")}.jpg'
jpgPath = f'/herajob/scripts_jpg/{jpgName}'
createImage(imgTitle, jpgPath, headTuple, dataTuple)
## 把生成的圖片上傳到阿里云oss
oss_dirpath = f'smddz/{jpgName}'
local_dirpath =f'/herajob/scripts_jpg/{jpgName}'
oss_parser(oss_dirpath,local_dirpath)
## 把oss的圖片發(fā)送到釘釘群
image_url = f'https://******.oss-cn-hangzhou./smddz/{jpgName}'
msg = f'### 今日標(biāo)題 \n >**今日XXX各小時段新增次留統(tǒng)計!** \n >![image]({image_url}) \n ###### 統(tǒng)計時間 : {datetime.now().strftime("%Y-%m-%d %H:%M:%S")} \n'
send_dingding_image(msg)
# print(f'告警信息: \n{msg}')
if __name__ == '__main__':
test()
推送到釘釘群消息如下:
點擊圖片詳情如下:
至此大功告成!??!后續(xù)還有再優(yōu)化的空間,比如生成趨勢圖/柱狀圖/餅圖/曲線圖等形式的圖片推送到釘釘群。
|