c# 使用正则表达式 提取章节小说正文全本篇
这一节主要内容是使用正则表达式提取网站的正文,主要面向于小说章节网站。其中涉及到一些其他知识点,比如异步读取、异步流写入等,代码中都会有详细的注解。现在流行的网络文学都是每日一更或几更,没有一个统一的下载入口。以下我将实现一个简单的章节小说下载器的功能,将章节小说以整本的形式下载保存,保守估计能下载网络上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文件中,在需要的时候可以继续扩充。
核心代码
private async Task downLoadNovel(byte[] bytes, string url) { title = string.Empty; nextPageUrl = string.Empty; content = string.Empty; novelInfo = string.Empty; try { byte[] response = bytes; if (bytes == null) { response = await httpClient.GetByteArrayAsync(url); } content = Encoding.Default.GetString(response, 0, response.Length - 1); //获取网页字符编码描述信息 var charSetMatch = Regex.Match(content, "<meta([^<]*)charset=([^<]*)\"", RegexOptions.IgnoreCase | RegexOptions.Multiline); string webCharSet = charSetMatch.Groups[2].Value; if (chartSet == null || chartSet == "") chartSet = webCharSet; if (chartSet != null && chartSet != "" && Encoding.GetEncoding(chartSet) != Encoding.Default) content = Encoding.GetEncoding(chartSet).GetString(response, 0, response.Length - 1); } catch (Exception ex) { throw ex; } //小说主域名 if (webSiteDomain.Length == 0) { var websiteDomainMath = Regex.Match(url, "(http).*(/)", RegexOptions.IgnoreCase); webSiteDomain = websiteDomainMath.Groups[0].Value; } //标题信息 var titleInfoMath = Regex.Match(content, "(<title>)([^>]*)(</title>)", RegexOptions.IgnoreCase | RegexOptions.Multiline); title = titleInfoMath.Groups[2].Value; content = content.Replace("'", "\"").Replace("\r\n", ""); for (int i = 0; i < contextPatterns.Length; i++) { var cpattern = contextPatterns[i]; if (novelInfo.Length == 0) { //正文信息 var webInfoMath = Regex.Matches(content, cpattern, RegexOptions.IgnoreCase | RegexOptions.Multiline); for (int j = 0; j < webInfoMath.Count; j++) { foreach (Group g in webInfoMath[j].Groups) { var value = Regex.Split(g.Value, contextNewLine, RegexOptions.IgnoreCase); if (value.Length > 5) { novelInfo = g.Value; foreach (var pattern in filterPatterns) novelInfo = Regex.Replace(novelInfo, pattern, new MatchEvaluator(p => null)); novelInfo = Regex.Replace(novelInfo, contextNewLine, new MatchEvaluator(p => "\r\n")); break; } } } } else break; } bytes = null; for (int i = 0; i < nextPagePatterns.Length; i++) { if (nextPageUrl.Length == 0) { //下一页信息 var webNextPageMath = Regex.Match(content, nextPagePatterns[i], RegexOptions.IgnoreCase | RegexOptions.Multiline); if (webNextPageMath.Groups.Count > 0) { foreach (Group g in webNextPageMath.Groups) { if (!g.Value.EndsWith("\"")) nextPageUrl = g.Value; if (nextPageUrl.StartsWith("/")) nextPageUrl = nextPageUrl.Substring(1); if (!nextPageUrl.StartsWith("http", true, null) && (Regex.IsMatch(nextPageUrl, "[a-z]") || Regex.IsMatch(nextPageUrl, "[0-9]")) && !url.EndsWith(nextPageUrl)) { nextPageUrl = webSiteDomain + nextPageUrl; } try { bytes = await httpClient.GetByteArrayAsync(nextPageUrl); break; } catch { continue; } } } } else break; } bool isAdd = false; cacheNovel.ForEach(p => { if (p == (title + novelInfo)) { isAdd = true; } }); if (!isAdd) { if (title.Length > 0) { writeNovelLog("正在下载章节:" + title); } writeNovelLog("章节长度:" + novelInfo.Length); cacheNovel.Add(title + novelInfo); if (nextPageUrl.Length > 0) { writeNovelLog("下一页:" + nextPageUrl); await downLoadNovel(bytes, nextPageUrl); } else { downloadFinish(); } } else { writeNovelLog("存在重复的章节,章节名称:" + title + " 地址:" + url); downloadFinish(); } }
最后效果