一只node爬蟲的升級打怪之路

我一直覺得,爬蟲是許多web開發(fā)人員難以回避的點。我們也應(yīng)該或多或少的去接觸這方面檬输,因為可以從爬蟲中學(xué)習(xí)到web開發(fā)中應(yīng)當(dāng)掌握的一些基本知識。而且匈棘,它還很有趣丧慈。

作者:Nick Bourdakos

我一直覺得,爬蟲是許多web開發(fā)人員難以回避的點。我們也應(yīng)該或多或少的去接觸這方面逃默,因為可以從爬蟲中學(xué)習(xí)到web開發(fā)中應(yīng)當(dāng)掌握的一些基本知識鹃愤。而且,它還很有趣完域。

我是一個知乎輕微重度用戶软吐,之前寫了一只爬蟲幫我爬取并分析它的數(shù)據(jù),我感覺這個過程還是挺有意思吟税,因為這是一個不斷給自己創(chuàng)造問題又去解決問題的過程凹耙。其中遇到了一些點,今天總結(jié)一下跟大家分享分享肠仪。

它都爬了什么?

先簡單介紹下我的爬蟲肖抱。它能夠定時抓取一個問題的關(guān)注量、瀏覽量异旧、回答數(shù)意述,以便于我將這些數(shù)據(jù)繪成圖表展現(xiàn)它的熱點趨勢。為了不讓我錯過一些熱門事件吮蛹,它還會定時去獲取我關(guān)注話題下的熱門問答荤崇,并推送到我的郵箱。

作為一個前端開發(fā)人員潮针,我必須為這個爬蟲系統(tǒng)做一個界面天试,能讓我登陸知乎帳號,添加關(guān)注的題目然低、話題喜每,看到可視化的數(shù)據(jù)。所以這只爬蟲還有登陸知乎雳攘、搜索題目的功能带兜。

然后來看下界面。





下面正兒八經(jīng)講它的開發(fā)歷程吨灭。

技術(shù)選型

Python得益于其簡單快捷的語法刚照、以及豐富的爬蟲庫,一直是爬蟲開發(fā)人員的首選喧兄∥夼希可惜我不熟。當(dāng)然最重要的是吠冤,作為一名前端開發(fā)人員浑彰,node能滿足爬蟲需求的話,自然更是首選拯辙。而且隨著node的發(fā)展郭变,也有許多好用的爬蟲庫颜价,甚至有puppeteer這樣直接能模擬Chrome訪問網(wǎng)頁的工具的推出,node在爬蟲方面應(yīng)該是妥妥能滿足我所有的爬蟲需求了诉濒。

于是我選擇從零搭建一個基于koa2的服務(wù)端周伦。為什么不直接選擇egg,express未荒,thinkjs這些更加全面的框架呢?因為我愛折騰嘛专挪。而且這也是一個學(xué)習(xí)的過程。如果以前不了解node片排,又對搭建node服務(wù)端有興趣狈蚤,可以看我之前的一篇文章-從零搭建Koa2 Server。

爬蟲方面我選擇了request+cheerio划纽。雖然知乎有很多地方用到了react脆侮,但得益于它絕大部分頁面還是服務(wù)端渲染,所以只要能請求網(wǎng)頁與接口(request)勇劣,解析頁面(cherrio)即可滿足我的爬蟲需求靖避。

其他不一一舉例了,我列個技術(shù)棧

服務(wù)端

koajs 做node server框架比默;

request + cheerio 做爬蟲服務(wù)幻捏;

mongodb 做數(shù)據(jù)存儲;

node-schedule 做任務(wù)調(diào)度命咐;

nodemailer 做郵件推送篡九。

客戶端

vuejs 前端框架;

museui Material Design UI庫醋奠;

chart.js 圖表庫榛臼。

技術(shù)選型妥善后,我們就要關(guān)心業(yè)務(wù)了窜司。首要任務(wù)就是真正的爬取到頁面沛善。

如何能爬取網(wǎng)站的數(shù)據(jù)?

