1、什么是网络爬虫
关于爬虫百度百科这样定义的:网络爬虫(又被称为网页蜘蛛,网络机器人,在FOAF社区中间,更经常的称为网页追逐者),是一种按照一定的规则,自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动索引、模拟程序或者蠕虫。从搜索引擎开始,爬虫应该就出现了,爬虫所做的事情就是分析URL、下载WebServer返回的HTML、分析HTML内容、构建HTTP请求的模拟、在爬虫过程中存储有用的信息等等。简单点说,就是把别人网站上的东西爬下来,至于爬做什么用就看你自己了。
写网络爬虫很多语言都可以写,比如众所周知的Python以及、PHP、C、Java等等。今天我就基于.Net中的HtmlAgilityPack类写一个简单的爬虫。
2、HtmlAgilityPack类
HtmlAgilityPack 是 .NET 下的一个 HTML 解析类库。支持用 XPath 来解析 HTML 。命名空间: HtmlAgilityPack,下载地址:http://htmlagilitypack.codeplex.com/releases/view/90925
2.1基本属性
Attributes 获取节点的属性集合
ChildNodes 获取子节点集合(包括文本节点)
Closed 该节点是否已关闭()
ClosingAttributes 在关闭标签的属性集合
FirstChild 获取第一个子节点
HasAttributes 判断该节点是否含有属性
HasChildNodes 判断该节点是否含有子节点
HasClosingAttributes 判断该节点的关闭标签是否含有属性()
Id 获取该节点的Id属性
InnerHtml 获取该节点的Html代码
InnerText 获取该节点的内容,与InnerHtml不同的地方在于它会过滤掉Html代码,而InnerHtml是连Html代码一起输出
LastChild 获取最后一个子节点
Line 获取该节点的开始标签或开始代码位于整个HTML源代码的第几行(行号)
LinePosition 获取该节点位于第几列
Name Html元素名
NextSibling 获取下一个兄弟节点
NodeType 获取该节点的节点类型
OriginalName 获取原始的未经更改的元素名
OuterHtml 整个节点的代码
OwnerDocument 节点所在的HtmlDocument文档
ParentNode 获取该节点的父节点
PreviousSibling 获取前一个兄弟节点
StreamPosition 该节点位于整个Html文档的字符位置
XPath 根据节点返回该节点的XPath
2.2方法
IEnumerable
IEnumerable
IEnumerable
IEnumerable
HtmlNode AppendChild(HtmlNode newChild); 将参数元素追加到为调用元素的子元素(追加在最后)
void AppendChildren(HtmlNodeCollection newChildren); 将参数集合中的元素追加为调用元素的子元素(追加在最后)
HtmlNode PrependChild(HtmlNode newChild); 将参数中的元素作为子元素,放在调用元素的最前面
void PrependChildren(HtmlNodeCollection newChildren); 将参数集合中的所有元素作为子元素,放在调用元素前面
static bool CanOverlapElement(string name); 确定是否可以保存重复的元素
IEnumerable
HtmlNode Clone(); 本节点克隆到一个新的节点
HtmlNode CloneNode(bool deep); 节点克隆到一个新的几点,参数确定是否连子元素一起克隆
HtmlNode CloneNode(string newName); 克隆的同时更改元素名
HtmlNode CloneNode(string newName, bool deep); 克隆的同时更改元素名。参数确定是否连子元素一起克隆
void CopyFrom(HtmlNode node); 创建重复的节点和其下的子树。
void CopyFrom(HtmlNode node, bool deep); 创建节点的副本。
XPathNavigator CreateNavigator(); 返回的一个对于此文档的XPathNavigator
static HtmlNode CreateNode(string html); 静态方法,允许用字符串创建一个新节点
XPathNavigator CreateRootNavigator(); 创建一个根路径的XPathNavigator
IEnumerable
IEnumerable
IEnumerable
IEnumerable
IEnumerable
IEnumerable
HtmlNode Element(string name); 根据参数名获取一个元素
IEnumerable
bool GetAttributeValue(string name, bool def); 帮助方法,用来获取此节点的属性的值(布尔类型)。如果未找到该属性,则将返回默认值。
int GetAttributeValue(string name, int def); 帮助方法,用来获取此节点的属性的值(整型)。如果未找到该属性,则将返回默认值。
string GetAttributeValue(string name, string def); 帮助方法,用来获取此节点的属性的值(字符串类型)。如果未找到该属性,则将返回默认值。
HtmlNode InsertAfter(HtmlNode newChild, HtmlNode refChild); 将一个节点插入到第二个参数节点的后面,与第二个参数是兄弟关系
HtmlNode InsertBefore(HtmlNode newChild, HtmlNode refChild); 讲一个节点插入到第二个参数节点的后面,与第二个参数是兄弟关系
static bool IsCDataElement(string name); 确定是否一个元素节点是一个 CDATA 元素节点。
static bool IsClosedElement(string name); 确定是否封闭的元素节点
static bool IsEmptyElement(string name); 确定是否一个空的元素节点。
static bool IsOverlappedClosingElement(string text); 确定是否文本对应于一个节点可以保留重叠的结束标记。
void Remove(); 从父集合中移除调用节点
void RemoveAll(); 移除调用节点的所有子节点以及属性
void RemoveAllChildren(); 移除调用节点的所有子节点
HtmlNode RemoveChild(HtmlNode oldChild); 移除调用节点的指定名字的子节点
HtmlNode RemoveChild(HtmlNode oldChild, bool keepGrandChildren);移除调用节点调用名字的子节点,第二个参数确定是否连孙子节点一起移除
HtmlNode ReplaceChild(HtmlNode newChild, HtmlNode oldChild); 将调用节点原有的一个子节点替换为一个新的节点,第二个参数是旧节点
HtmlNodeCollection SelectNodes(string xpath); 根据XPath获取一个节点集合
HtmlNode SelectSingleNode(string xpath); 根据XPath获取唯一的一个节点
HtmlAttribute SetAttributeValue(string name, string value); 设置调用节点的属性
string WriteContentTo(); 将该节点的所有子级都保存到一个字符串中。
void WriteContentTo(TextWriter outText); 将该节点的所有子级都保存到指定的 TextWriter。
string WriteTo(); 将当前节点保存到一个字符串中。
void WriteTo(TextWriter outText); 将当前节点保存到指定的 TextWriter。
void WriteTo(XmlWriter writer);
3、第一个爬虫程序
3.1在VS2017中建立一个web项目拖个服务器控件按钮上去(我VS是用2017的)
3.2后台代码及解释
////// 博客园精华单机按钮 /// /// /// protected void btntwo_Click(object sender, EventArgs e) { int sum = 0; for (int i = 1; i < 2; i++) { SqlDB sb = new SqlDB(); HtmlWeb wb = new HtmlWeb(); string webaddress= ""; if (i==1) { webaddress="http://www.cnblogs.com/pick/";//为啥http://www.cnblogs.com/pick/#p10进去还是第一页 string webbbb = GetHTML(webaddress); } else { webaddress = string.Format("http://www.cnblogs.com/pick/{0}", ("#p"+i.ToString()).ToString()); string webbbb = GetHTML(webaddress); } try { HtmlDocument doc = wb.Load(webaddress); HtmlNode node = doc.GetElementbyId("post_list"); if (node != null) { foreach (HtmlNode hnode in node.ChildNodes) { if (hnode.Attributes["class"] == null || hnode.Attributes["class"].Value != "post_item") continue; HtmlNode hn = HtmlNode.CreateNode(hnode.OuterHtml); //推荐 int recommend = Convert.ToInt32((hn.SelectSingleNode("//*[@class=\"diggnum\"]").InnerText).Trim()); //标题 string title = hn.SelectSingleNode("//*[@class=\"titlelnk\"]").InnerText; //网址 string webhref = hn.SelectSingleNode("//*[@class=\"titlelnk\"]").Attributes["href"].Value.ToString(); //介绍 string introduce = hn.SelectSingleNode("//*[@class=\"post_item_summary\"]").InnerText; string articletimetest = hn.SelectSingleNode("//*[@class=\"post_item_foot\"]").InnerText; //发表时间(陈树义 发布于 2017 - 11 - 15 10:13 评论(41)阅读(7372) string articletime = ((hn.SelectSingleNode("//*[@class=\"post_item_foot\"]").InnerText).Trim()).Replace("\r\n", "+"); //分割字符串 string[] st = articletime.Split('+'); //取出(发布于 2017 - 11 - 15 10:13) string pp = (st[1].ToString()).Trim(); //分割字符串 string[] qq = pp.Split(' '); //取出(2017/11/15 10 :13) DateTime gg = Convert.ToDateTime(qq[1].ToString() + " " + qq[2].ToString()); try { string sql = string.Format(@"insert into CnblogsList( Recommend, Title,Contents, Introduce, WebHref, ArticleTime) values({0},'{1}','{2}','{3}','{4}','{5}')", recommend, title, GetContentsString(webhref), introduce, webhref, gg); sb.ExecuteNonQuery(sql); sum++; } catch (Exception ex) { Response.Write(ex.Message); } Response.Write(GetContentsString(webhref)+"
"); // 陈树义 发布于 2017 - 11 - 15 10:13 评论(41)阅读(7372) Response.Write("推荐:" + (hn.SelectSingleNode("//*[@class=\"diggnum\"]").InnerText)+"
"); Response.Write("标题:" + (hn.SelectSingleNode("//*[@class=\"titlelnk\"]").InnerText) + "
"); Response.Write("标题对应的网址:" + (hn.SelectSingleNode("//*[@class=\"titlelnk\"]").Attributes["href"].Value.ToString()) + "
"); Response.Write("介绍:" + (hn.SelectSingleNode("//*[@class=\"post_item_summary\"]").InnerText) + "
"); Response.Write("时间:" + (hn.SelectSingleNode("//*[@class=\"post_item_foot\"]").InnerText) + "
"); } } else { Response.Write("节点为空 +++++ 出错节点Iiiii是:" + i.ToString() + "
网址:" + webaddress.ToString() + "
插入数据:"+ sum +"条"); return; } } catch (Exception esz) { Response.Write(esz.Message); } } }
这是获取网页源码的代码
////// 获取网页源码 /// /// /// public string GetHTML(string url) { WebClient web = new WebClient(); web.Encoding = Encoding.UTF8; string buffer = web.DownloadString(url); return buffer; }
上面的for循环目的是获取精华区后面页数的内容,但是效果并不是这样的
我早谷歌上F12调试发现并不是这样的额 请求地址不对。为以为在后面加?PageIndex= XX就可以实现 还是不可以 因为这是经过处理的,在Header上面有地址https://www.cnblogs.com/mvc/AggSite/PostList.aspx你们仔细就可以看到。这样做了如果页数写多了就会重复出现第一页的内容 数据库中是保存的 数据库的表结构我上图
我一弄才发现原来这个地址是博客园里面的内容时刻更新的。经过处理取出精华区的。一次去可以取出2天的内容 不管是发布在博客园首页的还是没有发布在首页的都有。我就爬虫爬了一次 试试看 这是第二个按钮的源码
////// 博客园数据最近大约2天 /// /// /// protected void BtnNowTowDay_Click(object sender, EventArgs e) { int sum = 0; for (int i = 1; i < 200; i++) { //这是SqlDB类 SqlDB sb = new SqlDB(); //实例HtmlWdb HtmlWeb wb = new HtmlWeb(); //网址 string webaddress = ""; webaddress = string.Format("https://www.cnblogs.com/mvc/AggSite/PostList.aspx?PageIndex={0}", + i); //网页源码 有html的 string webbbb = GetHTML(webaddress); try { HtmlDocument doc = wb.Load(webaddress); //获取div[@class='post_item的上级目录 //这个可以根据节点 ID选择 建议多看一下Xpath 就好理解了 HtmlNode node = doc.DocumentNode.SelectSingleNode("//div[@class='post_item']").SelectSingleNode(".."); //HtmlNode node = doc.GetElementbyId("#id"); if (node != null) { foreach (HtmlNode hnode in node.ChildNodes) { if (hnode.Attributes["class"] == null || hnode.Attributes["class"].Value != "post_item") continue; HtmlNode hn = HtmlNode.CreateNode(hnode.OuterHtml); //推荐 int recommend = Convert.ToInt32((hn.SelectSingleNode("//*[@class=\"diggnum\"]").InnerText).Trim()); //标题 string title = hn.SelectSingleNode("//*[@class=\"titlelnk\"]").InnerText; //网址 string webhref = hn.SelectSingleNode("//*[@class=\"titlelnk\"]").Attributes["href"].Value.ToString(); //介绍 string introduce = hn.SelectSingleNode("//*[@class=\"post_item_summary\"]").InnerText; string articletimetest = hn.SelectSingleNode("//*[@class=\"post_item_foot\"]").InnerText; //发表时间(陈树义 发布于 2017 - 11 - 15 10:13 评论(41)阅读(7372) string articletime = ((hn.SelectSingleNode("//*[@class=\"post_item_foot\"]").InnerText).Trim()).Replace("\r\n", "+"); //分割字符串 string[] st = articletime.Split('+'); //取出(发布于 2017 - 11 - 15 10:13) string pp = (st[1].ToString()).Trim(); //分割字符串 string[] qq = pp.Split(' '); //取出(2017/11/15 10 :13) DateTime gg = Convert.ToDateTime(qq[1].ToString() + " " + qq[2].ToString()); try { string sql = string.Format(@"insert into CnblogsList( Recommend, Title,Contents, Introduce, WebHref, ArticleTime) values({0},'{1}','{2}','{3}','{4}','{5}')", recommend, title, GetContentsString(webhref), introduce, webhref, gg); sb.ExecuteNonQuery(sql); sum++; } catch (Exception ex) { Response.Write(ex.Message); } } } else { Response.Write("节点为空 +++++ 出错节点Iiiii是:" + i.ToString() + "
网址:" + webaddress.ToString() + "
插入数据:" + sum + "条"); return; } } catch (Exception esz) { Response.Write(esz.Message); } } }
下面是住区自己博客园的后台
////// 抓取自己博客园的 /// /// /// protected void btnWY_Click(object sender, EventArgs e) { SqlDB sb = new SqlDB(); HtmlWeb wh = new HtmlWeb(); HtmlDocument doc = wh.Load("http://www.cnblogs.com/w5942066/"); HtmlNode node = doc.DocumentNode.SelectSingleNode("//div[@class='day']").SelectSingleNode(".."); if(node !=null) { foreach(HtmlNode hn in node.ChildNodes) { if(hn.Attributes["class"] == null || hn.Attributes["class"].Value!="day") { continue; } HtmlNode hnn = HtmlNode.CreateNode(hn.OuterHtml); //shijian string recommend = hnn.SelectSingleNode("//*[@class=\"dayTitle\"]").InnerText; //标题 string title = hnn.SelectSingleNode("//*[@class=\"postTitle\"]").InnerText; //网址 string webhref = hnn.SelectSingleNode("//*[@class=\"postTitle2\"]").Attributes["href"].Value.ToString(); //介绍 string introduce = hnn.SelectSingleNode("//*[@class=\"c_b_p_desc\"]").InnerText; //时间 // string articletimetest = hn.SelectSingleNode("//*[@class=\"postDesc\"]").InnerText; //发表时间(陈树义 发布于 2017 - 11 - 15 10:13 评论(41)阅读(7372) // string articletime = ((hn.SelectSingleNode("//*[@class=\"postDesc\"]").InnerText).Trim()).Replace("\r\n", "+"); //分割字符串 //string[] st = articletime.Split('+'); //取出(发布于 2017 - 11 - 15 10:13) //string pp = (st[1].ToString()).Trim(); //分割字符串 //string[] qq = pp.Split(' '); //取出(2017/11/15 10 :13) DateTimeFormatInfo dtFormat = new DateTimeFormatInfo(); dtFormat.ShortDatePattern = "yyyy年MM月dd日"; DateTime dt = Convert.ToDateTime(recommend, dtFormat); try { string sql = string.Format(@"insert into CnblogsList( Recommend, Title,Contents, Introduce, WebHref, ArticleTime) values({0},'{1}','{2}','{3}','{4}','{5}')", 520, title, GetContentsString(webhref), introduce, webhref, dt); sb.ExecuteNonQuery(sql); } catch (Exception ex) { Response.Write(ex.Message); } } }
3.3列表显示出来
这里我用的是一个原始的GridView前台页面代码
<form id="form1" runat="server"> <div class="main"> <h1 style="height:35px;line-height:30px;text-align:center;padding-top:50px;">博客园文章列表h1><br /><hr /> <asp:GridView ID="gdMain" runat="server" Width="100%" AllowPaging="True" AutoGenerateColumns="False" DataKeyNames="ID" DataSourceID="SqlDataSource1" CellPadding="4" ForeColor="#333333" GridLines="None" PageSize="30" > <AlternatingRowStyle BackColor="White" /> <Columns> <asp:BoundField HeaderStyle-Width="3%" ItemStyle-HorizontalAlign="Center" DataField="ID" HeaderText="编号" InsertVisible="False" ReadOnly="True" SortExpression="ID" > <HeaderStyle Width="10%">HeaderStyle> <ItemStyle HorizontalAlign="Center">ItemStyle> asp:BoundField> <asp:BoundField ItemStyle-Wrap="false" HeaderStyle-Width="10%" ItemStyle-HorizontalAlign="Left" DataField="Title" HeaderText="标题" SortExpression="Title" > <HeaderStyle Width="10%">HeaderStyle> <ItemStyle HorizontalAlign="Center">ItemStyle> asp:BoundField> <asp:BoundField ItemStyle-Wrap="false" HeaderStyle-Width="30%" ItemStyle-HorizontalAlign="left" DataField="Contents" HeaderText="内容" SortExpression="Contents" > <HeaderStyle Width="30%">HeaderStyle> <ItemStyle HorizontalAlign="Center">ItemStyle> asp:BoundField> <asp:BoundField ItemStyle-Wrap="false" HeaderStyle-Width="20%" ItemStyle-HorizontalAlign="left" DataField="WebHref" HeaderText="原文网址" SortExpression="WebHref" > <HeaderStyle Width="20%">HeaderStyle> <ItemStyle HorizontalAlign="Center">ItemStyle> asp:BoundField> <asp:BoundField ItemStyle-Wrap="false" HeaderStyle-Width="10%" ItemStyle-HorizontalAlign="Center" DataField="ArticleTime" HeaderText="时间" SortExpression="ArticleTime" > <HeaderStyle Width="10%">HeaderStyle> <ItemStyle HorizontalAlign="Center">ItemStyle> asp:BoundField> <asp:HyperLinkField DataNavigateUrlFields="ID" DataNavigateUrlFormatString="PageInfo.aspx?ID={0}" HeaderStyle-Width="5%" ItemStyle-HorizontalAlign="Center" Text="详细" DataTextFormatString="详细" Target="_blank"/> Columns> <EditRowStyle BackColor="#2461BF" /> <FooterStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> <HeaderStyle BackColor="#507CD1" Font-Bold="True" ForeColor="White" /> <PagerSettings PageButtonCount="30" /> <PagerStyle BackColor="#2461BF" ForeColor="White" HorizontalAlign="Center" /> <RowStyle BackColor="#EFF3FB" /> <SelectedRowStyle BackColor="#D1DDF1" Font-Bold="True" ForeColor="#333333" /> <SortedAscendingCellStyle BackColor="#F5F7FB" /> <SortedAscendingHeaderStyle BackColor="#6D95E1" /> <SortedDescendingCellStyle BackColor="#E9EBEF" /> <SortedDescendingHeaderStyle BackColor="#4870BE" Wrap="False" /> asp:GridView> <asp:SqlDataSource ID="SqlDataSource1" runat="server" ConnectionString="<%$ ConnectionStrings:TestBaseConnectionString %>" SelectCommand="select ID,SUBSTRING(Title,0,30)AS Title,SUBSTRING(Contents,0,30)AS Contents ,SUBSTRING(Webhref,0,20)AS WebHref , ArticleTime from CnblogsList">asp:SqlDataSource> div> form>
后台部分
namespace NetCrawlerTest.Winfrom { public partial class CnblogListData : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { //if(!IsPostBack) //{ // GetData(); //} } public void GetData() { SqlDB sb = new SqlDB(); string _Sql = "select ID,SUBSTRING(Title,0,50)AS Title,SUBSTRING(Contents,0,60)AS Contents ,SUBSTRING(Webhref,0,50)AS WebHref , ArticleTime from CnblogsList"; gdMain.DataSource = sb.ExecuteDataSet(_Sql).Tables[0]; gdMain.DataBind(); } } }
好了来看看效果
3.4具体的博客显示
前台代码就是上面图片中的PageInfo页面
<form id="form1" runat="server"> <div class="main"> <h1 style="height:35px;line-height:30px;text-align:center; padding-top:50px"><%=GetTitle() %>h1><br /> <div style="text-align:right;"> <a href="CnblogListData.aspx" target="_parent">返回列表a>            <a href="PageInfo.aspx?ID=<%=UpIDS() %>" style="width:100px; ">上一条a>    |     <a href="PageInfo.aspx?ID=<%=UpIDX() %>" style="width:100px;">下一条a>    div><hr /> <div style="text-align:center;">发表时间:<%=GetTime() %>div><br /> <div style="margin:10px 10px 30px 10px"><%=GetContens() %>div><br /> <div style="text-align:right"><a href="<%= GetWebhref() %>" target="_blank">原文链接:a><%= GetWebhref() %>div><br /><hr /> <div style="text-align:right;"> <a href="CnblogListData.aspx" target="_parent">返回列表a>            <a href="PageInfo.aspx?ID=<%=UpIDS() %>" style="width:100px; ">上一条a>    |     <a href="PageInfo.aspx?ID=<%=UpIDX() %>" style="width:100px;">下一条a>    div> div> <div style="height:50px;text-align:center;color:#fbe8e8;background-color:#000000;width:100%;line-height:50px;font-weight:600; position:fixed; border-bottom:2px #ffffff solid; bottom:0px;">行到水穷处,坐看云起时!转载声明:技术需要共享,欢迎转载!但请注明版权及出处! div> form>
后台代码
public partial class PageInfo : System.Web.UI.Page { protected void Page_Load(object sender, EventArgs e) { } SqlDB sb = new SqlDB(); //public int Sum = 0; ////// 获取标题 /// /// public string GetTitle() { string _sql = string.Format(@"SELECT TITLE FROM CNBLOGSLIST WHERE ID={0}", GetID()); return sb.ExecuteDataTable(_sql).Rows[0][0].ToString(); } /// /// 接收ID /// /// public int GetID() { return Convert.ToInt32(Request.QueryString["ID"].ToString()); } /// /// 获取时间 /// /// 时间 public string GetTime() { string _sql = string.Format(@"SELECT ARTICLETIME FROM CNBLOGSLIST WHERE ID={0}", GetID()); return sb.ExecuteDataTable(_sql).Rows[0][0].ToString(); } /// /// 获取内容 /// /// 内容 public string GetContens() { string _sql = string.Format(@"SELECT CONTENTS FROM CNBLOGSLIST WHERE ID={0}", GetID()); return (sb.ExecuteDataTable(_sql).Rows[0][0].ToString()).Replace("&prime", "'") ; } /// /// 获取原网址 /// /// 网址 public string GetWebhref() { string _sql = string.Format(@"SELECT WEBHREF FROM CNBLOGSLIST WHERE ID={0}", GetID()); return sb.ExecuteDataTable(_sql).Rows[0][0].ToString(); } /// /// 上一条 /// /// 上一条信息的ID public int UpIDS() { try { string _sql = string.Format(@"SELECT top 1 ID FROM CNBLOGSLIST AS n WHERE n.ID < {0} ORDER BY n.ID DESC ", GetID()); if (sb.ExecuteDataTable(_sql).Rows[0][0].ToString() != null) { return Convert.ToInt32(sb.ExecuteDataTable(_sql).Rows[0][0].ToString()); } else { return GetID(); } } catch(Exception) { return GetID(); } } /// /// 下一条 /// /// 下一条信息的ID public int UpIDX() { try { string _sql = string.Format(@"SELECT top 1 ID FROM CNBLOGSLIST AS n WHERE n.ID > {0} ORDER BY n.ID ASC ", GetID()); if (sb.ExecuteDataTable(_sql).Rows[0][0].ToString() != null) { return Convert.ToInt32(sb.ExecuteDataTable(_sql).Rows[0][0].ToString()); } else { return GetID(); } } catch(Exception) { return GetID(); } }
这些代码都很基础就不一一解释了 实现的效果还是可以的,文字都在 ,大部分的图片都显示不出来
希望看到的朋友能有所收获,别拿去干坏事 也干不了啥坏事,其他的网站维护你就弄不到了还要研究的。对此感谢博客园提供平台让我学有所获。初学者,大神勿喷谢谢!有兴趣的朋友不懂的可以留言。