python 爬虫 异步 子进程 ajax 音乐 爬取孤竹翊的歌

孤竹翊算是我最喜欢的唱歌的人了。喜欢了大概有5年,快6年了吧从初中听见《蜀相》以来就一直好喜欢她的歌。
建议你,可以一边动手一边听歌。这样最好了。
嗯,说说如何批量下载她的歌吧,过一段时间准备做一个webapp。
于是先来爬取她的歌好了。
首先,说说,看完这个东西会得到什么好了(如果你不会的话)。

1.应对异步加载

2.应对JS代码

3.简单的前端知识

4.使用抓包工具

5.使用异步编程以及子进程

6.处理内存泄露(如果发生)

7.听竹子的歌


5sing的播放器

一般来说都是用 <audio>标签实现的音频文件播放。而,为了美化,通常不会让这个东西给用户看见。
所以我们来找一找这个 <audio>

image.png

找到咯,然后呢。
把src属性找出来就好了。
话不多说了,直接看看代码吧。

from requests import get,post
from bs4 import BeautifulSoup
if __name__ == '__main__':
    a=get('http://5sing.kugou.com/echoriath/fc/1.html').text
    print(a)

但是呢,看看response的html。


没有找到audio

所以说,整个html中是没有audio存在的。
这个标签是后来才创建出来的哦,还有,src属性也是后来才加上的哦。


第二部分,看看 audio 标签是被什么创建的吧。
这里用到抓包工具fiddler 你也可以用其他的东西,但是我用这个。
这方面的教程有很多,随便找一下就可以了。
我在这里只说说一些坑

1.关于FILLDER的HTTPS

2.记得要清除浏览器缓存(要不然浏览器不会发出请求的,它会保存静态文件),还有就是设置FILLDER,要它禁止缓存。

好了,现在把他们整个5sing的前端代码全部下载下来就好了(别下图片,太多了)。

下载好的一部分

大概有50多个吧。
如果你没有装vsode,那么应该装上它(它很美,很好看,也很方便),还有要装node.js
然后,你要用npm下载一个 js-beautify
否则你看到的js代码都是一团乱的,而不是接下来整理的效果。
使用vsode的全局搜索功能(ctrl+shift+f)找到 audio

我的搜索结果

看,结果出来了。

这一行代码就是使得audio出现的东西了(得会会被jquery插入html)


关注这里

唯一需要关注的东西是,"src="=source 这句,如果可以找到source就是找到了下载地址了。

一路往上看,啊,发现,它们是在一个函数了里面。


image.png

看看谁调用了这个函数吧。


留心source参数

发现了,上一段语句是在另一个函数里面的。


留心source参数

那么是谁又在调用它呢。
对了,记得看好一点,它们的参数,我们找到最上一层,就可以找到source 这个变量的来源了。


留心file参数

好了,接下来添加两个语句


image.png

输出file到底是什么,还有就是查看调用栈。嗯,要用,fiddler才行。自己看如何做到自己看教程吧。


image.png

这时候,另外一个JS文件出现了。就是 listen.min.newm.js
好了。记得要用js-beautify啊。
看到1946行吧


image.png

调用了之前(并不是,我跳过了两个函数,照着之前方法向上找就好了。)说的函数。看传进去的参数。
只要得到了songObj 可以说事情就了了。


关注curSongObj

下面就解决了。


出现了

按照之前的方法,全局搜索,找到了。globals.js文件。
但是里面没有 globals.ticket 这个东西,它在


html 文件里面。
所以很无聊的加密了一下。我们把加密过程复制下来就好了。
如果是没有前端经验的人看了,以后会觉得奇怪,为什么JS文件有那么多个,可以互相引用呢。其实浏览器都支持了 AMD 和 CommonJS 这两个东西,使得他们可以相互引用,其中,AMD(也就是5SING代码中的)是异步的(现在html<script>标签也支持异步了加上 async就可以了)。做爬虫的话,前端知识是很必要的,当然后端也是如此,还要懂得密码学的东西和人工智能(这个我是完全不懂了)
感谢5sing的程序员们没有写 ‘au'+'dio’要不然就搜索不到了。当然还有最后的三个办法(骑驴找马吧算是)————用火焰图查看调用栈,不过这个很费力气就是了。
或者是另外自己定义一个子元素改变事件。
最后是直接改他们的JQuery源码,加入console.trace查看调用栈以及触发创建事件。总之放个钩子。
定义事件的实现方式会在其他文章里面写。