知乎并沒有對外開放接口能讓用戶獲取數(shù)據(jù),所以想獲取數(shù)據(jù)塞祈,就得自己去爬取網(wǎng)頁信息金刁。我們知道即使是網(wǎng)頁,它本質(zhì)上也是個GET請求的接口议薪,我們只要在服務(wù)端去請求對應(yīng)網(wǎng)頁的地址(客戶端請求會跨域)尤蛮,再把html結(jié)構(gòu)解析下,獲取想要的數(shù)據(jù)即可斯议。

那為什么我要搞一個登陸呢?因為非登陸帳號獲取信息产捞,知乎只會展現(xiàn)有限的數(shù)據(jù),而且也無法得知自己知乎帳戶關(guān)注的話題捅位、問題等信息轧葛。而且若是想自己的系統(tǒng)也給其他朋友使用,也必須搞一個帳戶系統(tǒng)艇搀。

模擬登陸

大家都會用Chrome等現(xiàn)代瀏覽器看請求信息尿扯,我們在知乎的登錄頁進行登陸,然后查看捕獲接口信息就能知道焰雕,登陸無非就是向一個登陸api發(fā)送賬戶衷笋、密碼等信息,如果成功矩屁。服務(wù)端會向客戶端設(shè)置一個cookie辟宗,這個cookie即是登陸憑證。

所以我們的思路也是如此吝秕,通過爬蟲服務(wù)端去請求接口泊脐,帶上我們的帳號密碼信息,成功后再將返回的cookie存到我們的系統(tǒng)數(shù)據(jù)庫烁峭,以后再去爬取其他頁面時容客,帶上此cookie即可。

當(dāng)然约郁,等我們真正嘗試時缩挑,會受到更多挫折,因為會遇到token鬓梅、驗證碼等問題供置。不過伯复,由于我們有客戶端了妻枕,可以將驗證碼的識別交給真正的人,而不是服務(wù)端去解析圖片字符闷营,這降低了我們實現(xiàn)登陸的難度坊罢。

一波三折的是娄柳,即使你把正確驗證碼提交了,還是會提示驗證碼錯誤艘绍。如果我們自己做過驗證碼提交的系統(tǒng)就能夠迅速的定位原因赤拒。如果沒做過,我們再次查看登陸時涉及的請求與響應(yīng)诱鞠,我們也能猜到:

在客戶端獲取驗證碼時挎挖,知乎服務(wù)端還會往客戶端設(shè)置一個新cookie,提交登陸請求時航夺,必須把驗證碼與此cookie一同提交蕉朵,來驗證此次提交的驗證碼確實是當(dāng)時給予用戶的驗證碼。

語言描述有些繞阳掐,我以圖的形式來表達一個登陸請求的完整流程始衅。



注:我編寫爬蟲時冷蚂,知乎還部分采取圖片字符驗證碼,現(xiàn)已全部改為“點擊倒立文字”的形式汛闸。這樣會加大提交正確驗證碼的難度蝙茶,但也并非無計可施。獲取圖片后诸老,由人工識別并點擊倒立文字隆夯,將點擊的坐標(biāo)提交到登陸接口即可。當(dāng)然有興趣有能力的同學(xué)也可以自己編寫算法識別驗證碼别伏。

爬取數(shù)據(jù)

上一步中蹄衷,我們已經(jīng)獲取到了登陸后的憑證cookie。用戶登陸成功后厘肮,我們把登陸的帳戶信息與其憑證cookie存到mongo中愧口。以后此用戶發(fā)起的爬取需求,包括對其跟蹤問題的數(shù)據(jù)爬取都根據(jù)此cookie爬取类茂。

當(dāng)然cookie是有時間期限的调卑,所以當(dāng)我們存cookie時,應(yīng)該把過期時間也記錄下來大咱,當(dāng)后面再獲取此cookie時恬涧,多加一步過期校驗,若過期了則返回過期提醒碴巾。

爬蟲的基礎(chǔ)搞定后溯捆,就可以真正去獲取想要的數(shù)據(jù)了。我的需求是想知道某個知乎問題的熱點趨勢厦瓢。先用瀏覽器去看看一個問題頁面下都有哪些數(shù)據(jù)提揍,可以被我爬取分析。舉個例子煮仇,比如這個問題:有哪些令人拍案叫絕的推理橋段劳跃。

