这一节主要内容是使用正则表达式提取网站的正文,主要面向于小说章节网站。其中涉及到一些其他知识点,比如异步读取、异步流写入等,代码中都会有详细的注解。现在流行的网络文学都是每日一更或几更,没有一个统一的下载入口。以下我将实现一个简单的章节小说下载器的功能,将章节小说以整本的形式下载保存,保守估计能下载网络上70%以上小说。

先看看小说网站的网页源码,天蚕土豆的大主宰第一章。

http://www.biquge.com/4_4606/991334.html 笔趣网

http://www.fqxsw.com/html/11739/4636404.html 番茄小说网

 正文正则

结果发现正文内容一般都是嵌套在div中,样式表可能会略有不同,所以正则表达式可以这样表示

(<div).*</div>

当然有div标签的不一定是正文内容,还有可能是其中不相关的数据。那么按照一般小说的规律,我们指定一个匹配符。

<br\\s*>

只有当匹配符超过5个以上的,我们才认为这是正文内容。

下一页正则

再来找下一页的链接。下一页的链接的格式一般存在两种格式

或是

所以正则表达式可以这样表示

<a.*href=(")(([^<]*[^"])[^>])(\s*)?>.*((→)|(下一页))

异步读取网页流

读取网页数据使用HttpClient异步方法,在读取过程中将主控制权返回到UI层,不会阻塞界面。具体原理请查看我上一篇文章

await httpClient.GetByteArrayAsync(url);

配置文件

为了匹配更多的网站信息,我把正则表达式存在一个ini文件中,在需要的时候可以继续扩充。

核心代码

  1. private async Task downLoadNovel(byte[] bytes, string url)
  2. {
  3. title = string.Empty;
  4. nextPageUrl = string.Empty;
  5. content = string.Empty;
  6. novelInfo = string.Empty;
  7.  
  8. try
  9. {
  10. byte[] response = bytes;
  11. if (bytes == null)
  12. {
  13. response = await httpClient.GetByteArrayAsync(url);
  14. }
  15. content = Encoding.Default.GetString(response, , response.Length - );
  16. //获取网页字符编码描述信息
  17. var charSetMatch = Regex.Match(content, "<meta([^<]*)charset=([^<]*)\"", RegexOptions.IgnoreCase | RegexOptions.Multiline);
  18.  
  19. string webCharSet = charSetMatch.Groups[].Value;
  20. if (chartSet == null || chartSet == "")
  21. chartSet = webCharSet;
  22.  
  23. if (chartSet != null && chartSet != "" && Encoding.GetEncoding(chartSet) != Encoding.Default)
  24. content = Encoding.GetEncoding(chartSet).GetString(response, , response.Length - );
  25. }
  26. catch (Exception ex)
  27. {
  28. throw ex;
  29. }
  30. //小说主域名
  31. if (webSiteDomain.Length == )
  32. {
  33. var websiteDomainMath = Regex.Match(url, "(http).*(/)", RegexOptions.IgnoreCase);
  34. webSiteDomain = websiteDomainMath.Groups[].Value;
  35. }
  36.  
  37. //标题信息
  38. var titleInfoMath = Regex.Match(content, "(<title>)([^>]*)(</title>)", RegexOptions.IgnoreCase | RegexOptions.Multiline);
  39. title = titleInfoMath.Groups[].Value;
  40.  
  41. content = content.Replace("'", "\"").Replace("\r\n", "");
  42.  
  43. for (int i = ; i < contextPatterns.Length; i++)
  44. {
  45. var cpattern = contextPatterns[i];
  46. if (novelInfo.Length == )
  47. {
  48. //正文信息
  49. var webInfoMath = Regex.Matches(content, cpattern, RegexOptions.IgnoreCase | RegexOptions.Multiline);
  50.  
  51. for (int j = ; j < webInfoMath.Count; j++)
  52. {
  53. foreach (Group g in webInfoMath[j].Groups)
  54. {
  55. var value = Regex.Split(g.Value, contextNewLine, RegexOptions.IgnoreCase);
  56. if (value.Length > )
  57. {
  58. novelInfo = g.Value;
  59. foreach (var pattern in filterPatterns)
  60. novelInfo = Regex.Replace(novelInfo, pattern, new MatchEvaluator(p => null));
  61.  
  62. novelInfo = Regex.Replace(novelInfo, contextNewLine, new MatchEvaluator(p => "\r\n"));
  63. break;
  64. }
  65. }
  66. }
  67.  
  68. }
  69. else
  70. break;
  71. }
  72.  
  73. bytes = null;
  74.  
  75. for (int i = ; i < nextPagePatterns.Length; i++)
  76. {
  77. if (nextPageUrl.Length == )
  78. {
  79. //下一页信息
  80. var webNextPageMath = Regex.Match(content, nextPagePatterns[i], RegexOptions.IgnoreCase | RegexOptions.Multiline);
  81. if (webNextPageMath.Groups.Count > )
  82. {
  83. foreach (Group g in webNextPageMath.Groups)
  84. {
  85. if (!g.Value.EndsWith("\""))
  86. nextPageUrl = g.Value;
  87. if (nextPageUrl.StartsWith("/"))
  88. nextPageUrl = nextPageUrl.Substring();
  89. if (!nextPageUrl.StartsWith("http", true, null) && (Regex.IsMatch(nextPageUrl, "[a-z]") || Regex.IsMatch(nextPageUrl, "[0-9]")) && !url.EndsWith(nextPageUrl))
  90. {
  91. nextPageUrl = webSiteDomain + nextPageUrl;
  92. }
  93. try
  94. {
  95. bytes = await httpClient.GetByteArrayAsync(nextPageUrl);
  96. break;
  97. }
  98. catch
  99. {
  100. continue;
  101. }
  102. }
  103.  
  104. }
  105. }
  106. else
  107. break;
  108. }
  109. bool isAdd = false;
  110. cacheNovel.ForEach(p =>
  111. {
  112. if (p == (title + novelInfo))
  113. {
  114. isAdd = true;
  115. }
  116. });
  117.  
  118. if (!isAdd)
  119. {
  120. if (title.Length > )
  121. {
  122. writeNovelLog("正在下载章节:" + title);
  123. }
  124.  
  125. writeNovelLog("章节长度:" + novelInfo.Length);
  126.  
  127. cacheNovel.Add(title + novelInfo);
  128.  
  129. if (nextPageUrl.Length > )
  130. {
  131. writeNovelLog("下一页:" + nextPageUrl);
  132.  
  133. await downLoadNovel(bytes, nextPageUrl);
  134. }
  135. else
  136. {
  137. downloadFinish();
  138. }
  139. }
  140. else
  141. {
  142. writeNovelLog("存在重复的章节,章节名称:" + title + " 地址:" + url);
  143. downloadFinish();
  144. }
  145. }