第三部分
把加密有关的文件都放在一个js文件里面就好了。然后,让它接受输入。
看js代码

{
    _keyStr: "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=",
    encode:function(input){
        var output = "";
        var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
        var i = 0;
        input = globals.base64._utf8_encode(input);
        while (i < input.length) {
            chr1 = input.charCodeAt(i++);
            chr2 = input.charCodeAt(i++);
            chr3 = input.charCodeAt(i++);
            enc1 = chr1 >> 2;
            enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
            enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
            enc4 = chr3 & 63;
            if (isNaN(chr2)) {
                enc3 = enc4 = 64;
            } else if (isNaN(chr3)) {
                enc4 = 64;
            }
            output = output +
            globals.base64._keyStr.charAt(enc1) + globals.base64._keyStr.charAt(enc2) +
            globals.base64._keyStr.charAt(enc3) + globals.base64._keyStr.charAt(enc4);
        }
        return output;
    },
    decode:function(input){
        var output = "";
        var chr1, chr2, chr3;
        var enc1, enc2, enc3, enc4;
        var i = 0;
        input = input.replace(/[^A-Za-z0-9\+\/\=]/g, "");
        while (i < input.length) {
            enc1 = globals.base64._keyStr.indexOf(input.charAt(i++));
            enc2 = globals.base64._keyStr.indexOf(input.charAt(i++));
            enc3 = globals.base64._keyStr.indexOf(input.charAt(i++));
            enc4 = globals.base64._keyStr.indexOf(input.charAt(i++));
            chr1 = (enc1 << 2) | (enc2 >> 4);
            chr2 = ((enc2 & 15) << 4) | (enc3 >> 2);
            chr3 = ((enc3 & 3) << 6) | enc4;
            output = output + String.fromCharCode(chr1);
            if (enc3 != 64) {
                output = output + String.fromCharCode(chr2);
            }
            if (enc4 != 64) {
                output = output + String.fromCharCode(chr3);
            }
        }
        output = globals.base64._utf8_decode(output);
        return output;
    },
    _utf8_encode: function (input) {
        input = input.replace(/\r\n/g,"\n");
        var utftext = "";
        for (var n = 0; n < input.length; n++) {
            var c = input.charCodeAt(n);
            if (c < 128) {
                utftext += String.fromCharCode(c);
            } else if((c > 127) && (c < 2048)) {
                utftext += String.fromCharCode((c >> 6) | 192);
                utftext += String.fromCharCode((c & 63) | 128);
            } else {
                utftext += String.fromCharCode((c >> 12) | 224);
                utftext += String.fromCharCode(((c >> 6) & 63) | 128);
                utftext += String.fromCharCode((c & 63) | 128);
            }

        }
        return utftext;
    },
    _utf8_decode: function (input) {
        var string = "";
        var i = 0;
        var c = c1 = c2 = 0;
        while ( i < input.length ) {
            c = input.charCodeAt(i);
            if (c < 128) {
                string += String.fromCharCode(c);
                i++;
            } else if((c > 191) && (c < 224)) {
                c2 = input.charCodeAt(i+1);
                string += String.fromCharCode(((c & 31) << 6) | (c2 & 63));
                i += 2;
            } else {
                c2 = input.charCodeAt(i+1);
                c3 = input.charCodeAt(i+2);
                string += String.fromCharCode(((c & 15) << 12) | ((c2 & 63) << 6) | (c3 & 63));
                i += 3;
            }
        }
        return string;
    }
};
以上代码不完整。还要加上输入的东西

看python代码

from requests import get
import re
import json
import subprocess

text=get('http://5sing.kugou.com/fc/16236596.html').text

globalsReExp=re.compile(r'("ticket"\s*:\s*"(?:\w|=)+")')

aj=json.loads('{%s}'%(globalsReExp.search(text).group()))
aac=subprocess.Popen('node fortest.js',stdin=subprocess.PIPE,stdout=subprocess.PIPE,cwd='你的路径')
aac.stdin.write(aj['ticket'].encode())
aac.stdin.close()
ccc=aac.wait()
resulet =aac.stdout.read()
print(resulet.decode())

如何获取播放列表是很简单的,就不说了。


好了,准备第四部分,用协程或者多进程加快爬虫速度(其实,我无论怎么搞都是一样的,因为我用的是校园网,只有2M)
按照直接的写法是这样的。

from requests import get
import re
import json
import subprocess
from lxml import etree
from time import clock