打開鏈接后,頁面上最直接展現(xiàn)出來的有關(guān)注者浙垫,被瀏覽刨仑,1xxxx個回答,還要默認展示的幾個高贊回答及其點贊評論數(shù)量夹姥。右鍵查看網(wǎng)站源代碼杉武,確認這些數(shù)據(jù)是服務(wù)端渲染出來的,我們就可以通過request請求網(wǎng)頁辙售,再通過cherrio轻抱,使用css選擇器定位到數(shù)據(jù)節(jié)點,獲取并存儲下來旦部。代碼示例如下:

async getData (cookie, qid) {

const options = {

url: `${zhihuRoot}/question/${qid}`,

method: 'GET',

headers: {

'Cookie': cookie,

'Accept-Encoding': 'deflate, sdch, br' // 不允許gzip,開啟gzip會開啟知乎客戶端渲染祈搜,導(dǎo)致無法爬取

}

}

const rs = await this.request(options)

if (rs.error) {

return this.failRequest(rs)

}

const $ = cheerio.load(rs)

const NumberBoard = $('.NumberBoard-item .NumberBoard-value')

const $title = $('.QuestionHeader-title')

$title.find('button').remove()

return {

success: true,

title: $title.text(),

data: {

qid: qid,

followers: Number($(NumberBoard[0]).text()),

readers: Number($(NumberBoard[1]).text()),

answers: Number($('h4.List-headerText span').text().replace(' 個回答', ''))

}

}

}

這樣我們就爬取了一個問題的數(shù)據(jù)较店,只要我們能夠按一定時間間隔不斷去執(zhí)行此方法獲取數(shù)據(jù),最終我們就能繪制出一個題目的數(shù)據(jù)曲線容燕,分析起熱點趨勢梁呈。

那么問題來了,如何去做這個定時任務(wù)呢?

定時任務(wù)

我使用了node-schedule做任務(wù)調(diào)度缰趋。如果之前做過定時任務(wù)的同學(xué)捧杉,可能對其類似cron的語法比較熟悉陕见,不熟悉也沒關(guān)系秘血,它提供了not-cron-like的,更加直觀的設(shè)置去配置任務(wù)评甜,看下文檔就能大致了解灰粮。

當(dāng)然這個定時任務(wù)不是簡單的不斷去執(zhí)行上述的爬取方法getData。因為這個爬蟲系統(tǒng)不僅是一個用戶忍坷,一個用戶不僅只跟蹤了一個問題粘舟。

所以我們此處的完整任務(wù)應(yīng)該是遍歷系統(tǒng)的每個cookie未過期用戶,再遍歷每個用戶的跟蹤問題佩研,再去獲取這些問題的數(shù)據(jù)柑肴。

系統(tǒng)還有另外兩個定時任務(wù),一個是定時爬取用戶關(guān)注話題的熱門回答旬薯,另一個是推送這個話題熱門回答給相應(yīng)的用戶晰骑。這兩個任務(wù)跟上述任務(wù)大致流程一樣,就不細講了绊序。

但是在我們做定時任務(wù)時會有個細節(jié)問題硕舆,就是如何去控制爬取時的并發(fā)問題。具體舉例來說:如果爬蟲請求并發(fā)太高骤公,知乎可能是會限制此IP的訪問的抚官,所以我們需要讓爬蟲請求一個一個的,或者若干個若干個的進行阶捆。

簡單思考下凌节,我們會采取循環(huán)await。我不假思索的寫下了如下代碼:

// 爬蟲方法

async function getQuestionData () {

// do spider action

}

// questions為獲取到的關(guān)注問答

questions.forEach(await getQuestionData)

然而執(zhí)行之后洒试,我們會發(fā)現(xiàn)這樣其實還是并發(fā)執(zhí)行的刊咳,為什么呢?其實仔細想下就明白了。forEach只是循環(huán)的語法糖儡司,如果沒有這個方法娱挨,讓你來實現(xiàn)它,你會怎么寫呢?你大概也寫的出來:

Array.prototype.forEach = function (callback) {

for (let i = 0; i < this.length; i++) {

callback(this[i], i, this)

}

}

雖然forEach本身會更復(fù)雜點捕犬,但大致就是這樣吧跷坝。這時候我們把一個異步方法作為參數(shù)callback傳遞進去酵镜,然后循環(huán)執(zhí)行它,這個執(zhí)行依舊是并發(fā)執(zhí)行柴钻,并非是同步的淮韭。

