折腾:
【未解决】自己写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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 | # 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 首页 登录 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 -> 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环境需要准备好:
1 2 | pip install bs4 pip install oauth2 |
不过发现此处有
Pipfile
所以去尝试恢复环境
1 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | 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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | 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", }, "modified": "2020-03-13T22:34:29", ... "slug": "f6956c30ef0b475fa2b99c2f49622e35", "status": "inherit", "type": "attachment", "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 "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】
已回复帖子