更新
前言 其实已经有较为成熟的朋友圈项目了。那么我之前为什么不添加这个功能呢?
首先是因为比较懒,且感觉部署起来比较复杂,速度也有一点慢。
其次就是因为洪哥的友链比较多,相对应的鱼塘(Heo的朋友圈名字)的文章也就非常的多,所以我之前都是直接看他的。
最近可能是因为项目更新的问题,洪哥的鱼塘不能实时的更新内容了。也就没法再白嫖了。 经过一番思考,觉得自己应该可以写出这个功能,于是就决定自己写一个简单的,最主要的就是练手。
首先我选用nodejs来写,但是不知道为什么本地运行一切正常而部署到服务器却总是有一些小BUG。
我就想着要不换成python吧,毕竟在爬虫方面python更专业一些,顺便也尝试尝试如何在服务器运行python(以前没弄过)。
经过大半天的折腾终于是做出来了。至于优化什么的就再说吧。
前端样式是参考的 @林木木 和 @Heo 的,将两者小小的结合了一下。效果还是很不错的。
具体效果请转至:友链订阅
代码 首先我写了一个link.json文件来存放友链相关信息,并在其中写上了每一个文件的rss地址,这个文件对我来说是有用的,但是对一些人来说可能就没什么作用。且我之前用node写了一个api,为了图省事我直接把获取数据的api写在这个文件里了,这就导致整个功能更乱了。
这也是不适合写成教程的原因,我写的太有针对性,而每个人的情况又有所不同。
所以现在我写的这个朋友圈功能的流程是:python爬取数据并存储 -> node获取数据并响应请求发送数据 -> 前端接受并渲染数据。看似明确,实则很乱。
link.json json存放友链信息 可以参考:link.json
friends.py python获取数据
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 from webbrowser import getimport requestsimport jsonimport xmltodictimport timejsonUrl = 'https://blog.leonus.cn/link.json' def xmltojson (xmlstr ): xmlparse = xmltodict.parse(xmlstr) jsonstr = json.dumps(xmlparse, indent=1 ) return jsonstr def timestamp (shijian, type ): try : s_t = time.strptime(shijian, type ) mkt = int (time.mktime(s_t)) except : return False return (mkt) def getData (item ): try : ls = [] data = json.loads(xmltojson(requests.get(item["link" ]+item["rss" ], timeout=3 , headers={ 'user-agent' : 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/106.0.0.0 Safari/537.36 Edg/106.0.1370.52' }).content.decode("utf-8" ))) if "feed" in data: als = data["feed" ]["entry" ] for e in als: ls.append({ "articletitle" : e["title" ]["#text" ] if "#text" in e["title" ] else e["title" ], "articleLink" : e["id" ], "articletime" : timestamp(e["published" ][:19 ], "%Y-%m-%dT%H:%M:%S" ), "name" : item["name" ], "link" : item["link" ], "avatar" : item["avatar" ] }) return ls elif "rss" in data: als = data["rss" ]["channel" ]["item" ] for e in als: ls.append({ "articletitle" : e["title" ]["#text" ] if "#text" in e["title" ] else e["title" ], "articleLink" : e["link" ], "articletime" : timestamp(e["pubDate" ][:25 ], "%a, %d %b %Y %H:%M:%S" ) or timestamp(e["pubDate" ][:23 ], "%a, %d %b %y %H:%M:%S" ), "name" : item["name" ], "link" : item["link" ], "avatar" : item["avatar" ] }) return ls else : return 0 except : return 0 def start (): articleList = [] ls = eval (requests.get(jsonUrl).text)['link_list' ] print ('开始爬取,当前时间:' + str (time.strftime("%Y-%m-%d %H:%M:%S" , time.localtime()))) for index, item in enumerate (ls): if not (item["rss" ]): continue print ("正在爬取第" + str ((index+1 )) + "个:" + str ((item['name' ])) + ",共" + str (len (ls)) + "个。" , end='' ) for i in range (3 ): if i >= 1 : print ('重试中...' ) time.sleep(3 ) data = getData(item) if data: articleList.extend(data) print ("爬取成功" ) break else : print ("爬取失败" ) articleList.sort(key=lambda x: x['articletime' ], reverse=True ) articleList = articleList[:120 ] with open ('friends.json' , 'w' , encoding='utf-8' ) as f: articleList = json.dumps({ 'lastGetTime' : time.time(), 'data' : articleList }) f.write(str (articleList)) while True : start() time.sleep(14400 )
api.js nodejs实现api
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 const express = require ('express' );const fs = require ('fs' );const app = express ()app.all ('*' , function (req, res, next ) { res.header ('Access-Control-Allow-Origin' , '*' ); res.header ('Access-Control-Allow-Headers' , 'Content-Type' ); res.header ('Access-Control-Allow-Methods' , '*' ); res.header ('Content-Type' , 'application/json;charset=utf-8' ); next (); }); app.get ('/friends' , async (req, res) => { var data = JSON .parse (fs.readFileSync ('../friend/friends.json' )) let start = req.query .start ? Number (req.query .start ) : 0 let end = req.query .length ? start + Number (req.query .length ) : start + 30 data.data = data.data .slice (start, end) res.send (JSON .stringify (data)) }) app.listen (3000 )
friends/index.md hexo新建friends页面,写下如下代码
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 --- title: 友链订阅 date: 2022-10-04 21:51:54 type: aside: false aplayer: comments: keywords: description: highlight_shrink: --- <style > div#page { background: none; border: 0; padding: 0; } #page h1.page-title { color: var(--font-color); margin: 0 0 10px; text-align: left; } [data-theme=dark] #twikoo .tk-content, #twikoo .tk-content { padding: 0; background: transparent; } .tk-comments-container>.tk-comment, .tk-submit:nth-child(1), .talk_ item { background: var(--card-bg); border: 1px rgba(188, 188, 188, 0.8) solid; box-shadow: 0 5px 10px rgb(189 189 189 / 10%); transition: all .3s ease-in-out; border-radius: 12px; } .tk-comments-container>.tk-comment:hover, .tk-submit:nth-child(1):hover { border-color: #6dc3fd; } .tk-submit { padding: 20px 10px 0; } .tk-comments-container>.tk-comment { padding: 15px; } .friends { display: flex; flex-wrap: wrap; justify-content: space-between; } .item, .friends_bottom button { background: var(--card-bg); transition: 0.3s; overflow: hidden; position: relative; width: 49.3%; border: 1px solid var(--search-input-color); border-radius: 12px; padding: .6rem 1rem; margin: .5rem 0; box-shadow: 0 0 15px rgb(151 151 151 / 10%); } .item:hover, .friends_ bottom button:hover { border-color: var(--leonus-main); transform: scale(1.03); } .item:hover .num, .item:hover .time { opacity: .7 } a.num:hover { text-decoration: none !important; } .info { display: flex; line-height: 1; margin: 5px 0; justify-content: space-between; } .author a, a.title { display: block; color: var(--font-color) !important; font-size: 18px; } a.title { margin-bottom: 10px; } .author a:hover, a.title:hover { text-decoration: none !important; color: var(--leonus-main) !important; } .author { display: flex; align-items: center; } .author a { margin-left: 5px; font-size: 16px; } .author img { height: 22px; margin: 0 !important; border-radius: 50%; } span.time { opacity: 0.5; } a.num { color: var(--font-color) !important; position: absolute; font-size: 50px; top: -15%; right: .5rem; font-style: italic; opacity: .3; line-height: 1; } .friends_bottom { display: none; } .friends_ bottom button { display: block; margin: 10px auto; color: var(--font-color); font-size: 1.3rem; } .friends_bottom span { display: block; opacity: .8; text-align: right; width: 100%; padding: 0 10px; } @media screen and (max-width: 900px) { .item, .friends_ bottom button { width: 95%; } .friends { justify-content: center; } #page h1.page-title { font-size: 1.5rem; } } </style > <div class ="content" > </div > <div class ="friends_bottom" > <button onclick ="lookMore()" > 查看更多</button > <span > 最后更新:2022-01-01 08:00:00</span > </div > <script > var start = 0 var lookMore = function() { start += 30 getData('add') } var toTime = function(time, type) { let d = new Date(time), ls = [d.getFullYear(), d.getMonth() + 1, d.getDate(), d.getHours(), d.getMinutes(), d.getSeconds()]; for (let i = 0; i < ls.length; i++) { ls[i] = ls[i] <= 9 ? '0' + ls[i] : ls[i] + '' } if (type == 'long') return ls[0] + '-' + ls[1] + '-' + ls[2] + ' ' + ls[3] + ':' + ls[4] + ':' + ls[5] else return ls[0].slice(2) + '/' + ls[1] + '/' + ls[2] } var getData = function(type) { fetch(`https://xxx.xxxx.xx/friends?start=${start}&length=30`).then(res => res.json()).then(res => { let div = document.createElement('div') div.className = 'friends' let html = '' res.data.forEach((e, index) => { html += ` <div class ="item" > <a target="_blank" href="${e.articleLink}" class ="title" > ${e.articletitle}</a > <a target="_blank" href="${e.articleLink}" class ="num" > ${start + index + 1}</a > <div class ="info" > <div class ="author" > <img class ="no-lightbox" src="${e.avatar}" > <a target ="_blank" href ="${e.link}" > ${e.name}</a > </div > <span class ="time" > ${toTime(e.articletime * 1000)}</span > </div > </div > ` div.innerHTML = html document.querySelector('.content').appendChild(div) document.querySelector('.friends_bottom span').innerHTML = '最后更新:' + toTime(res.lastGetTime * 1000, 'long') document.querySelector('.friends_ bottom').style.display = 'block' if (start + 30 >= 150) document.querySelector('.friends_bottom').style.display = 'none' }); }).then(leonus.scrollFn) } getData() </script >
后记 写出来主要是提供一个参考(顺便水一篇文章)。 代码可能会有一些乱,但是能跑,后面再慢慢优化。 如果有一些基础的且有兴趣的可以自行修改尝试。