所以我們?nèi)绻雽崿F(xiàn)真正的同步請求,還是需要用for循環(huán)去執(zhí)行贴届,如下:

async function getQuestionData () {

// do spider action

}

for (let i = 0; i < questions.length; i++) {

await getQuestionData()

}

除了for循環(huán)靠粪,還可以通過for-of,如果對這方面感興趣毫蚓,可以去多了解下數(shù)組遍歷的幾個方法占键,順便研究下ES6的迭代器Iterator。

其實如果業(yè)務(wù)量大元潘,即使這樣做也是不夠的畔乙。還需要更加細分任務(wù)顆粒度,甚至要加代理IP來分散請求翩概。

合理搭建服務(wù)端

下面說的點跟爬蟲本身沒有太大關(guān)系了牲距,屬于服務(wù)端架構(gòu)的一些分享,如果只關(guān)心爬蟲本身的話钥庇,可以不用再往下閱讀了牍鞠。

我們把爬蟲功能都寫的差不多了,后面只要編寫相應(yīng)的路由评姨,能讓前端訪問到數(shù)據(jù)就好了难述。但是編寫一個沒那么差勁的服務(wù)端,還是需要我們深思熟慮的参咙。

合理分層

我看過一些前端同學(xué)寫的node服務(wù)龄广,經(jīng)常就會把系統(tǒng)所有的接口(router action)都寫到一個文件中,好一點的會根據(jù)模塊分幾個對于文件蕴侧。

但是如果我們接觸過其他成熟的后端框架择同、或者大學(xué)學(xué)過一些J2EE等知識,就會本能意識的進行一些分層:

model 數(shù)據(jù)層净宵。負責(zé)數(shù)據(jù)持久化敲才,通俗說就是連接數(shù)據(jù)庫,對應(yīng)數(shù)據(jù)庫表的實體數(shù)據(jù)模型;

service 業(yè)務(wù)邏輯層择葡。顧名思義紧武,就是負責(zé)實現(xiàn)各種業(yè)務(wù)邏輯。

controller 控制器敏储。調(diào)取業(yè)務(wù)邏輯服務(wù)阻星,實現(xiàn)數(shù)據(jù)傳遞,返回客戶端視圖或數(shù)據(jù)。

當(dāng)然也有些框架或者人會將業(yè)務(wù)邏輯service實現(xiàn)在controller中妥箕,亦或者是model層中滥酥。我個人認為一個稍微復(fù)雜的項目,應(yīng)該是單獨抽離出抽象的業(yè)務(wù)邏輯的畦幢。

比如在我這個爬蟲系統(tǒng)中坎吻,我將數(shù)據(jù)庫的添刪改查操作按model層對應(yīng)抽離出service,另外再將爬取頁面的服務(wù)宇葱、郵件推送的服務(wù)瘦真、用戶鑒權(quán)的服務(wù)抽離到對應(yīng)的service。

最終我們的api能夠設(shè)計的更加易讀黍瞧,整個系統(tǒng)也更加易拓展诸尽。

分層在koa上的實踐

如果是直接使用一個成熟的后端框架,分層這事我們是不用多想的雷逆。node這樣的框架也有弦讽,我之前介紹的我廠開源的api-mocker采用的egg.js污尉,也幫我們做好了合理的分層膀哲。

但是如果自己基于koa從零搭建一個服務(wù)端,在這方面上就會遇到一些挫折被碗。koa本身邏輯非常簡單某宪,就是調(diào)取一系列中間件(就是一個個function),來處理請求锐朴。官方自己提供的koa-router兴喂,即是幫助我們識別請求路徑,然后加載對應(yīng)的接口方法焚志。

我們?yōu)榱藚^(qū)分業(yè)務(wù)模塊衣迷,會把一些接口方法寫在同一個controller中,比如我的questionController負責(zé)處理問題相關(guān)的接口;topicController負責(zé)處理話題相關(guān)的接口酱酬。

那么我們可能會這樣編寫路由文件:

const Router = require('koa-router')

const router = new Router()

const question = require('./controller/question')

const topic = require('./controller/topic')

router.post('/api/question', question.create)

router.get('/api/question', question.get)

router.get('/api/topic', topic.get)

router.post('/api/topic/follow', topic.follow)