def getList(url):
    text = get(url).text
#可以不要编码,这样速度也会快,反正都可以找到的。
    html = etree.HTML(text)
    for each in html.xpath('//div[@class="song_list"]/ul/li/strong[@class="lt list_name"]/a'):
        name = each.xpath('./text()')
        url = each.xpath('./@href')
        yield url

def getTicket(url):
    text = get(url).text
#可以不要编码,这样速度也会快,反正都可以找到的。
    globalsReExp = re.compile(r'("ticket"\s*:\s*"(?:\w|=|\+)+")')
    aj = json.loads('{%s}' % (globalsReExp.search(text).group()))
    aac = subprocess.Popen('node fortest.js', stdin=subprocess.PIPE, stdout=subprocess.PIPE,
                           cwd='你的JS路径')
    aac.stdin.write(aj['ticket'].encode())
    aac.stdin.close()
    aac.wait()
    resulet = aac.stdout.read()
    return resulet.decode()

if __name__ == '__main__':
    startTime=clock()
    songList=getList('http://5sing.kugou.com/echoriath/fc/1.html')
    for each in songList:
        print(getTicket(each[0]))
    print(clock()-startTime)

输出结果

{"songName":"流光断代史【词:suixinsuiyuan】","songUrl":"http://data.5sing.kgimg.com/G108/M04/12/01/rA0DAFn4VFmAY9YlAKjELjKjvb4408.mp3"}

{"songName":"愿你 171019","songUrl":"http://data.5sing.kgimg.com/G107/M0A/17/03/S5QEAFnyhnqACGgKAFa5LA6wRhk579.mp3"}

{"songName":"颂歌【词:雨霁天青】","songUrl":"http://data.5sing.kgimg.com/G086/M0A/1C/15/9oYBAFlqL-CAIobGAM9awCoY2WY199.mp3"}

{"songName":"初恋之歌【词:SUIXINSUIYUAN】","songUrl":"http://data.5sing.kgimg.com/G113/M08/06/13/sQ0DAFlguV2AbTYgAIn_u1mOrsA360.mp3"}

{"songName":"我说的不朽【词:雨霁天青】","songUrl":"http://data.5sing.kgimg.com/G088/M05/1C/0D/OJQEAFjE1EOAUnToALRjsSnvyn8627.mp3"}

{"songName":"WZ · 咲","songUrl":"http://data.5sing.kgimg.com/G091/M04/11/13/-4YBAFi6t8CAP3mpAKWEhYPbu7A148.mp3"}

{"songName":"……【词:W君】","songUrl":"http://data.5sing.kgimg.com/G065/M00/04/09/IZQEAFfbnL-AUqR3ADKG2XxygvI801.mp3"}

{"songName":"在路上【词:稗子】","songUrl":"http://data.5sing.kgimg.com/G070/M08/09/17/5oYBAFdqd4SAWMEIAG6SUdsnB24361.mp3"}

{"songName":"有一天【SS·贵鬼 | 词:suixinsuiyuan】","songUrl":"http://data.5sing.kgimg.com/G058/M0A/04/1F/eg0DAFbF1H2AIQ6FAH2notXT3Kc138.mp3"}

{"songName":"(黑)历史 · 相憾(记蜀丞相)【词:浅措】","songUrl":"http://data.5sing.kgimg.com/G053/M00/01/15/1YYBAFbEPJSAbATiADhb2yA0M4Y075.mp3"}

11.898513153268777

花了12秒,额,貌似还算快了吧。其实我前几次都是20秒以上的,可能是缓存了还是网络变好了什么的快了。
然后,要知道,计算是费时间的。所以,要是可以一边计算一边下载就好了,这样就不会浪费时间了啊。
下面就是 asyncio 咯。3.4以后才有啊,3.5之后才出现了async/await (JS现在也可以了,所以有人说JS学了PYTHON,现在以及不用加分号了,import关键字也是更像PYTHON而不是JAVA)关键字,之前貌似也有twisted可以用,但是我不会啊。额,常用的scrapy框架就是twisted搞的,所以很多时候都装不上,主要因为没有装py3的twisted。
细心的人发现了,我在getList 里面用的是 yield ,这个函数是个生成器函数了。说白了就是会在执行的时候中断,执行其他函数了。额,关于asyncio 的内容还是请百度,教程很多的。要是有过JS经验的人会听见一个叫做EVENTLOOP的东西大概会很耳熟了。

import asyncio
import time
import string
from requests import get
async def xiazai(url):
    response = get(url)
    print(response.statusCode)

