岂止于大,一文读懂大数据及其在推荐系统的应用
moboyou 2025-04-07 17:30 48 浏览
本系列文章将从最简单的概念开始,逐步讲解推荐系统的发展历程和最新实践。以产品经理的视角,阐述推荐系统涉及的算法,技术和架构。本章是第三章,将系统性地介绍推荐系统的基石之一:大数据。
大数据是数据智能时代的“铁公基”,是一系列计算和存储的基础设施。推荐系统也是建立在大数据的基础之上的,大量的数据挖掘和模型训练都离不开大数据。
大数据这个名词起的很好,对于非技术人员来说也能get到大的含义:数据量大,算力强大。
但是这强大的能力是怎么来的?大数据生态体系架构是怎么分工的?
不具体做过的同学并不清楚。今天就继续站在产品经理的角度深入浅出地来讲述这些问题。其实不一定是产品经理,对大数据感兴趣的同学都可以看看。本文比较长,如果时间不够,建议先Mark下。
01 大数据的诞生与分布式
在大数据技术诞生之前,数据的存储和处理大半壁江山都是Oracle和MySql和等数据库软件的。这些传统数据库的文件系统是单机的,也就是说,数据只能在一台机器上跑。它们在处理成TB(1024GB)甚至上PB(1024TB)级别数据时就会特别吃力。
第一个解决了这个问题的公司是谷歌。谷歌解决这个问题的思路就是分布式。谷歌分别于2003-2004年发表了三篇重量级的论文,奠定了大数据的基础。
简单的说,谷歌通过论文公布了以下三个工具如何创造:
- 分布式文件系统:Google File System
- 分布式并行计算框架:Google MapReduce
- 分布式数据库:BigTable
这个就是大数据技术的开始。从谷歌的这个原点开始,大数据经过多年发展,形成了今天的全貌。
可以用以下三个分类来去解构整个大数据生态:
- 分布式存储:把文件切成多份,分布地存储到多台机器上。常用的组件有:HDFS,HBase等。
- 分布式计算:把数据计算的任务切分成多个,分配到多台机器上计算。常用的组件有:MapReduce,Spark,Storm,Flink等。
- 分布式工具:数据辅助类工具,资源调度管理工具等。常见的组件有:YARN,Hive,Flume,Sqoop,Elastic Search,ZooKeeper等。
大数据整个生态体系非常庞大,相关的产品不胜枚举,让人看着眼花缭乱,有种物种大爆发的感觉。但是也并非无迹可寻。我们理解大数据生态,从这三个方面去理解就会轻松很多。本文也是严格按照这个方式来讲述。
02 本文讲述需要用的数据表
鉴于大数据生态过于庞大复杂,为了使得文章更加浅显易懂,本文不对技术概念做过多的描述,而是直接到操作层面,以图示的方式讲解。因为要讲述的是数据系统,我这里建一张数据表USER,用这张数据表在各个系统中处理过程来给大家讲述大数据各个组件的工作原理和机制。
03 Hadoop
整个互联网技术生态圈中,谷歌是超级绩优生。每次发表论文,都会引起众多人员前来“抄作业”。Hadoop之父Doug Cutting和他的团队就是其中一个。他花了两年的业余时间,照着谷歌的论文实现了一个分布式数据系统,并用自己儿子的大象毛绒玩具的名字给这个系统命名为Hadoop。Hadoop的Logo也是个头大象。
后来到了2006年,Hadoop被引入Apache基金会,成为一个开源的顶级项目。随着Hadoop的不断发展,在Hadoop项目中,诞生了HBase,Hive,Pig,Zookeeper等子项目,并先后被Apache基金会将其独立升级成为顶级项目。
同时,Apache基金会也不断兼收并蓄其他大数据项目,基本把绝大多数优秀的大数据项目都收入麾下,大有天下武功出少林的味道。大数据各种开源组件中,Apache旗下的才是少林正宗。
闲话不多说,我们来讲Hadoop。
现在的Hadoop主要分三个部分:
- 分布式存储:HDFS(Hadoop Distributed File System),一个高可靠、高吞吐量的分布式文件系统,实现海量数据存储。
- 分布式计算:MapReduce,一个分布式离线并行计算计算框架,实现海量计算。
- 分布式工具:YARN,一个集群资源调度管理的框架,实现硬件资源的调配。
04 图解分布式
这里先具象的解释下,什么是大数据的分布式长什么样?
我们在安装Hadoop的时候,要把同一个Hadoop的软件安装包,安装在三台以上不同的机器上。安装好后,我们就可以得到一个Hadoop分布式集群。
如下图,我们将Hadoop的三个组件,安装在集群每一台机器上,这个集群有七台机器。每一台机器叫一个节点。这七个节点中,要选出一个做老大,我们有什么事情先找这个老大,这个就是主节点。因为主节点很重要,它要是挂了,整个集群就挂了。所以一般主节点配置两台,一台备份。其他五个节点叫做从节点。在Hadoop中,主节点命名为Master,从节点为Slave。
05 分布式文件系统:HDFS
下面将Hadoop的第一个组件HDFS。HDFS作为分布式的文件系统,它是以文本的方式来保存数据的,也就是说,数据一般会跟我们的TXT文件那样。一个文件会被按照设定,切分成不同的数据块,然后每个数据块会被复制成多份,然后分布式地存到不同的节点中。HDFS把集群节点分成两类:NameNode和DataNode。
- NameNode可以理解成数据目录,它记录了每个数据块在哪台机器的哪个位置。这个功能由主节点承担。
- DataNode顾名思义就是存数据的节点了。
下面以USER数据表为例,举例说明HDFS的运作过程。实际的系统中,因为NameNode负荷比较重,会设置一个SecondaryNameNode来协助NameNode的工作(注意是协助,而不是备份)。
上图中:
- HDFS先把深蓝色的文本数据切分成三份。每份数据按照系统设定,如64MB或128MB一块。
- 将三份数据块复制成三份,这样就有九份数据。
- 把上一步得到的九份数据划分到不同的DataNode中,并建好目录。
- 按照储存分配,把各个数据块保存。
HDFS优点:数据容量大,是大数据最主要的数据存储方式。很多其他组件都是建立在HDFS的基础上,应用非常广泛。
HDFS的缺点:它是个文件系统,而不是数据库系统,不能像关系数据库系统那样建立索引等工具提升处理速度和定位到数据位置。因为数据记录的形式是一行一行的字符串,没办法定位,不支持对数据的直接修改。
06 分布式离线计算引擎:MapReduce
MapReduce是专门负责分布式计算的,它是是一个组合词,可以分成两个部分:Map和Reduce。
简单的说,Map就是把要计算的数据分解成多个计算任务,而Reduce则是Map过程得到的结果进行汇总。假设现在我们要计算在USER表中,男女各有多少人。
MapReduce计算详细过程如下:
上图中:
- Splitting阶段,把数据分成三块。Splitting一般就是在HDFS存储时的切分的基础上再次切分。
- Mapper则为每个切分的数据块建一个Mapping任务,逐条地计算每条数据的结果,然后在磁盘中存起来。
- Shuffling也就是将Mapping阶段的结果进行分拣,shuffle任务要等Mapping完全完成后才能开展。
- Reducing就是将分拣好的数据,进行汇总。
通过这种方式,MapReduce获得了强大的运算能力。
MapReduce优点:计算能力强大,运算耗费成本低廉,在运算能力一般的机器上也能运行。
MapReduce缺点:它诞生的年代,内存还很贵,在计算的过程中,中间结果要不断存入磁盘中,以减少内存的压力,但是这样导致了MapReduce运算速度比较慢。另外就是代码不简洁,学习曲线比较陡,后面在HIVE部分会讲述。
07 资源管理调度系统:YARN
YARN的全称是Yet Another Resource Negotiator,直译就是另一种资源协调者。
看名字就知道,他是Hadoop中其中一种资源调度器。它的最重要的组件是ResourceManager,通过这个组件,YARN可以为我们的MapReduce任务进行资源分配,分配的资源就是一个个的计算容器,每个计算的任务就在这个容器中跑。由于优秀,后来YARN进一步被发扬光大,成了Hadoop以外的很多大数据组件的资源调度框架。
08 数据分析和数据仓库工具:HIVE
在MapReduce的中,处理数据作业的程序是要通过JAVA来实现的,但是JAVA在写MapReduce的过程并不简洁。像我们前面提到的统计数据表中男女人数这个简单的操作,需要严格按照Input到Reducing这五步来实现,代码一般长达上百行。
但是统计这个数据结果,对于SQL来说,就是一句话的事:SELECT sex,COUNT(1) FROM USER GROUP BY sex。
为了不再写又长又烦的JAVA,Hive应运而生。Hive组件就主要做的事情是,将SQL代码转译成MapReduce,然后放入YARN构建的容器里面跑出结果。
这样,我们要统计USER表中的男女人数的时候,就不用写JAVA实现也能调动MapReduce了,这里我们画图说明一下:
另外,Hive除了转译SQL的功能外,它也可以用来建数据库和数据表。一个数据库拥有的能力它都有,而且还可以处理大量的数据,所以一般还会将Hive用来建数据仓库。数据仓库的主要功能,就是把历史数据都存进去,可以反复地统计计算使用。数据仓库最主要的特征就是数据量特别大。
由于它是构建于HADOOP的基础上,Hive可计算的容量大,运算能力强,但是速度不快。Hive也不能直接修改数据。对Hive进行数据的修改操作非常费时。
最后,介绍一下Hive和MySql的区别:
- MySql是关系型数据库,它适合用于联机事务数据管理。如用户注册,修改昵称等数据操作,可以让用户对数据进行实时快速地增删改查。
- HIVE是建立的HADOOP基础上的数据仓库工具。它适合计算大容量数据的场景,因为计算速度比较慢,不能用来进行实时响应的事务性场景。
09 离线计算和流式计算DAG计算引擎:SPARK
Spark是分布式计算引擎,是大数据生态体系中的一个速度快,功能强大的一个组件。Spark整个框架非常庞大,提供能非常丰富的离线计算和流式计算能力。
本小节先介绍Spark的离线计算功能。
得益于摩尔定律下的硬件基础设施的升级,内存变得越来越便宜,与MapReduce主要把中间结果不断写入磁盘不同,Spark主要把数据放在内存中计算。这样Spark在速度上比起MapReduce有上千倍的提升。
当内存不足的时候,Spark也会需要将中间结果存入磁盘。但是在这种情况下,Spark在速度上比起MapReduce也有上百倍的提升。因为Spark提供了RDD,DataFrame,DataSet这样的数据表工具,并为这些数据表提供了一系列高效计算的算子,有效提升了速度。多个算子叠加就成了一个DAG(Directed Acyclic Graph),所以Spark也被成为DAG计算引擎。
下面介绍Spark的计算流程。
因为RDD,DataFrame,DataSet都是类似关系数据库的数据表,流程机制都差不多,这里选用DataFrame来解释。当Spark处理USER表的数据时,会经过以下过程:
上图中:
- 数据读取:数据读取可以从文本,HDFS,HIVE,JSON等多种格式中读取。
- 转换成DataFrame:把读取的数据表转换成Spark的内置格式DataFrame,并上图中命名为USER_DF。有了DataFrame就可以直接对它进行算子操作。上图中用到的groupby和count都是算子。算子的作用就是执行某类计算的操作。
- groupby算子运算:groupby就是分组的算子。它实现了按照男女对数据进行了分组,得到一个分组后的新DataFrame,上图中命名为GROUPBY_DF。每次算子运算后,都得到一个新的DataFrame。
- count算子运算:从上一步的结果中,再次进行个数计算,得出最后结果RESULT_DF。
多个算子混合后,就形成了一个DAG,上面的操作,会形成以下的DAG。Spark通过构建DAG,然后下发给从节点计算。
同时这个计算过程也是像MapReduce那样,将计算任务分解成Map和Reduce两个阶段,分布式地在多台机器中计算,这里不再描述。
10 离线计算和流式计算
MapReduce和Spark都是离线计算的代表,离线计算就是计算前已经有了所有需要计算的数据,且每次计算都是所有的数据都参与的运算。因为每次都是一整批数据做计算,所以离线计算一般又叫批量计算。但是日常的事务中,有很多场景是需要不断实时的更新数据的。
假设我们的USER表,现在因为有个新用户注册,多了一条用户数据“蔡九,女,27”。离线计算就要把新数据汇总,然后再进行计算:
增加一条用户日志,就要对全部的进行计算,在数据量非常庞大的时候,这根本是不可能的事情。所以我们还要另外一种计算方式,那就是流式计算。流式计算因其实时性,又常被叫做在线计算和实时计算。
以下是流式计算的过程,同样的新增数据,流失计算只要对新增数据进行计算,然后汇总更新:
流式计算引擎有:SPARK streaming,Storm和Flink。它们都可以提供流式计算的服务,但是有些不同。
- SPARK streaming是使用微批的方式计算流数据。微批就是小批量的意思。SPARK streaming并不是一条一条地计算新数据流,而且小批量的计算。比如几分钟算一次,或者几十条算一次。像割韭菜那样,等长够高了,再收割,一茬又一茬。
- Storm和Flink就是来一条计算一条地处理数据流了。像流水线作业那样,不断地逐个逐个处理。值得一提的是,最近两年Flink很火,在推荐系统上被广泛用来计算如用户画像等实时性较高的数据。
11 分布式结构化存储系统:HBASE
前面说到谷歌三篇关于大数据的论文,这里再补充一下它们后来的演变结果:
- 分布式文件系统:Google File System,演变成Hadoop的HDFS。
- 分布式并行计算框架:Google MapReduce,演变成Hadoop的MapReduce。
- 分布式数据库:BigTable,演变成了另外一个大名鼎鼎的HBase。
这就是HBase的诞生。
HBase是一个NoSql数据库。它在整个大数据生态中的定位是对数据进行实时的操作:查询,更新,删除和插入。前面说到HIVE是能处理大量数据,但是速度慢且不能对数据进行修改,而MySql等传统数据库响应快但是运算能力不足。HBase的出现,就是为了解决这些问题。
NoSql数据库的意思就是不支持SQL语句的数据库,HBase有以下特征:
- HBase最终存储是基于HDFS的,有存储海量数据能力。
- HBase的增删改查是分布式的,也就是一次修改,是多个节点服务器的修改。
- HBase的表结构跟关系型数据库是不一样的。
- HBase有很快的读取响应速度。
为了方便理解,我们直接画图看HBase怎么工作。如何把我们的USER数据表放入HBase后,它是长成这样子的:
其中:
- rowkey是行键,是每行数据的编号。
- Users_info叫做列族,是保存数据的表头。
- HBASE中,每个数据都被保存成为key:value的键值对,每个键值对叫做cell(单元格)。如name:张三就是一个cell。
MySql中,修改数据就会覆盖掉旧数据。但是在HBase中,同个键值对可以保留多个数据版本。这个版本会以时间戳的形式来标记。如张三,有一天改名叫张三丰了,它并不会覆盖掉原有的张三,而是两个版本都保存起来。
如下如所示:
HBase有强大的读取和增删改查能力,加上可以保存不同时间的数据版本,在推荐系统中,用户画像的结果数据,离线召回,近线召回等数据,都是保存在HBase中。另外,HBase可以做到快速响应,推荐系统中需要快速读取的数据,都可以存在HBase中。
12 数据采集
大数据的诞生,并不是取代掉传统的关系型数据库,而是变成一种补充。传统数据库存不下的数据,大数据来存。传统数据库算不了的数据,大数据来算。大数据系统只是把现有系统的数据采集到大数据中,做存储和计算,对已有的业务流程和系统架构并没有什么影响。
将数据采集到大数据,一般用到两个工具:Sqoop、Flume。其实不同公司使用的数据采集工具不一样,这里只是简单的介绍。
关系型数据是我们最常见的一种数据,Sqoop是关系型数据库和大数据之间数据流动的一个桥梁。它可以用来将MySql和Oracle的数据导入HDFS,HBASE中,或者将大数据中的数据导入MySql或Oracle。
另外一种常见的数据是来自于日志系统的数据,在生产环境中,我们的搜索,推荐,广告服务每时每刻都在产生大量的流式日志。这些日志数据格式不一,形态各异,他们都是非结构数据。
这些数据一般都是以文本的方式保存在各个业务系统中,对于推荐系统而言,最重要用户的埋点行为数据就存在日志中。而对于这些非关系型数据,我们需要采集到大数据的时候,就需要用到Flume。Flume可以采集批量数据也可以采集流数据。
这两个工具知道作用即可,不用深究太多。
13 分布式消息队列:KAFKA
采用Sqoop或者Flume做数据采集的时候,可以说是一对一的直采专线服务模式。我们把生产数据的系统叫生产者,消费数据的系统叫消费者。随着系统的发展,一般生产者和消费者都会越来越多,全部一对一直采,连接数就会指数上升,且难以维护。如果生产者的数据没能及时被消费者接收或者丢包,数据就会丢失。
为了解决这些问题,Kafka被创造了出来。
通过Kafka,生产者只要把数据打包好,标记好Topic,扔到Kafka的消息队列上就可以了。而消费者,只要做的事情就是订阅该生产者的Topic。当有新的数据到达时,Kafka就会告知消费者去取。
数据会被暂时保存在Kafka的硬盘中一段时间(一般7天),消费者随时可以来取。被保存的一系列数据块,就是一个个按时间排序的消息队列。同样的,Kakfa也是分布式的,它会被安装在多个节点中,数据也会被保存在多个节点中。
14 Lambda架构
能看到这里,你已经很不容易了,前面已经将本文需要介绍的大数据组件讲完了。
大数据实在是太庞大了,而且各司其职,分工得特别细。这么多大数据的框架,有离线计算和流式计算,不同的分布式存储和不同的分布式工具,这些框架是怎么构建成一个大数据系统的呢?
这就要介绍大数据的Lambda架构。
Lambda架构算大数据系统里面举足轻重的架构,数据通道分为两条分支:实时流和离线。
实时流依照流式架构,保障了其实时性,而离线则以批处理方式为主,保障了最终一致性,适用于同时存在实时和离线需求的情况。
抽象掉所有的框架,可以把Lambda架构简化成如下方式:
推荐系统是个存储和算力消耗的大户,它需要离线计算,对时间不敏感的数据进行大批量的计算。也需要实时流式计算,对用户画像,物品画像数据进行实时的更新。
把本章中说到的大数据的各个框架组件按照Lambda架构的方式组建后,我们可以得到下图:
实际的情况比上图还要更复杂些,但是对于本文来说,借用机器学习的术语,再复杂就要“过拟合”了,适当的DropOut可以防止过拟合。扔掉一些,可能是更好的。
总结
千言万语,汇总成一句话:大数据是由分布式存储,分布式计算和辅助性组件构成一个庞大的数据技术生态体系。它有几个要知识点:
- 要理解分布式存储的机制。因为数据量大,数据的存储的最终载体是最简单文本数据,没有很多花里胡哨的东西。这些文本数据被切割成多个数据块,分布式地保存在不同的数据节点中。
- 要理解分布式计算中的MapReduce机制。理解HIVE的工作机制。理解什么是离线计算,什么是流式计算。
- 最后,可以的话,记住Lambda架构。
写在最后的话:
大数据太多知识点了,受篇幅所限,这次只选择性地介绍推荐系统需要用到的大数据开源类组件。这个生态体系还在不断的发展中,我也还在路上。不足之处,还请各路高手不吝指教。
下篇文章将介绍用户画像,敬请期待,谢谢!
作者:菠萝王子;公众号:菠萝王子AI分享
本文由 @菠萝王子 原创发布于人人都是产品经理。未经许可,禁止转载
题图来自Unsplash,基于CC0协议
相关推荐
- php宝塔搭建部署实战服务类家政钟点工保姆网站源码
-
大家好啊,我是测评君,欢迎来到web测评。本期给大家带来一套php开发的服务类家政钟点工保姆网站源码,感兴趣的朋友可以自行下载学习。技术架构PHP7.2+nginx+mysql5.7+JS...
- 360自动收录简介及添加360自动收录功能的详细教程
-
以前我们都是为博客站点添加百度实时推送功能,现在360已经推出了自动收录功能,个人认为这个功能应该跟百度的实时推送功能差不多,所以我们也应该添加上这个功能,毕竟360在国内的份额还是不少的。360自动...
- 介绍一个渗透测试中使用的WEB扫描工具:Skipfish
-
Skipfish简介Skipfish是一款主动的、轻量级的Web应用程序安全侦察工具。它通过执行递归爬取和基于字典的探测来为目标站点准备交互式站点地图。该工具生成的最终报告旨在作为专业Web应用程序安...
- 好程序员大数据培训分享Apache-Hadoop简介
-
好程序员大数据培训分享Apache-Hadoop简介 好程序员大数据培训分享Apache-Hadoop简介,一、Hadoop出现的原因:现在的我们,生活在数据大爆炸的年代。国际数据公司已经预测在20...
- LPL比赛数据可视化,完成这个项目,用尽了我的所有Python知识
-
LPL比赛数据可视化效果图完成这个项目,我感觉我已经被掏空了,我几乎用尽了我会的所有知识html+css+javascript+jQuery+python+requests+numpy+mysql+p...
- 网站被谷歌标记“有垃圾内容”但找不到具体页面?
-
谷歌的垃圾内容判定机制复杂,有时违规页面藏得深(如用户注册页、旧测试内容),或是因第三方插件漏洞被注入垃圾代码,导致站长反复排查仍毫无头绪。本文提供一套低成本、高执行性的解决方案。你将学会如何利用谷歌...
- 黑客必学知识点--如何轻松绕过CDN,找到真实的IP地址
-
信息收集(二)1、cms识别基础为什么要找CMS信息呢?因为有了CMS信息之后,会给我们很多便利,我们可以搜索相应CMS,有没有公开的漏洞利用根据敏感文件的判断:robots.txt文件robots....
- Scrapy 爬虫完整案例-提升篇
-
1Scrapy爬虫完整案例-提升篇1.1Scrapy爬虫进阶案例一Scrapy爬虫案例:东莞阳光热线问政平台。网站地址:http://wz.sun0769.com/index.php/que...
- 如何写一个疯狂的爬虫!
-
自己在做张大妈比价(http://hizdm.com)的时候我先后写了两个版本的爬虫(php版本和python版本),虽然我试图将他们伪装的很像人但是由于京东的价格接口是一个对外开放的接口,如果访问频...
- 程序员简历例句—范例Java、Python、C++模板
-
个人简介通用简介:有良好的代码风格,通过添加注释提高代码可读性,注重代码质量,研读过XXX,XXX等多个开源项目源码从而学习增强代码的健壮性与扩展性。具备良好的代码编程习惯及文档编写能力,参与多个高...
- Python爬虫高级之JS渗透登录新浪微博 | 知了独家研究
-
小伙伴们看到标题可能会想,我能直接自己登陆把登陆后的cookie复制下来加到自定义的请求头里面不香嘛,为什么非要用python模拟登录的过程?如果我们是长期爬取数据,比如每天早上中午和晚上定时爬取新浪...
- 使用Selenium实现微博爬虫:预登录、展开全文、翻页
-
前言想实现爬微博的自由吗?这里可以实现了!本文可以解决微博预登录、识别“展开全文”并爬取完整数据、翻页设置等问题。一、区分动态爬虫和静态爬虫1、静态网页静态网页是纯粹的HTML,没有后台数据库,不含程...
- 《孤注一掷》关于黑客的彩蛋,你知道多少?
-
电影总是能引发人们的好奇心,尤其是近日上映的电影《孤注一掷》。这部电影由宁浩监制,申奥编剧执导,是一部反诈骗犯罪片。今天给大家讲解一下影片潘生用的什么语言,以及写了哪些程序。揭秘影片中的SQL注入手法...
- python爬虫实战之Headers信息校验-Cookie
-
一、什么是cookie上期我们了解了User-Agent,这期我们来看下如何利用Cookie进行用户模拟登录从而进行网站数据的爬取。首先让我们来了解下什么是Cookie:Cookie指某些网站为了辨别...
- 「2022 年」崔庆才 Python3 爬虫教程 - urllib 爬虫初体验
-
首先我们介绍一个Python库,叫做urllib,利用它我们可以实现HTTP请求的发送,而不用去关心HTTP协议本身甚至更低层的实现。我们只需要指定请求的URL、请求头、请求体等信息即...
- 一周热门
- 最近发表
- 标签列表
-
- curseforge官网网址 (16)
- 外键约束 oracle (36)
- oracle的row number (32)
- 唯一索引 oracle (34)
- oracle in 表变量 (28)
- oracle导出dmp导出 (28)
- oracle 数据导出导入 (16)
- oracle两个表 (20)
- oracle 数据库 字符集 (20)
- oracle安装补丁 (19)
- matlab化简多项式 (20)
- 多线程的创建方式 (29)
- 多线程 python (30)
- java多线程并发处理 (32)
- 宏程序代码一览表 (35)
- c++需要学多久 (25)
- c语言编程小知识大全 (17)
- css class选择器用法 (25)
- css样式引入 (30)
- html5和css3新特性 (19)
- css教程文字移动 (33)
- php简单源码 (36)
- php个人中心源码 (25)
- 网站管理平台php源码 (19)
- php小说爬取源码 (23)