学习: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%Aa412
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
相关文章推荐
- Python3写爬虫(四)多线程实现数据爬取
- Scrapy的架构介绍
- 爬虫笔记
- c#调用COM组件
- C#实现把指定数据写入串口
- C#动态创建button的方法
- C#使用第三方组件生成二维码汇总
- C#中抽象方法与虚拟方法的区别
- c#中虚函数的相关使用方法
- C#实现给图片加水印的方法
- C#使用加边法计算行列式的值
- C#实现多线程的同步方法实例分析
- C#中尾递归的使用、优化及编译器优化
- C#中的delegate委托类型基本学习教程
- C#实现子窗体与父窗体通信方法实例总结
- C#通用邮件发送类分享
- 举例讲解C#中自动实现的属性
- C#中this的用法集锦
- C#数据结构之顺序表(SeqList)实例详解
- C#.NET获取拨号连接的宽带连接方法