facebook社交网络分析

1 数据介绍

1.1 数据背景

该数据集来源于斯坦福大型网络数据集网站(http://snap.stanford.edu/data/ego-Facebook.html),由facebook的朋友列表组成,facebook上用户间的社交网络,共有节点数4039个,边缘数88233条。

1.2 数据预处理

本社交网络分析案例是基于R 语言程序实现的。原始数据为.txt文件,读入R语言中,展示如下:

建立社交网络关系图:

代码如下:

g <- graph.data.frame(facebook)#加载数据框jpeg(filename='facebook.jpg',width=2000,height=2000,units='px')#生成图片,大小是2000*2000px

plot(g,   

vertex.size=2, #节点大小

layout=layout.fruchterman.reingold, #布局方式

vertex.shape='circle', #带边框

vertex.label.cex=0.5, #节点字体大小

vertex.label.color='black', #节点字体颜色

edge.arrow.size=0) #连线的箭头的大小

dev.off() #关闭图形设备,将缓冲区中的数据写入文件

建立某个人的社交网络关系图,以其中某4个人为例:


建立某个用户的二层社交网络关系图:

同时建立某两个人的社交网络关系图:

根据联系人的多少决定节点的大小和色彩,连线设成弧线,如下图:

      邻接矩阵即反映节点之间是否有连边的方形矩阵,维度为结点的个数,如果两个结点之间有连边,则矩阵中相应的位置为1,没有连边则为0。无向网络的邻接矩阵是对称矩阵,而有向网络的邻接矩阵是非对称矩阵。将数据转换成邻接矩阵如下:

代码如下:

g1<-graph.data.frame(as.matrix(facebook))

g2<-get.adjacency(g1,sparse=FALSE) ###转换为邻接矩阵

2 统计特征分析

2.1 网络基本属性

我们发现网络的网络的节点个数为4029,边条数为88233,为有向网络。网络密度用来形容网络的结构复杂程度,越大说明网络越复杂,网络越能够放在一块,facebook社交网络的网络密度为0.00540992,说明该网络不是很容易聚一块儿。该网络的直径为17,整个网络的平均距离为4.337746。

2.2 度分布

节点的度:度越大,该节点越重要,就facebook朋友间社交网络而言,度越大,表示与该用户相联系的用户越多。经计算,我们发现,最小的度为1,节点数为75,最大的度为1045,节点数为1。该facebook社交网络为有向网络,故又分为入度和出度。就入度而言,最小入度为2,节点数为1。最大入度为1134,节点数为5。就出度而言,最小出度为1,节点数为16,最大出度为1134,节点数为6。

度分布:

degree.distribution(g)

plot(degree.distribution(g))

入度与出度:

2.3 网络聚类系数

网络聚类系数可以衡量网络中关联性如何,值越大代表交互关系越大。说明网络越复杂,越能放在一块儿聚类。Facebook社交网络的网络聚类系数为0.5191893;网络的平均聚类系数为0.6169473;每个节点的聚类系数如下图(截取部分):

2.4 节点重要性

(1)点的中心度

用于描述在某个点上,有多少条线,强调某点单独的价值。

(截取部分如下):

(2)中间中心度

代表最短距离是否都经过该点,如果都经过说明这个点很重要,其中包括线的中心度。强调点在其他点之间调节能力,控制能力指数,中介调节效应。

点的中间中心度(截取部分如下):


线的中间中心度(截取部分如下):

(3)接近中心度

接近中心度,用于描述该点与网络中其他点距离之和的倒数,越大说明越在中心,越能够很快到达其他点,强调点在网络的价值。

截取部分如下:

(4)特征向量中心度

根据相邻点的重要性来衡量该点的价值。首先计算邻接矩阵,然后计算邻接矩阵的特征向量。强调点在网络的价值,并且比接近中心度厉害的是,点价值是根据近邻点来决定的。

截取部分如下:

(5)排序

将节点中心度、接近中心度、中间中心度、特征向量中心度排序如下:

我们发现,在facebook社交网络中,拥有低“特征向量中心度”和高“中间性”的人是很重要的联系人,而拥有高“特征向量中心度”和低“中间性”的人与重要的人有关联。

2.5社区结构

社群划分跟聚类差不多,社群内边密度要高于社群间边密度,社群内部连接相对紧密,各个社群之间连接相对稀疏。

1、 社区划分

上图图1为我们将社区划分为8个,图2为在划分完的社区中,社区成员1构成的网络关系图。

2、 基于随机游走的社区划分

随机游走,利用距离相似度,用合并层次聚类方法建立社群,其特点是运行时间短,但是效果不是特别好,也会出现某类巨多。从中我们可以看出facebook社交网络被划分为了68个社区。

从图中可以直观地看出好友网络已经被划分为若干相对独立的子群

查看不同子群中的成员如下,以其中两个子群为例:

查看起中介作用的好友,画出散点图如下:

3、 基于点连接的社区发现

点连接为某点与某社群有关系就是某社群的,通常社区划分效果最差,常常是某一大类超级多。对于该facebook社交网络来说,因其为有向图,所以分为强关联与弱关联,两点间有双向连线为强关联,有单向连线,则为弱关联。


3 链接预测

我选择的是基于邻居的相似度指标进行链路预测。

根据基于邻居的8种相似度链路预测算法的AUC值的对比,RA指标的AUC值在各种情况下都表现较优,因此选择RA指标来进行链路预测。

此处考虑最简单的情况,每一个起传递作用的中间节点都有一个单位资源,将资源等可能的分配给它所有邻居节点,因此相似度定义为:节点i从j接受到的资源总量(同样为节点j从i接收到的资源总量)。计算公式为:

算法过程:

(1)首先按照8:2的比例划分训练集和测试集,并得到训练集邻接矩阵和测试集邻接矩阵;

(2)得到未观测边集、测试边集和未存在边集;

(3)得到每个训练集中,节点的邻居节点;

(4)得到未观测边连接的两节点的公共邻居节点,并计算未观测边得分r,将得分按降序排序,取前10,即为尚未存在的边中可能会首先产生连接的10条边。

(5)评估精度(AUC),AUC值约为0.50。

附代码:

install.packages("igraph")

library(igraph)

library("Matrix")

demo(package="igraph") #查看igraph子项目

demo(package="igraph", community) #查看igraph子项目中community示例

facebook<-read.table("/Users/zhangyuhao/Desktop/facebook.txt",header = T)

g <- graph.data.frame(facebook)#加载数据框

head(facebook)#看数据表的前6行

tail(facebook)#看数据表的后6行

data1 = as.matrix(facebook)

g1<-graph.data.frame(as.matrix(facebook))

g2<-get.adjacency(g1,sparse=FALSE) ###转换为邻接矩阵

###建立社交网络图

jpeg(filename='facebook.jpg',width=2000,height=2000,units='px')#生成图片,大小是2000*2000px

plot(g,

    vertex.size=2, #节点大小

    layout=layout.fruchterman.reingold, #布局方式

    vertex.shape='circle', #带边框

    vertex.label.cex=0.5, #节点字体大小

    vertex.label.color='black', #节点字体颜色

    edge.arrow.size=0) #连线的箭头的大小

#关闭图形设备,将缓冲区中的数据写入文件

dev.off()

####关系图中某人或某几个人的关系图

#某个人的关系图:

jpeg(filename='mem1_1.jpg',width=2000,height=2000,units='px')

gn<-graph.neighborhood(g, order=1)

plot(gn[[1]],

    vertex.size=5, #节点大小

    layout=layout.fruchterman.reingold, #布局方式

    vertex.shape='circle', #带边框

    vertex.label.cex=2, #节点字体大小

    vertex.label.color='black', #节点字体颜色

    )

dev.off()


jpeg(filename='mem1_2.jpg',width=2000,height=2000,units='px')

gn<-graph.neighborhood(g, order=1)

plot(gn[[2]],

    vertex.size=5, #节点大小

    layout=layout.fruchterman.reingold, #布局方式

    vertex.shape='circle', #带边框

    vertex.label.cex=2, #节点字体大小

    vertex.label.color='black', #节点字体颜色

)

dev.off()

jpeg(filename='mem1_100.jpg',width=2000,height=2000,units='px')

gn<-graph.neighborhood(g, order=1)

plot(gn[[100]],

    vertex.size=5, #节点大小

    layout=layout.fruchterman.reingold, #布局方式

    vertex.shape='circle', #带边框

    vertex.label.cex=1, #节点字体大小

    vertex.label.color='black', #节点字体颜色

)

dev.off()

jpeg(filename='mem1_4039.jpg',width=2000,height=2000,units='px')

gn<-graph.neighborhood(g, order=1)

plot(gn[[4039]],

    vertex.size=5, #节点大小

    layout=layout.fruchterman.reingold, #布局方式

    vertex.shape='circle', #带边框

    vertex.label.cex=2, #节点字体大小

    vertex.label.color='black', #节点字体颜色

)

dev.off()

#某个人的两层关系图:

jpeg(filename='mem2_2.jpg',width=2000,height=2000,units='px')

gn<-graph.neighborhood(g, order=2)

plot(gn[[2]],

    vertex.size=5, #节点大小

    layout=layout.fruchterman.reingold, #布局方式

    vertex.shape='circle', #带边框

    vertex.label.cex=2, #节点字体大小

    vertex.label.color='black', #节点字体颜色

    vertex.label.color ='pink',

)

dev.off()

#某个人的两层关系图:

jpeg(filename='mem2_10.jpg',width=2000,height=2000,units='px')

gn<-graph.neighborhood(g, order=2)

plot(gn[[10]],

    vertex.size=5, #节点大小

    layout=layout.fruchterman.reingold, #布局方式

    vertex.shape='circle', #带边框

    vertex.label.cex=2, #节点字体大小

    vertex.label.color='black', #节点字体颜色

    )

dev.off()

#某两个人的关系图:

jpeg(filename='mem4.jpg',width=2000,height=2000,units='px')

gn<-graph.neighborhood(g, order=1)

plot(gn[[2]]+gn[[3]], layout=layout.fruchterman.reingold)

dev.off()

#根据联系人的多少决定节点的大小和色彩,连线设成弧线

source("http://michael.hahsler.net/SMU/ScientificCompR/code/map.R")

E(g)$curved <- 0.2 #将连线设成弧线,数值越大弧线越弯

jpeg(filename='mem5.jpg',width=2000,height=2000,units='px')

layout=layout.fruchterman.reingold

plot(g,

  layout=layout,

    vertex.size=map(degree(g),c(1,20)),

    vertex.color=map(degree(g),c(1,20)))

dev.off()

#####统计特征分析

###网络基本属性

V(g) #V(g)可以用来查看网络g的节点

E(g) #E(g)可以用来查看网络g的边

length(V(g)) #节点个数

length(E(g)) #边条数

is.igraph(g) #判断

is.directed(g) #判断是否有向图

graph.density(g) #网络密度

dm <- diameter(g)#计算网路的直径,即MAX(任意两个节点之间的最短路径)

dm

average.path.length(g)#计算整个网路的平均距离(任意两节点直接的最短路径的平均值)

clusters(g)$no#孤立点个数

edge.connectivity(g)

graph.adhesion(g)

reciprocity(g)

shortest.paths(g)#两点间的最短路径

###网络聚类系数

transitivity(g,type = "global") #网络的聚类系数

transitivity(g,type = "average") #网络的平均聚类系数

transitivity(g,type = "local") #每个节点的聚类系数

###度分布

degree<-degree(g,mode="all") #统计节点的度

table(degree)

degree.distribution(g)

plot(degree.distribution(g), xlab="node degree")

lines(degree.distribution(g))

degree(g,mode="in") #mode=in点入度;out=点出度;total点度中心度,三者统称绝对点中心度

degree(g,mode="out") #mode=in点入度;out=点出度;total点度中心度,三者统称绝对点中心度

degree(g,normalized = T) #相对点中心度=绝对点中心度/最大度数(可以作为不同网络结构的比较,相对数与绝对数的区别)

###节点的重要性

#- 拥有较高出/入度数的节点也拥有较高的“度中心性”

#- 与其他节点之间有短路径的节点拥有较高的“密集中心性”

#- 与其他节点对之间有最短路径的节点拥有较高的“中间性”

#- 连接了许多中心性较高节点的节点拥有较高的“特征向量中心性”

#- 本地簇系数意味着相邻节点的互联性

degree(g) #点的中心度

closeness(g)#接近中心度

betweenness(g,normalized = T) #点的中间中心度

edge.betweenness(g) #线的中间中心度

evcent(g)$vector #特征向量中心度

transitivity(g, type="local") #本地簇

order(degree(g))[1:10]

order(closeness(g))[1:10]

order(betweenness(g))[1:10]

order(evcent(g)$vector)[1:10]

order(degree(g),decreasing = T)[1:10]

order(closeness(g),decreasing = T)[1:10]

order(betweenness(g),decreasing = T)[1:10]

order(evcent(g)$vector,decreasing = T)[1:10]

###社区结构

#1、设定社区的数目

sg1 <- cluster_spinglass(g, spins=8, gamma=1.0) #spins是社区的数目

jpeg(filename='dolphins_commu9.jpg',width=800,height=800,units='px')

layout=layout.fruchterman.reingold

plot(g, layout=layout, vertex.size=5, vertex.color= rainbow(10, .8, .8, alpha=.8)[sg1$membership],)

dev.off()

#画出某一社区,画出上面社区中,membership为1的社区。

sg1 <- cluster_spinglass(g, spins=8, gamma=1.0)

jpeg(filename='mem10.jpg',width=2000,height=2000,units='px')

layout=layout.fruchterman.reingold

subg <- induced.subgraph(g, which(membership(sg1)==1))

plot(subg,

    layout=layout,

    vertex.size=5,

    vertex.color= 1,)

dev.off()

###2、基于随机游走的社会划分

com = walktrap.community(g, steps = 5)

sg = data.frame(name = com$names, sg = com$membership + 1)

subgroup = vector("list", length(unique(com$membership)))

for(i in 1:length(unique(com$mem))){

subgroup[[i]] = as.character(sg[sg[, 2]== i, 1])}

rm(i)

## subgroup

V(g)$sg = com$membership + 1

V(g)$color = rainbow(max(V(g)$sg))[V(g)$sg]

png("net_walktrap.jpg", width = 2000, height = 2000)

par(mar = c(0, 0, 0, 0))

set.seed(14)

plot(g, layout = layout.fruchterman.reingold,

    vertex.size = 5,

    vertex.color = V(g)$color,

    vertex.label = NA,

    edge.color = grey(0.5),

    edge.arrow.mode = "-")

dev.off()

subgroup[[9]]

subgroup[[2]]

V(g)$bte = betweenness(g, directed = F)

png("net_betweenness.png", width = 500, height = 500)

par(mar = c(0, 2, 0, 0))

plot(V(g)$bte)

dev.off()

#基于点连接的社群发现——clusters

clusters(g,mode="strong")

clusters(g,mode="weak")

###链接预测

###基于邻居的相似度指标——资源分配(RA)指标

#划分训练集和测试集

edge = matrix(0,nrow=88233,ncol=2,dimnames=list(c(1:88233),c("Source","Target")))

k=1

for(i in 1:length(data1[,1]))

{

for(j in i:length(data1[1,]))

{

  if(facebook[i,j]==1)

{

    edge[k,1]=i

    edge[k,2]=j

    k=k+1

}

}

}#得到边数据


v = length(data1[,1])

e = length(edge[,1])

set.seed(20181231)

sp = sample(2, nrow(facebook), replace = TRUE, prob=c(0.8, 0.2))

train_edge = as.data.frame(edge[sp == 1,])

test_edge = as.data.frame(edge[sp == 2,])

#转换为邻接矩阵形式

train = matrix(0,nrow=1000,ncol=1000,dimnames=list(c(1:1000),c(1:1000)))

test = matrix(0,nrow=1000,ncol=1000,dimnames=list(c(1:1000),c(1:1000)))

for(i in 1:length(train_edge[,1]))

{

train[train_edge[i,1],train_edge[i,2]]=1

}

for(i in 1:length(test_edge[,1]))

{

test[test_edge[i,1],test_edge[i,2]]=1

}

g_train = graph.adjacency(train,mode="undirected",weighted=T)

g_test = graph.adjacency(test,mode="undirected",weighted=T)

#未观测边集

train_edge = train_edge[order(train_edge[,1],train_edge[,2]),]

max = (v*(v-1))/2

row = max-length(train_edge[,1])

edge_no = matrix(0,nrow=row,ncol=2,dimnames=list(c(1:row),c("Source","Target")))

t=1

s=1

for(i in 1:v)

{

x = matrix(1:v,nrow=v,ncol=1)

for(j in 1:v)

{

  if(s<=length(train_edge[,1])&&train_edge[s,1]==i)

{

    x[train_edge[s,2],1]=0

    s=s+1

}

  else

    break

}

k=i+1

while(k<=v)

{

  if(x[k,1]!=0)

{

    edge_no[t,1] = i

    edge_no[t,2] = x[k,1]

    t=t+1

}

  k=k+1

}

}

rm(x)

#计算两点间的公共邻居结点及其度

degree_train = degree(g_train)

degree_train_each = as.data.frame(matrix(0,nrow=v,ncol=2,dimnames=list(c(1:v),c("Id","Degree"))))

for(i in 1:v)

{

degree_train_each[i,1]=i

degree_train_each[i,2]=degree_train[[i]]

}#得到训练集每个节点的度

#得到每个节点的邻居节点

naber = as.data.frame(matrix(0,nrow=max(degree),ncol=v,dimnames=list(c(1:max(degree)),c(1:v))))

for(i in 1:v)

{

k=1

for(j in 1:v)

{

  if(train[i,j]==1)

{

    naber[k,i]=j

    k=k+1

}

}

}

#得到未观测边连接的两节点的公共邻居节点,并计算未观测边得分r

r = as.data.frame(matrix(0,nrow=row,ncol=1))

edge_no = cbind(edge_no,r)

names(edge_no)=c("Source","Target","r")

for(i in 1:length(edge_no[,1]))

{

x = edge_no[i,1]

y = edge_no[i,2]

r=0

same = intersect(naber[,x],naber[,y])

if(same[1]==0)

  edge_no[i,3]=r

else

{

  for(k in 1:length(same))

{

    if(same[k]==0)

      break

    else

      if(degree_train_each[same[k],2]!=0)

        r=r+1/(degree_train_each[same[k],2])

}

  edge_no[i,3]=r

}

}

#排序

edge_no = edge_no[order(edge_no[,1],edge_no[,2]),]

Top10 = edge_no[order(edge_no[,3],decreasing=T),][1:10,]


####评估精度(AUC)

#计算测试边集中的边的得分

r = as.data.frame(matrix(0,nrow=length(test_edge[,1]),ncol=1))

test_edge = cbind(test_edge,r)

names(test_edge)=c("Source","Target","r")

i=1

j=1

while(i<=length(test_edge[,1])&&j<=length(edge_no[,1]))

{

if(edge_no[j,1]>test_edge[i,1])

  i=i+1

else

  if(edge_no[j,1]

    j=j+1

  else

    if(edge_no[j,2]>test_edge[i,2])

      i=i+1

    else

      if(edge_no[j,2]

        j=j+1

  else

      {

        test_edge[i,3]=edge_no[j,3]

        i=i+1

        j=j+1

      }

}


#计算未存在边集中的边的得分

#构建未存在边集

max = (v*(v-1))/2

row1 = max-length(edge[,1])

edge_not_exist = matrix(0,nrow=row1,ncol=2,dimnames=list(c(1:row1),c("Source","Target")))

t=1

s=1

for(i in 1:v)

{

x = matrix(1:v,nrow=v,ncol=1)

for(j in 1:v)

{

  if(s<=e&&edge[s,1]==i)

{

    x[edge[s,2],1]=0

    s=s+1

}

  else

    break

}

k=i+1

while(k<=v)

{

  if(x[k,1]!=0)

{

    edge_not_exist[t,1] = i

    edge_not_exist[t,2] = x[k,1]

    t=t+1

}

  k=k+1

}

}

rm(x)

r = as.data.frame(matrix(0,nrow=length(edge_not_exist[,1]),ncol=1))

edge_not_exist = cbind(edge_not_exist,r)

names(edge_not_exist)=c("Source","Target","r")

i=1

j=1

while(i<=length(edge_not_exist[,1])&&j<=length(edge_no[,1]))

{

if(edge_no[j,1]>edge_not_exist[i,1])

  i=i+1

else

  if(edge_no[j,1]

    j=j+1

  else

    if(edge_no[j,2]>edge_not_exist[i,2])

      i=i+1

    else

      if(edge_no[j,2]

        j=j+1

      else

      {

        edge_not_exist[i,3]=edge_no[j,3]

        i=i+1

        j=j+1

      }

}

#比较测试边集和未存在边集,得AUC

len1 = length(test_edge[,1])

len2 = length(edge_not_exist[,1])

down = len1*len2

n1 = 0

n0 = 0

for(i in 1:length(test_edge[,1]))

{

for(j in 1:length(edge_not_exist[,1]))

{

  if(test_edge[i,3]>edge_not_exist[j,3])

    n1=n1+1

  if(test_edge[i,3]==edge_not_exist[j,3])

    n0=n0+1

}

}

AUC = (n1+0.5*n0)/down

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

推荐阅读更多精彩内容