您的位置:首页 > 编程语言 > C#

学习:C#实现一个简单的爬虫

2016-12-14 22:10 573 查看

学习目标:

使用C#做到一个简单的爬虫,获得网页上想要的内容。

学习前的一个问题:

什么是爬虫

通俗版(懒人版)

百科版(太长不看版)

基本思路版

推荐看 [通俗版] 和 [基本思路版]

涉及的内容:

web请求和响应(获得网页的html)

WebRequest


WebResponse
类(程序中使用的是
HttpWebResponse
,在本程序中使用哪一个都可以实现功能,具体的区别看MSDN吧)

字符串的处理(筛选html中的URL和想要获得内容)

string.
后的诸多内容(看到
string
后面那个小点没)

先来两个小实例:

实例1. 获得一个网页的所有html

先看MSDN-WebRequest的最下面有示例,看不懂或者有问题再看下面,看懂了直接跳实例2

——Steps by Steps——

既然说到要使用
WebRequest
类,那就来一个

WebRequest request = WebRequest.Create("目标网页的URl");
// 使用WebRequest和WebResponse都需要using.System.Net;


ok啦,这就模仿我们向浏览器输入了一个网页,可是我们什么都看不见啊,那是因为我们没有得到响应,那就再用
WebResponse
类获得响应

WebResponse response = (WebResponse)request.GetResponse();
// request 是上面一段代码中定义的


到现在你还是什么都看不到,响应是有了,但是没有显示出来还是什么都没有的,我们定义一个流来保存获得的内容

Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream, Encoding.Default);
// 使用流要添加 using.System.IO;


问题:等等,我们为什么要用流来保存内容,直接显示在textBox或者其他地方不可以吗?

回答:因为
response.
中能获得内容的只有
GetResponseStream()


追问:我看了一下,还有一个
toString()


回答:Just trying。你会发现只是把
response
的数据类型显示了出来,这里可不是简单的类型转换

说个小故事,其实一开始我也是不知道为什么用流的,那就try咯,
response.
打完再看看有什么符合要求的,见建议1

有了“流”,还需要“读流”

问:
StreamReader
里面的参数怎么写

答:里面的参数可以有很多,貌似最多9个(+9重载),这里的
dataStream
是流,第二个
Encoding.Default
是流的编码模式,在这里用的是默认模式

拓展:在使用流来保存和读取文本文件(.txt)时,貌似用的是
Encoding.GetEncoding("GB2312")
,才不会出现乱码

这个时候你就可以把
reader
好好折磨一番了

textBox1.Text = reader.ReadToEnd();
//在设计器中加了一个textBox,属性MultilLine = true,ScrollBars = Vertical(垂直滚动条)


终于,你可以在textBox里面看到得到的html了

以上代码组合起来是

WebRequest request = WebRequest.Create("目标网页的URL");

WebResponse response = (WebResponse)request.GetResponse();

Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream, Encoding.Default);

textBox1.Text += reader.ReadToEnd();


到这里,你就能看到你需要的网页的html了

什么是html?

推荐看菜鸟教程的HTML学习板块(很赞的网站),无需精通,了解一下就好,对后面的内容筛选也有好处

实例2. 各种字符串处理

也是先看MSDN-string,再简单看看下面,看看一些实际的用法

! 声明,以下统一用下面的
str


string str = "123456789-abcdefghijklmn!@#$%^&*";


str.Split();

Split 分割 , 简单用法:

按照后面括号内的字符将str划分为几部分,由于这样不是返回一个str,所以要用string[]数组来保存结果

string[] strSplit = str.Split('-');
string str1 = strSplit[0];  //str1 = "123456789"
string str2 = strSplit[1];  //str2 = "abcdefghijklmn!@#$%^&*"
//老师在随机点名中用过的


其实不仅能够切割为两个部分,要看str中有多少个用于切割的字符,详细用法看C#中Split详解或者MSDN

str.IndexOf();

只有一个参数时,获得括号内指定字符串的首字母的索引

这个拓展方法是带有一定筛选功能的,因为指定的字符串就可以是筛选的内容,例如

int a = str.IndexOf("456"); //a = 3
int b = str.IndexOf("no"); //b = -1


注意两个问题:

1. 索引都是从0开始算的

2. 当遇到不符合的情况时,默认是返回-1,看b

妙用

string temp = "456";
int a = str.IndexOf(temp);
int location_7 = a + temp.Length;


上面的代码能够得到数字
7
的索引,也就是可以得到temp后面第一位的索引

还有两个参数的用法,看MSDN

str.Contains()

检查当前
str
中是否含有括号后指定的字符串,返回的是一个
bool
类型的值

bool a = str.Contains("abc");   //a = true;
bool b = str.Contains("no");    //b = false;


这样一看貌似与
str.IndexOf()
有点相像喔,不过
str.IndexOf()
是筛选+截取内容,比
str.Contains()
更丰富一点

str.Remove();

remove 移除

将括号内的索引之后(包括索引本身)的都移除

string newStr = str.Remove(9); //newStr = "123456789"


