#python 之 微信订阅号群发接口发图文和视频
####说明:
######微信订阅号群发没有上传视频接口。想要实现群发视频就需要用腾讯视频连接(微信支持腾讯视频),群发接口支持图文(也就是图片和文字)和HTML 。封面图需要长传返回media_id、插图需要上传图片获取url放入html中,再构建图文,最后群发。
视频:视频也需要构建标签插入HTML中就可显示。
ps: 视频必须使用腾讯视频,微信只支持腾讯视频。腾讯视频上传由于难度比较大没有开发。
视频标签结构:
<iframe class="video_iframe" data-vidtype="2" allowfullscreen="" frameborder="0" style="position:relative;z-index:1;" data-ratio="1.7647058823529411" data-w="480" data-src="video_url"></iframe><br>
视频连接也许需要做处理:如下
处理后:
https://v.qq.com/iframe/preview.html?vid=l0778mjb4lp&width=500&height=375&auto=0
如不处理将无法显示。
######微信官方接口连接:https://mp.weixin.qq.com/wiki?t=resource/res_main&id=mp1481187827_i0l21
####一下是代码实现:
释:代码并非相当完善,如有不足请指点,只希望能帮到。error"] = dicts.get(“errcode”)
# -*- coding: utf-8 -*-
"""
-------------------------------------------------
File Name : update_wechat_post.py
date : 18-12-26
Author : Hebel
-------------------------------------------------
Description:
note:
-------------------------------------------------
"""
from urllib.request import urlretrieve
from html.parser import HTMLParser
import requests
import json
import time
import random
import re
import hashlib
import logging
import os
class UploadNewsWechat(object):
"""微信公众号发图文插入视频"""
def __init__(self, content, cookies=None, proxy_info=None, type="image"):
self.account_info = content.get("password")
self.appId = self.account_info.split("|")[0]
self.secret = self.account_info.split("|")[1]
self.contentAll = content.get("wechat_content")
self.imageUrlName = []
self.thumbUrlName = []
self.errorlist = []
self.tokenUrl = "https://api.weixin.qq.com/cgi-bin/token?grant_type=client_credential&appid={}&secret={}"
self.startUpload = "https://api.weixin.qq.com/cgi-bin/media/upload?access_token={token}&type=%s"%type
self.ImageUrl = "https://api.weixin.qq.com/cgi-bin/media/uploadimg?access_token={token}"
self.newsIdUrl = "https://api.weixin.qq.com/cgi-bin/media/uploadnews?access_token={token}"
self.sendUrl = "https://api.weixin.qq.com/cgi-bin/message/mass/sendall?access_token={token}"
self.logging = logging.basicConfig(filename="logging.log", level=logging.DEBUG,format='[%(asctime)s]%(name)s:%(levelname)s:%(message)s')
self.filePath = ""
self.uid = None
self.test_id = 0
self.jpg_image_file_name = "jpg_Image.jpg"
self.gif_image_file_name = "gif_Image.jpg"
def __del__(self):
"""删除下载图片文件"""
try:
os.remove(self.jpg_image_file_name)
os.remove(self.gif_image_file_name)
except:
pass
def getImageId(self, token, image_url, define_type="id"):
"""上传封面图片获得media_id"""
try:
file_name = self.gif_image_file_name if "gif" in image_url else self.jpg_image_file_name
urlretrieve(url=image_url, filename=file_name)
except:
if "url" in define_type:
return None
raise IOError("下载图片失败,请检查图片link")
try:
if "id" in define_type:
url = self.startUpload.format(token=token)
files = {'data': open(file_name, "rb")}
resp = requests.post(url=url, files=files)
if not resp.status_code is 200:
raise IOError("上传图片失败")
dicts_data = resp.json()
media_id = dicts_data.get("media_id", None)
if not media_id:
raise IOError("上传图片失败")
return media_id
else:
url = self.ImageUrl.format(token=token)
files = {'data': open(file_name, "rb")}
resp = requests.post(url=url, files=files)
if not resp.status_code is 200:
return None
dicts_data = resp.json()
img_url = dicts_data.get("url", None)
return img_url
except Exception as err:
if "url" in define_type:
return None
raise err
def html_decode(self, html):
"""替换掉那些没有成功转码的html标签"""
htmlCodes = (("'", '''), ('"', '"'), ('>', '>'), ('<', '<'), ('&', '&'))
for code in htmlCodes:
html = html.replace(code[1], code[0])
return html
def buildContent(self, token, html, define_type="image"):
""" 对图片和视频链接进行处理"""
#处理文章内的图片
if "image" in define_type:
pattern = '<img src="(.*?)"[^>]*/>'
re_image = re.compile(pattern)
img_list = re_image.findall(html)
if not img_list:
return html
new_html = re_image.sub("%s", html)
img_url_all = []
for url in img_list:
img_url = self.getImageId(token, url, define_type="url")
img_html = '<img src="%s" title="" alt=""/>'%img_url
img_url_all.append(img_html)
return new_html%tuple(img_url_all)
# 处理文章内的视频
else:
pattern = '<img name="video\|https://v\.qq.com/x/page/(.*?)\..*?"[^>]*/>'
re_image = re.compile(pattern)
new_html = re_image.sub("%s", html)
video_url_list = re_image.findall(html)
if not video_url_list:
return html
video_iframe_all = []
for video_id in video_url_list:
video_url = "https://v.qq.com/iframe/preview.html?vid={video_id}&width=500&height=375&auto=0".format(video_id=video_id)
video_html = '<iframe class="video_iframe" data-vidtype="2" allowfullscreen="" frameborder="0" style="position:relative;z-index:1;" data-ratio="1.7647058823529411" data-w="480" data-src="{video_url}"></iframe><br>'.format(video_url=video_url)
video_iframe_all.append(video_html)
return new_html%tuple(video_iframe_all)
def getToken(self, appId, secret):
"""使用id和秘钥请求得到token,用于数据上传"""
get_token_Url = self.tokenUrl.format(appId, secret)
resp = requests.get(url=get_token_Url, timeout=5)
if resp.status_code is 200:
dict_data = resp.json()
token = dict_data.get("access_token", None)
if not token is None:
return token
else:
errmsg = dict_data.get("errmsg")
if "ip" in errmsg:
ip_list = re.findall("\d+\.\d+\.\d+\.\d+", errmsg)
if ip_list:
ip = ip_list[0]
result = "此ip:{ip}不在当前微信公众号白名单内, 如需群发请手动加入".format(ip=ip)
return Exception(result)
return Exception(errmsg)
return Exception(dict_data)
return Exception("获取token请求失败,status_code:%s"%resp.status_code)
def buildNews(self, token, content_list):
"""重构html ,所有url替换为有效的url"""
if not isinstance(content_list, list):
raise ValueError("数据类型错误")
data_list = []
for dicts_data in content_list:
articles = {} #重新创建数据字典
cover_image_url = dicts_data.get("thumb_media_id", None)
if cover_image_url is None:
raise ValueError("封面图片是不可缺的")
#上传封面图片获取封面图片di
image_id = self.getImageId(token=token, image_url=cover_image_url)
articles["thumb_media_id"] = image_id
articles["author"] = dicts_data.get("author","")
title = dicts_data.get("title", None)
if title is None:
raise ValueError("标题不能为空")
articles['title'] = title
content = dicts_data.get("content",None)
if content is None:
raise ValueError("未获取到内容数据html")
#解析文章内容,html转码,替换图片,替换视频链接
html_parser = HTMLParser()
html = html_parser.unescape(content)
html = self.html_decode(html)
#处理图片数据
new_html = self.buildContent(token, html)
new_html = self.buildContent(token, new_html, define_type="video")
# 组建单个文章数据
articles['content'] = new_html
articles['content_source_url'] = dicts_data.get("content_source_url","http://")
articles['digest'] = dicts_data.get("digest", "")
articles['show_cover_pic'] = dicts_data.get("show_cover_pic", "0")
data_list.append(articles)
return {"articles":data_list}
def uploadNews(self, token, content):
"""上传图文内容获得新的media_id """
content = json.dumps(content, ensure_ascii=False).encode()
url = self.newsIdUrl.format(token=token)
headers = {'content-type': 'charset=utf-8'}
resp = requests.post(url=url, data=content, headers=headers, timeout=5)
if not resp.status_code is 200:
raise IOError("上传图文失败,状态码:%s"%resp.status_code)
dicts_data = resp.json()
media_id = dicts_data.get("media_id",None)
if media_id is None:
raise ValueError("上传图文,获取media_id失败,%s"%dicts_data)
return media_id
def buildSendData(self, media_id):
"""构建群发数据"""
sendData = {}
filter = {}
mpnews = {}
filter["is_to_all"] = True
mpnews["media_id"] = media_id
sendData["filter"] = filter
sendData["mpnews"] = mpnews
sendData["msgtype"] = "mpnews"
sendData["send_ignore_reprint"] = 0
return sendData
def sendNews(self, token, sendData):
"""群发请求"""
sendData = json.dumps(sendData)
url = self.sendUrl.format(token=token)
try:
resp = requests.post(url=url, data=sendData, timeout=5)
except Exception as err:
raise IOError("请求群发失败, %s"%err)
dicts_data = resp.json()
errcode = dicts_data.get("errcode")
if errcode is 0:
return {"state": 0, "status": "群发成功:%s" % dicts_data.get("errmsg"),"url": ""}
else:
return {"state": 1, "status": "%s" % dicts_data.get("errmsg"), "url": ""}
def run(self):
try:
if self.appId and self.secret:
#获取token
token = self.getToken(appId=self.appId, secret=self.secret)
if isinstance(token, Exception):
raise token
if isinstance(self.contentAll, list):
raise TypeError("wechat_content数据类型错误,必须是列表")
#更新更换图文中的图片link和视频link
news_data = self.buildNews(token=token, content_list=self.contentAll)
# 上传图文内容得到media_id
media_id = self.uploadNews(token=token,content=news_data)
#构建群发数据
data_dicts = self.buildSendData(media_id=media_id)
#开始群发
self.sendNews(token=token, sendData=data_dicts)
else:
raise ValueError("password数据缺失")
except Exception as err:
return {"state": 1, "status": "%s"%str(err), "url": ""}
# if __name__ == '__main__':
# wechat = UploadNewsWechat(content=data)
# result = wechat.run()
# print(result)