异步下载网页流、解析数据

最后效果

c# 使用正则表达式 提取章节小说正文全本篇的更多相关文章

  1. 将RegEx(正则表达式提取器)与JMeter一起使用

    JMeter的,最流行的开源性能测试工具,可以工作正则表达式,用正则表达式提取.正则表达式是一种用于通过使用高级操作提取文本的必需部分的工具.正则表达式在测试Web应用程序时很流行,因为它们可用于验证 ...

  2. JMeter中的关联-正则表达式提取(2)

    JMeter获取正则表达式中的提取的所有关联值的解决方法: 需求如下: { : ", : "results": : [ : : { : : : "total_e ...

  3. JMeter中的关联-正则表达式提取(1)

    运用Jmeter正则提取器,可以从请求的响应结果中取到需要的内容,从而实现关联. jmeter之关联 的个人理解: 关联是请求与请求之间存在数据依赖关系,需要从上一个请求获取下一个请求需要回传回去的数 ...

  4. asp.net正则表达式提取网页网址、标题、图片实例以及过滤所有HTML标签实例

    无论你用什么语言,正则表达式的处理方法都是非常灵活.高效的,尤其是对某些字符串的抓取.过滤方面,更显其优势. 正则表达式的写法通常比较简单,几行短代码便能轻松完成看似很复杂的事情,更值得称赞的是,它的 ...

  5. Jmeter—5 关联 响应数据传递-正则表达式提取器

    在测试过程中,遇到一个问题:用户登录成功后服务器会返回一个登录凭证,之后所有的操作都需要带上此凭证.我们怎么获取登录凭证并传递给后续的操作? Jmeter提供了正则表达式提取器,用变量提取参数,后续通 ...

  6. JMeter学习-011-JMeter 后置处理器实例之 - 正则表达式提取器(三)多参数获取进阶引用篇

    前两篇文章分表讲述了 后置处理器 - 正则表达式提取器概述及简单实例.多参数获取,相应博文敬请参阅 简单实例.多参数获取. 此文主要讲述如何引用正则表达式提取器获取的数据信息.其实,正则表达式提取器获 ...

  7. JMeter学习-009-JMeter 后置处理器实例之 - 正则表达式提取器(二)多参数获取

    前文简述了通过后置处理器 - 正则表达式提取器 获取 HTTP请求 响应结果中的特定数据,未看过的亲,敬请参阅 JMeter学习-008-JMeter 后置处理器实例之 - 正则表达式提取器(一). ...

  8. JMeter学习-008-JMeter 后置处理器实例之 - 正则表达式提取器(一)概述及简单实例

    上文我们讲述了如何对 HTTP请求 的响应数据进行断言,以判断响应是否符合我们的预期,敬请参阅:JMeter学习-007-JMeter 断言实例之一 - 响应断言 那么我们如何获取 HTTP请求 响应 ...

  9. Jmeter正则表达式提取器的使用方法(转)

    下面简单介绍一下Jmeter正则表达式提取器的使用方法. 1.添加Jmeter正则表达式提取器:在具体的Request下添加Jmeter正则表达式提取器(Jmeter正则表达式在“后置处理器”下面)  ...

随机推荐

  1. IE8 松散耦合进程框架(Loosely-Coupled IE (LCIE)--特性介绍

    官方介绍:http://blogs.msdn.com/b/ie/archive/2008/03/11/ie8-and-loosely-coupled-ie-lcie.aspx 参考文档:http:// ...

  2. Zookeeper Invalid config, exiting abnormally

    Zookeeper Invalid config, exiting abnormally     出现 Invalid config, exiting abnormally 的情况可能有3个: 是否开 ...

  3. CUDA 6.5 && VS2013 && Win7:创建CUDA项目

    运行环境: Win7+VS2013+CUDA6.5 1.创建win32空项目 2.右键项目解决方案-->生成项目依赖项-->生成自定义 3.右键项目解决方案-->属性-->配置 ...

  4. Linux 守护进程和超级守护进程(xinetd)

    一 .Linux守护进程 Linux 服务器在启动时需要启动很多系统服务,它们向本地和网络用户提供了Linux的系统功能接口,直接面向应用程序和用户.提供这些服务的程序是由运行在后台的守护进程来执行的 ...

  5. Android 调用浏览器和嵌入网页

    Android App开发时由于布局相对麻烦,很多时候一个App通常是由html5和原生控件相结合而成.简单的网页应用可以直接内嵌html5页面即可,对于需要调用复杂的底层功能时则采用原生控件的方式进 ...

  6. discuz 帖子模块用到的表及自动发帖函数

    最近在做一个discuz的插件,由于需要程序自动生成并调用discuz已经存在插件的帖子.然而这就相当于自动发帖的功能了.网上找了一下,大部分都是通过curl模拟登陆,模拟发帖的,这显然不满足我的要求 ...

  7. iOS __block类型变量作用域

    看下图 在c语言中,2个独立的函数是不可能互相访问局部变量的,但是__block提供了这个功能,它不单单能读变量,还可以对变量进行写!上图说明,block获得了i最后的真实值5,没有只取得0,这都是& ...

  8. (转)SQL Server 的事务和锁(二)-Range S-S锁

    在这篇随笔中,我们的主要关注点在 Key-Range Lock.Key-Range Lock有 S-S.S-U.I-N.X-X几种情况.我们一个一个来说,力求明白.遗憾的是,这里可能会比较冗长,那么死 ...

  9. ffplay mini 媒体播放器

    下载 http://pan.baidu.com/s/1dDcp3lZ 一定要解压到 D:\ffplay\ 目录下 双击 OpenWith_FFPlay_mini.reg 注册ffplay 在视频文件名 ...

  10. Ubuntu及Windows ADB设备no permissions的解决方案

    不少人曾在Windows下及Ubuntu下都遇到过Android设备无法识别的情况,就是run as Android Application的时候,target显示"??????" ...