Q: 这种是删除后面的内容,怎么做到删除前面的呢

A: 先使用
IndexOf()
获得需要删除的字符串的最后一个字符的index,再使用
subString(str, index)
截取就好;或者使用
str.Replace();


str.Replace();

replace 替换

string newStr = str.Replace("123456789", "num"); //newStr = "num-abcdefghijklmn!@#$%^&*"


上面说到的删除前面的内容就可以用一个空字符来替换就行

str.Replace("要删除的内容", "");


str.subString();

第一个参数startIndex(从哪里开始截取),第二个参数length(即为截取的长度)

string newStr = str.Substring(0, 9); //newStr = "123456789"


妙用:

实现逐字符读取字符串

string ch;
for (int i = 0; i < str.Length; i++)
ch = str.Substring(i, 1);


总结:

以上几个字符串处理在实际使用时可以实现一样的功能,随意用

实操爬虫

做完了两个小实例,制作爬虫的基本知识就具备了,下面开始实战。



在这里涉及到了业务逻辑,要看你需要爬的网页,爬的内容,然后根据这些来确定和简化你的程序,不同的需求导致了代码的不同,以下代码可能不通用,但是思想是通用的

以下是作为示例的校园网通知(这里只有内网可上)的特殊性:

特性1:由于爬的是校园网的通知信息,页面之间属于平行关系,而且每个页面的URL都是有规律的,那只有知道了有多少页,那么待访问的URL就全部知道了,这样就简化了待访问URL的获取。

第一页的URL

http://stu.gdmc.edu.cn/more/more_tz.php?page=1


第二页的URL

http://stu.gdmc.edu.cn/more/more_tz.php?page=2


最后一页的URL

http://stu.gdmc.edu.cn/more/more_tz.php?page=104


特性2:需要爬的内容也是平行的,对于截取来说比较简单

在这里需要的内容的通知的标题,时间,和通知的URL



<A href="../admin/view_n.php?id=15523"
里面的就是该通知的URL

需要截取的内容都是很有规律的

还是一步一步来

Steps 1:既然知道了有104条待访问的URL,又知道这些URL是又规律的,那就用一个循环将这些URL都爬一遍

for (int i = 1; i <= 104; i++)
{
WebRequest request = WebRequest.Create("http://stu.gdmc.edu.cn/more/more_tz.php?page=" + i);
WebResponse response = (WebResponse)request.GetResponse();

Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream, Encoding.Default);

textBox1.Text += reader.ReadToEnd();
}


Steps 2:爬每个页面的时候把需要的内容爬下来,用实例2中的知识将特定字符串储存到textBox中

运用你的智慧把字符串处理玩出来吧

用获取标题做个例子:

for (int i = 0; i <= 104; i++)
{
WebRequest request = WebRequest.Create("http://stu.gdmc.edu.cn/more/more_tz.php?page=" + i);
WebResponse response = (WebResponse)request.GetResponse();

Stream dataStream = response.GetResponseStream();
StreamReader reader = new StreamReader(dataStream, Encoding.Default);

string rl;
while ((rl = reader.ReadLine()) != null)
{
string judge1 = "onClick=\"return js_callpage(this.href);\">";
string judge2 = "</a>";
int a = -1, b = -1; //用a,b来判断judge1和judge2是否存在
if ((a = rl.IndexOf(judge1)) != -1 && (b = rl.IndexOf(judge2)) != -1)
{
//start和end用来表示需要截取内容的开头和末尾的索引
int start = a + judge1.Length;
int end = b;
string title = rl.Substring(start, end - start);
textBox1.Text += title;
}
}
}


这里的
judge1
judge2
是因为看网页的html时发现的

<A href="../admin/view_n.php?id=15532"onClick="return js_callpage(this.href);">关于公布2016年下半年英语四、六级考试“考生须知”的通知</a>


通性:找不变的作为筛选的条件,这里不变的就是

onClick="return js_callpage(this.href);">


judge2
是用来截取到最后一个字的索引的,也是不变的

这里有个要注意的问题1

往往html中会含有符号
"
,这也是字符串里面开始和结束的标记,所以在用的时候要用到转移字符,将html的
"
改为
\"
, (教材P57)

对比一下

html
onClick="return js_callpage(this.href);">


改变
onClick=\"return js_callpage(this.href);\">


当然啦,上面代码实现截字符串的方法有点麻烦,更好的办法是之定义一个方法,功能是给出
judge1
judge2
等待截取的字符串
就可以得到中间的字符串,用的时候调用就好

public string subStr(string str, string startStr, string endStr)
{
int startIndex = str.IndexOf(startStr, 0) + startStr.Length;
int endIndex = str.IndexOf(endStr, 0);

string resultStr = str.Substring(startIndex, endIndex - startIndex);
return resultStr;
}


总结几个小问题:

问题1 就是用html作为筛选条件时遇到
"
的问题

上面有提

问题2 html中使用的是相对地址,我们需要的绝对地址

例如查看html中可以看到
A href="../admin/view_n.php?id=15532"
这样的地址,以
../
开头的地址,这是相对地址,