module.exports = router

我的question文件可能是這樣寫的:

class Question {

async get () {

// return data

}

async create () {

// create question and return data

}

}

module.exports = new Question()

那么問題就來了

單純這樣寫是沒有辦法真正的以面向?qū)ο蟮男问絹砭帉慶ontroller的壶谒。為什么呢?

因為我們將question對象的屬性方法作為中間件傳遞到了koa-router中,然后由koa底層來合并這些中間件方法膳沽,作為參數(shù)傳遞到http.createServer方法中汗菜,最終由node底層監(jiān)聽請求時調(diào)用。那這個this到底會是誰挑社,不進行調(diào)試陨界,或者查看koa與node源代碼,是無從得知的痛阻。但是無論如何方法調(diào)用者肯定不是這個對象自身了(實際上它會是undefined)菌瘪。

也就是說,我們不能通過this來獲取對象自身的屬性或方法阱当。

那怎么辦呢?有的同學(xué)可能會選擇將自身一些公共方法俏扩,直接寫在class外部缀皱,或者寫在某個utils文件中,然后在接口方法中使用动猬。比如這樣:

const error = require('utils/error')

const success = (ctx, data) => {

ctx.body = {

success: true,

data: data

}

}

class Question {

async get () {

success(data)

}

async create () {

error(result)

}

}

module.exports = new Question()

這樣確實ok啤斗,但是又會有新的問題—這些方法就不是對象自己的屬性,也就沒辦法被子類繼承了赁咙。

為什么需要繼承呢?因為有時候我們希望一些不同的controller有著公共的方法或?qū)傩耘チe個例子:我希望我所有的成功or失敗都是這樣的格式:

{

success: false,

message: '對應(yīng)的錯誤消息'

}

{

success: true,

data: '對應(yīng)的數(shù)據(jù)'

}

按照koa的核心思想,這個通用的格式轉(zhuǎn)化彼水,應(yīng)該是專門編寫一個中間件崔拥,在路由中間件之后(即執(zhí)行完controller里的方法之后)去做專門處理并response。

然而這樣會導(dǎo)致每有一個公共方法凤覆,就必須要加一個中間件链瓦。而且controller本身已經(jīng)失去了對這些方法的控制權(quán)。這個中間件是執(zhí)行自身還是直接next()將會非常難判斷盯桦。

如果是抽離成utils方法再引用慈俯,也不是不可以,就是方法多的話拥峦,聲明引用稍微麻煩些贴膘,而且沒有抽象類的意義。

更理想的狀態(tài)應(yīng)該是如剛才所說的略号,大家都繼承一個抽象的父類刑峡,然后去調(diào)用父類的公共相應(yīng)方法即可,如:

class AbstractController {

success (ctx, data) {

ctx.body = {

success: true,

data: data

}

}

error (ctx, error) {

ctx.body = {

success: false,

msg: error

}

}

}

class Question extends AbstractController {

async get (ctx) {

const data = await getData(ctx.params.id)

return super.success(ctx, data)

}

}

這樣就方便多了玄柠,不過如果寫過koa的人可能會有這樣的煩惱突梦,一個上下文ctx總是要作為參數(shù)傳遞來傳遞去。比如上述控制器的所有中間件方法都得傳ctx參數(shù)羽利,調(diào)用父類方法時宫患,又要傳它,還會使得方法損失一些可讀性铐伴。

所以總結(jié)一下撮奏,我們有如下問題:

controller中的方法無法調(diào)用自身的其他方法、屬性;

調(diào)用父類方法時当宴,需要傳遞上下文參數(shù)ctx畜吊。

解決它

其實解決的辦法很簡單,我們只要想辦法讓controller方法中的this指向?qū)嵗瘜ο笞陨砘福侔裞tx掛在到這個this上即可玲献。

怎么做呢?我們只要再封裝一下koa-router就好了,如下所示:

const Router = require('koa-router')

const router = new Router()

const question = require('./controller/question')

const topic = require('./controller/topic')

const routerMap = [

['post', '/api/question', question, 'create'],

['get', '/api/question', question, 'get'],

['get', '/api/topic', topic, 'get'],

['post', '/api/topic/follow', topic, 'follow']

]

