首页 > 技术文章 > c# 使用正则表达式 提取章节小说正文全本篇

yzp12sina 2014-01-15 10:39 原文

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

 

最后效果

 

推荐阅读