折腾:
【未解决】自己写Python脚本同步印象笔记到WordPress
期间,先去研究,怎么用Python把印象笔记中的内容,发布到自己的WordPress网站crifan.com中。
先去研究:
【已解决】用Python发布帖子到WordPress用什么库和调用哪些接口
不过在此期间,还是先去处理:
【已解决】用Python处理印象笔记帖子:缩放图片并保存回印象笔记
和期间的:
【已解决】Python中更新印象笔记中帖子中附件图片的数据
和:
【已解决】Python更新印象笔记note帖子的内容
现在整个HTML都处理好了,然后继续:
【已解决】用Python通过WordPress的REST的API发布文章post
然后还剩下一些其他代码逻辑优化
包括:tags,category等内容
从获取印象笔记获取相关信息,生成对应的标签列表tags和分类
不过在此之前,先去优化现在代码逻辑:
把之前的函数,改为Class类的实现
把Evernote和WordPress分别独立到各自的类中
期间遇到:
【已解决】Python中class的staticmethod中bool参数本身值是False但传入却是True
目前先优化为:
libs/crifan/Evernote.py
# Update: 20200317 # Author: Crifan Li import logging import sys sys.path.append("lib") sys.path.append("libs/evernote-sdk-python3/lib") from libs.crifan import utils # import evernote.edam.userstore.constants as UserStoreConstants # import evernote.edam.noteStore as NoteStore # import evernote.edam.type.ttypes as Types # from evernote.api.client import EvernoteClient # from evernote.edam import * from evernote import * from evernote.api import * from evernote.api.client import * from evernote.edam.limits import * from evernote.edam.type import * import evernote.edam.type.ttypes as Types from evernote.edam.type.ttypes import * from evernote.edam.notestore import * from evernote.edam.notestore.ttypes import * from evernote.edam.notestore.NoteStore import * from evernote.edam.userstore import * from evernote.edam.userstore.constants import * class Evernote(object): """ Operate Evernote Yinxiang note via python 首页 http://sandbox.yinxiang.com 登录 https://sandbox.yinxiang.com/Login.action?offer=www_menu Web版 主页 https://sandbox.yinxiang.com/Home.action?_sourcePage=q4oOUtrE7iDiMUD9T65RG_YvRLZ-1eYO3fqfqRu0fynRL_1nukNa4gH1t86pc1SP&__fp=RRdnsZFJxJY3yWPvuidLz-TPR6I9Jhx8&hpts=1576292029828&showSwitchService=true&usernameImmutable=false&login=&login=登录&login=true&username=green-waste%40163.com&hptsh=10h%2BVHVzIGiSBhmRcxjMg47ZqdQ%3D#n=5b863474-107d-43e0-8087-b566329b24ab&s=s1&ses=4&sh=2&sds=5& 获取token https://sandbox.yinxiang.com/api/DeveloperToken.action -> NoteStore URL: https://sandbox.yinxiang.com/shard/s1/notestore """ def __init__(self, authToken, isSandbox=False, isChina=True): self.authToken = authToken self.isSandbox = isSandbox self.isChina = isChina # logging.debug("self.isSandbox=%s, self.isChina=%s", self.isSandbox, self.isChina) self.host = Evernote.getHost(self.isSandbox, self.isChina) self.client = self.initClient() logging.info("self.client=%s", self.client) self.userStore = self.client.get_user_store() logging.info("self.userStore=%s", self.userStore) self.isSdkLatest = self.userStore.checkVersion( "Evernote EDAMTest (Python)", EDAM_VERSION_MAJOR, # UserStoreConstants.EDAM_VERSION_MAJOR, EDAM_VERSION_MINOR, # UserStoreConstants.EDAM_VERSION_MINOR ) if self.isSdkLatest: logging.info("Evernote API version is latest") else: logging.warning("Evernote API version is NOT latest -> need update") self.noteStore = self.client.get_note_store() logging.info("self.noteStore=%s", self.noteStore) @staticmethod def getHost(isSandbox=False, isChina=True): # logging.debug("isSandbox=%s, isChina=%s", isSandbox, isChina) evernoteHost = "" if isChina: if isSandbox: evernoteHost = "sandbox.yinxiang.com" else: evernoteHost = "app.yinxiang.com" else: if isSandbox: evernoteHost = "sandbox.evernote.com" else: evernoteHost = "app.evernote.com" return evernoteHost @staticmethod def isImageResource(curResource): """check is image media or not Args: curMedia (Resource): Evernote Resouce instance Returns: bool Raises: """ isImage = False curResMime = curResource.mime # 'image/png' 'image/jpeg' # libs/evernote-sdk-python3/lib/evernote/edam/limits/constants.py matchImage = re.match("^image/", curResMime) logging.debug("matchImage=%s", matchImage) if matchImage: """ image/gif image/jpeg image/png """ isImage = True logging.info("curResMime=%s -> isImage=%s", curResMime, isImage) return isImage def initClient(self): client = EvernoteClient( token=self.authToken, # sandbox=self.isSandbox, # china=self.isChina, service_host=self.host ) return client def listNotebooks(self): notebookList = self.noteStore.listNotebooks() return notebookList def findNotes(self, notebookId): """Process each note""" logging.info("notebookId=%s", notebookId) # find all notes in notebook searchOffset = 0 searchPageSize = 1000 searchFilter = NoteStore.NoteFilter() searchFilter.order = NoteSortOrder.UPDATED searchFilter.ascending = False searchFilter.notebookGuid = notebookId logging.info("searchFilter=%s", searchFilter) resultSpec = NotesMetadataResultSpec() resultSpec.includeTitle = True resultSpec.includeContentLength = True resultSpec.includeCreated = True resultSpec.includeUpdated = True resultSpec.includeDeleted = True resultSpec.includeNotebookGuid = True resultSpec.includeTagGuids = True resultSpec.includeAttributes = True resultSpec.includeLargestResourceMime = True resultSpec.includeLargestResourceSize = True logging.info("resultSpec=%s", resultSpec) # foundNoteResult = self.noteStore.findNotesMetadata( # authenticationToken=self.authToken, # filter=searchFilter, # offset=searchOffset, # maxNotes=pageSize, # resultSpec=resultSpec # ) foundNoteResult = self.noteStore.findNotesMetadata( self.authToken, searchFilter, searchOffset, searchPageSize, resultSpec) logging.info("foundNoteResult=%s", foundNoteResult) totalNotes = foundNoteResult.totalNotes logging.info("totalNotes=%s", totalNotes) foundNoteList = foundNoteResult.notes return foundNoteList def getNoteDetail(self, noteId): """get note detail Args: noteId (str): Evernote note id Returns: Note with detail Raises: Examples: '9bf6cecf-d91e-4391-a034-199c744424db' """ # noteDetail = self.noteStore.getNote(self.authToken, noteId, True, True, True, True) # noteDetail = self.noteStore.getNote( # authenticationToken=self.authToken, # guid=noteId, # withContent=True, # withResourcesData=True, # # withResourcesRecognition=True, # withResourcesRecognition=False, # # withResourcesAlternateData=True, # withResourcesAlternateData=False, # ) noteDetail = self.noteStore.getNote(self.authToken, noteId, True, True, False, False) return noteDetail def resizeNoteImage(self, noteDetail, isSync=False): """Resize note each media image and update note content, and sync to evernote if necessary Args: noteDetail (Note): evernote note isSync (bool): whether to sync to evernote Returns: updated note Raises: """ newResList = [] originResList = noteDetail.resources for curIdx, eachResource in enumerate(originResList): if Evernote.isImageResource(eachResource): imgFilename = eachResource.attributes.fileName logging.info("[%d] imgFilename=%s", curIdx, imgFilename) resBytes = eachResource.data.body resizedImgBytes, imgFormat = utils.resizeSingleImage(resBytes) reizedImgLen = len(resizedImgBytes) # 77935 resizedImgMd5Bytes = utils.calcMd5(resizedImgBytes, isRespBytes=True) # '3110e1e7994dc119ff92439c5758e465' newMime = utils.ImageFormatToMime[imgFormat] # 'image/jpeg' newData = Types.Data() # newData = ttypes.Data() newData.size = reizedImgLen newData.bodyHash = resizedImgMd5Bytes newData.body = resizedImgBytes newRes = Types.Resource() # newRes = ttypes.Resource() newRes.mime = newMime newRes.data = newData newRes.attributes = eachResource.attributes newResList.append(newRes) else: """ audio/wav audio/mpeg audio/amr application/pdf ... """ newResList.append(eachResource) noteDetail = updateNoteResouces(noteDetail, newResList) return noteDetail
后续继续优化。
此处继续,目的优先是把贴子内容,真实的发布到crifan.com上为准。
然后再逐步优化细节和添加功能。
先去调试看看
发现Python环境需要准备好:
pip install bs4 pip install oauth2
不过发现此处有
Pipfile
所以去尝试恢复环境
pipenv install
不过后来发现,python 3.7和3.8等都混淆了
所以去删除本地pipenv的虚拟环境
重建虚拟环境
最后才可以开始正常调试
然后遇到:
【已解决】Evernote同步出错:Exception has occurred AttributeError Store object has no attribute syncNote
此处测试期间,发现图片是被从 png 压缩更新为 jpeg了
然后update同步更新后,再去看帖子内容
果然是 图片已经更新为 jpg了
说明updateNote
成功更新内容了
继续调试,尽快让帖子内容可以同步上传到WordPress中。
然后去优化和更新图片上传逻辑:
【已解决】用Python把Evernote的note中图片上传到Wordpress中并更新Note笔记
【总结】
此处把可以把Evernote中的note笔记,发布到wordpress网站crifan.com的相关代码贴出来:
EvernoteToWordpress.py
def uploadNoteToWordpress(curNote): """Upload note content new html to wordpress Args: curNote (Note): evernote Note Returns: (bool, dict) Raises: """ # created=1582809109000, updated=1584190363000, # dateTimestampInt = curNote.updated dateTimestampInt = curNote.created # 1582809109000 dateTimestampFloat = float(dateTimestampInt) / 1000.0 # 1582809109.0 datetimeObj = utils.timestampToDatetime(dateTimestampFloat) outputFormat = "%Y-%m-%dT%H:%M:%S" dateStr = datetimeObj.strftime(format=outputFormat) # '2020-02-27T21:11:49' isUploadOk, respInfo = gWordpress.createPost( title=curNote.title, content=curNote.content, dateStr=dateStr, slug=curNote.attributes.sourceURL, ) logging.info("isUploadOk=%s, respInfo=%s", isUploadOk, respInfo) return isUploadOk, respInfo
libs/crifan/crifanWordpress.py
def createPost(self, title, content, dateStr, slug, status="draft", postFormat="standard" ): """Create wordpress standard post by call REST api: POST /wp-json/wp/v2/posts Args: title (str): post title content (str): post content of html dateStr (str): date string slug (str): post slug url status (str): status, default to 'draft' postFormat (str): post format, default to 'standard' Returns: (bool, dict) True, uploaded post info False, error detail Raises: """ curHeaders = { "Authorization": self.authorization, "Content-Type": "application/json", "Accept": "application/json", } logging.info("curHeaders=%s", curHeaders) postDict = { "title": title, "content": content, # "date_gmt": dateStr, "date": dateStr, "slug": slug, "status": status, "format": postFormat, # TODO: featured_media, categories, tags, excerpt } logging.debug("postDict=%s", postDict) # postDict={'title': '【已解决】Mac中给pip更换源以加速下载', 'content': '<div>\n 折腾:\n </div>。。。。。。。。。</div>\n', 'date': '20200227T211149', 'slug': 'mac_pip_change_source_server_to_spped_up_download', 'status': 'draft', 'format': 'standard'} createPostUrl = self.apiPosts resp = requests.post( createPostUrl, proxies=self.requestsProxies, headers=curHeaders, # data=json.dumps(postDict), json=postDict, # internal auto do json.dumps ) logging.info("resp=%s", resp) isUploadOk, respInfo = crifanWordpress.processCreateResponse(resp) return isUploadOk, respInfo @staticmethod def processCreateResponse(resp): """Process common wordpress POST response for /wp-json/wp/v2/media /wp-json/wp/v2/posts Args: resp (Response): requests response Returns: (bool, dict) True, created item info False, error detail Raises: """ isCreateOk, respInfo = False, {} if resp.ok: respJson = resp.json() logging.info("respJson=%s", respJson) """ for /wp-json/wp/v2/media { "id": 70401, "date": "2020-03-13T22:34:29", "date_gmt": "2020-03-13T14:34:29", "guid": { "rendered": "https://www.crifan.com/files/pic/uploads/2020/03/f6956c30ef0b475fa2b99c2f49622e35.png", "raw": "https://www.crifan.com/files/pic/uploads/2020/03/f6956c30ef0b475fa2b99c2f49622e35.png" }, "modified": "2020-03-13T22:34:29", ... "slug": "f6956c30ef0b475fa2b99c2f49622e35", "status": "inherit", "type": "attachment", "link": "https://www.crifan.com/f6956c30ef0b475fa2b99c2f49622e35/", "title": { "raw": "f6956c30ef0b475fa2b99c2f49622e35", "rendered": "f6956c30ef0b475fa2b99c2f49622e35" }, for /wp-json/wp/v2/posts { "id": 70410, "date": "2020-02-27T21:11:49", "date_gmt": "2020-02-27T13:11:49", "guid": { "rendered": "https://www.crifan.com/?p=70410", "raw": "https://www.crifan.com/?p=70410" }, "modified": "2020-02-27T21:11:49", "modified_gmt": "2020-02-27T13:11:49", "password": "", "slug": "mac_pip_change_source_server_to_spped_up_download", "status": "draft", "type": "post", "link": "https://www.crifan.com/?p=70410", "title": { 'raw": "【已解决】Mac中给pip更换源以加速下载", "rendered": "【已解决】Mac中给pip更换源以加速下载" }, "content": { ... """ newId = respJson["id"] newUrl = respJson["guid"]["rendered"] newSlug = respJson["slug"] newLink = respJson["link"] newTitle = respJson["title"]["rendered"] logging.info("newId=%s, newUrl=%s, newSlug=%s, newLink=%s, newTitle=%s", newId, newUrl, newSlug, newLink, newTitle) isCreateOk = True respInfo = { "id": newId, # 70393 "url": newUrl, # https://www.crifan.com/files/pic/uploads/2020/03/f6956c30ef0b475fa2b99c2f49622e35.png "slug": newSlug, # f6956c30ef0b475fa2b99c2f49622e35 "link": newLink, # https://www.crifan.com/f6956c30ef0b475fa2b99c2f49622e35/ "title": newTitle, # f6956c30ef0b475fa2b99c2f49622e35 } else: isCreateOk = False # respInfo = resp.status_code, resp.text respInfo = { "errCode": resp.status_code, "errMsg": resp.text, } logging.info("isCreateOk=%s, respInfo=%s", isCreateOk, respInfo) return isCreateOk, respInfo
供参考。
最新和完整代码详见:
- crifanWordpress.py
- crifanEvernoteToWordpress.py
- crifanEvernote.py
【后记20201205】
先去确认:
【记录】Python发布帖子到WordPress后确认内容无误
再去继续加新功能:
【已解决】给Python发布印象笔记帖子内容到WordPress文章时加上分类和标签
【后记20210103】
已回复帖子