这篇文章介绍了作者部署友链朋友圈的全过程,包括前端和后端的部署方式、友链适配以及常用命令等。作者通过自己的实践记录分享了友链朋友圈的部署经验,对于需要部署友链朋友圈的读者有一定的参考价值。
此内容根据文章生成,并经过人工审核,仅用于文章内容的解释与总结
投诉
此文章已更新:友链朋友圈5 - 我的部署历程与主题样式分享
冰老师很久很久以前做的友链朋友圈,功能很棒但是配置过程为了追求免费涵盖了特别特别多业务。之前部署成功过一起,但是后来担心GitHub action限额和leancloud的多个限制,在一次无法使用的情况下,彻底被我给移除了。
上周得知友链朋友圈已经支持服务器部署了,全程使用服务器不用第三方服务,那不得整一个?
从功能性上一开始用的原装的,后来发现木木的功能更多一些,所以现在用的是木木的前端结构。这个文章只作为记录整个部署过程,不作为教程使用。
前端部署文档
在前端部署用的木木的部署方式:立即访问文档
后端部署文档
在后端部署用的官方文档:立即访问官方文档
在部署方式上使用的是最简单的server
+sqlite
方式,推荐有服务器的小伙伴使用这个方式。
部署后端
因为用的宝塔面板,所以很轻松的安装了文档要求的python版本。
后来按照文档执行./server.sh
时发现api进程始终无法运行。
后来与二猹探讨完后发现是因为没有安装aiohttp
库,直接pip3 install aiohttp
安装成功后就可以正常运行了。
友链适配
因为前端很久之前就部署好了,所以可以轻松无缝移植新api。但是出现一个问题就是因为我的友链是分成两段的,一个是我自己魔改的友链样式,专门给100文章+的友链样式,一种是butterfly自带样式。使用butterfly主题抓取规则还不能抓取我的友链。观察了一下发现本身自带通用友链规则,调了半天发现最初的通用规则是有dom结构要求的,必须按照他这个结构来,但是修改友链结构不仅会破坏原有的友链样式,而且有些样式本身就不适合这个结构(因为原有规则的css太模糊了,如果有多个img标签抓取的就有问题)。
自己动手丰衣足食,写了一个commonPro
(后来被迫更名为common2
的通用规则)。
规则如下:
1 2 3 4 5 6 7
| def get_common2_url(self,response,queue): avatar = response.css('.cf-friends-avatar::attr(data-lazy-src)').extract() if not avatar: avatar = response.css('img.cf-friends-avatar::attr(src)').extract() link = response.css('a.cf-friends-link::attr(href)').extract() name = response.css('.cf-friends-name::text').extract() self.handle(avatar, link, name, queue)
|
只需要在需要抓取的内容添加class类名即可,不需要去改任何dom结构。
最终成功完成了友链适配。
前端部署
前端没有遇到太多的问题,在刚开始部署的时候发现点击更多按钮后,图片因为lazyload的原因没有去主动加载,翻阅lazylaod文档之后看到这个函数,执行之后就会检查图片是否加载,并加载需要加载的图片。
1
| lazyLoadInstance.update();
|
调了一些结构,比如木木不支持点击头像查看卡片,我改了一下结构给弄支持了,还有一些小调整。
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 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383
|
var fdata = { jsonurl: '', apiurl: '', apipublicurl: 'https://moments.zhheo.com/', initnumber: 30, stepnumber: 30, article_sort: 'created', error_img: 'https://sdn.geekzu.org/avatar/57d8260dfb55501c37dde588e7c3852c' }
if(typeof(fdataUser) !=="undefined"){ for(var key in fdataUser) { if(fdataUser[key]){ fdata[key] = fdataUser[key]; } } } var article_num = '',sortNow='',UrlNow='',friends_num='' var container = document.getElementById('cf-container') || document.getElementById('fcircleContainer') ;
var localSortNow = localStorage.getItem("sortNow") var localUrlNow = localStorage.getItem("urlNow") if(localSortNow && localUrlNow){ sortNow = localSortNow UrlNow = localUrlNow }else{ sortNow = fdata.article_sort if(fdata.jsonurl){ UrlNow = fdata.apipublicurl+'postjson?jsonlink='+ fdata.jsonurl+"&" }else if(fdata.apiurl){ UrlNow = fdata.apiurl+'all?' }else{ UrlNow = fdata.apipublicurl+'all?' } console.log("当前模式:"+UrlNow) localStorage.setItem("urlNow",UrlNow) localStorage.setItem("sortNow",sortNow) }
function loadStatistical(sdata){ article_num = sdata.article_num friends_num = sdata.friends_num var messageBoard =` <div id="cf-state" class="cf-new-add"> <div class="cf-state-data"> <div class="cf-data-friends" onclick="openToShow()"> <span class="cf-label">订阅</span> <span class="cf-message">${sdata.friends_num}</span> </div> <div class="cf-data-active" onclick="changeEgg()"> <span class="cf-label">活跃</span> <span class="cf-message">${sdata.active_num}</span> </div> <div class="cf-data-article" onclick="clearLocal()"> <span class="cf-label">日志</span> <span class="cf-message">${sdata.article_num}</span> </div> </div> <div id="cf-change"> <span id="cf-change-created" data-sort="created" onclick="changeSort(event)" class="${sortNow == 'created' ? 'cf-change-now':''}">Created</span> | <span id="cf-change-updated" data-sort="updated" onclick="changeSort(event)" class="${sortNow == 'updated' ? 'cf-change-now':''}" >Updated</span> </div> </div> `; var loadMoreBtn = ` <div id="cf-more" class="cf-new-add" onclick="loadNextArticle()"><i class="fas fa-angle-double-down"></i></div> <div id="cf-footer" class="cf-new-add"> <span id="cf-version-up" onclick="checkVersion()"></span> <span class="cf-data-lastupdated">更新于:${sdata.last_updated_time}</span> <span class="cf-data-lastupdated">订阅:${sdata.friends_num} 活跃:${sdata.active_num} 日志:${sdata.article_num}</span> </div> <div id="cf-overlay" class="cf-new-add" onclick="closeShow()"></div> <div id="cf-overshow" class="cf-new-add"></div> `; if(container){ container.insertAdjacentHTML('afterend', loadMoreBtn); } }
function loadArticleItem(datalist,start,end){ var articleItem = ''; var articleNum = article_num; var endFor = end if(end > articleNum){endFor = articleNum} if(start < articleNum){ for (var i = start;i<endFor;i++){ var item = datalist[i]; articleItem +=` <div class="cf-article"> <a class="cf-article-title" href="${item.link}" target="_blank" rel="noopener nofollow" data-title="${item.title}">${item.title}</a> <span class="cf-article-floor">${item.floor}</span> <div class="cf-article-avatar no-lightbox flink-item-icon"> <a onclick="openMeShow(event)" data-link="${item.link}" class="" target="_blank" rel="noopener nofollow" href="javascript:;"><img class="cf-img-avatar avatar" src="${item.avatar}" alt="avatar" onerror="this.src='${fdata.error_img}'; this.onerror = null;"><span class="cf-article-author">${item.author}</span></a> <span class="cf-article-time"> <span class="cf-time-created" style="${sortNow == 'created' ? '':'display:none'}">${item.created}</span> <span class="cf-time-updated" style="${sortNow == 'updated' ? '':'display:none'}"><i class="fas fa-history">更新于</i>${item.updated}</span> </span> </div> </div> `; } container.insertAdjacentHTML('beforeend', articleItem); fetchNextArticle() }else{ document.getElementById('cf-more').outerHTML = `<div id="cf-more" class="cf-new-add" onclick="loadNoArticle()"><small>一切皆有尽头!</small></div>` } }
function loadFcircleShow(userinfo,articledata){ var showHtml = ` <div class="cf-overshow"> <div class="cf-overshow-head"> <img class="cf-img-avatar avatar" src="${userinfo.avatar}" alt="avatar" onerror="this.src='${fdata.error_img}'; this.onerror = null;"> <a class="" target="_blank" rel="noopener nofollow" href="${userinfo.link}">${userinfo.author}</a> </div> <div class="cf-overshow-content"> ` for (var i = 0;i<userinfo.article_num;i++){ var item = articledata[i]; showHtml += ` <p><a class="cf-article-title" href="${item.link}" target="_blank" rel="noopener nofollow" data-title="${item.title}">${item.title}</a><span>${item.created}</span></p> ` } showHtml += '</div></div>' document.getElementById('cf-overshow').insertAdjacentHTML('beforeend', showHtml); document.getElementById('cf-overshow').className = 'cf-show-now'; }
function fetchNextArticle(){ var start = document.getElementsByClassName('cf-article').length var end = start + fdata.stepnumber var articleNum = article_num; if(end > articleNum){ end = articleNum } if(start < articleNum){ UrlNow = localStorage.getItem("urlNow") var fetchUrl = UrlNow+"rule="+sortNow+"&start="+start+"&end="+end fetch(fetchUrl) .then(res => res.json()) .then(json =>{ var nextArticle = eval(json.article_data); console.log("已预载"+"?rule="+sortNow+"&start="+start+"&end="+end) localStorage.setItem("nextArticle",JSON.stringify(nextArticle)) }) }else if(start = articleNum){ document.getElementById('cf-more').outerHTML = `<div id="cf-more" class="cf-new-add" onclick="loadNoArticle()"><small>一切皆有尽头!</small></div>` } }
function loadNextArticle(){ var nextArticle = JSON.parse(localStorage.getItem("nextArticle")); var articleItem = "" for (var i = 0;i<nextArticle.length;i++){ var item = nextArticle[i]; articleItem +=` <div class="cf-article"> <a class="cf-article-title" href="${item.link}" target="_blank" rel="noopener nofollow" data-title="${item.title}">${item.title}</a> <span class="cf-article-floor">${item.floor}</span> <div class="cf-article-avatar no-lightbox flink-item-icon"> <a onclick="openMeShow(event)" data-link="${item.link}" class="" target="_blank" rel="noopener nofollow" href="javascript:;"><img class="cf-img-avatar avatar" src="${item.avatar}" alt="avatar" onerror="this.src='${fdata.error_img}'; this.onerror = null;"><span class="cf-article-author">${item.author}</span></a> <span class="cf-article-time"> <span class="cf-time-created" style="${sortNow == 'created' ? '':'display:none'}">${item.created}</span> <span class="cf-time-updated" style="${sortNow == 'updated' ? '':'display:none'}"><i class="fas fa-history">更新于</i>${item.updated}</span> </span> </div> </div> `; } container.insertAdjacentHTML('beforeend', articleItem); lazyLoadInstance.update(); fetchNextArticle() }
function loadNoArticle(){ var articleSortData = sortNow+"ArticleData" localStorage.removeItem(articleSortData) localStorage.removeItem("statisticalData") document.getElementById('cf-more').remove() window.scrollTo(0,document.getElementsByClassName('cf-state').offsetTop) }
function clearLocal(){ localStorage.removeItem("updatedArticleData") localStorage.removeItem("createdArticleData") localStorage.removeItem("nextArticle") localStorage.removeItem("statisticalData") localStorage.removeItem("sortNow") localStorage.removeItem("urlNow") location.reload(); }
function checkVersion(){ var url = fdata.apiurl+"version" fetch(url) .then(res => res.json()) .then(json =>{ console.log(json) var nowStatus = json.status,nowVersion = json.current_version,newVersion = json.latest_version var versionID = document.getElementById('cf-version-up') if(nowStatus == 0){ versionID.innerHTML = "当前版本:v"+ nowVersion }else if(nowStatus == 1){ versionID.innerHTML = "发现新版本:v"+ nowVersion + " ↦ " + newVersion }else{ versionID.innerHTML = "网络错误,检测失败!" } }) }
function changeEgg(){ if(fdata.jsonurl || fdata.apiurl ){ document.querySelectorAll('.cf-new-add').forEach(el => el.remove()); localStorage.removeItem("updatedArticleData") localStorage.removeItem("createdArticleData") localStorage.removeItem("nextArticle") localStorage.removeItem("statisticalData") container.innerHTML = "" UrlNow = localStorage.getItem("urlNow") var UrlNowPublic = fdata.apipublicurl+'all?' if(UrlNow !== UrlNowPublic){ changeUrl = fdata.apipublicurl+'all?' }else{ if(fdata.jsonurl){ changeUrl = fdata.apipublicurl+'postjson?jsonlink='+ fdata.jsonurl+"&" }else if(fdata.apiurl){ changeUrl = fdata.apiurl+'all?' } } localStorage.setItem("urlNow",changeUrl) FetchFriendCircle(sortNow,changeUrl) }else{ clearLocal() } }
function FetchFriendCircle(sortNow,changeUrl){ var end = fdata.initnumber var fetchUrl = UrlNow + "rule="+sortNow+"&start=0&end="+end if(changeUrl){ fetchUrl = changeUrl + "rule="+sortNow+"&start=0&end="+end } fetch(fetchUrl) .then(res => res.json()) .then(json =>{ var statisticalData = json.statistical_data; var articleData = eval(json.article_data); var articleSortData = sortNow+"ArticleData"; loadStatistical(statisticalData); loadArticleItem(articleData ,0,end) localStorage.setItem("statisticalData",JSON.stringify(statisticalData)) localStorage.setItem(articleSortData,JSON.stringify(articleData)) }) }
function changeSort(event){ sortNow = event.currentTarget.dataset.sort localStorage.setItem("sortNow",sortNow) document.querySelectorAll('.cf-new-add').forEach(el => el.remove()); container.innerHTML = ""; changeUrl = localStorage.getItem("urlNow") initFriendCircle(sortNow,changeUrl) if(fdata.apiurl){ checkVersion() } }
function openMeShow(event){ event.preventDefault() var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/; var meLink = event.currentTarget.dataset.link.replace(parse_url, '$1:$2$3') console.log(meLink) var fetchUrl = '' if(fdata.apiurl){ fetchUrl = fdata.apiurl + "post?link="+meLink }else{ fetchUrl = fdata.apipublicurl + "post?link="+meLink } if(noClick == 'ok'){ noClick = 'no' fetchShow(fetchUrl) } }
function closeShow(){ document.getElementById('cf-overlay').className -= 'cf-show-now'; document.getElementById('cf-overshow').className -= 'cf-show-now'; document.getElementById('cf-overshow').innerHTML = '' }
var noClick = 'ok'; function openToShow(){ var fetchUrl = '' if(fdata.apiurl){ fetchUrl = fdata.apiurl + "post" }else{ fetchUrl = fdata.apipublicurl + "post" } if(noClick == 'ok'){ noClick = 'no' fetchShow(fetchUrl) } }
function fetchShow(url){ var closeHtml = ` <div class="cf-overshow-close" onclick="closeShow()"></div> ` document.getElementById('cf-overlay').className = 'cf-show-now'; document.getElementById('cf-overshow').insertAdjacentHTML('afterbegin', closeHtml); console.log("开往"+url) fetch(url) .then(res => res.json()) .then(json =>{ noClick = 'ok' var statisticalData = json.statistical_data; var articleData = eval(json.article_data); loadFcircleShow(statisticalData,articleData) }) }
function initFriendCircle(sortNow,changeUrl){ var articleSortData = sortNow+"ArticleData"; var localStatisticalData = JSON.parse(localStorage.getItem("statisticalData")); var localArticleData = JSON.parse(localStorage.getItem(articleSortData)); container.innerHTML = ""; FetchFriendCircle(sortNow,changeUrl) }
initFriendCircle(sortNow)
|
大功告成
总体结果而言比较令人满意,以后就是我自己经常访问的渠道了。
立刻前往鱼塘
常用命令
傻瓜化部署、停止服务
查看日志