async def shuchu(a):
    await download('http://5sing.kugou.com/echoriath/fc/1.html')
    return
async def ganhuo(zifu):
    res= await shuchu(zifu)
    print(res)

if __name__ == '__main__':
    a=time.time()
    # for each in range(0,5):
    #     get('http://5sing.kugou.com/echoriath/fc/1.html')
    #     time.sleep(1)
    loop=asyncio.get_event_loop()
    tasks=[]
    for eachChar in ['1','3','5','7','9']:
        tasks.append(asyncio.ensure_future(ganhuo(eachChar)))
    loop.run_until_complete(asyncio.wait(tasks))
    print(time.time()-a)

试用试下,发现其实呢,get这个函数其实是无法异步的哦, 会一直到下载完了才会下一个的,还有,subprocess也不是异步(但是它可以,看下面)。
所以如果不是用aiohttp的话就不行了啊。所以不可以用requests咯。
下载aiohttp吧,然后自己看教程就好了。

import asyncio
import time
import string
from requests import get
import subprocess
import aiohttp
async def xiazai(url):
    async with aiohttp.ClientSession() as session:
        async with session.get('http://5sing.kugou.com/echoriath/fc/1.html') as r:
            aaa=await r.read()
            print(aaa)
        session.close()

async def shuchu(a):
    await download('http://5sing.kugou.com/echoriath/fc/1.html')
    return
async def ganhuo(zifu):
    res= await shuchu(zifu)
    print(res)



if __name__ == '__main__':
    a=time.time()
    # for each in range(0,5):
    #     get('http://5sing.kugou.com/echoriath/fc/1.html')
    #     time.sleep(1)
    loop=asyncio.get_event_loop()
    tasks=[]
    for eachChar in ['1','3','5','7','9']:
        tasks.append(asyncio.ensure_future(xiazai(eachChar)))
    loop.run_until_complete(asyncio.wait(tasks))
    loop.close()
    print(time.time()-a)

只用了1秒多吧。中间就算用了 asyncio.sleep(1)模拟也就是2秒多的样子吧(我猜的)。
然后,关键是实现一个东西。subprocess.Popen这个玩意要实现异步才可以,因为这个是io以外时间用的最多的了(不过貌似,node.js比python性能好很多诶,如果用python实现里面的js代码肯定更慢了。)
稍加改造,就得到了一个,异步IO的爬虫了。
接下来看看如何做到subprocess.Popen的异步吧。


第五部分了
asyncio.create_subprocess_exec
这个可以实现子进程的异步操作。

如果在windows 下,你得这样做才行了。因为,windows的select只可以用在socket上,而对于eventloop的默认实现方式是基于select的,在windos上得要用基于proactor的才行咯。可以去看下关于JS的事件循环和reactor和proactor以及一些关于系统的内容,网络上都有,就不多说了。

import sys,asyncio
from subprocess import PIPE
import subprocess
import sys
async  def acfun():
    sp=await asyncio.create_subprocess_shell('node acfun.js',cwd='D:\\PythonProject\\debug',stdin=subprocess.PIPE, stdout=subprocess.PIPE)
    sp.stdin.write('cca'.encode())
    sp.stdin.close()
    await sp.wait()
    acc=sp.stdout
    aa=await acc.read(3)
    print(aa)


if __name__=='__main__':
    if sys.platform =='win32':
        loop = asyncio.ProactorEventLoop()
        asyncio.set_event_loop(loop)
        loop.run_until_complete(acfun())
        loop.close()

这样子我们就实现了异步的子进程,以及异步的网络io,最后是下载歌曲了,也要异步或者说吧所有的songUrl收集起来在一起下载。


第六部分了,这是。会把前面的东西全部组合起来用。是不是对python的异步有点感觉了呢。其实不妨试试看用JS的爬虫啊,解析JS,肯定方便啊,而且人家速度也快,回调写起来比较方便了,以及还有一个promise。

import asyncio
import aiohttp
import subprocess
import json
from requests import get
import re
from lxml import etree

BASEURLOFPAGE='http://5sing.kugou.com'
async def downloader(url):
    async with aiohttp.ClientSession() as session:
        async with session.get(url) as result:
            html = await result.read()
        session.close()
    return html