routerMap.map(route => {

const [ method, path, controller, action ] = route

router[method](path, async (ctx, next) =>

controller[action].bind(Object.assign(controller, { ctx }))(ctx, next)

)

})

module.exports = router

大意就是在路由傳遞controller方法時,將controller自身與ctx合并捌年,通過bind指定該方法的this瓢娜。這樣我們就能通過this獲取方法所屬controller對象的其他方法。此外子類方法與父類方法也能通過this.ctx來獲取上下文對象ctx礼预。

但是bind之前我們其實應(yīng)該考慮以下眠砾,其他中間件以及koa本身會不會也干了類似的事,修改了this的值托酸。如何判斷呢褒颈,兩個辦法:

調(diào)試。在我們未bind之前励堡,在中間件方法中打印一下this谷丸,是undefined的話自然就沒被綁定。

看koa-router/koa/node的源代碼应结。

事實是刨疼,自然是沒有的。那我們就放心的bind吧鹅龄。

寫在最后

上述大概就是編寫這個小工具時揩慕,遇到的一些點,感覺可以總結(jié)的砾层。也并沒有什么技術(shù)難點漩绵,不過可以借此學(xué)習(xí)學(xué)習(xí)一些相關(guān)的知識贱案,包括網(wǎng)站安全肛炮、爬與反爬、宝踪、koa底層原理等等侨糟。

這個工具本身非常的個人色彩,不一定滿足大家的需要瘩燥。而且它在半年前就寫好了秕重,只不過最近被我挖墳?zāi)贸鰜砜偨Y(jié)。而且就在我即將寫完文章時厉膀,我發(fā)現(xiàn)知乎提示我的賬號不安全了溶耘。我估計是以為同一IP同一賬戶發(fā)起過多的網(wǎng)絡(luò)請求,我這臺服務(wù)器IP已經(jīng)被認為是不安全的IP了服鹅,在這上面登錄的賬戶都會被提示不安全凳兵。所以我不建議大家將其直接拿來使用。

當(dāng)然企软,如果還是對其感興趣庐扫,本地測試下或者學(xué)習(xí)使用,還是沒什么大問題的⌒瓮ィ或者還有更深的興趣的話铅辞,可以自己嘗試去繞開知乎的安全策略。

為此我在這里為大家準(zhǔn)備了一些資料萨醒,希望對大家有幫助斟珊,需要的話可以加我QQ:3300863615? 免費領(lǐng)取哦


?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市富纸,隨后出現(xiàn)的幾起案子倍宾,更是在濱河造成了極大的恐慌,老刑警劉巖胜嗓,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件高职,死亡現(xiàn)場離奇詭異,居然都是意外死亡辞州,警方通過查閱死者的電腦和手機怔锌,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來变过,“玉大人埃元,你說我怎么就攤上這事∶恼” “怎么了岛杀?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長崭孤。 經(jīng)常有香客問我类嗤,道長,這世上最難降的妖魔是什么辨宠? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任遗锣,我火速辦了婚禮,結(jié)果婚禮上嗤形,老公的妹妹穿的比我還像新娘精偿。我一直安慰自己,他們只是感情好赋兵,可當(dāng)我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布笔咽。 她就那樣靜靜地躺著,像睡著了一般霹期。 火紅的嫁衣襯著肌膚如雪叶组。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天经伙,我揣著相機與錄音扶叉,去河邊找鬼勿锅。 笑死,一個胖子當(dāng)著我的面吹牛枣氧,可吹牛的內(nèi)容都是我干的溢十。 我是一名探鬼主播,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼达吞,長吁一口氣:“原來是場噩夢啊……” “哼张弛!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起酪劫,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤吞鸭,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后覆糟,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體刻剥,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年滩字,在試婚紗的時候發(fā)現(xiàn)自己被綠了造虏。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡麦箍,死狀恐怖漓藕,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情挟裂,我是刑警寧澤享钞,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站诀蓉,受9級特大地震影響栗竖,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜交排,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一划滋、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧埃篓,春花似錦、人聲如沸根资。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽玄帕。三九已至部脚,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間裤纹,已是汗流浹背委刘。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工丧没, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人锡移。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓呕童,卻偏偏與公主長得像,于是被迫代替她去往敵國和親淆珊。 傳聞我的和親對象是個殘疾皇子夺饲,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內(nèi)容