摘要:目前這個(gè)爬蟲(chóng)還是比較簡(jiǎn)單的類(lèi)型的,直接抓取頁(yè)面,然后在頁(yè)面中提取數(shù)據(jù),保存數(shù)據(jù)到數(shù)據(jù)庫(kù)。總結(jié)寫(xiě)這個(gè)項(xiàng)目其實(shí)主要的難點(diǎn)在于程序穩(wěn)定性的控制,容錯(cuò)機(jī)制的設(shè)置,以及錯(cuò)誤的記錄,目前這個(gè)項(xiàng)目基本能夠?qū)崿F(xiàn)直接運(yùn)行一次性跑通整個(gè)流程。
前言
之前研究數(shù)據(jù),零零散散的寫(xiě)過(guò)一些數(shù)據(jù)抓取的爬蟲(chóng),不過(guò)寫(xiě)的比較隨意。有很多地方現(xiàn)在看起來(lái)并不是很合理 這段時(shí)間比較閑,本來(lái)是想給之前的項(xiàng)目做重構(gòu)的。
后來(lái) 利用這個(gè)周末,索性重新寫(xiě)了一個(gè)項(xiàng)目,就是本項(xiàng)目 guwen-spider。目前這個(gè)爬蟲(chóng)還是比較簡(jiǎn)單的類(lèi)型的, 直接抓取頁(yè)面,然后在頁(yè)面中提取數(shù)據(jù),保存數(shù)據(jù)到數(shù)據(jù)庫(kù)。
通過(guò)與之前寫(xiě)的對(duì)比,我覺(jué)得難點(diǎn)在于整個(gè)程序的健壯性,以及相應(yīng)的容錯(cuò)機(jī)制。在昨天寫(xiě)代碼的過(guò)程中其實(shí)也有反映, 真正的主體代碼其實(shí)很快就寫(xiě)完了 ,花了大部分時(shí)間是在
做穩(wěn)定性的調(diào)試, 以及尋求一種更合理的方式來(lái)處理數(shù)據(jù)與流程控制的關(guān)系。
項(xiàng)目的背景是抓取一個(gè)一級(jí)頁(yè)面是目錄列表 ,點(diǎn)擊一個(gè)目錄進(jìn)去 是一個(gè)章節(jié) 及篇幅列表 ,點(diǎn)擊章節(jié)或篇幅進(jìn)入具體的內(nèi)容頁(yè)面。
概述本項(xiàng)目github地址 : guwen-spider (PS:最后面還有彩蛋 ~~逃
項(xiàng)目技術(shù)細(xì)節(jié)
項(xiàng)目大量用到了 ES7 的async 函數(shù), 更直觀(guān)的反應(yīng)程序了的流程。為了方便,在對(duì)數(shù)據(jù)遍歷的過(guò)程中直接使用了著名的async這個(gè)庫(kù),所以不可避免的還是用到了回調(diào)promise ,因?yàn)閿?shù)據(jù)的處理發(fā)生在回調(diào)函數(shù)中,不可避免的會(huì)遇到一些數(shù)據(jù)傳遞的問(wèn)題,其實(shí)也可以直接用ES7的async await 寫(xiě)一個(gè)方法來(lái)實(shí)現(xiàn)相同的功能。這里其實(shí)最贊的一個(gè)地方是使用了 Class 的 static 方法封裝對(duì)數(shù)據(jù)庫(kù)的操作, static 顧名思義 靜態(tài)方法 就跟 prototype 一樣 ,不會(huì)占用額外空間。
項(xiàng)目主要用到了
1 ES7的 async await 協(xié)程做異步有關(guān)的邏輯處理。
2 使用 npm的 async庫(kù) 來(lái)做循環(huán)遍歷,以及并發(fā)請(qǐng)求操作。
3 使用 log4js 來(lái)做日志處理
4 使用 cheerio 來(lái)處理dom的操作。
5 使用 mongoose 來(lái)連接mongoDB 做數(shù)據(jù)的保存以及操作。
目錄結(jié)構(gòu)
├── bin // 入口
│? ├── booklist.js // 抓取書(shū)籍邏輯
│? ├── chapterlist.js // 抓取章節(jié)邏輯
│? ├── content.js // 抓取內(nèi)容邏輯
│? └── index.js // 程序入口
├── config // 配置文件
├── dbhelper // 數(shù)據(jù)庫(kù)操作方法目錄
├── logs // 項(xiàng)目日志目錄
├── model // mongoDB 集合操作實(shí)例
├── node_modules
├── utils // 工具函數(shù)
├── package.json
項(xiàng)目實(shí)現(xiàn)方案分析
項(xiàng)目是一個(gè)典型的多級(jí)抓取案例,目前只有三級(jí),即 書(shū)籍列表, 書(shū)籍項(xiàng)對(duì)應(yīng)的 章節(jié)列表,一個(gè)章節(jié)鏈接對(duì)應(yīng)的內(nèi)容。 抓取這樣的結(jié)構(gòu)可以采用兩種方式, 一是 直接從外層到內(nèi)層 內(nèi)層抓取完以后再執(zhí)行下一個(gè)外層的抓取, 還有一種就是先把外層抓取完成保存到數(shù)據(jù)庫(kù),然后根據(jù)外層抓取到所有內(nèi)層章節(jié)的鏈接,再次保存,然后從數(shù)據(jù)庫(kù)查詢(xún)到對(duì)應(yīng)的鏈接單元 對(duì)之進(jìn)行內(nèi)容抓取。這兩種方案各有利弊,其實(shí)兩種方式我都試過(guò), 后者有一個(gè)好處,因?yàn)閷?duì)三個(gè)層級(jí)是分開(kāi)抓取的, 這樣就能夠更方便,盡可能多的保存到對(duì)應(yīng)章節(jié)的相關(guān)數(shù)據(jù)。 可以試想一下 ,如果采用前者 按照正常的邏輯
對(duì)一級(jí)目錄進(jìn)行遍歷抓取到對(duì)應(yīng)的二級(jí)章節(jié)目錄, 再對(duì)章節(jié)列表進(jìn)行遍歷 抓取內(nèi)容,到第三級(jí) 內(nèi)容單元抓取完成 需要保存時(shí),如果需要很多的一級(jí)目錄信息,就需要 這些分層的數(shù)據(jù)之間進(jìn)行數(shù)據(jù)傳遞 ,想想其實(shí)應(yīng)該是比較復(fù)雜的一件事情。所以分開(kāi)保存數(shù)據(jù) 一定程度上避開(kāi)了不必要的復(fù)雜的數(shù)據(jù)傳遞。
目前我們考慮到 其實(shí)我們要抓取到的古文書(shū)籍?dāng)?shù)量并不多,古文書(shū)籍大概只有180本囊括了各種經(jīng)史。其和章節(jié)內(nèi)容本身是一個(gè)很小的數(shù)據(jù) ,即一個(gè)集合里面有180個(gè)文檔記錄。 這180本書(shū)所有章節(jié)抓取下來(lái)一共有一萬(wàn)六千個(gè)章節(jié),對(duì)應(yīng)需要訪(fǎng)問(wèn)一萬(wàn)六千個(gè)頁(yè)面爬取到對(duì)應(yīng)的內(nèi)容。所以選擇第二種應(yīng)該是合理的。
項(xiàng)目實(shí)現(xiàn)
主程有三個(gè)方法 bookListInit ,chapterListInit,contentListInit, 分別是抓取書(shū)籍目錄,章節(jié)列表,書(shū)籍內(nèi)容的方法對(duì)外公開(kāi)暴露的初始化方法。通過(guò)async 可以實(shí)現(xiàn)對(duì)這三個(gè)方法的運(yùn)行流程進(jìn)行控制,書(shū)籍目錄抓取完成將數(shù)據(jù)保存到數(shù)據(jù)庫(kù),然后執(zhí)行結(jié)果返回到主程序,如果運(yùn)行成功 主程序則執(zhí)行根據(jù)書(shū)籍列表對(duì)章節(jié)列表的抓取,同理對(duì)書(shū)籍內(nèi)容進(jìn)行抓取。
項(xiàng)目主入口
/** * 爬蟲(chóng)抓取主入口 */ const start = async() => { let booklistRes = await bookListInit(); if (!booklistRes) { logger.warn("書(shū)籍列表抓取出錯(cuò),程序終止..."); return; } logger.info("書(shū)籍列表抓取成功,現(xiàn)在進(jìn)行書(shū)籍章節(jié)抓取..."); let chapterlistRes = await chapterListInit(); if (!chapterlistRes) { logger.warn("書(shū)籍章節(jié)列表抓取出錯(cuò),程序終止..."); return; } logger.info("書(shū)籍章節(jié)列表抓取成功,現(xiàn)在進(jìn)行書(shū)籍內(nèi)容抓取..."); let contentListRes = await contentListInit(); if (!contentListRes) { logger.warn("書(shū)籍章節(jié)內(nèi)容抓取出錯(cuò),程序終止..."); return; } logger.info("書(shū)籍內(nèi)容抓取成功"); } // 開(kāi)始入口 if (typeof bookListInit === "function" && typeof chapterListInit === "function") { // 開(kāi)始抓取 start(); }
引入的 bookListInit ,chapterListInit,contentListInit, 三個(gè)方法
booklist.js
/** * 初始化方法 返回抓取結(jié)果 true 抓取成果 false 抓取失敗 */ const bookListInit = async() => { logger.info("抓取書(shū)籍列表開(kāi)始..."); const pageUrlList = getPageUrlList(totalListPage, baseUrl); let res = await getBookList(pageUrlList); return res; }
chapterlist.js
/** * 初始化入口 */ const chapterListInit = async() => { const list = await bookHelper.getBookList(bookListModel); if (!list) { logger.error("初始化查詢(xún)書(shū)籍目錄失敗"); } logger.info("開(kāi)始抓取書(shū)籍章節(jié)列表,書(shū)籍目錄共:" + list.length + "條"); let res = await asyncGetChapter(list); return res; };
content.js
/** * 初始化入口 */ const contentListInit = async() => { //獲取書(shū)籍列表 const list = await bookHelper.getBookLi(bookListModel); if (!list) { logger.error("初始化查詢(xún)書(shū)籍目錄失敗"); return; } const res = await mapBookList(list); if (!res) { logger.error("抓取章節(jié)信息,調(diào)用 getCurBookSectionList() 進(jìn)行串行遍歷操作,執(zhí)行完成回調(diào)出錯(cuò),錯(cuò)誤信息已打印,請(qǐng)查看日志!"); return; } return res; }
內(nèi)容抓取的思考
書(shū)籍目錄抓取其實(shí)邏輯非常簡(jiǎn)單,只需要使用async.mapLimit做一個(gè)遍歷就可以保存數(shù)據(jù)了,但是我們?cè)诒4鎯?nèi)容的時(shí)候 簡(jiǎn)化的邏輯其實(shí)就是 遍歷章節(jié)列表 抓取鏈接里的內(nèi)容。但是實(shí)際的情況是鏈接數(shù)量多達(dá)幾萬(wàn) 我們從內(nèi)存占用角度也不能全部保存到一個(gè)數(shù)組中,然后對(duì)其遍歷,所以我們需要對(duì)內(nèi)容抓取進(jìn)行單元化。
普遍的遍歷方式 是每次查詢(xún)一定的數(shù)量,來(lái)做抓取,這樣缺點(diǎn)是只是以一定數(shù)量做分類(lèi),數(shù)據(jù)之間沒(méi)有關(guān)聯(lián),以批量方式進(jìn)行插入,如果出錯(cuò) 則容錯(cuò)會(huì)有一些小問(wèn)題,而且我們想一本書(shū)作為一個(gè)集合多帶帶保存會(huì)遇到問(wèn)題。因此我們采用第二種就是以一個(gè)書(shū)籍單元進(jìn)行內(nèi)容抓取和保存。
這里使用了 async.mapLimit(list, 1, (series, callback) => {})這個(gè)方法來(lái)進(jìn)行遍歷,不可避免的用到了回調(diào),感覺(jué)很惡心。async.mapLimit()的第二個(gè)參數(shù)可以設(shè)置同時(shí)請(qǐng)求數(shù)量。
/* * 內(nèi)容抓取步驟: * 第一步得到書(shū)籍列表, 通過(guò)書(shū)籍列表查到一條書(shū)籍記錄下 對(duì)應(yīng)的所有章節(jié)列表, * 第二步 對(duì)章節(jié)列表進(jìn)行遍歷獲取內(nèi)容保存到數(shù)據(jù)庫(kù)中 * 第三步 保存完數(shù)據(jù)后 回到第一步 進(jìn)行下一步書(shū)籍的內(nèi)容抓取和保存 */ /** * 初始化入口 */ const contentListInit = async() => { //獲取書(shū)籍列表 const list = await bookHelper.getBookList(bookListModel); if (!list) { logger.error("初始化查詢(xún)書(shū)籍目錄失敗"); return; } const res = await mapBookList(list); if (!res) { logger.error("抓取章節(jié)信息,調(diào)用 getCurBookSectionList() 進(jìn)行串行遍歷操作,執(zhí)行完成回調(diào)出錯(cuò),錯(cuò)誤信息已打印,請(qǐng)查看日志!"); return; } return res; } /** * 遍歷書(shū)籍目錄下的章節(jié)列表 * @param {*} list */ const mapBookList = (list) => { return new Promise((resolve, reject) => { async.mapLimit(list, 1, (series, callback) => { let doc = series._doc; getCurBookSectionList(doc, callback); }, (err, result) => { if (err) { logger.error("書(shū)籍目錄抓取異步執(zhí)行出錯(cuò)!"); logger.error(err); reject(false); return; } resolve(true); }) }) } /** * 獲取單本書(shū)籍下章節(jié)列表 調(diào)用章節(jié)列表遍歷進(jìn)行抓取內(nèi)容 * @param {*} series * @param {*} callback */ const getCurBookSectionList = async(series, callback) => { let num = Math.random() * 1000 + 1000; await sleep(num); let key = series.key; const res = await bookHelper.querySectionList(chapterListModel, { key: key }); if (!res) { logger.error("獲取當(dāng)前書(shū)籍: " + series.bookName + " 章節(jié)內(nèi)容失敗,進(jìn)入下一部書(shū)籍內(nèi)容抓取!"); callback(null, null); return; } //判斷當(dāng)前數(shù)據(jù)是否已經(jīng)存在 const bookItemModel = getModel(key); const contentLength = await bookHelper.getCollectionLength(bookItemModel, {}); if (contentLength === res.length) { logger.info("當(dāng)前書(shū)籍:" + series.bookName + "數(shù)據(jù)庫(kù)已經(jīng)抓取完成,進(jìn)入下一條數(shù)據(jù)任務(wù)"); callback(null, null); return; } await mapSectionList(res); callback(null, null); }
數(shù)據(jù)抓取完了 怎么保存是個(gè)問(wèn)題
這里我們通過(guò)key 來(lái)給數(shù)據(jù)做分類(lèi),每次按照key來(lái)獲取鏈接,進(jìn)行遍歷,這樣的好處是保存的數(shù)據(jù)是一個(gè)整體,現(xiàn)在思考數(shù)據(jù)保存的問(wèn)題
1 可以以整體的方式進(jìn)行插入
優(yōu)點(diǎn) : 速度快 數(shù)據(jù)庫(kù)操作不浪費(fèi)時(shí)間。
缺點(diǎn) : 有的書(shū)籍可能有幾百個(gè)章節(jié) 也就意味著要先保存幾百個(gè)頁(yè)面的內(nèi)容再進(jìn)行插入,這樣做同樣很消耗內(nèi)存,有可能造成程序運(yùn)行不穩(wěn)定。
2可以以每一篇文章的形式插入數(shù)據(jù)庫(kù)。
優(yōu)點(diǎn) : 頁(yè)面抓取即保存的方式 使得數(shù)據(jù)能夠及時(shí)保存,即使后續(xù)出錯(cuò)也不需要重新保存前面的章節(jié),
缺點(diǎn) : 也很明顯 就是慢 ,仔細(xì)想想如果要爬幾萬(wàn)個(gè)頁(yè)面 做 幾萬(wàn)次*N 數(shù)據(jù)庫(kù)的操作 這里還可以做一個(gè)緩存器一次性保存一定條數(shù) 當(dāng)條數(shù)達(dá)到再做保存這樣也是一個(gè)不錯(cuò)的選擇。
/** * 遍歷單條書(shū)籍下所有章節(jié) 調(diào)用內(nèi)容抓取方法 * @param {*} list */ const mapSectionList = (list) => { return new Promise((resolve, reject) => { async.mapLimit(list, 1, (series, callback) => { let doc = series._doc; getContent(doc, callback) }, (err, result) => { if (err) { logger.error("書(shū)籍目錄抓取異步執(zhí)行出錯(cuò)!"); logger.error(err); reject(false); return; } const bookName = list[0].bookName; const key = list[0].key; // 以整體為單元進(jìn)行保存 saveAllContentToDB(result, bookName, key, resolve); //以每篇文章作為單元進(jìn)行保存 // logger.info(bookName + "數(shù)據(jù)抓取完成,進(jìn)入下一部書(shū)籍抓取函數(shù)..."); // resolve(true); }) }) }
兩者各有利弊,這里我們都做了嘗試。 準(zhǔn)備了兩個(gè)錯(cuò)誤保存的集合,errContentModel, errorCollectionModel,在插入出錯(cuò)時(shí) 分別保存信息到對(duì)應(yīng)的集合中,二者任選其一即可。增加集合來(lái)保存數(shù)據(jù)的原因是 便于一次性查看以及后續(xù)操作, 不用看日志。
(PS ,其實(shí)完全用 errorCollectionModel 這個(gè)集合就可以了 ,errContentModel這個(gè)集合可以完整保存章節(jié)信息)
//保存出錯(cuò)的數(shù)據(jù)名稱(chēng) const errorSpider = mongoose.Schema({ chapter: String, section: String, url: String, key: String, bookName: String, author: String, }) // 保存出錯(cuò)的數(shù)據(jù)名稱(chēng) 只保留key 和 bookName信息 const errorCollection = mongoose.Schema({ key: String, bookName: String, })
我們將每一條書(shū)籍信息的內(nèi)容 放到一個(gè)新的集合中,集合以key來(lái)進(jìn)行命名。
總結(jié)寫(xiě)這個(gè)項(xiàng)目 其實(shí)主要的難點(diǎn)在于程序穩(wěn)定性的控制,容錯(cuò)機(jī)制的設(shè)置,以及錯(cuò)誤的記錄,目前這個(gè)項(xiàng)目基本能夠?qū)崿F(xiàn)直接運(yùn)行 一次性跑通整個(gè)流程。 但是程序設(shè)計(jì)也肯定還存在許多問(wèn)題 ,歡迎指正和交流。
彩蛋寫(xiě)完這個(gè)項(xiàng)目 做了一個(gè)基于React開(kāi)的前端網(wǎng)站用于頁(yè)面瀏覽 和一個(gè)基于koa2.x開(kāi)發(fā)的服務(wù)端, 整體技術(shù)棧相當(dāng)于是 React + Redux + Koa2 ,前后端服務(wù)是分開(kāi)部署的,各自獨(dú)立可以更好的去除前后端服務(wù)的耦合性,比如同一套服務(wù)端代碼,不僅可以給web端 還可以給 移動(dòng)端 ,app 提供支持。目前整個(gè)一套還很簡(jiǎn)陋,但是可以滿(mǎn)足基本的查詢(xún)?yōu)g覽功能。希望后期有時(shí)間可以把項(xiàng)目變得更加豐富。
本項(xiàng)目地址 地址 : guwen-spider
對(duì)應(yīng)前端 React + Redux + semantic-ui 地址 : guwen-react
對(duì)應(yīng)Node端 Koa2.2 + mongoose 地址 : guwen-node
項(xiàng)目挺簡(jiǎn)單的 ,但是多了一個(gè)學(xué)習(xí)和研究 從前端到服務(wù)端的開(kāi)發(fā)的環(huán)境。
以上です
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://www.ezyhdfw.cn/yun/19165.html
摘要:責(zé)編現(xiàn)代化的方式開(kāi)發(fā)一個(gè)圖片上傳工具前端掘金對(duì)于圖片上傳,大家一定不陌生。之深入事件機(jī)制前端掘金事件綁定的方式原生的事件綁定方式有幾種想必有很多朋友說(shuō)種目前,在本人目前的研究中,只有兩種半兩種半還有半種的且聽(tīng)我道來(lái)。 Ajax 與數(shù)據(jù)傳輸 - 前端 - 掘金背景 在沒(méi)有ajax之前,前端與后臺(tái)傳數(shù)據(jù)都是靠表單傳輸,使用表單的方法傳輸數(shù)據(jù)有一個(gè)比較大的問(wèn)題就是每次提交數(shù)據(jù)都會(huì)刷新頁(yè)面,用...
摘要:以下這些項(xiàng)目,你拿來(lái)學(xué)習(xí)學(xué)習(xí)練練手。當(dāng)你每個(gè)步驟都能做到很優(yōu)秀的時(shí)候,你應(yīng)該考慮如何組合這四個(gè)步驟,使你的爬蟲(chóng)達(dá)到效率最高,也就是所謂的爬蟲(chóng)策略問(wèn)題,爬蟲(chóng)策略學(xué)習(xí)不是一朝一夕的事情,建議多看看一些比較優(yōu)秀的爬蟲(chóng)的設(shè)計(jì)方案,比如說(shuō)。 (一)如何學(xué)習(xí)Python 學(xué)習(xí)Python大致可以分為以下幾個(gè)階段: 1.剛上手的時(shí)候肯定是先過(guò)一遍Python最基本的知識(shí),比如說(shuō):變量、數(shù)據(jù)結(jié)構(gòu)、語(yǔ)法...
摘要:用將倒放這次讓我們一個(gè)用做一個(gè)小工具將動(dòng)態(tài)圖片倒序播放發(fā)現(xiàn)引力波的機(jī)構(gòu)使用的包美國(guó)科學(xué)家日宣布,他們?nèi)ツ暝率状翁綔y(cè)到引力波。宣布這一發(fā)現(xiàn)的,是激光干涉引力波天文臺(tái)的負(fù)責(zé)人。這個(gè)機(jī)構(gòu)誕生于上世紀(jì)年代,進(jìn)行引力波觀(guān)測(cè)已經(jīng)有近年。 那些年我們寫(xiě)過(guò)的爬蟲(chóng) 從寫(xiě) nodejs 的第一個(gè)爬蟲(chóng)開(kāi)始陸陸續(xù)續(xù)寫(xiě)了好幾個(gè)爬蟲(chóng),從爬拉勾網(wǎng)上的職位信息到爬豆瓣上的租房帖子,再到去爬知乎上的妹子照片什么的,爬蟲(chóng)...
摘要:年前無(wú)心工作,上班刷知乎發(fā)現(xiàn)一篇分享爬蟲(chóng)的文章。另外攜帶的數(shù)據(jù)是用來(lái)告訴服務(wù)器當(dāng)前請(qǐng)求是從哪個(gè)頁(yè)面請(qǐng)求過(guò)來(lái)的。 年前無(wú)心工作,上班刷知乎發(fā)現(xiàn)一篇分享python爬蟲(chóng)的文章。 感覺(jué)他爬取的網(wǎng)站里的妹子都好好看哦,超喜歡這里的,里面?zhèn)€個(gè)都是美女。 無(wú)小意丶:自我發(fā)掘爬蟲(chóng)實(shí)戰(zhàn)1:宅男女神網(wǎng)妹子圖片批量抓取,分類(lèi)保存到本地和MongoDB數(shù)據(jù)庫(kù) 無(wú)奈python雖然入門(mén)過(guò)但太久沒(méi)用早已荒廢,最...
摘要:很基礎(chǔ),不喜勿噴轉(zhuǎn)載注明出處爬蟲(chóng)實(shí)戰(zhàn)項(xiàng)目之鏈家效果圖思路爬蟲(chóng)究竟是怎么實(shí)現(xiàn)的通過(guò)訪(fǎng)問(wèn)要爬取的網(wǎng)站地址,獲得該頁(yè)面的文檔內(nèi)容,找到我們需要保存的數(shù)據(jù),進(jìn)一步查看數(shù)據(jù)所在的元素節(jié)點(diǎn),他們?cè)谀撤矫嬉欢ㄊ怯幸?guī)律的,遵循規(guī)律,操作,保存數(shù)據(jù)。 說(shuō)明 作為一個(gè)前端界的小學(xué)生,一直想著自己做一些項(xiàng)目向全棧努力。愁人的是沒(méi)有后臺(tái),搜羅之后且學(xué)會(huì)了nodejs和express寫(xiě)成本地的接口給前端頁(yè)面調(diào)用...
閱讀 4002·2023-04-26 00:36
閱讀 2744·2021-11-16 11:44
閱讀 1183·2021-11-15 17:58
閱讀 1745·2021-09-30 09:47
閱讀 1287·2019-08-30 13:05
閱讀 1614·2019-08-30 12:55
閱讀 2489·2019-08-30 11:02
閱讀 2884·2019-08-29 17:01