async def analysis(text):
    html = etree.HTML(text)
    songLinks = []
    xpathExp='//div[@class="song_list"]/ul/li/strong[@class="lt list_name"]/a'
    for each in html.xpath(xpathExp):
        name = each.xpath('./text()')[0]
        print(name)
        url = each.xpath('./@href')[0]
        spReturn = await jsCodeExec(url)
        songLinks.append(json.loads(spReturn.decode()))
    return songLinks

def getNextPage(html):
    tree = etree.HTML(html)
    xpathExp='//span[@class="page_list"]/a[text()="下一页"]/@href'
    suffix = tree.xpath(xpathExp)
    if(len(suffix)==0):
        return False
    href = ''.join([BASEURLOFPAGE,suffix[0]])
    return  href

async def jsCodeExec(url):
    globalsReExp = re.compile(b'("ticket"\s*:\s*"(?:\w|=)+")')
    content = await downloader(url)
    aj = json.loads('{%s}' % ((globalsReExp.search(content).group())).decode())
    sp= await asyncio.create_subprocess_shell('node fortest.js',cwd='[你的路径]',
                                              stdin=subprocess.PIPE, 
                                              stdout=subprocess.PIPE)
    sp.stdin.write(aj['ticket'].encode())
    sp.stdin.close()
    await sp.wait()
    spOut = sp.stdout
    songInfo = await spOut.read()
    return songInfo

async def instuctor(entry):
    allUrls=[]
    html = get(entry).text
    # html = html.decode()
    while True:
        nextPageUrl = getNextPage(html)
        print(nextPageUrl)
        if(nextPageUrl):
            urlsHub = await analysis(html)
            allUrls.extend(urlsHub)
            html = get(nextPageUrl).text
        else:
            urlsHub=await analysis(html)
            allUrls.extend(urlsHub)
            break
    print('结束了')
    print(allUrls)
    return allUrls


if __name__ == '__main__':

    ENTRY='http://5sing.kugou.com/echoriath/fc/1.html'
    loop = asyncio.ProactorEventLoop()
    asyncio.set_event_loop(loop)
    loop.run_until_complete(instuctor(ENTRY))
    loop.close()

要用倒是可以的但是,事实上没有任何的用处,并不会变快的(我猜大概不会),而且没有应对反爬的任何手段例如,ip池,控制速度(我就不用了,反正网速太慢了。),和UA以及请求头之类的。而且这个东西除了输出名字以外没有任何的log产生,除了问题也不好找。

可以看出来一点,我一直在用循环一步一步往下走,虽然,用了异步但是,其实我的loop里面只有一个任务而已。我再怎么await都不会产生任何效果来着。你可以看到,我在使用 aiohttp的时候也用了requests.get,这是因为我决定,一个一个网页地走(没原因,网速慢而已)
如果要真的产生效果,要这样子做。

async def analysis(text):
    html = etree.HTML(text)
    songLinks = []
    xpathExp='//div[@class="song_list"]/ul/li/strong[@class="lt list_name"]/a'
    jsTasks=[]

    for each in html.xpath(xpathExp):
        name = each.xpath('./text()')[0]
        url = each.xpath('./@href')[0]
        jsTasks.append(jsCodeExec(url))
        # spReturn = await jsCodeExec(url)
        # songLinks.append(json.loads(spReturn.decode()))
    dones, pendings = await asyncio.wait(jsTasks)
    for each in dones:
        songLinks.append(each.result())
    return songLinks

一共是12秒(2M网络下)。
当然还可以更快,但是我的网络就这样了,刚才听歌都卡住了。
基于同步的方式(单线程/进程),大概是45秒(和网络也有关系吧),而且一旦在下载的时候卡住就麻烦了,花费的时间就更多了。


第七部分咯。关于内存的事情
也许已经发现了吧,我在instructor这个函数里面用的是循环来着的,而不是去递归,虽然我本身比较喜欢递归,但是,如果说爬取的网站深度很大,而且每一页都是好几百个链接怎么办啊。基于Python 解释器的特点,会把上一层的东西保存下来的,所以会产生无法回收的垃圾,然后产生内存溢出。

刚才用了11s就爬完全部了,CPU一直是100%(平时都是10%左右的)。好快啊。我以前低估我的电脑了呵呵,虽然很卡,鼠标有点卡住了。当然,不是上面那段代码,是修改过了的。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 212,383评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,522评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,852评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,621评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,741评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,929评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,076评论 3 410
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,803评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,265评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,582评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,716评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,395评论 4 333
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,039评论 3 316
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,798评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,027评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,488评论 2 361
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,612评论 2 350

推荐阅读更多精彩内容