../
应该代表的是上一层目录(不是很清楚,在这里代表上一层目录反而是错的)

/
代表的是根目录

那就转化咯

下面又用了一个自定义方法:

public string formatUrl(string rootUrl, string oldUrl)
{
//rootUrl指作为搜索的根Url
//oldUrl指rootUrl搜索时搜到的子Url

//获得根Url的根目录rootPath
string rootPath = rootUrl.Replace("http://", "");
int i = rootPath.IndexOf("/");
rootPath = "http://" + rootPath.Substring(0, i);

if (oldUrl.StartsWith("/"))
{
return rootPath + oldUrl;
}
else if (oldUrl.StartsWith("../"))
{
string temp = rootUrl;
while (oldUrl.StartsWith("../"))
{
int a = temp.LastIndexOf("/");
temp.Substring(0, a);
//因为"../"占据三个位置
oldUrl = oldUrl.Substring(3);
}

return temp + "/" + oldUrl;
}

//if (oldUrl.StartsWith("/"))
//{
//    return rootPath + oldUrl;
//}
//else if (oldUrl.StartsWith("../"))
//{
//    return rootPath + oldUrl.Substring(2);
//}

return oldUrl;
}


说明一下:

rootUrl
指的是当前网页的url,
oldUrl
指的是当前url的html里面的的相对路径,也是需要我们变成绝对路径的url,
rootPath
是指rootUrl的根目录

截止这里,一只
弱鸡
的爬虫就出来了,没错,就是一个
草鸡弱鸡
的爬虫,也正是因为弱,才有了巨大的发展空间

改进方向:

爬虫与数据库

数据库保存爬出来的数据

数据库简单整理爬出来的数据

在数据库的基础上叠加新的数据,不用每次都整个网页的爬

爬虫自身的强化

使用异步来提高爬的效率

爬一些js的网站

应对“反爬”的网站

减轻被爬网站的负荷

爬虫的数据分析

这里就不是用数据库了,具体用什么,我也不知道

题外话

既然老师和很多大牛都说“未来的时代是数据的时代”,“谁拥有数据,谁就拥有未来”,大公司数据的来源可以是收购蕴含大量用户数据的公司,例如Microsoft收购LinkedIn,有评论称一是为了进军云服务,而是为了LinkedIn的数据。爬虫也是一个获得数据的途径,那么这样来说,爬虫也算是跟随未来脚步的一个方法,有空要多研究一下。

资源汇总:

https://www.baidu.com/s?ie=utf-8&f=3&rsv_bp=1&tn=mswin_oem_dg&wd=c%23%E7%88%AC%E8%99%AB%E6%8A%93%E5%8F%96%E7%BD%91%E9%A
a412
1%B5%E6%95%B0%E6%8D%AE&oq=%E7%88%AC%E8%99%AB&rsv_pq=9ad8b8980002356a&rsv_t=1941Y%2B8RWT0e6BQz9YiqKopPGQ%2BGOL1ztMVJ3matmwpxts%2BTY2KvPxy5YgkWe0CVrk9l&rqlang=cn&rsv_enter=1&rsv_sug3=5&rsv_sug1=2&rsv_sug7=100&rsv_sug2=1&prefixsug=c%23%E7%88%AC%E8%99%AB&rsp=0&inputT=5207&rsv_sug4=6206 | c#爬虫抓取网页数据_百度搜索

http://www.cnblogs.com/falcon-fei/archive/2012/02/29/2379881.html | c#关于网页内容抓取,简单爬虫的实现。(包括动态,静态的) - falcon_fei - 博客园

http://www.jb51.net/article/81482.htm | 基于C#实现网络爬虫 C#抓取网页Html源码C#教程脚本之家

http://blog.csdn.net/nihao87224/article/details/7304463 | c#关于网页内容抓取,简单爬虫的实现。(包括动态,静态的) - nihao87224的专栏 - 博客频道 - CSDN.NET

http://www.jb51.net/article/75402.htm | 轻松学习C#的ArrayList类C#教程脚本之家

http://www.cnblogs.com/Jiajun/archive/2012/06/17/2552458.html | 用C#实现网络爬虫(二) - 有来 有去 - 博客园

https://www.baidu.com/s?tn=mswin_oem_dg&ie=utf-16&word=%E7%BD%91%E7%BB%9C%E7%88%AC%E8%99%AB%E5%A6%82%E4%BD%95%E7%88%AC%E9%93%BE%E6%8E%A5 | 网络爬虫如何爬链接_百度搜索

http://blog.csdn.net/lemon_tree12138/article/details/47447731 | 网络爬虫:使用多线程爬取网页链接 - 钱文海的专栏 - 博客频道 - CSDN.NET

http://blog.csdn.net/lemon_tree12138/article/details/47424301 | 网络爬虫初步:从一个入口链接开始不断抓取页面中的网址并入库 - 钱文海的专栏 - 博客频道 - CSDN.NET

http://blog.csdn.net/u013761665/article/category/5756931 | 网络爬虫 - 钱文海的专栏 - 博客频道 - CSDN.NET
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  爬虫 c#